diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 837850ac6..7b4bc5e4d 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3147,13 +3147,13 @@ - [ ] update_server - [ ] update_server_engine_attributes -## organizations - 8% implemented +## organizations - 19% implemented - [ ] accept_handshake - [ ] attach_policy - [ ] cancel_handshake - [X] create_account - [X] create_organization -- [ ] create_organizational_unit +- [X] create_organizational_unit - [ ] create_policy - [ ] decline_handshake - [ ] delete_organization @@ -3163,7 +3163,7 @@ - [ ] describe_create_account_status - [ ] describe_handshake - [X] describe_organization -- [ ] describe_organizational_unit +- [X] describe_organizational_unit - [ ] describe_policy - [ ] detach_policy - [ ] disable_aws_service_access @@ -3184,7 +3184,7 @@ - [ ] list_parents - [ ] list_policies - [ ] list_policies_for_target -- [ ] list_roots +- [X] list_roots - [ ] list_targets_for_policy - [ ] move_account - [ ] remove_account_from_organization diff --git a/moto/organizations/models.py b/moto/organizations/models.py index 265654030..461072ce3 100644 --- a/moto/organizations/models.py +++ b/moto/organizations/models.py @@ -12,6 +12,7 @@ ORGANIZATION_ARN_FORMAT = 'arn:aws:organizations::{0}:organization/{1}' MASTER_ACCOUNT_ARN_FORMAT = 'arn:aws:organizations::{0}:account/{1}/{0}' ACCOUNT_ARN_FORMAT = 'arn:aws:organizations::{0}:account/{1}/{2}' ROOT_ARN_FORMAT = 'arn:aws:organizations::{0}:root/{1}/{2}' +OU_ARN_FORMAT = 'arn:aws:organizations::{0}:ou/{1}/{2}' class FakeOrganization(BaseModel): @@ -95,32 +96,6 @@ class FakeAccount(BaseModel): } -class FakeOrganizationalUnit(BaseModel): - - def __init__(self, organization, **kwargs): - self.organization_id = organization.id - self.master_account_id = organization.master_account_id - self.id = utils.make_random_ou_id() - self.name = kwargs['Name'] - - @property - def arn(self): - return OU_ARN_FORMAT.format( - self.master_account_id, - self.organization_id, - self.id - ) - - def describe(self): - return { - 'OrganizationalUnit': { - 'Id': self.id, - 'Arn': self.arn, - 'Name': self.name, - } - } - - class FakeRoot(BaseModel): def __init__(self, organization, **kwargs): @@ -150,12 +125,40 @@ class FakeRoot(BaseModel): } + +class FakeOrganizationalUnit(BaseModel): + + def __init__(self, organization, root_id, **kwargs): + self.organization_id = organization.id + self.master_account_id = organization.master_account_id + self.id = utils.make_random_ou_id(root_id) + self.name = kwargs['Name'] + self.parent_id = kwargs['ParentId'] + + @property + def arn(self): + return OU_ARN_FORMAT.format( + self.master_account_id, + self.organization_id, + self.id + ) + + def describe(self): + return { + 'OrganizationalUnit': { + 'Id': self.id, + 'Arn': self.arn, + 'Name': self.name, + } + } + class OrganizationsBackend(BaseBackend): def __init__(self): self.org = None self.accounts = [] self.roots = [] + self.ou = [] def create_organization(self, **kwargs): self.org = FakeOrganization(kwargs['FeatureSet']) @@ -170,14 +173,27 @@ class OrganizationsBackend(BaseBackend): Roots=[root.describe() for root in self.roots] ) + def create_organizational_unit(self, **kwargs): + new_ou = FakeOrganizationalUnit(self.org, self.roots[0].id, **kwargs) + self.ou.append(new_ou) + return new_ou.describe() + + def describe_organizational_unit(self, **kwargs): + ou = [ + ou for ou in self.ou if ou.id == kwargs['OrganizationalUnitId'] + ].pop(0) + return ou.describe() + def create_account(self, **kwargs): new_account = FakeAccount(self.org, **kwargs) self.accounts.append(new_account) return new_account.create_account_status def describe_account(self, **kwargs): - account = [account for account in self.accounts - if account.account_id == kwargs['AccountId']][0] + account = [ + account for account in self.accounts + if account.account_id == kwargs['AccountId'] + ].pop(0) return account.describe() def list_accounts(self): diff --git a/moto/organizations/responses.py b/moto/organizations/responses.py index 1804a3fcf..4f0643cf6 100644 --- a/moto/organizations/responses.py +++ b/moto/organizations/responses.py @@ -36,6 +36,16 @@ class OrganizationsResponse(BaseResponse): self.organizations_backend.list_roots() ) + def create_organizational_unit(self): + return json.dumps( + self.organizations_backend.create_organizational_unit(**self.request_params) + ) + + def describe_organizational_unit(self): + return json.dumps( + self.organizations_backend.describe_organizational_unit(**self.request_params) + ) + def create_account(self): return json.dumps( self.organizations_backend.create_account(**self.request_params) diff --git a/tests/test_organizations/test_organizations_boto3.py b/tests/test_organizations/test_organizations_boto3.py index f4dc3b445..0e398bd43 100644 --- a/tests/test_organizations/test_organizations_boto3.py +++ b/tests/test_organizations/test_organizations_boto3.py @@ -13,10 +13,12 @@ from moto.organizations.models import ( MASTER_ACCOUNT_ARN_FORMAT, ACCOUNT_ARN_FORMAT, ROOT_ARN_FORMAT, + OU_ARN_FORMAT, ) from .test_organizations_utils import ( ORG_ID_REGEX, ROOT_ID_REGEX, + OU_ID_REGEX, ACCOUNT_ID_REGEX, CREATE_ACCOUNT_STATUS_ID_REGEX, ) @@ -53,6 +55,18 @@ def validate_organization(response): }]) +def validate_organizationa_unit(org, response): + response.should.have.key('OrganizationalUnit').should.be.a(dict) + ou = response['OrganizationalUnit'] + ou.should.have.key('Id').should.match(OU_ID_REGEX) + ou.should.have.key('Arn').should.equal(OU_ARN_FORMAT.format( + org['MasterAccountId'], + org['Id'], + ou['Id'], + )) + ou.should.have.key('Name').should.equal(ou_name) + + def validate_account(org, account): sorted(account.keys()).should.equal([ 'Arn', @@ -113,6 +127,9 @@ def test_describe_organization(): #assert False +# Organizational Units +ou_name = 'ou01' + @mock_organizations def test_list_roots(): client = boto3.client('organizations', region_name='us-east-1') @@ -135,6 +152,38 @@ def test_list_roots(): #assert False +@mock_organizations +def test_create_organizational_unit(): + client = boto3.client('organizations', region_name='us-east-1') + org = client.create_organization(FeatureSet='ALL')['Organization'] + root_id = client.list_roots()['Roots'][0]['Id'] + response = client.create_organizational_unit( + ParentId=root_id, + Name=ou_name, + ) + #print(yaml.dump(response, default_flow_style=False)) + validate_organizationa_unit(org, response) + #assert False + + +@mock_organizations +def test_describe_organizational_unit(): + client = boto3.client('organizations', region_name='us-east-1') + org = client.create_organization(FeatureSet='ALL')['Organization'] + root_id = client.list_roots()['Roots'][0]['Id'] + ou_id = client.create_organizational_unit( + ParentId=root_id, + Name=ou_name, + )['OrganizationalUnit']['Id'] + response = client.describe_organizational_unit( + OrganizationalUnitId=ou_id, + ) + print(yaml.dump(response, default_flow_style=False)) + validate_organizationa_unit(org, response) + #assert False + + +# Accounts mockname = 'mock-account' mockdomain = 'moto-example.org' mockemail = '@'.join([mockname, mockdomain])