Merge pull request #1050 from dod-ccpo/cloud-provision-interface
Update CloudProviderInterface for provision job consumption
This commit is contained in:
commit
85f8c8f9e0
@ -1,84 +1,147 @@
|
|||||||
|
from typing import Dict
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from atst.models.environment_role import CSPRole
|
||||||
|
from atst.models.user import User
|
||||||
|
from atst.models.environment import Environment
|
||||||
|
from atst.models.environment_role import EnvironmentRole
|
||||||
|
|
||||||
|
|
||||||
class CloudProviderInterface:
|
class CloudProviderInterface:
|
||||||
def create_application(self, name): # pragma: no cover
|
def create_environment(
|
||||||
"""Create an application in the cloud with the provided name. Returns
|
self, auth_credentials: Dict, user: User, environment: Environment
|
||||||
the ID of the created object.
|
) -> str:
|
||||||
|
"""Create a new environment in the CSP.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
auth_credentials -- Object containing CSP account credentials
|
||||||
|
user -- ATAT user authorizing the environment creation
|
||||||
|
environment -- ATAT Environment model
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: ID of created environment
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def delete_application(self, cloud_id): # pragma: no cover
|
def create_atat_admin_user(
|
||||||
"""Delete an application in the cloud with the provided cloud_id. Returns
|
self, auth_credentials: Dict, csp_environment_id: str
|
||||||
True for success or raises an error.
|
) -> Dict:
|
||||||
|
"""Creates a new, programmatic user in the CSP. Grants this user full permissions to administer
|
||||||
|
the CSP.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
auth_credentials -- Object containing CSP account credentials
|
||||||
|
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
|
||||||
|
Something like:
|
||||||
|
{
|
||||||
|
"user_id": string,
|
||||||
|
"credentials": dict, # structure TBD based on csp
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def create_user(self, user): # pragma: no cover
|
def create_environment_baseline(
|
||||||
"""Create an account in the CSP for specified user. Returns the ID of
|
self, auth_credentials: Dict, csp_environment_id: str
|
||||||
the created user.
|
) -> Dict:
|
||||||
|
"""Provision the necessary baseline entities (such as roles) in the given environment
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
auth_credentials -- Object containing CSP account credentials
|
||||||
|
csp_environment_id -- ID of the CSP Environment to provision roles against.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Returns dict that associates the resource identities with their ATAT representations.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def create_role(self, environment_role): # pragma: no cover
|
def create_or_update_user(
|
||||||
"""Takes an `atst.model.EnvironmentRole` object and allows the
|
self, auth_credentials: Dict, user_info: EnvironmentRole, csp_role_id: str
|
||||||
specified user access to the specified cloud entity.
|
) -> str:
|
||||||
|
"""Creates a user or updates an existing user's role.
|
||||||
|
|
||||||
This _does not_ return a token, but is intended to perform any necessary
|
Arguments:
|
||||||
setup to allow a token to be generated in the future (for example,
|
auth_credentials -- Object containing CSP account credentials
|
||||||
add the user to the cloud entity in some fashion).
|
user_info -- instance of EnvironmentRole containing user data
|
||||||
|
if it has a csp_user_id it will try to update that user
|
||||||
|
csp_role_id -- The id of the role the user should be given in the CSP
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: Returns the interal csp_user_id of the created/updated user account
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def delete_role(self, environment_role): # pragma: no cover
|
def suspend_user(self, auth_credentials: Dict, csp_user_id: str) -> bool:
|
||||||
"""Takes an `atst.model.EnvironmentRole` object and performs any action
|
"""Revoke all privileges for a user. Used to prevent user access while a full
|
||||||
necessary in the CSP to remove the specified user from the specified
|
delete is being processed.
|
||||||
environment. This method does not return anything.
|
|
||||||
|
Arguments:
|
||||||
|
auth_credentials -- Object containing CSP account credentials
|
||||||
|
csp_user_id -- CSP internal user identifier
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool -- True on success
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_access_token(self, environment_role): # pragma: no cover
|
def delete_user(self, auth_credentials: Dict, csp_user_id: str) -> bool:
|
||||||
"""Takes an `atst.model.EnvironmentRole` object and returns a federated
|
"""Given the csp-internal id for a user, initiate user deletion.
|
||||||
access token that gives the specified user access to the specified
|
|
||||||
environment with the proper permissions.
|
Arguments:
|
||||||
|
auth_credentials -- Object containing CSP account credentials
|
||||||
|
csp_user_id -- CSP internal user identifier
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool -- True on success
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TBDException: Some part of user deletion failed
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def calculator_url(self): # pragma: no cover
|
def get_calculator_url(self) -> str:
|
||||||
"""Returns a URL for the CSP's estimate calculator."""
|
"""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) -> str:
|
||||||
|
"""Returns the login url for a given environment
|
||||||
|
This may move to be a computed property on the Environment domain object
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class MockCloudProvider(CloudProviderInterface):
|
class MockCloudProvider(CloudProviderInterface):
|
||||||
def create_application(self, name):
|
def create_environment(self, auth_credentials, user, environment):
|
||||||
"""Returns an id that represents what would be an application in the
|
|
||||||
cloud."""
|
|
||||||
return uuid4().hex
|
return uuid4().hex
|
||||||
|
|
||||||
def delete_application(self, name):
|
def create_atat_admin_user(self, auth_credentials, csp_environment_id):
|
||||||
"""Returns an id that represents what would be an application in the
|
return {"id": uuid4().hex, "credentials": {}}
|
||||||
cloud."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def create_user(self, user):
|
def create_environment_baseline(self, auth_credentials, csp_environment_id):
|
||||||
"""Returns an id that represents what would be an user in the cloud."""
|
return {
|
||||||
return uuid4().hex
|
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):
|
def create_or_update_user(self, auth_credentials, user_info, csp_role_id):
|
||||||
# Currently, there is nothing to mock out, so just do nothing.
|
return {"id": uuid4().hex}
|
||||||
|
|
||||||
|
def suspend_user(self, auth_credentials, csp_user_id):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delete_role(self, environment_role):
|
def delete_user(self, auth_credentials, csp_user_id):
|
||||||
# Currently nothing to do.
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_access_token(self, environment_role):
|
def get_calculator_url(self):
|
||||||
# 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):
|
|
||||||
return "https://www.rackspace.com/en-us/calculator"
|
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"
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from flask import current_app as app
|
|
||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
from atst.models import EnvironmentRole, ApplicationRole
|
from atst.models import EnvironmentRole, ApplicationRole
|
||||||
|
|
||||||
@ -10,10 +8,6 @@ class EnvironmentRoles(object):
|
|||||||
env_role = EnvironmentRole(
|
env_role = EnvironmentRole(
|
||||||
application_role=application_role, environment=environment, role=role
|
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
|
return env_role
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -45,7 +39,7 @@ class EnvironmentRoles(object):
|
|||||||
def delete(cls, application_role_id, environment_id):
|
def delete(cls, application_role_id, environment_id):
|
||||||
existing_env_role = EnvironmentRoles.get(application_role_id, environment_id)
|
existing_env_role = EnvironmentRoles.get(application_role_id, environment_id)
|
||||||
if existing_env_role:
|
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.delete(existing_env_role)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return True
|
return True
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from flask import current_app as app
|
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
@ -13,7 +12,6 @@ class Environments(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, application, name):
|
def create(cls, application, name):
|
||||||
environment = Environment(application=application, name=name)
|
environment = Environment(application=application, name=name)
|
||||||
environment.cloud_id = app.csp.cloud.create_application(environment.name)
|
|
||||||
db.session.add(environment)
|
db.session.add(environment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return environment
|
return environment
|
||||||
@ -101,6 +99,6 @@ class Environments(object):
|
|||||||
if commit:
|
if commit:
|
||||||
db.session.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
|
return environment
|
||||||
|
@ -131,4 +131,4 @@ def csp_environment_access():
|
|||||||
|
|
||||||
@bp.route("/jedi-csp-calculator")
|
@bp.route("/jedi-csp-calculator")
|
||||||
def jedi_csp_calculator():
|
def jedi_csp_calculator():
|
||||||
return redirect(app.csp.cloud.calculator_url())
|
return redirect(app.csp.cloud.get_calculator_url())
|
||||||
|
@ -30,6 +30,6 @@ def access_environment(environment_id):
|
|||||||
env_role = EnvironmentRoles.get_by_user_and_environment(
|
env_role = EnvironmentRoles.get_by_user_and_environment(
|
||||||
g.current_user.id, environment_id
|
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))
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
|
|
||||||
@ -19,10 +18,6 @@ def environment(application_role):
|
|||||||
|
|
||||||
|
|
||||||
def test_create(application_role, environment, monkeypatch):
|
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(
|
environment_role = EnvironmentRoles.create(
|
||||||
application_role, environment, "network admin"
|
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.application_role == application_role
|
||||||
assert environment_role.environment == environment
|
assert environment_role.environment == environment
|
||||||
assert environment_role.role == "network admin"
|
assert environment_role.role == "network admin"
|
||||||
mock_create_role.assert_called_with(environment_role)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get(application_role, environment):
|
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):
|
def test_delete(application_role, environment, monkeypatch):
|
||||||
mock_delete_role = MagicMock()
|
EnvironmentRoleFactory.create(
|
||||||
monkeypatch.setattr(
|
|
||||||
"atst.domain.environment_roles.app.csp.cloud.delete_role", mock_delete_role
|
|
||||||
)
|
|
||||||
|
|
||||||
environment_role = EnvironmentRoleFactory.create(
|
|
||||||
application_role=application_role, environment=environment
|
application_role=application_role, environment=environment
|
||||||
)
|
)
|
||||||
assert EnvironmentRoles.delete(application_role.id, environment.id)
|
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)
|
assert not EnvironmentRoles.delete(application_role.id, environment.id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from tests.factories import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Reinstate and update once jobs api is up")
|
||||||
def test_create_environments():
|
def test_create_environments():
|
||||||
application = ApplicationFactory.create()
|
application = ApplicationFactory.create()
|
||||||
environments = Environments.create_many(application, ["Staging", "Production"])
|
environments = Environments.create_many(application, ["Staging", "Production"])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user