From 45dbf9454e78c9327d75d3be579068e2c7ccd72c Mon Sep 17 00:00:00 2001 From: dandds Date: Sun, 9 Feb 2020 14:52:29 -0500 Subject: [PATCH 1/4] models for creating billing owner --- atst/domain/csp/cloud/azure_cloud_provider.py | 5 +++ atst/domain/csp/cloud/models.py | 40 +++++++++++++++++-- tests/domain/cloud/test_models.py | 35 ++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 00a107ca..9df8051a 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -18,6 +18,8 @@ from .models import ( ApplicationCSPResult, BillingInstructionCSPPayload, BillingInstructionCSPResult, + BillingOwnerCSPPayload, + BillingOwnerCSPResult, BillingProfileCreationCSPPayload, BillingProfileCreationCSPResult, BillingProfileTenantAccessCSPPayload, @@ -776,6 +778,9 @@ class AzureCloudProvider(CloudProviderInterface): if response.ok: return PrincipalAdminRoleCSPResult(**response.json()) + def create_billing_owner(self, payload: BillingOwnerCSPPayload): + pass + def force_tenant_admin_pw_update(self, creds, tenant_owner_id): # use creds to update to force password recovery? # not sure what the endpoint/method for this is, yet diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index ebf69a4c..140e39fc 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -487,10 +487,7 @@ class ProductPurchaseVerificationCSPResult(AliasModel): premium_purchase_date: str -class UserCSPPayload(BaseCSPPayload): - display_name: str - tenant_host_name: str - email: str +class UserMixin(BaseModel): password: Optional[str] @property @@ -506,6 +503,12 @@ class UserCSPPayload(BaseCSPPayload): return password or token_urlsafe(16) +class UserCSPPayload(BaseCSPPayload, UserMixin): + display_name: str + tenant_host_name: str + email: str + + class UserCSPResult(AliasModel): id: str @@ -554,3 +557,32 @@ class ReportingCSPPayload(BaseCSPPayload): return values except (KeyError, IndexError): raise ValueError("Invoice section ID not present in payload") + + +class BillingOwnerCSPPayload(BaseCSPPayload, UserMixin): + """ + This class needs to consume data in the shape it's in from the + top-level portfolio CSP data, but return it in the shape + needed for user provisioning. + """ + + first_name: str + last_name: str + domain_name: str + password_recovery_email_address: str + + @property + def display_name(self): + return f"{self.first_name} {self.last_name}" + + @property + def tenant_host_name(self): + return self.domain_name + + @property + def email(self): + return self.password_recovery_email_address + + +class BillingOwnerCSPResult(AliasModel): + id: str diff --git a/tests/domain/cloud/test_models.py b/tests/domain/cloud/test_models.py index 10c81293..c0aedd50 100644 --- a/tests/domain/cloud/test_models.py +++ b/tests/domain/cloud/test_models.py @@ -8,6 +8,7 @@ from atst.domain.csp.cloud.models import ( ManagementGroupCSPPayload, ManagementGroupCSPResponse, UserCSPPayload, + BillingOwnerCSPPayload, ) @@ -121,3 +122,37 @@ def test_UserCSPPayload_user_principal_name(): def test_UserCSPPayload_password(): payload = UserCSPPayload(**user_payload) assert payload.password + + +class TestBillingOwnerCSPPayload: + user_payload = { + "tenant_id": "123", + "first_name": "Han", + "last_name": "Solo", + "domain_name": "rebelalliance", + "password_recovery_email_address": "han@moseisley.cantina", + } + + def test_display_name(self): + payload = BillingOwnerCSPPayload(**self.user_payload) + assert payload.display_name == "Han Solo" + + def test_tenant_host_name(self): + payload = BillingOwnerCSPPayload(**self.user_payload) + assert payload.tenant_host_name == self.user_payload["domain_name"] + + def test_mail_nickname(self): + payload = BillingOwnerCSPPayload(**self.user_payload) + assert payload.mail_nickname == "han.solo" + + def test_password(self): + payload = BillingOwnerCSPPayload(**self.user_payload) + assert payload.password + + def test_user_principal_name(self): + payload = BillingOwnerCSPPayload(**self.user_payload) + assert payload.user_principal_name == f"han.solo@rebelalliance.onmicrosoft.com" + + def test_email(self): + payload = BillingOwnerCSPPayload(**self.user_payload) + assert payload.email == self.user_payload["password_recovery_email_address"] From 23aeb77821e257c5662829f36b70cdf20e8ce97e Mon Sep 17 00:00:00 2001 From: dandds Date: Sun, 9 Feb 2020 16:46:29 -0500 Subject: [PATCH 2/4] create billing owner --- atst/domain/csp/cloud/azure_cloud_provider.py | 66 +++++++++++++++++-- atst/domain/csp/cloud/models.py | 7 +- tests/domain/cloud/test_azure_csp.py | 44 +++++++++++++ tests/domain/cloud/test_models.py | 11 ++-- 4 files changed, 112 insertions(+), 16 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 9df8051a..5d6e91d8 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -779,7 +779,65 @@ class AzureCloudProvider(CloudProviderInterface): return PrincipalAdminRoleCSPResult(**response.json()) def create_billing_owner(self, payload: BillingOwnerCSPPayload): - pass + graph_token = self._get_tenant_principal_token( + payload.tenant_id, resource=self.graph_resource + ) + if graph_token is None: + raise AuthenticationException( + "Could not resolve graph token for tenant admin" + ) + + # Step 1: Create an AAD identity for the user + user_result = self._create_active_directory_user(graph_token, payload) + # Step 2: Set the recovery email + self._update_active_directory_user_email(graph_token, user_result.id, payload) + # Step 3: Find the Billing Administrator role ID + billing_admin_role_id = self._get_billing_owner_role(graph_token) + # Step 4: Assign the Billing Administrator role to the new user + self._assign_billing_owner_role( + graph_token, billing_admin_role_id, user_result.id + ) + + return BillingOwnerCSPResult(id=user_result.id) + + def _assign_billing_owner_role(self, graph_token, billing_admin_role_id, user_id): + request_body = { + "roleDefinitionId": billing_admin_role_id, + "principalId": user_id, + "resourceScope": "/", + } + + auth_header = { + "Authorization": f"Bearer {graph_token}", + } + + url = f"{self.graph_resource}/beta/roleManagement/directory/roleAssignments" + + response = self.sdk.requests.post(url, headers=auth_header, json=request_body) + + if response.ok: + return True + else: + raise UserProvisioningException("Could not assign billing admin role") + + def _get_billing_owner_role(self, graph_token): + auth_header = { + "Authorization": f"Bearer {graph_token}", + } + + url = f"{self.graph_resource}/v1.0/directoryRoles" + + response = self.sdk.requests.get(url, headers=auth_header) + + if response.ok: + result = response.json() + for role in result["value"]: + if role["displayName"] == "Billing Administrator": + return role["id"] + else: + raise UserProvisioningException( + "Could not find Billing Administrator role ID; role may not be enabled." + ) def force_tenant_admin_pw_update(self, creds, tenant_owner_id): # use creds to update to force password recovery? @@ -861,7 +919,7 @@ class AzureCloudProvider(CloudProviderInterface): return result - def _create_active_directory_user(self, graph_token, payload: UserCSPPayload): + def _create_active_directory_user(self, graph_token, payload): request_body = { "accountEnabled": True, "displayName": payload.display_name, @@ -886,9 +944,7 @@ class AzureCloudProvider(CloudProviderInterface): else: raise UserProvisioningException(f"Failed to create user: {response.json()}") - def _update_active_directory_user_email( - self, graph_token, user_id, payload: UserCSPPayload - ): + def _update_active_directory_user_email(self, graph_token, user_id, payload): request_body = {"otherMails": [payload.email]} auth_header = { diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 140e39fc..b4920856 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -566,15 +566,10 @@ class BillingOwnerCSPPayload(BaseCSPPayload, UserMixin): needed for user provisioning. """ - first_name: str - last_name: str + display_name = "billing_admin" domain_name: str password_recovery_email_address: str - @property - def display_name(self): - return f"{self.first_name} {self.last_name}" - @property def tenant_host_name(self): return self.domain_name diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 1d397257..d2ad9857 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -16,6 +16,7 @@ from atst.domain.csp.cloud.models import ( ApplicationCSPResult, BillingInstructionCSPPayload, BillingInstructionCSPResult, + BillingOwnerCSPPayload, BillingProfileCreationCSPPayload, BillingProfileCreationCSPResult, BillingProfileTenantAccessCSPPayload, @@ -988,3 +989,46 @@ def test_create_user_role_failure(mock_azure: AzureCloudProvider): with pytest.raises(UserProvisioningException): mock_azure.create_user_role(payload) + + +def test_create_billing_owner(mock_azure: AzureCloudProvider): + with patch.object( + AzureCloudProvider, + "_get_tenant_principal_token", + wraps=mock_azure._get_tenant_principal_token, + ) as _get_tenant_principal_token: + _get_tenant_principal_token.return_value = "token" + + final_result = "1-2-3" + + # create_billing_owner does: POST, PATCH, GET, POST + + def make_mock_result(return_value=None): + mock_result_create = Mock() + mock_result_create.ok = True + mock_result_create.json.return_value = return_value + + return mock_result_create + + post_results = [make_mock_result({"id": final_result}), make_mock_result()] + + mock_post = lambda *a, **k: post_results.pop(0) + + # mock POST so that it pops off results in the order we want + mock_azure.sdk.requests.post = mock_post + # return value for PATCH doesn't matter much + mock_azure.sdk.requests.patch.return_value = make_mock_result() + # return value for GET needs to be a JSON object with a list of role definitions + mock_azure.sdk.requests.get.return_value = make_mock_result( + {"value": [{"displayName": "Billing Administrator", "id": "4567"}]} + ) + + payload = BillingOwnerCSPPayload( + tenant_id=uuid4().hex, + domain_name="rebelalliance", + password_recovery_email_address="many@bothans.org", + ) + + result = mock_azure.create_billing_owner(payload) + + assert result.id == final_result diff --git a/tests/domain/cloud/test_models.py b/tests/domain/cloud/test_models.py index c0aedd50..ee352d0a 100644 --- a/tests/domain/cloud/test_models.py +++ b/tests/domain/cloud/test_models.py @@ -127,15 +127,13 @@ def test_UserCSPPayload_password(): class TestBillingOwnerCSPPayload: user_payload = { "tenant_id": "123", - "first_name": "Han", - "last_name": "Solo", "domain_name": "rebelalliance", "password_recovery_email_address": "han@moseisley.cantina", } def test_display_name(self): payload = BillingOwnerCSPPayload(**self.user_payload) - assert payload.display_name == "Han Solo" + assert payload.display_name == "billing_admin" def test_tenant_host_name(self): payload = BillingOwnerCSPPayload(**self.user_payload) @@ -143,7 +141,7 @@ class TestBillingOwnerCSPPayload: def test_mail_nickname(self): payload = BillingOwnerCSPPayload(**self.user_payload) - assert payload.mail_nickname == "han.solo" + assert payload.mail_nickname == "billing_admin" def test_password(self): payload = BillingOwnerCSPPayload(**self.user_payload) @@ -151,7 +149,10 @@ class TestBillingOwnerCSPPayload: def test_user_principal_name(self): payload = BillingOwnerCSPPayload(**self.user_payload) - assert payload.user_principal_name == f"han.solo@rebelalliance.onmicrosoft.com" + assert ( + payload.user_principal_name + == f"billing_admin@rebelalliance.onmicrosoft.com" + ) def test_email(self): payload = BillingOwnerCSPPayload(**self.user_payload) From 80894a5fb9a4e2f64535c8f316ca627e1e3c2032 Mon Sep 17 00:00:00 2001 From: dandds Date: Sun, 9 Feb 2020 16:52:29 -0500 Subject: [PATCH 3/4] add to mock and FSMstates --- atst/domain/csp/cloud/azure_cloud_provider.py | 2 +- atst/domain/csp/cloud/mock_cloud_provider.py | 9 +++++++++ atst/domain/csp/cloud/models.py | 2 +- atst/models/mixins/state_machines.py | 1 + tests/domain/test_portfolio_state_machine.py | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 5d6e91d8..5d9ca275 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -798,7 +798,7 @@ class AzureCloudProvider(CloudProviderInterface): graph_token, billing_admin_role_id, user_result.id ) - return BillingOwnerCSPResult(id=user_result.id) + return BillingOwnerCSPResult(billing_owner_id=user_result.id) def _assign_billing_owner_role(self, graph_token, billing_admin_role_id, user_id): request_body = { diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 7ec0636f..3454ec74 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -18,6 +18,8 @@ from .models import ( ApplicationCSPResult, BillingInstructionCSPPayload, BillingInstructionCSPResult, + BillingOwnerCSPPayload, + BillingOwnerCSPResult, BillingProfileCreationCSPPayload, BillingProfileCreationCSPResult, BillingProfileTenantAccessCSPResult, @@ -366,6 +368,13 @@ class MockCloudProvider(CloudProviderInterface): return PrincipalAdminRoleCSPResult(**dict(id="principal_assignment_id")) + def create_billing_owner(self, payload: BillingOwnerCSPPayload): + 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 BillingOwnerCSPResult(billing_owner_id="foo") + def create_or_update_user(self, auth_credentials, user_info, csp_role_id): self._authorize(auth_credentials) diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index b4920856..4656c432 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -580,4 +580,4 @@ class BillingOwnerCSPPayload(BaseCSPPayload, UserMixin): class BillingOwnerCSPResult(AliasModel): - id: str + billing_owner_id: str diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index a7edf268..8342853a 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -26,6 +26,7 @@ class AzureStages(Enum): PRINCIPAL_ADMIN_ROLE = "tenant principal admin" TENANT_ADMIN_OWNERSHIP = "tenant admin ownership" TENANT_PRINCIPAL_OWNERSHIP = "tenant principial ownership" + BILLING_OWNER = "billing owner" def _build_csp_states(csp_stages): diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index 2c27cfd8..5c7ca25c 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -113,6 +113,7 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): FSMStates.PRINCIPAL_ADMIN_ROLE_CREATED, FSMStates.TENANT_ADMIN_OWNERSHIP_CREATED, FSMStates.TENANT_PRINCIPAL_OWNERSHIP_CREATED, + FSMStates.BILLING_OWNER_CREATED, ] if portfolio.csp_data is not None: From ceb8cdc198253b1286b47bc3aa22bc3ea2acb3c2 Mon Sep 17 00:00:00 2001 From: dandds Date: Tue, 11 Feb 2020 15:06:09 -0500 Subject: [PATCH 4/4] update state machine enum --- ...7e00_add_billing_owner_to_state_machine.py | 289 ++++++++++++++++++ tests/domain/cloud/test_azure_csp.py | 2 +- 2 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 alembic/versions/9f2813487e00_add_billing_owner_to_state_machine.py diff --git a/alembic/versions/9f2813487e00_add_billing_owner_to_state_machine.py b/alembic/versions/9f2813487e00_add_billing_owner_to_state_machine.py new file mode 100644 index 00000000..8acc7616 --- /dev/null +++ b/alembic/versions/9f2813487e00_add_billing_owner_to_state_machine.py @@ -0,0 +1,289 @@ +"""add billing owner to state machine + +Revision ID: 9f2813487e00 +Revises: 418b52c1cedf +Create Date: 2020-02-11 14:56:26.886945 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '9f2813487e00' # pragma: allowlist secret +down_revision = '418b52c1cedf' # pragma: allowlist secret +branch_labels = None +depends_on = None + + +def upgrade(): + 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", + "BILLING_OWNER_CREATED", + "BILLING_OWNER_IN_PROGRESS", + "BILLING_OWNER_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, + ) + + +def downgrade(): + 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", + "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", + "BILLING_OWNER_CREATED", + "BILLING_OWNER_IN_PROGRESS", + "BILLING_OWNER_FAILED", + name="fsmstates", + native_enum=False, + ), + existing_nullable=False, + ) diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 4c3ea545..518af459 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -1077,7 +1077,7 @@ def test_create_billing_owner(mock_azure: AzureCloudProvider): result = mock_azure.create_billing_owner(payload) - assert result.id == final_result + assert result.billing_owner_id == final_result def test_update_tenant_creds(mock_azure: AzureCloudProvider):