From 4da68a1fabef2a44771facd4fef51f23e735d0a6 Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 5 Feb 2020 15:56:53 -0500 Subject: [PATCH] WIP: orchestration for env role creation --- atst/domain/csp/cloud/azure_cloud_provider.py | 14 +---- .../csp/cloud/cloud_provider_interface.py | 2 +- atst/domain/csp/cloud/models.py | 13 ++-- atst/domain/environment_roles.py | 4 +- atst/jobs.py | 59 ++++++++++++++++++- atst/queue.py | 4 ++ tests/domain/test_environment_roles.py | 6 +- tests/test_jobs.py | 41 ++++++++++++- 8 files changed, 116 insertions(+), 27 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 53e98b2a..2808dd88 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -935,17 +935,7 @@ class AzureCloudProvider(CloudProviderInterface): ) def create_user_role(self, payload: UserRoleCSPPayload): - # creds TBD - graph_token = "" - # graph_token = self._get_up_token_for_resource( - # username, - # password, - # payload.tenant_id, - # self.sdk.cloud.endpoints.resource_manager - # ) - # graph_token = self._get_tenant_principal_token( - # payload.tenant_id - # ) + graph_token = self._get_tenant_principal_token(payload.tenant_id) if graph_token is None: raise AuthenticationException( "Could not resolve graph token for tenant admin" @@ -968,7 +958,7 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" - response = self.sdk.requests.post(url, headers=auth_header, json=request_body) + response = self.sdk.requests.put(url, headers=auth_header, json=request_body) if response.ok: return UserRoleCSPResult(**response.json()) diff --git a/atst/domain/csp/cloud/cloud_provider_interface.py b/atst/domain/csp/cloud/cloud_provider_interface.py index d173396a..7c262062 100644 --- a/atst/domain/csp/cloud/cloud_provider_interface.py +++ b/atst/domain/csp/cloud/cloud_provider_interface.py @@ -1,7 +1,7 @@ from typing import Dict -class CloudProviderInterface: +class CloudProviderInterface: # pragma: no cover def set_secret(self, secret_key: str, secret_value: str): raise NotImplementedError() diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 16065cca..ec2a0798 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -502,15 +502,14 @@ class UserCSPResult(AliasModel): id: str -class UserRoles(str, Enum): - owner = "owner" - contributor = "contributor" - billing = "billing" - - class UserRoleCSPPayload(BaseCSPPayload): + class Roles(str, Enum): + owner = "owner" + contributor = "contributor" + billing = "billing" + management_group_id: str - role: UserRoles + role: Roles user_object_id: str diff --git a/atst/domain/environment_roles.py b/atst/domain/environment_roles.py index 603c5f29..9c503485 100644 --- a/atst/domain/environment_roles.py +++ b/atst/domain/environment_roles.py @@ -110,9 +110,9 @@ class EnvironmentRoles(object): def disable(cls, environment_role_id): environment_role = EnvironmentRoles.get_by_id(environment_role_id) - if environment_role.csp_user_id and not environment_role.environment.is_pending: + if environment_role.cloud_id and not environment_role.environment.is_pending: credentials = environment_role.environment.csp_credentials - app.csp.cloud.disable_user(credentials, environment_role.csp_user_id) + app.csp.cloud.disable_user(credentials, environment_role.cloud_id) environment_role.status = EnvironmentRole.Status.DISABLED db.session.add(environment_role) diff --git a/atst/jobs.py b/atst/jobs.py index 986b2004..988d4acc 100644 --- a/atst/jobs.py +++ b/atst/jobs.py @@ -8,11 +8,17 @@ from atst.domain.csp.cloud.exceptions import GeneralCSPException from atst.domain.csp.cloud import CloudProviderInterface from atst.domain.applications import Applications from atst.domain.environments import Environments +from atst.domain.environment_roles import EnvironmentRoles from atst.domain.portfolios import Portfolios from atst.domain.application_roles import ApplicationRoles from atst.models.utils import claim_for_update, claim_many_for_update +from atst.models import CSPRole from atst.utils.localization import translate -from atst.domain.csp.cloud.models import ApplicationCSPPayload, UserCSPPayload +from atst.domain.csp.cloud.models import ( + ApplicationCSPPayload, + UserCSPPayload, + UserRoleCSPPayload, +) class RecordFailure(celery.Task): @@ -138,6 +144,41 @@ def do_create_environment(csp: CloudProviderInterface, environment_id=None): ) +def do_create_environment_role(csp: CloudProviderInterface, environment_role_id=None): + env_role = EnvironmentRoles.get_by_id(environment_role_id) + + with claim_for_update(env_role) as env_role: + + if env_role.cloud_id is not None: + return + + env = env_role.environment + csp_details = env.application.portfolio.csp_data + app_role = env_role.application_role + + role = None + if env_role.role == CSPRole.ADMIN: + role = UserRoleCSPPayload.Roles.owner + elif env_role.role == CSPRole.BILLING_READ: + role = UserRoleCSPPayload.Roles.billing + elif env_role.role == CSPRole.CONTRIBUTOR: + role = UserRoleCSPPayload.Roles.contributor + + # resolve role + payload = UserRoleCSPPayload( + tenant_id=csp_details.get("tenant_id"), + management_group_id=env.cloud_id, + user_object_id=app_role.cloud_id, + role=role, + ) + result = csp.create_user_role(payload) + + env_role.cloud_id = result.id + db.session.add(env_role) + db.session.commit() + # TODO: should send notification email to the user, maybe with their portal login name + + def do_create_atat_admin_user(csp: CloudProviderInterface, environment_id=None): environment = Environments.get(environment_id) @@ -186,6 +227,16 @@ def create_user(self, application_role_ids=None): ) +@celery.task(bind=True, base=RecordFailure) +def create_environment_role(self, environment_role_id=None): + do_work( + do_create_environment_role, + self, + app.csp.cloud, + environment_role_id=environment_role_id, + ) + + @celery.task(bind=True, base=RecordFailure) def create_environment(self, environment_id=None): do_work(do_create_environment, self, app.csp.cloud, environment_id=environment_id) @@ -219,6 +270,12 @@ def dispatch_create_user(self): create_user.delay(application_role_ids=application_role_ids) +@celery.task(bind=True) +def dispatch_create_environment_role(self): + for environment_role_id in EnvironmentRoles.get_pending_creation(): + create_environment_role.delay(environment_role_id=environment_role_id) + + @celery.task(bind=True) def dispatch_create_environment(self): for environment_id in Environments.get_environments_pending_creation( diff --git a/atst/queue.py b/atst/queue.py index 3f97f88c..6db3f05a 100644 --- a/atst/queue.py +++ b/atst/queue.py @@ -27,6 +27,10 @@ def update_celery(celery, app): "task": "atst.jobs.dispatch_create_user", "schedule": 60, }, + "beat-dispatch_create_environment_role": { + "task": "atst.jobs.dispatch_create_environment_role", + "schedule": 60, + }, } class ContextTask(celery.Task): diff --git a/tests/domain/test_environment_roles.py b/tests/domain/test_environment_roles.py index df1e50a9..50151352 100644 --- a/tests/domain/test_environment_roles.py +++ b/tests/domain/test_environment_roles.py @@ -115,14 +115,14 @@ def test_disable_checks_env_role_provisioning_status(): cloud_id="cloud-id", root_user_info={"credentials": "credentials"} ) env_role1 = EnvironmentRoleFactory.create(environment=environment) - assert not env_role1.csp_user_id + assert not env_role1.cloud_id env_role1 = EnvironmentRoles.disable(env_role1.id) assert env_role1.disabled env_role2 = EnvironmentRoleFactory.create( - environment=environment, csp_user_id="123456" + environment=environment, cloud_id="123456" ) - assert env_role2.csp_user_id + assert env_role2.cloud_id env_role2 = EnvironmentRoles.disable(env_role2.id) assert env_role2.disabled diff --git a/tests/test_jobs.py b/tests/test_jobs.py index 54f4c5dc..3d3826de 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -1,9 +1,10 @@ import pendulum import pytest from uuid import uuid4 -from unittest.mock import Mock +from unittest.mock import Mock, MagicMock from atst.domain.csp.cloud import MockCloudProvider +from atst.domain.csp.cloud.models import UserRoleCSPResult from atst.domain.portfolios import Portfolios from atst.models import ApplicationRoleStatus @@ -12,12 +13,14 @@ from atst.jobs import ( dispatch_create_environment, dispatch_create_application, dispatch_create_user, + dispatch_create_environment_role, dispatch_create_atat_admin_user, dispatch_provision_portfolio, create_environment, do_create_user, do_provision_portfolio, do_create_environment, + do_create_environment_role, do_create_application, do_create_atat_admin_user, ) @@ -310,3 +313,39 @@ def test_provision_portfolio_create_tenant( # monkeypatch.setattr("atst.jobs.provision_portfolio", mock) # dispatch_provision_portfolio.run() # mock.delay.assert_called_once_with(portfolio_id=portfolio.id) + + +def test_dispatch_create_environment_role(monkeypatch): + portfolio = PortfolioFactory.create(csp_data={"tenant_id": "123"}) + app_role = ApplicationRoleFactory.create( + application=ApplicationFactory.create(portfolio=portfolio), + status=ApplicationRoleStatus.ACTIVE, + cloud_id="123", + ) + env_role = EnvironmentRoleFactory.create(application_role=app_role) + + mock = Mock() + monkeypatch.setattr("atst.jobs.create_environment_role", mock) + + dispatch_create_environment_role.run() + + mock.delay.assert_called_once_with(environment_role_id=env_role.id) + + +def test_create_environment_role(): + portfolio = PortfolioFactory.create(csp_data={"tenant_id": "123"}) + app = ApplicationFactory.create(portfolio=portfolio) + app_role = ApplicationRoleFactory.create( + application=app, status=ApplicationRoleStatus.ACTIVE, cloud_id="123", + ) + env = EnvironmentFactory.create(application=app, cloud_id="123") + env_role = EnvironmentRoleFactory.create( + environment=env, application_role=app_role, cloud_id=None + ) + + csp = Mock() + result = UserRoleCSPResult(id="a-cloud-id") + csp.create_user_role = MagicMock(return_value=result) + do_create_environment_role(csp, environment_role_id=env_role.id) + + assert env_role.cloud_id == "a-cloud-id"