RAM - implement CRUD endpoints (#3158)
* Add ram.create_resource_share * Add ram.get_resource_shares * Add ram.update_resource_share * Add ram.delete_resource_share * Add ram.enable_sharing_with_aws_organization * Fix server tests * Add CR suggestions
This commit is contained in:
parent
1e5b8acac6
commit
a507314d45
@ -81,6 +81,7 @@ mock_opsworks = lazy_load(".opsworks", "mock_opsworks")
|
||||
mock_opsworks_deprecated = lazy_load(".opsworks", "mock_opsworks_deprecated")
|
||||
mock_organizations = lazy_load(".organizations", "mock_organizations")
|
||||
mock_polly = lazy_load(".polly", "mock_polly")
|
||||
mock_ram = lazy_load(".ram", "mock_ram")
|
||||
mock_rds = lazy_load(".rds", "mock_rds")
|
||||
mock_rds_deprecated = lazy_load(".rds", "mock_rds_deprecated")
|
||||
mock_rds2 = lazy_load(".rds2", "mock_rds2")
|
||||
|
@ -48,6 +48,7 @@ BACKENDS = {
|
||||
"opsworks": ("opsworks", "opsworks_backends"),
|
||||
"organizations": ("organizations", "organizations_backends"),
|
||||
"polly": ("polly", "polly_backends"),
|
||||
"ram": ("ram", "ram_backends"),
|
||||
"rds": ("rds2", "rds2_backends"),
|
||||
"redshift": ("redshift", "redshift_backends"),
|
||||
"resource-groups": ("resourcegroups", "resourcegroups_backends"),
|
||||
|
5
moto/ram/__init__.py
Normal file
5
moto/ram/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .models import ram_backends
|
||||
from ..core.models import base_decorator
|
||||
|
||||
ram_backend = ram_backends["us-east-1"]
|
||||
mock_ram = base_decorator(ram_backends)
|
39
moto/ram/exceptions.py
Normal file
39
moto/ram/exceptions.py
Normal file
@ -0,0 +1,39 @@
|
||||
from __future__ import unicode_literals
|
||||
from moto.core.exceptions import JsonRESTError
|
||||
|
||||
|
||||
class InvalidParameterException(JsonRESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
super(InvalidParameterException, self).__init__(
|
||||
"InvalidParameterException", message
|
||||
)
|
||||
|
||||
|
||||
class MalformedArnException(JsonRESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
super(MalformedArnException, self).__init__("MalformedArnException", message)
|
||||
|
||||
|
||||
class OperationNotPermittedException(JsonRESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self):
|
||||
super(OperationNotPermittedException, self).__init__(
|
||||
"OperationNotPermittedException",
|
||||
"Unable to enable sharing with AWS Organizations. "
|
||||
"Received AccessDeniedException from AWSOrganizations with the following error message: "
|
||||
"You don't have permissions to access this resource.",
|
||||
)
|
||||
|
||||
|
||||
class UnknownResourceException(JsonRESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
super(UnknownResourceException, self).__init__(
|
||||
"UnknownResourceException", message
|
||||
)
|
247
moto/ram/models.py
Normal file
247
moto/ram/models.py
Normal file
@ -0,0 +1,247 @@
|
||||
import re
|
||||
import string
|
||||
from datetime import datetime
|
||||
import random
|
||||
from uuid import uuid4
|
||||
|
||||
from boto3 import Session
|
||||
from moto.core import BaseBackend, BaseModel, ACCOUNT_ID
|
||||
from moto.core.utils import unix_time
|
||||
from moto.organizations import organizations_backends
|
||||
from moto.ram.exceptions import (
|
||||
MalformedArnException,
|
||||
InvalidParameterException,
|
||||
UnknownResourceException,
|
||||
OperationNotPermittedException,
|
||||
)
|
||||
|
||||
|
||||
def random_resource_id(size):
|
||||
return "".join(random.choice(string.digits + "abcdef") for _ in range(size))
|
||||
|
||||
|
||||
class ResourceShare(BaseModel):
|
||||
# List of shareable resources can be found here
|
||||
# https://docs.aws.amazon.com/ram/latest/userguide/shareable.html
|
||||
SHAREABLE_RESOURCES = [
|
||||
"cluster", # Amazon Aurora cluster
|
||||
"component", # Amazon EC2 Image Builder component
|
||||
"group", # AWS Resource Groups
|
||||
"image", # Amazon EC2 Image Builder image
|
||||
"image-recipe", # Amazon EC2 Image Builder image recipe
|
||||
"license-configuration", # AWS License Manager configuration
|
||||
"mesh", # AWS App Mesh
|
||||
"prefix-list", # Amazon EC2 prefix list
|
||||
"project", # AWS CodeBuild project
|
||||
"report-group", # AWS CodeBuild report group
|
||||
"resolver-rule", # Amazon Route 53 forwarding rule
|
||||
"subnet", # Amazon EC2 subnet
|
||||
"transit-gateway", # Amazon EC2 transit gateway
|
||||
]
|
||||
|
||||
def __init__(self, region, **kwargs):
|
||||
self.region = region
|
||||
|
||||
self.allow_external_principals = kwargs.get("allowExternalPrincipals", True)
|
||||
self.arn = "arn:aws:ram:{0}:{1}:resource-share/{2}".format(
|
||||
self.region, ACCOUNT_ID, uuid4()
|
||||
)
|
||||
self.creation_time = datetime.utcnow()
|
||||
self.feature_set = "STANDARD"
|
||||
self.last_updated_time = datetime.utcnow()
|
||||
self.name = kwargs["name"]
|
||||
self.owning_account_id = ACCOUNT_ID
|
||||
self.principals = []
|
||||
self.resource_arns = []
|
||||
self.status = "ACTIVE"
|
||||
|
||||
@property
|
||||
def organizations_backend(self):
|
||||
return organizations_backends["global"]
|
||||
|
||||
def add_principals(self, principals):
|
||||
for principal in principals:
|
||||
match = re.search(
|
||||
r"^arn:aws:organizations::\d{12}:organization/(o-\w+)$", principal
|
||||
)
|
||||
if match:
|
||||
organization = self.organizations_backend.describe_organization()
|
||||
if principal == organization["Organization"]["Arn"]:
|
||||
continue
|
||||
else:
|
||||
raise UnknownResourceException(
|
||||
"Organization {} could not be found.".format(match.group(1))
|
||||
)
|
||||
|
||||
match = re.search(
|
||||
r"^arn:aws:organizations::\d{12}:ou/(o-\w+)/(ou-[\w-]+)$", principal
|
||||
)
|
||||
if match:
|
||||
roots = self.organizations_backend.list_roots()
|
||||
root_id = next(
|
||||
(
|
||||
root["Id"]
|
||||
for root in roots["Roots"]
|
||||
if root["Name"] == "Root" and match.group(1) in root["Arn"]
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if root_id:
|
||||
ous = self.organizations_backend.list_organizational_units_for_parent(
|
||||
ParentId=root_id
|
||||
)
|
||||
if any(principal == ou["Arn"] for ou in ous["OrganizationalUnits"]):
|
||||
continue
|
||||
|
||||
raise UnknownResourceException(
|
||||
"OrganizationalUnit {} in unknown organization could not be found.".format(
|
||||
match.group(2)
|
||||
)
|
||||
)
|
||||
|
||||
if not re.match(r"^\d{12}$", principal):
|
||||
raise InvalidParameterException(
|
||||
"Principal ID {} is malformed. "
|
||||
"Verify the ID and try again.".format(principal)
|
||||
)
|
||||
|
||||
for principal in principals:
|
||||
self.principals.append(principal)
|
||||
|
||||
def add_resources(self, resource_arns):
|
||||
for resource in resource_arns:
|
||||
match = re.search(
|
||||
r"^arn:aws:[a-z0-9-]+:[a-z0-9-]*:[0-9]{12}:([a-z-]+)[/:].*$", resource
|
||||
)
|
||||
if not match:
|
||||
raise MalformedArnException(
|
||||
"The specified resource ARN {} is not valid. "
|
||||
"Verify the ARN and try again.".format(resource)
|
||||
)
|
||||
|
||||
if match.group(1) not in self.SHAREABLE_RESOURCES:
|
||||
raise MalformedArnException(
|
||||
"You cannot share the selected resource type."
|
||||
)
|
||||
|
||||
for resource in resource_arns:
|
||||
self.resource_arns.append(resource)
|
||||
|
||||
def delete(self):
|
||||
self.last_updated_time = datetime.utcnow()
|
||||
self.status = "DELETED"
|
||||
|
||||
def describe(self):
|
||||
return {
|
||||
"allowExternalPrincipals": self.allow_external_principals,
|
||||
"creationTime": unix_time(self.creation_time),
|
||||
"featureSet": self.feature_set,
|
||||
"lastUpdatedTime": unix_time(self.last_updated_time),
|
||||
"name": self.name,
|
||||
"owningAccountId": self.owning_account_id,
|
||||
"resourceShareArn": self.arn,
|
||||
"status": self.status,
|
||||
}
|
||||
|
||||
def update(self, **kwargs):
|
||||
self.allow_external_principals = kwargs.get(
|
||||
"allowExternalPrincipals", self.allow_external_principals
|
||||
)
|
||||
self.last_updated_time = datetime.utcnow()
|
||||
self.name = kwargs.get("name", self.name)
|
||||
|
||||
|
||||
class ResourceAccessManagerBackend(BaseBackend):
|
||||
def __init__(self, region_name=None):
|
||||
super(ResourceAccessManagerBackend, self).__init__()
|
||||
self.region_name = region_name
|
||||
self.resource_shares = []
|
||||
|
||||
@property
|
||||
def organizations_backend(self):
|
||||
return organizations_backends["global"]
|
||||
|
||||
def reset(self):
|
||||
region_name = self.region_name
|
||||
self.__dict__ = {}
|
||||
self.__init__(region_name)
|
||||
|
||||
def create_resource_share(self, **kwargs):
|
||||
resource = ResourceShare(self.region_name, **kwargs)
|
||||
resource.add_principals(kwargs.get("principals", []))
|
||||
resource.add_resources(kwargs.get("resourceArns", []))
|
||||
|
||||
self.resource_shares.append(resource)
|
||||
|
||||
response = resource.describe()
|
||||
response.pop("featureSet")
|
||||
|
||||
return dict(resourceShare=response)
|
||||
|
||||
def get_resource_shares(self, **kwargs):
|
||||
owner = kwargs["resourceOwner"]
|
||||
|
||||
if owner not in ["SELF", "OTHER-ACCOUNTS"]:
|
||||
raise InvalidParameterException(
|
||||
"{} is not a valid resource owner. "
|
||||
"Specify either SELF or OTHER-ACCOUNTS and try again.".format(owner)
|
||||
)
|
||||
|
||||
if owner == "OTHER-ACCOUNTS":
|
||||
raise NotImplementedError(
|
||||
"Value 'OTHER-ACCOUNTS' for parameter 'resourceOwner' not implemented."
|
||||
)
|
||||
|
||||
resouces = [resource.describe() for resource in self.resource_shares]
|
||||
|
||||
return dict(resourceShares=resouces)
|
||||
|
||||
def update_resource_share(self, **kwargs):
|
||||
arn = kwargs["resourceShareArn"]
|
||||
|
||||
resource = next(
|
||||
(resource for resource in self.resource_shares if arn == resource.arn),
|
||||
None,
|
||||
)
|
||||
|
||||
if not resource:
|
||||
raise UnknownResourceException(
|
||||
"ResourceShare {} could not be found.".format(arn)
|
||||
)
|
||||
|
||||
resource.update(**kwargs)
|
||||
response = resource.describe()
|
||||
response.pop("featureSet")
|
||||
|
||||
return dict(resourceShare=response)
|
||||
|
||||
def delete_resource_share(self, arn):
|
||||
resource = next(
|
||||
(resource for resource in self.resource_shares if arn == resource.arn),
|
||||
None,
|
||||
)
|
||||
|
||||
if not resource:
|
||||
raise UnknownResourceException(
|
||||
"ResourceShare {} could not be found.".format(arn)
|
||||
)
|
||||
|
||||
resource.delete()
|
||||
|
||||
return dict(returnValue=True)
|
||||
|
||||
def enable_sharing_with_aws_organization(self):
|
||||
if not self.organizations_backend.org:
|
||||
raise OperationNotPermittedException
|
||||
|
||||
return dict(returnValue=True)
|
||||
|
||||
|
||||
ram_backends = {}
|
||||
for region in Session().get_available_regions("ram"):
|
||||
ram_backends[region] = ResourceAccessManagerBackend(region)
|
||||
for region in Session().get_available_regions("ram", partition_name="aws-us-gov"):
|
||||
ram_backends[region] = ResourceAccessManagerBackend(region)
|
||||
for region in Session().get_available_regions("ram", partition_name="aws-cn"):
|
||||
ram_backends[region] = ResourceAccessManagerBackend(region)
|
39
moto/ram/responses.py
Normal file
39
moto/ram/responses.py
Normal file
@ -0,0 +1,39 @@
|
||||
from __future__ import unicode_literals
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import ram_backends
|
||||
import json
|
||||
|
||||
|
||||
class ResourceAccessManagerResponse(BaseResponse):
|
||||
SERVICE_NAME = "ram"
|
||||
|
||||
@property
|
||||
def ram_backend(self):
|
||||
return ram_backends[self.region]
|
||||
|
||||
@property
|
||||
def request_params(self):
|
||||
try:
|
||||
if self.method == "DELETE":
|
||||
return None
|
||||
|
||||
return json.loads(self.body)
|
||||
except ValueError:
|
||||
return {}
|
||||
|
||||
def create_resource_share(self):
|
||||
return json.dumps(self.ram_backend.create_resource_share(**self.request_params))
|
||||
|
||||
def get_resource_shares(self):
|
||||
return json.dumps(self.ram_backend.get_resource_shares(**self.request_params))
|
||||
|
||||
def update_resource_share(self):
|
||||
return json.dumps(self.ram_backend.update_resource_share(**self.request_params))
|
||||
|
||||
def delete_resource_share(self):
|
||||
return json.dumps(
|
||||
self.ram_backend.delete_resource_share(self._get_param("resourceShareArn"))
|
||||
)
|
||||
|
||||
def enable_sharing_with_aws_organization(self):
|
||||
return json.dumps(self.ram_backend.enable_sharing_with_aws_organization())
|
12
moto/ram/urls.py
Normal file
12
moto/ram/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
from .responses import ResourceAccessManagerResponse
|
||||
|
||||
url_bases = ["https?://ram.(.+).amazonaws.com"]
|
||||
|
||||
url_paths = {
|
||||
"{0}/createresourceshare$": ResourceAccessManagerResponse.dispatch,
|
||||
"{0}/deleteresourceshare/?$": ResourceAccessManagerResponse.dispatch,
|
||||
"{0}/enablesharingwithawsorganization$": ResourceAccessManagerResponse.dispatch,
|
||||
"{0}/getresourceshares$": ResourceAccessManagerResponse.dispatch,
|
||||
"{0}/updateresourceshare$": ResourceAccessManagerResponse.dispatch,
|
||||
}
|
381
tests/test_ram/test_ram.py
Normal file
381
tests/test_ram/test_ram.py
Normal file
@ -0,0 +1,381 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import boto3
|
||||
import sure # noqa
|
||||
from botocore.exceptions import ClientError
|
||||
from nose.tools import assert_raises
|
||||
|
||||
from moto import mock_ram, mock_organizations
|
||||
from moto.core import ACCOUNT_ID
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_create_resource_share():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# when
|
||||
response = client.create_resource_share(name="test")
|
||||
|
||||
# then
|
||||
resource = response["resourceShare"]
|
||||
resource["allowExternalPrincipals"].should.be.ok
|
||||
resource["creationTime"].should.be.a(datetime)
|
||||
resource["lastUpdatedTime"].should.be.a(datetime)
|
||||
resource["name"].should.equal("test")
|
||||
resource["owningAccountId"].should.equal(ACCOUNT_ID)
|
||||
resource["resourceShareArn"].should.match(
|
||||
r"arn:aws:ram:us-east-1:\d{12}:resource-share/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
)
|
||||
resource["status"].should.equal("ACTIVE")
|
||||
resource.should_not.have.key("featureSet")
|
||||
|
||||
# creating a resource share with the name should result in a second one
|
||||
# not overwrite/update the old one
|
||||
# when
|
||||
response = client.create_resource_share(
|
||||
name="test",
|
||||
allowExternalPrincipals=False,
|
||||
resourceArns=[
|
||||
"arn:aws:ec2:us-east-1:{}:transit-gateway/tgw-123456789".format(ACCOUNT_ID)
|
||||
],
|
||||
)
|
||||
|
||||
# then
|
||||
resource = response["resourceShare"]
|
||||
resource["allowExternalPrincipals"].should_not.be.ok
|
||||
resource["creationTime"].should.be.a(datetime)
|
||||
resource["lastUpdatedTime"].should.be.a(datetime)
|
||||
resource["name"].should.equal("test")
|
||||
resource["owningAccountId"].should.equal(ACCOUNT_ID)
|
||||
resource["resourceShareArn"].should.match(
|
||||
r"arn:aws:ram:us-east-1:\d{12}:resource-share/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
)
|
||||
resource["status"].should.equal("ACTIVE")
|
||||
|
||||
response = client.get_resource_shares(resourceOwner="SELF")
|
||||
response["resourceShares"].should.have.length_of(2)
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_create_resource_share_errors():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# invalid ARN
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.create_resource_share(name="test", resourceArns=["inalid-arn"])
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("CreateResourceShare")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("MalformedArnException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"The specified resource ARN inalid-arn is not valid. "
|
||||
"Verify the ARN and try again."
|
||||
)
|
||||
|
||||
# valid ARN, but not shareable resource type
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.create_resource_share(
|
||||
name="test", resourceArns=["arn:aws:iam::{}:role/test".format(ACCOUNT_ID)]
|
||||
)
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("CreateResourceShare")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("MalformedArnException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"You cannot share the selected resource type."
|
||||
)
|
||||
|
||||
# invalid principal ID
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.create_resource_share(
|
||||
name="test",
|
||||
principals=["invalid"],
|
||||
resourceArns=[
|
||||
"arn:aws:ec2:us-east-1:{}:transit-gateway/tgw-123456789".format(
|
||||
ACCOUNT_ID
|
||||
)
|
||||
],
|
||||
)
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("CreateResourceShare")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("InvalidParameterException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"Principal ID invalid is malformed. Verify the ID and try again."
|
||||
)
|
||||
|
||||
|
||||
@mock_ram
|
||||
@mock_organizations
|
||||
def test_create_resource_share_with_organization():
|
||||
# given
|
||||
client = boto3.client("organizations", region_name="us-east-1")
|
||||
org_arn = client.create_organization(FeatureSet="ALL")["Organization"]["Arn"]
|
||||
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||
ou_arn = client.create_organizational_unit(ParentId=root_id, Name="test")[
|
||||
"OrganizationalUnit"
|
||||
]["Arn"]
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# share in whole Organization
|
||||
# when
|
||||
response = client.create_resource_share(
|
||||
name="test",
|
||||
principals=[org_arn],
|
||||
resourceArns=[
|
||||
"arn:aws:ec2:us-east-1:{}:transit-gateway/tgw-123456789".format(ACCOUNT_ID)
|
||||
],
|
||||
)
|
||||
|
||||
# then
|
||||
response["resourceShare"]["name"].should.equal("test")
|
||||
|
||||
# share in an OU
|
||||
# when
|
||||
response = client.create_resource_share(
|
||||
name="test",
|
||||
principals=[ou_arn],
|
||||
resourceArns=[
|
||||
"arn:aws:ec2:us-east-1:{}:transit-gateway/tgw-123456789".format(ACCOUNT_ID)
|
||||
],
|
||||
)
|
||||
|
||||
# then
|
||||
response["resourceShare"]["name"].should.equal("test")
|
||||
|
||||
|
||||
@mock_ram
|
||||
@mock_organizations
|
||||
def test_create_resource_share_with_organization_errors():
|
||||
# given
|
||||
client = boto3.client("organizations", region_name="us-east-1")
|
||||
client.create_organization(FeatureSet="ALL")
|
||||
root_id = client.list_roots()["Roots"][0]["Id"]
|
||||
client.create_organizational_unit(ParentId=root_id, Name="test")
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# unknown Organization
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.create_resource_share(
|
||||
name="test",
|
||||
principals=[
|
||||
"arn:aws:organizations::{}:organization/o-unknown".format(ACCOUNT_ID)
|
||||
],
|
||||
resourceArns=[
|
||||
"arn:aws:ec2:us-east-1:{}:transit-gateway/tgw-123456789".format(
|
||||
ACCOUNT_ID
|
||||
)
|
||||
],
|
||||
)
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("CreateResourceShare")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("UnknownResourceException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"Organization o-unknown could not be found."
|
||||
)
|
||||
|
||||
# unknown OU
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.create_resource_share(
|
||||
name="test",
|
||||
principals=[
|
||||
"arn:aws:organizations::{}:ou/o-unknown/ou-unknown".format(ACCOUNT_ID)
|
||||
],
|
||||
resourceArns=[
|
||||
"arn:aws:ec2:us-east-1:{}:transit-gateway/tgw-123456789".format(
|
||||
ACCOUNT_ID
|
||||
)
|
||||
],
|
||||
)
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("CreateResourceShare")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("UnknownResourceException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"OrganizationalUnit ou-unknown in unknown organization could not be found."
|
||||
)
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_get_resource_shares():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
client.create_resource_share(name="test")
|
||||
|
||||
# when
|
||||
response = client.get_resource_shares(resourceOwner="SELF")
|
||||
|
||||
# then
|
||||
response["resourceShares"].should.have.length_of(1)
|
||||
resource = response["resourceShares"][0]
|
||||
resource["allowExternalPrincipals"].should.be.ok
|
||||
resource["creationTime"].should.be.a(datetime)
|
||||
resource["featureSet"].should.equal("STANDARD")
|
||||
resource["lastUpdatedTime"].should.be.a(datetime)
|
||||
resource["name"].should.equal("test")
|
||||
resource["owningAccountId"].should.equal(ACCOUNT_ID)
|
||||
resource["resourceShareArn"].should.match(
|
||||
r"arn:aws:ram:us-east-1:\d{12}:resource-share/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
)
|
||||
resource["status"].should.equal("ACTIVE")
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_get_resource_shares_errors():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# invalid resource owner
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.get_resource_shares(resourceOwner="invalid")
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("GetResourceShares")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("InvalidParameterException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"invalid is not a valid resource owner. "
|
||||
"Specify either SELF or OTHER-ACCOUNTS and try again."
|
||||
)
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_update_resource_share():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
arn = client.create_resource_share(name="test")["resourceShare"]["resourceShareArn"]
|
||||
|
||||
# when
|
||||
time.sleep(0.1)
|
||||
response = client.update_resource_share(resourceShareArn=arn, name="test-update")
|
||||
|
||||
# then
|
||||
resource = response["resourceShare"]
|
||||
resource["allowExternalPrincipals"].should.be.ok
|
||||
resource["name"].should.equal("test-update")
|
||||
resource["owningAccountId"].should.equal(ACCOUNT_ID)
|
||||
resource["resourceShareArn"].should.match(
|
||||
r"arn:aws:ram:us-east-1:\d{12}:resource-share/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
)
|
||||
resource["status"].should.equal("ACTIVE")
|
||||
resource.should_not.have.key("featureSet")
|
||||
creation_time = resource["creationTime"]
|
||||
resource["lastUpdatedTime"].should.be.greater_than(creation_time)
|
||||
|
||||
response = client.get_resource_shares(resourceOwner="SELF")
|
||||
response["resourceShares"].should.have.length_of(1)
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_update_resource_share_errors():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# invalid resource owner
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.update_resource_share(
|
||||
resourceShareArn="arn:aws:ram:us-east-1:{}:resource-share/not-existing".format(
|
||||
ACCOUNT_ID
|
||||
),
|
||||
name="test-update",
|
||||
)
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("UpdateResourceShare")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("UnknownResourceException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"ResourceShare arn:aws:ram:us-east-1:{}:resource-share/not-existing could not be found.".format(
|
||||
ACCOUNT_ID
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_delete_resource_share():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
arn = client.create_resource_share(name="test")["resourceShare"]["resourceShareArn"]
|
||||
|
||||
# when
|
||||
time.sleep(0.1)
|
||||
response = client.delete_resource_share(resourceShareArn=arn)
|
||||
|
||||
# then
|
||||
response["returnValue"].should.be.ok
|
||||
|
||||
response = client.get_resource_shares(resourceOwner="SELF")
|
||||
response["resourceShares"].should.have.length_of(1)
|
||||
resource = response["resourceShares"][0]
|
||||
resource["status"].should.equal("DELETED")
|
||||
creation_time = resource["creationTime"]
|
||||
resource["lastUpdatedTime"].should.be.greater_than(creation_time)
|
||||
|
||||
|
||||
@mock_ram
|
||||
def test_delete_resource_share_errors():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# invalid resource owner
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.delete_resource_share(
|
||||
resourceShareArn="arn:aws:ram:us-east-1:{}:resource-share/not-existing".format(
|
||||
ACCOUNT_ID
|
||||
)
|
||||
)
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("DeleteResourceShare")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("UnknownResourceException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"ResourceShare arn:aws:ram:us-east-1:{}:resource-share/not-existing could not be found.".format(
|
||||
ACCOUNT_ID
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@mock_ram
|
||||
@mock_organizations
|
||||
def test_enable_sharing_with_aws_organization():
|
||||
# given
|
||||
client = boto3.client("organizations", region_name="us-east-1")
|
||||
client.create_organization(FeatureSet="ALL")
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# when
|
||||
response = client.enable_sharing_with_aws_organization()
|
||||
|
||||
# then
|
||||
response["returnValue"].should.be.ok
|
||||
|
||||
|
||||
@mock_ram
|
||||
@mock_organizations
|
||||
def test_enable_sharing_with_aws_organization_errors():
|
||||
# given
|
||||
client = boto3.client("ram", region_name="us-east-1")
|
||||
|
||||
# no Organization defined
|
||||
# when
|
||||
with assert_raises(ClientError) as e:
|
||||
client.enable_sharing_with_aws_organization()
|
||||
ex = e.exception
|
||||
ex.operation_name.should.equal("EnableSharingWithAwsOrganization")
|
||||
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||
ex.response["Error"]["Code"].should.contain("OperationNotPermittedException")
|
||||
ex.response["Error"]["Message"].should.equal(
|
||||
"Unable to enable sharing with AWS Organizations. "
|
||||
"Received AccessDeniedException from AWSOrganizations with the following error message: "
|
||||
"You don't have permissions to access this resource."
|
||||
)
|
Loading…
Reference in New Issue
Block a user