From 1fe9399f99435b975e74988fa854889eff3e3a35 Mon Sep 17 00:00:00 2001 From: tomdds Date: Thu, 29 Aug 2019 14:06:18 -0400 Subject: [PATCH] Update CloudProviderInterface for provision job consumption --- atst/domain/csp/cloud.py | 156 +++++++++++++++++-------- atst/domain/environment_roles.py | 8 +- atst/domain/environments.py | 4 +- atst/routes/__init__.py | 2 +- atst/routes/applications/__init__.py | 4 +- tests/domain/test_environment_roles.py | 14 +-- tests/domain/test_environments.py | 7 -- 7 files changed, 114 insertions(+), 81 deletions(-) diff --git a/atst/domain/csp/cloud.py b/atst/domain/csp/cloud.py index 5fa322e2..c96f8616 100644 --- a/atst/domain/csp/cloud.py +++ b/atst/domain/csp/cloud.py @@ -1,84 +1,144 @@ from uuid import uuid4 +from atst.models.environment_role import CSPRole + class CloudProviderInterface: - def create_application(self, name): # pragma: no cover - """Create an application in the cloud with the provided name. Returns - the ID of the created object. + def get_auth(self, auth_credentials): + """Validate credentials and create auth object + + Arguments: + auth_credentials -- Object containing credentials + + Returns: + object: An object to be passed into subsequent calls """ raise NotImplementedError() - def delete_application(self, cloud_id): # pragma: no cover - """Delete an application in the cloud with the provided cloud_id. Returns - True for success or raises an error. + def create_environment(self, auth, user): + """Create a new environment in the CSP. + + Arguments: + auth -- Object representing authorization for the CSP + user -- ATAT user authorizing the environment creation + + Returns: + string: ID of created environment """ raise NotImplementedError() - def create_user(self, user): # pragma: no cover - """Create an account in the CSP for specified user. Returns the ID of - the created user. + def create_atat_admin_user(self, auth, csp_environment_id): + """Creates a new, programmatic user in the CSP. Grants this user full permissions to administer + the CSP. Returns a dictionary containing user details, including user's API credentials. + + Arguments: + auth -- Object representing authorization for the CSP + csp_environment_id -- ID of the CSP Environment the admin user should be created in + + Returns: + object: Object representing new remote admin user, including credentials """ raise NotImplementedError() - def create_role(self, environment_role): # pragma: no cover - """Takes an `atst.model.EnvironmentRole` object and allows the - specified user access to the specified cloud entity. + def create_environment_baseline_roles(self, auth, csp_environment_id): + """Provision the baseline set of roles that align with the available + environment roles in the ATAT system. - This _does not_ return a token, but is intended to perform any necessary - setup to allow a token to be generated in the future (for example, - add the user to the cloud entity in some fashion). + Arguments: + auth -- Object representing authorization for the CSP + csp_environment_id -- ID of the CSP Environment to provision roles against. + + Returns: + dict: Returns dict of role name => csp role IDs. """ raise NotImplementedError() - def delete_role(self, environment_role): # pragma: no cover - """Takes an `atst.model.EnvironmentRole` object and performs any action - necessary in the CSP to remove the specified user from the specified - environment. This method does not return anything. + def create_or_update_user(self, auth, user_info, csp_role_id): + """Creates a user or updates an existing user's role. + + Arguments: + auth -- Object representing authorization for the CSP + user_info -- object containing user data, if it has a csp_user_id + it will try to update a user with that id + csp_role_id -- The id of the role the user should be given in the CSP + + Raises: + NotImplementedError: [description] """ raise NotImplementedError() - def get_access_token(self, environment_role): # pragma: no cover - """Takes an `atst.model.EnvironmentRole` object and returns a federated - access token that gives the specified user access to the specified - environment with the proper permissions. + def suspend_user(self, auth, csp_user_id): + """Revoke all privileges for a user. Used to prevent user access while a full + delete is being processed. + + Arguments: + auth -- Object representing authorization for the CSP + csp_user_id -- CSP internal user identifier + + Returns: + None """ raise NotImplementedError() - def calculator_url(self): # pragma: no cover - """Returns a URL for the CSP's estimate calculator.""" + def delete_user(self, auth, csp_user_id): + """Given the csp-internal id for a user, initiate user deletion. + + Arguments: + auth -- Object representing authorization for the CSP + csp_user_id -- CSP internal user identifier + + Returns: + None + + Raises: + TBDException: Some part of user deletion failed + """ + raise NotImplementedError() + + def get_calculator_url(self): + """Returns the calculator url for the CSP. + This will likely be a static property elsewhere once a CSP is chosen. + """ + raise NotImplementedError() + + def get_environment_login_url(self, environment): + """Returns the login url for a given environment + This may move to be a computed property on the Environment domain object + """ raise NotImplementedError() class MockCloudProvider(CloudProviderInterface): - def create_application(self, name): - """Returns an id that represents what would be an application in the - cloud.""" + def get_auth(self, auth_credentials): + return {} + + def create_environment(self, auth, user): return uuid4().hex - def delete_application(self, name): - """Returns an id that represents what would be an application in the - cloud.""" - return True + def create_atat_admin_user(self, auth, csp_environment_id): + return {"id": uuid4().hex, "credentials": {}} - def create_user(self, user): - """Returns an id that represents what would be an user in the cloud.""" - return uuid4().hex + def create_environment_baseline_roles(self, auth, csp_environment_id): + return { + CSPRole.BASIC_ACCESS: uuid4().hex, + CSPRole.NETWORK_ADMIN: uuid4().hex, + CSPRole.BUSINESS_READ: uuid4().hex, + CSPRole.TECHNICAL_READ: uuid4().hex, + } - def create_role(self, environment_role): - # Currently, there is nothing to mock out, so just do nothing. + def create_or_update_user(self, auth, environment_role): + return {"id": uuid4().hex} + + def suspend_user(self, auth, csp_user_id): pass - def delete_role(self, environment_role): - # Currently nothing to do. + def delete_user(self, auth, csp_user_id): pass - def get_access_token(self, environment_role): - # for now, just create a mock token using the user and environment - # cloud IDs and the name of the role in the environment - user_id = environment_role.application_role.user.cloud_id or "" - env_id = environment_role.environment.cloud_id or "" - role_details = environment_role.role - return "::".join([user_id, env_id, role_details]) - - def calculator_url(self): + def get_calculator_url(self): return "https://www.rackspace.com/en-us/calculator" + + def get_environment_login_url(self, environment): + """Returns the login url for a given environment + """ + return "https://www.mycloud.com/my-env-login" diff --git a/atst/domain/environment_roles.py b/atst/domain/environment_roles.py index 9ae57782..b28e94b2 100644 --- a/atst/domain/environment_roles.py +++ b/atst/domain/environment_roles.py @@ -1,5 +1,3 @@ -from flask import current_app as app - from atst.database import db from atst.models import EnvironmentRole, ApplicationRole @@ -10,10 +8,6 @@ class EnvironmentRoles(object): env_role = EnvironmentRole( application_role=application_role, environment=environment, role=role ) - # TODO: move cloud_id behavior to invitation acceptance - # if not user.cloud_id: - # user.cloud_id = app.csp.cloud.create_user(user) - app.csp.cloud.create_role(env_role) return env_role @classmethod @@ -45,7 +39,7 @@ class EnvironmentRoles(object): def delete(cls, application_role_id, environment_id): existing_env_role = EnvironmentRoles.get(application_role_id, environment_id) if existing_env_role: - app.csp.cloud.delete_role(existing_env_role) + # TODO: Set status to pending_delete db.session.delete(existing_env_role) db.session.commit() return True diff --git a/atst/domain/environments.py b/atst/domain/environments.py index bc98bf9f..e21d4cb9 100644 --- a/atst/domain/environments.py +++ b/atst/domain/environments.py @@ -1,4 +1,3 @@ -from flask import current_app as app from sqlalchemy.orm.exc import NoResultFound from atst.database import db @@ -13,7 +12,6 @@ class Environments(object): @classmethod def create(cls, application, name): environment = Environment(application=application, name=name) - environment.cloud_id = app.csp.cloud.create_application(environment.name) db.session.add(environment) db.session.commit() return environment @@ -101,6 +99,6 @@ class Environments(object): if commit: db.session.commit() - app.csp.cloud.delete_application(environment.cloud_id) + # TODO: How do we work around environment deletion being a largely manual process in the CSPs return environment diff --git a/atst/routes/__init__.py b/atst/routes/__init__.py index eaa447be..f2d6da13 100644 --- a/atst/routes/__init__.py +++ b/atst/routes/__init__.py @@ -131,4 +131,4 @@ def csp_environment_access(): @bp.route("/jedi-csp-calculator") def jedi_csp_calculator(): - return redirect(app.csp.cloud.calculator_url()) + return redirect(app.csp.cloud.get_calculator_url()) diff --git a/atst/routes/applications/__init__.py b/atst/routes/applications/__init__.py index 6ba4bda0..8ec1e4cf 100644 --- a/atst/routes/applications/__init__.py +++ b/atst/routes/applications/__init__.py @@ -30,6 +30,6 @@ def access_environment(environment_id): env_role = EnvironmentRoles.get_by_user_and_environment( g.current_user.id, environment_id ) - token = app.csp.cloud.get_access_token(env_role) + login_url = app.csp.cloud.get_environment_login_url(env_role.environment) - return redirect(url_for("atst.csp_environment_access", token=token)) + return redirect(url_for("atst.csp_environment_access", login_url=login_url)) diff --git a/tests/domain/test_environment_roles.py b/tests/domain/test_environment_roles.py index b50d352d..a8618981 100644 --- a/tests/domain/test_environment_roles.py +++ b/tests/domain/test_environment_roles.py @@ -1,5 +1,4 @@ import pytest -from unittest.mock import MagicMock from atst.domain.environment_roles import EnvironmentRoles @@ -19,10 +18,6 @@ def environment(application_role): def test_create(application_role, environment, monkeypatch): - mock_create_role = MagicMock() - monkeypatch.setattr( - "atst.domain.environment_roles.app.csp.cloud.create_role", mock_create_role - ) environment_role = EnvironmentRoles.create( application_role, environment, "network admin" @@ -30,7 +25,6 @@ def test_create(application_role, environment, monkeypatch): assert environment_role.application_role == application_role assert environment_role.environment == environment assert environment_role.role == "network admin" - mock_create_role.assert_called_with(environment_role) def test_get(application_role, environment): @@ -55,16 +49,10 @@ def test_get_by_user_and_environment(application_role, environment): def test_delete(application_role, environment, monkeypatch): - mock_delete_role = MagicMock() - monkeypatch.setattr( - "atst.domain.environment_roles.app.csp.cloud.delete_role", mock_delete_role - ) - - environment_role = EnvironmentRoleFactory.create( + EnvironmentRoleFactory.create( application_role=application_role, environment=environment ) assert EnvironmentRoles.delete(application_role.id, environment.id) - mock_delete_role.assert_called_with(environment_role) assert not EnvironmentRoles.delete(application_role.id, environment.id) diff --git a/tests/domain/test_environments.py b/tests/domain/test_environments.py index 412aca96..e850f2df 100644 --- a/tests/domain/test_environments.py +++ b/tests/domain/test_environments.py @@ -15,13 +15,6 @@ from tests.factories import ( ) -def test_create_environments(): - application = ApplicationFactory.create() - environments = Environments.create_many(application, ["Staging", "Production"]) - for env in environments: - assert env.cloud_id is not None - - def test_update_env_role(): env_role = EnvironmentRoleFactory.create(role=CSPRole.BASIC_ACCESS.value) new_role = CSPRole.TECHNICAL_READ.value