diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py index c6afb6d20..7a03cb7be 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py @@ -479,23 +479,37 @@ def get_ou_id(self, ou_path, parent_ou_id=None): parent_ou_id = self.root_id # Parse ou_path and find the ID - ou_hierarchy = ou_path.strip("/").split("/") - hierarchy_index = 0 + ou_path_as_list = ou_path.strip('/').split('/') - while hierarchy_index < len(ou_hierarchy): + for ou in ou_path_as_list: org_units = self.list_organizational_units_for_parent(parent_ou_id) - for ou in org_units: - if ou["Name"] == ou_hierarchy[hierarchy_index]: - parent_ou_id = ou["Id"] - hierarchy_index += 1 + + for org_unit in org_units: + if org_unit["Name"] == ou: + parent_ou_id = org_unit["Id"] break else: - raise ValueError( - f"Could not find ou with name {ou_hierarchy} in OU list {org_units}.", - ) + LOGGER.info( + 'No OU found with name {%s} and parent {%s}, will create.', ou, parent_ou_id + ) + new_ou = self.create_ou(parent_ou_id, ou) + parent_ou_id = new_ou['OrganizationalUnit']['Id'] return parent_ou_id + def create_ou(self, parent_ou_id, name): + try: + ou = self.client.create_organizational_unit( + ParentId=parent_ou_id, + Name=name + ) + except ClientError as client_err: + message = f'Failed to create OU called {name}, with parent {parent_ou_id}' + LOGGER.exception(message, client_err) + raise OrganizationsException(message) from client_err + return ou + + def move_account(self, account_id, ou_path): self.root_id = self.get_ou_root_id() ou_id = self.get_ou_id(ou_path) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/stubs/stub_organizations.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/stubs/stub_organizations.py index 371941cb6..e2508e004 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/stubs/stub_organizations.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/stubs/stub_organizations.py @@ -62,3 +62,24 @@ # adding dependency on datetime } } + +create_organizational_unit = { + 'OrganizationalUnit': { + 'Id': 'new_ou_id', + 'Arn': 'new_ou_arn', + 'Name': 'new_ou_name' + } +} + +list_organizational_units_for_parent = [ + { + 'OrganizationalUnits': [ + { + 'Id': 'existing_id', + 'Arn': 'some_ou_arn', + 'Name': 'existing' + }, + ], + 'NextToken': 'string' + } +] diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/test_organizations.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/test_organizations.py index 65dc72b83..ba775b0fd 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/test_organizations.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/test_organizations.py @@ -7,12 +7,13 @@ import os import boto3 -from pytest import fixture +from pytest import fixture, raises from stubs import stub_organizations from mock import Mock, patch from cache import Cache from organizations import Organizations, OrganizationsException from botocore.stub import Stubber +from botocore.exceptions import ClientError import unittest @@ -21,6 +22,32 @@ def cls(): return Organizations(boto3, "123456789012") +def test_create_ou(cls): + cls.client = Mock() + cls.client.create_organizational_unit.return_value = stub_organizations.create_organizational_unit + + ou = cls.create_ou("some_parent_id", "some_ou_name") + + assert ou['OrganizationalUnit']["Id"] == "new_ou_id" + assert ou['OrganizationalUnit']["Name"] == "new_ou_name" + +def test_create_ou_throws_client_error(cls): + cls.client = Mock() + cls.client.create_organizational_unit.side_effect = ClientError(operation_name='test', error_response={'Error': {'Code': 'Test', 'Message': 'Test Message'}}) + with raises(OrganizationsException): + cls.create_ou("some_parent_id", "some_ou_name") + + +def test_get_ou_id_can_create_ou_one_layer(cls): + cls.client = Mock() + cls.client.create_organizational_unit.return_value = stub_organizations.create_organizational_unit + cls.client.get_paginator("list_organizational_units_for_parent").paginate.return_value = stub_organizations.list_organizational_units_for_parent + + ou_id = cls.get_ou_id("/existing/new") + + assert ou_id == "new_ou_id" + + def test_get_parent_info(cls): cls.client = Mock() cls.client.list_parents.return_value = stub_organizations.list_parents diff --git a/src/template.yml b/src/template.yml index f772b75dd..fcbab4c96 100644 --- a/src/template.yml +++ b/src/template.yml @@ -1256,6 +1256,7 @@ Resources: - "logs:PutLogEvents" - "organizations:AttachPolicy" - "organizations:CreatePolicy" + - "organizations:CreateOrganizationalUnit" - "organizations:DeletePolicy" - "organizations:DescribeAccount" - "organizations:DescribeOrganization"