WIP: orchestration for env role creation

This commit is contained in:
dandds 2020-02-05 15:56:53 -05:00
parent 7c7dd08827
commit 4da68a1fab
8 changed files with 116 additions and 27 deletions

View File

@ -935,17 +935,7 @@ class AzureCloudProvider(CloudProviderInterface):
) )
def create_user_role(self, payload: UserRoleCSPPayload): def create_user_role(self, payload: UserRoleCSPPayload):
# creds TBD graph_token = self._get_tenant_principal_token(payload.tenant_id)
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
# )
if graph_token is None: if graph_token is None:
raise AuthenticationException( raise AuthenticationException(
"Could not resolve graph token for tenant admin" "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" 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: if response.ok:
return UserRoleCSPResult(**response.json()) return UserRoleCSPResult(**response.json())

View File

@ -1,7 +1,7 @@
from typing import Dict from typing import Dict
class CloudProviderInterface: class CloudProviderInterface: # pragma: no cover
def set_secret(self, secret_key: str, secret_value: str): def set_secret(self, secret_key: str, secret_value: str):
raise NotImplementedError() raise NotImplementedError()

View File

@ -502,15 +502,14 @@ class UserCSPResult(AliasModel):
id: str id: str
class UserRoles(str, Enum): class UserRoleCSPPayload(BaseCSPPayload):
class Roles(str, Enum):
owner = "owner" owner = "owner"
contributor = "contributor" contributor = "contributor"
billing = "billing" billing = "billing"
class UserRoleCSPPayload(BaseCSPPayload):
management_group_id: str management_group_id: str
role: UserRoles role: Roles
user_object_id: str user_object_id: str

View File

@ -110,9 +110,9 @@ class EnvironmentRoles(object):
def disable(cls, environment_role_id): def disable(cls, environment_role_id):
environment_role = EnvironmentRoles.get_by_id(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 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 environment_role.status = EnvironmentRole.Status.DISABLED
db.session.add(environment_role) db.session.add(environment_role)

View File

@ -8,11 +8,17 @@ from atst.domain.csp.cloud.exceptions import GeneralCSPException
from atst.domain.csp.cloud import CloudProviderInterface from atst.domain.csp.cloud import CloudProviderInterface
from atst.domain.applications import Applications from atst.domain.applications import Applications
from atst.domain.environments import Environments from atst.domain.environments import Environments
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.portfolios import Portfolios from atst.domain.portfolios import Portfolios
from atst.domain.application_roles import ApplicationRoles from atst.domain.application_roles import ApplicationRoles
from atst.models.utils import claim_for_update, claim_many_for_update 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.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): 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): def do_create_atat_admin_user(csp: CloudProviderInterface, environment_id=None):
environment = Environments.get(environment_id) 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) @celery.task(bind=True, base=RecordFailure)
def create_environment(self, environment_id=None): def create_environment(self, environment_id=None):
do_work(do_create_environment, self, app.csp.cloud, environment_id=environment_id) 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) 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) @celery.task(bind=True)
def dispatch_create_environment(self): def dispatch_create_environment(self):
for environment_id in Environments.get_environments_pending_creation( for environment_id in Environments.get_environments_pending_creation(

View File

@ -27,6 +27,10 @@ def update_celery(celery, app):
"task": "atst.jobs.dispatch_create_user", "task": "atst.jobs.dispatch_create_user",
"schedule": 60, "schedule": 60,
}, },
"beat-dispatch_create_environment_role": {
"task": "atst.jobs.dispatch_create_environment_role",
"schedule": 60,
},
} }
class ContextTask(celery.Task): class ContextTask(celery.Task):

View File

@ -115,14 +115,14 @@ def test_disable_checks_env_role_provisioning_status():
cloud_id="cloud-id", root_user_info={"credentials": "credentials"} cloud_id="cloud-id", root_user_info={"credentials": "credentials"}
) )
env_role1 = EnvironmentRoleFactory.create(environment=environment) 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) env_role1 = EnvironmentRoles.disable(env_role1.id)
assert env_role1.disabled assert env_role1.disabled
env_role2 = EnvironmentRoleFactory.create( 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) env_role2 = EnvironmentRoles.disable(env_role2.id)
assert env_role2.disabled assert env_role2.disabled

View File

@ -1,9 +1,10 @@
import pendulum import pendulum
import pytest import pytest
from uuid import uuid4 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 import MockCloudProvider
from atst.domain.csp.cloud.models import UserRoleCSPResult
from atst.domain.portfolios import Portfolios from atst.domain.portfolios import Portfolios
from atst.models import ApplicationRoleStatus from atst.models import ApplicationRoleStatus
@ -12,12 +13,14 @@ from atst.jobs import (
dispatch_create_environment, dispatch_create_environment,
dispatch_create_application, dispatch_create_application,
dispatch_create_user, dispatch_create_user,
dispatch_create_environment_role,
dispatch_create_atat_admin_user, dispatch_create_atat_admin_user,
dispatch_provision_portfolio, dispatch_provision_portfolio,
create_environment, create_environment,
do_create_user, do_create_user,
do_provision_portfolio, do_provision_portfolio,
do_create_environment, do_create_environment,
do_create_environment_role,
do_create_application, do_create_application,
do_create_atat_admin_user, do_create_atat_admin_user,
) )
@ -310,3 +313,39 @@ def test_provision_portfolio_create_tenant(
# monkeypatch.setattr("atst.jobs.provision_portfolio", mock) # monkeypatch.setattr("atst.jobs.provision_portfolio", mock)
# dispatch_provision_portfolio.run() # dispatch_provision_portfolio.run()
# mock.delay.assert_called_once_with(portfolio_id=portfolio.id) # 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"