diff --git a/alembic/versions/542bd3215dec_state_machine_stage_added.py b/alembic/versions/542bd3215dec_state_machine_stage_added.py new file mode 100644 index 00000000..8a3410af --- /dev/null +++ b/alembic/versions/542bd3215dec_state_machine_stage_added.py @@ -0,0 +1,264 @@ +"""state machine stage added. + +Revision ID: 542bd3215dec +Revises: 567bfb019a87 +Create Date: 2020-02-06 12:01:58.077840 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "542bd3215dec" # pragma: allowlist secret +down_revision = "567bfb019a87" # pragma: allowlist secret +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "portfolio_state_machines", + "state", + type_=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "PRODUCT_PURCHASE_CREATED", + "PRODUCT_PURCHASE_IN_PROGRESS", + "PRODUCT_PURCHASE_FAILED", + "PRODUCT_PURCHASE_VERIFICATION_CREATED", + "PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS", + "PRODUCT_PURCHASE_VERIFICATION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "INITIAL_MGMT_GROUP_CREATED", + "INITIAL_MGMT_GROUP_IN_PROGRESS", + "INITIAL_MGMT_GROUP_FAILED", + "INITIAL_MGMT_GROUP_VERIFICATION_CREATED", + "INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS", + "INITIAL_MGMT_GROUP_VERIFICATION_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_type=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_nullable=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "portfolio_state_machines", + "state", + type_=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_type=sa.Enum( + "UNSTARTED", + "STARTING", + "STARTED", + "COMPLETED", + "FAILED", + "TENANT_CREATED", + "TENANT_IN_PROGRESS", + "TENANT_FAILED", + "BILLING_PROFILE_CREATION_CREATED", + "BILLING_PROFILE_CREATION_IN_PROGRESS", + "BILLING_PROFILE_CREATION_FAILED", + "BILLING_PROFILE_VERIFICATION_CREATED", + "BILLING_PROFILE_VERIFICATION_IN_PROGRESS", + "BILLING_PROFILE_VERIFICATION_FAILED", + "BILLING_PROFILE_TENANT_ACCESS_CREATED", + "BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS", + "BILLING_PROFILE_TENANT_ACCESS_FAILED", + "TASK_ORDER_BILLING_CREATION_CREATED", + "TASK_ORDER_BILLING_CREATION_IN_PROGRESS", + "TASK_ORDER_BILLING_CREATION_FAILED", + "TASK_ORDER_BILLING_VERIFICATION_CREATED", + "TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS", + "TASK_ORDER_BILLING_VERIFICATION_FAILED", + "BILLING_INSTRUCTION_CREATED", + "BILLING_INSTRUCTION_IN_PROGRESS", + "BILLING_INSTRUCTION_FAILED", + "PRODUCT_PURCHASE_CREATED", + "PRODUCT_PURCHASE_IN_PROGRESS", + "PRODUCT_PURCHASE_FAILED", + "PRODUCT_PURCHASE_VERIFICATION_CREATED", + "PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS", + "PRODUCT_PURCHASE_VERIFICATION_FAILED", + "TENANT_PRINCIPAL_APP_CREATED", + "TENANT_PRINCIPAL_APP_IN_PROGRESS", + "TENANT_PRINCIPAL_APP_FAILED", + "TENANT_PRINCIPAL_CREATED", + "TENANT_PRINCIPAL_IN_PROGRESS", + "TENANT_PRINCIPAL_FAILED", + "TENANT_PRINCIPAL_CREDENTIAL_CREATED", + "TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS", + "TENANT_PRINCIPAL_CREDENTIAL_FAILED", + "ADMIN_ROLE_DEFINITION_CREATED", + "ADMIN_ROLE_DEFINITION_IN_PROGRESS", + "ADMIN_ROLE_DEFINITION_FAILED", + "PRINCIPAL_ADMIN_ROLE_CREATED", + "PRINCIPAL_ADMIN_ROLE_IN_PROGRESS", + "PRINCIPAL_ADMIN_ROLE_FAILED", + "INITIAL_MGMT_GROUP_CREATED", + "INITIAL_MGMT_GROUP_IN_PROGRESS", + "INITIAL_MGMT_GROUP_FAILED", + "INITIAL_MGMT_GROUP_VERIFICATION_CREATED", + "INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS", + "INITIAL_MGMT_GROUP_VERIFICATION_FAILED", + "TENANT_ADMIN_OWNERSHIP_CREATED", + "TENANT_ADMIN_OWNERSHIP_IN_PROGRESS", + "TENANT_ADMIN_OWNERSHIP_FAILED", + "TENANT_PRINCIPAL_OWNERSHIP_CREATED", + "TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS", + "TENANT_PRINCIPAL_OWNERSHIP_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_nullable=False, + ) + diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 425fe649..acaef28a 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -24,6 +24,10 @@ from .models import ( BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, CostManagementQueryCSPResult, + InitialMgmtGroupCSPPayload, + InitialMgmtGroupCSPResult, + InitialMgmtGroupVerificationCSPPayload, + InitialMgmtGroupVerificationCSPResult, EnvironmentCSPPayload, EnvironmentCSPResult, KeyVaultCredentials, @@ -188,6 +192,38 @@ class AzureCloudProvider(CloudProviderInterface): return ApplicationCSPResult(**response) + def create_initial_mgmt_group(self, payload: InitialMgmtGroupCSPPayload): + creds = self._source_creds(payload.tenant_id) + credentials = self._get_credential_obj( + { + "client_id": creds.root_sp_client_id, + "secret_key": creds.root_sp_key, + "tenant_id": creds.root_tenant_id, + }, + resource=self.sdk.cloud.endpoints.resource_manager, + ) + response = self._create_management_group( + credentials, payload.management_group_name, payload.display_name, + ) + + return InitialMgmtGroupCSPResult(**response) + + def create_initial_mgmt_group_verification( + self, payload: InitialMgmtGroupVerificationCSPPayload + ): + creds = self._source_creds(payload.tenant_id) + credentials = self._get_credential_obj( + { + "client_id": creds.root_sp_client_id, + "secret_key": creds.root_sp_key, + "tenant_id": creds.root_tenant_id, + }, + resource=self.sdk.cloud.endpoints.resource_manager, + ) + + response = self._get_management_group(credentials, payload.tenant_id,) + return InitialMgmtGroupVerificationCSPResult(**response.result()) + def _create_management_group( self, credentials, management_group_id, display_name, parent_id=None, ): @@ -214,6 +250,11 @@ class AzureCloudProvider(CloudProviderInterface): # instead? return create_request.result() + def _get_management_group(self, credentials, management_group_id): + mgmgt_group_client = self.sdk.managementgroups.ManagementGroupsAPI(credentials) + response = mgmgt_group_client.management_groups.get(management_group_id) + return response + def _create_policy_definition( self, credentials, subscription_id, management_group_id, properties, ): diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index da1cccd8..b33600ae 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -24,6 +24,10 @@ from .models import ( BillingProfileTenantAccessCSPResult, BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, + InitialMgmtGroupCSPPayload, + InitialMgmtGroupCSPResult, + InitialMgmtGroupVerificationCSPPayload, + InitialMgmtGroupVerificationCSPResult, CostManagementQueryCSPResult, CostManagementQueryProperties, ProductPurchaseCSPPayload, @@ -281,6 +285,29 @@ class MockCloudProvider(CloudProviderInterface): } ) + def create_initial_mgmt_group(self, payload: InitialMgmtGroupCSPPayload): + self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) + self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) + self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) + + return InitialMgmtGroupCSPResult( + id=f"{AZURE_MGMNT_PATH}{payload.management_group_name}", + ) + + def create_initial_mgmt_group_verification( + self, payload: InitialMgmtGroupVerificationCSPPayload + ): + self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) + self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) + self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) + + return InitialMgmtGroupVerificationCSPResult( + **dict( + id="Test Id" + # id=f"{AZURE_MGMNT_PATH}{payload.management_group_name}" + ) + ) + def create_product_purchase(self, payload: ProductPurchaseCSPPayload): self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION) self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION) diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 25521bb9..5ac784bc 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -320,7 +320,7 @@ class ManagementGroupCSPPayload(AliasModel): tenant_id: str management_group_name: Optional[str] display_name: str - parent_id: str + parent_id: Optional[str] @validator("management_group_name", pre=True, always=True) def supply_management_group_name_default(cls, name): @@ -340,16 +340,25 @@ class ManagementGroupCSPPayload(AliasModel): @validator("parent_id", pre=True, always=True) def enforce_parent_id_pattern(cls, id_): - if AZURE_MGMNT_PATH not in id_: - return f"{AZURE_MGMNT_PATH}{id_}" - else: - return id_ + if id_: + if AZURE_MGMNT_PATH not in id_: + return f"{AZURE_MGMNT_PATH}{id_}" + else: + return id_ class ManagementGroupCSPResponse(AliasModel): id: str +class ManagementGroupGetCSPPayload(BaseCSPPayload): + management_group_name: str + + +class ManagementGroupGetCSPResponse(AliasModel): + id: str + + class ApplicationCSPPayload(ManagementGroupCSPPayload): pass @@ -358,6 +367,22 @@ class ApplicationCSPResult(ManagementGroupCSPResponse): pass +class InitialMgmtGroupCSPPayload(ManagementGroupCSPPayload): + pass + + +class InitialMgmtGroupCSPResult(ManagementGroupCSPResponse): + pass + + +class InitialMgmtGroupVerificationCSPPayload(ManagementGroupGetCSPPayload): + pass + + +class InitialMgmtGroupVerificationCSPResult(ManagementGroupGetCSPResponse): + pass + + class EnvironmentCSPPayload(ManagementGroupCSPPayload): pass diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index a7edf268..64355d54 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -24,6 +24,8 @@ class AzureStages(Enum): TENANT_PRINCIPAL_CREDENTIAL = "tenant principal credential" ADMIN_ROLE_DEFINITION = "admin role definition" PRINCIPAL_ADMIN_ROLE = "tenant principal admin" + INITIAL_MGMT_GROUP = "initial management group" + INITIAL_MGMT_GROUP_VERIFICATION = "initial management group verification" TENANT_ADMIN_OWNERSHIP = "tenant admin ownership" TENANT_PRINCIPAL_OWNERSHIP = "tenant principial ownership" diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index f5c1a461..4098c06c 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -155,37 +155,38 @@ class PortfolioStateMachine( print(exc.json()) app.logger.info(payload) self.fail_stage(stage) + else: + # TODO: Determine best place to do this, maybe @reconstructor + self.csp = app.csp.cloud - # TODO: Determine best place to do this, maybe @reconstructor - self.csp = app.csp.cloud + try: + func_name = f"create_{stage}" + response = getattr(self.csp, func_name)(payload_data) + if self.portfolio.csp_data is None: + self.portfolio.csp_data = {} + self.portfolio.csp_data.update(response.dict()) + db.session.add(self.portfolio) + db.session.commit() + except PydanticValidationError as exc: + app.logger.error( + f"Failed to cast response to valid result class {self.__repr__()}:", + exc_info=1, + ) + app.logger.info(exc.json()) + print(exc.json()) + app.logger.info(payload_data) + # TODO: Ensure that failing the stage does not preclude a Celery retry + self.fail_stage(stage) + # TODO: catch and handle general CSP exception here + except (ConnectionException, UnknownServerException) as exc: + app.logger.error( + f"CSP api call. Caught exception for {self.__repr__()}.", + exc_info=1, + ) + # TODO: Ensure that failing the stage does not preclude a Celery retry + self.fail_stage(stage) - try: - func_name = f"create_{stage}" - response = getattr(self.csp, func_name)(payload_data) - if self.portfolio.csp_data is None: - self.portfolio.csp_data = {} - self.portfolio.csp_data.update(response.dict()) - db.session.add(self.portfolio) - db.session.commit() - except PydanticValidationError as exc: - app.logger.error( - f"Failed to cast response to valid result class {self.__repr__()}:", - exc_info=1, - ) - app.logger.info(exc.json()) - print(exc.json()) - app.logger.info(payload_data) - # TODO: Ensure that failing the stage does not preclude a Celery retry - self.fail_stage(stage) - # TODO: catch and handle general CSP exception here - except (ConnectionException, UnknownServerException) as exc: - app.logger.error( - f"CSP api call. Caught exception for {self.__repr__()}.", exc_info=1, - ) - # TODO: Ensure that failing the stage does not preclude a Celery retry - self.fail_stage(stage) - - self.finish_stage(stage) + self.finish_stage(stage) def is_csp_data_valid(self, event): """ diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index c89e6a77..e0b44925 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -22,6 +22,10 @@ from atst.domain.csp.cloud.models import ( BillingProfileTenantAccessCSPResult, BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, + InitialMgmtGroupCSPPayload, + InitialMgmtGroupCSPResult, + InitialMgmtGroupVerificationCSPPayload, + InitialMgmtGroupVerificationCSPResult, CostManagementQueryCSPResult, EnvironmentCSPPayload, EnvironmentCSPResult, @@ -65,6 +69,12 @@ def mock_management_group_create(mock_azure, spec_dict): ) +def mock_management_group_get(mock_azure, spec_dict): + mock_azure.sdk.managementgroups.ManagementGroupsAPI.return_value.management_groups.get.return_value.result.return_value = ( + spec_dict + ) + + def test_create_environment_succeeds(mock_azure: AzureCloudProvider): environment = EnvironmentFactory.create() mock_management_group_create(mock_azure, {"id": "Test Id"}) @@ -109,6 +119,41 @@ def test_create_application_succeeds(mock_azure: AzureCloudProvider): assert result.id == "Test Id" +def test_create_initial_mgmt_group_succeeds(mock_azure: AzureCloudProvider): + application = ApplicationFactory.create() + mock_management_group_create(mock_azure, {"id": "Test Id"}) + mock_azure = mock_get_secret(mock_azure) + + payload = InitialMgmtGroupCSPPayload( + tenant_id="1234", + display_name=application.name, + management_group_name=str(uuid4()), + ) + result: InitialMgmtGroupCSPResult = mock_azure.create_initial_mgmt_group(payload) + + assert result.id == "Test Id" + + +def test_create_initial_mgmt_group_verification_succeeds( + mock_azure: AzureCloudProvider, +): + application = ApplicationFactory.create() + mock_management_group_get(mock_azure, {"id": "Test Id"}) + mock_azure = mock_get_secret(mock_azure) + + management_group_name = str(uuid4()) + + payload = InitialMgmtGroupVerificationCSPPayload( + tenant_id="1234", management_group_name=management_group_name + ) + result: InitialMgmtGroupVerificationCSPResult = mock_azure.create_initial_mgmt_group_verification( + payload + ) + + assert result.id == "Test Id" + # assert result.name == management_group_name + + def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider): subscription_id = str(uuid4()) management_group_id = str(uuid4()) diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index 2c27cfd8..2c8796d5 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -111,6 +111,8 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): FSMStates.TENANT_PRINCIPAL_CREDENTIAL_CREATED, FSMStates.ADMIN_ROLE_DEFINITION_CREATED, FSMStates.PRINCIPAL_ADMIN_ROLE_CREATED, + FSMStates.INITIAL_MGMT_GROUP_CREATED, + FSMStates.INITIAL_MGMT_GROUP_VERIFICATION_CREATED, FSMStates.TENANT_ADMIN_OWNERSHIP_CREATED, FSMStates.TENANT_PRINCIPAL_OWNERSHIP_CREATED, ] @@ -131,10 +133,12 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): "user_id": user_id, "password": "jklfsdNCVD83nklds2#202", # pragma: allowlist secret "domain_name": domain_name, + "display_name": "mgmt group display name", + "management_group_name": "mgmt-group-uuid", "first_name": ppoc.first_name, "last_name": ppoc.last_name, "country_code": "US", - "password_recovery_email_address": ppoc.email, + "password_recovery_email_address": "email@example.com", # ppoc.email, "address": { # TODO: TBD if we're sourcing this from data or config "company_name": "", "address_line_1": "",