Send email to PPOC when portfolio is provisioned

When a portfolio state machine transitions to the COMPLETED state, an
email is sent to the PPOC letting them know it's ready, and provides
them with their username needed to create a password.
This commit is contained in:
graham-dds 2020-02-12 13:54:24 -05:00
parent c8991a95bf
commit 8f52443b5d
5 changed files with 78 additions and 8 deletions

View File

@ -3,7 +3,7 @@
"files": "^.secrets.baseline$|^.*pgsslrootcert.yml$", "files": "^.secrets.baseline$|^.*pgsslrootcert.yml$",
"lines": null "lines": null
}, },
"generated_at": "2020-02-10T21:40:38Z", "generated_at": "2020-02-12T18:51:01Z",
"plugins_used": [ "plugins_used": [
{ {
"base64_limit": 4.5, "base64_limit": 4.5,
@ -82,7 +82,7 @@
"hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3",
"is_secret": false, "is_secret": false,
"is_verified": false, "is_verified": false,
"line_number": 43, "line_number": 44,
"type": "Secret Keyword" "type": "Secret Keyword"
} }
], ],

View File

@ -219,6 +219,7 @@ To generate coverage reports for the Javascript tests:
- `ASSETS_URL`: URL to host which serves static assets (such as a CDN). - `ASSETS_URL`: URL to host which serves static assets (such as a CDN).
- `AZURE_ACCOUNT_NAME`: The name for the Azure blob storage account - `AZURE_ACCOUNT_NAME`: The name for the Azure blob storage account
- `AZURE_LOGIN_URL`: The URL used to login for an Azure instance.
- `AZURE_STORAGE_KEY`: A valid secret key for the Azure blob storage account - `AZURE_STORAGE_KEY`: A valid secret key for the Azure blob storage account
- `AZURE_TO_BUCKET_NAME`: The Azure blob storage container name for task order uploads - `AZURE_TO_BUCKET_NAME`: The Azure blob storage container name for task order uploads
- `BLOB_STORAGE_URL`: URL to Azure blob storage container. - `BLOB_STORAGE_URL`: URL to Azure blob storage container.

View File

@ -18,6 +18,7 @@ from atst.domain.environments import Environments
from atst.domain.environment_roles import EnvironmentRoles from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.portfolios import Portfolios from atst.domain.portfolios import Portfolios
from atst.models import CSPRole, JobFailure from atst.models import CSPRole, JobFailure
from atst.models.mixins.state_machines import FSMStates
from atst.domain.task_orders import TaskOrders from atst.domain.task_orders import TaskOrders
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.queue import celery from atst.queue import celery
@ -177,10 +178,30 @@ def do_work(fn, task, csp, **kwargs):
raise task.retry(exc=e) raise task.retry(exc=e)
def send_PPOC_email(portfolio_dict):
ppoc_email = portfolio_dict.get("password_recovery_email_address")
user_id = portfolio_dict.get("user_id")
domain_name = portfolio_dict.get("domain_name")
send_mail(
recipients=[ppoc_email],
subject=translate("email.portfolio_ready.subject"),
body=translate(
"email.portfolio_ready.body",
{
"password_reset_address": app.config.get("AZURE_LOGIN_URL"),
"username": f"{user_id}@{domain_name}.onmicrosoft.com",
},
),
)
def do_provision_portfolio(csp: CloudProviderInterface, portfolio_id=None): def do_provision_portfolio(csp: CloudProviderInterface, portfolio_id=None):
portfolio = Portfolios.get_for_update(portfolio_id) portfolio = Portfolios.get_for_update(portfolio_id)
fsm = Portfolios.get_or_create_state_machine(portfolio) fsm = Portfolios.get_or_create_state_machine(portfolio)
fsm.trigger_next_transition(csp_data=portfolio.to_dictionary()) fsm.trigger_next_transition(csp_data=portfolio.to_dictionary())
if fsm.current_state == FSMStates.COMPLETED:
send_PPOC_email(portfolio.to_dictionary())
@celery.task(bind=True, base=RecordFailure) @celery.task(bind=True, base=RecordFailure)

View File

@ -4,6 +4,7 @@ AZURE_AADP_QTY=5
AZURE_ACCOUNT_NAME AZURE_ACCOUNT_NAME
AZURE_CLIENT_ID AZURE_CLIENT_ID
AZURE_GRAPH_RESOURCE="https://graph.microsoft.com/" AZURE_GRAPH_RESOURCE="https://graph.microsoft.com/"
AZURE_LOGIN_URL="https://portal.azure.com/"
AZURE_POLICY_LOCATION=policies AZURE_POLICY_LOCATION=policies
AZURE_POWERSHELL_CLIENT_ID AZURE_POWERSHELL_CLIENT_ID
AZURE_ROLE_DEF_ID_BILLING_READER="fa23ad8b-c56e-40d8-ac0c-ce449e1d2c64" AZURE_ROLE_DEF_ID_BILLING_READER="fa23ad8b-c56e-40d8-ac0c-ce449e1d2c64"

View File

@ -7,8 +7,7 @@ from azure.core.exceptions import AzureError
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.csp.cloud.models import UserRoleCSPResult
from atst.domain.portfolios import Portfolios from atst.models import ApplicationRoleStatus, Portfolio, FSMStates
from atst.models import ApplicationRoleStatus
from atst.jobs import ( from atst.jobs import (
RecordFailure, RecordFailure,
@ -24,6 +23,7 @@ from atst.jobs import (
do_create_environment, do_create_environment,
do_create_environment_role, do_create_environment_role,
do_create_application, do_create_application,
send_PPOC_email,
) )
from tests.factories import ( from tests.factories import (
ApplicationFactory, ApplicationFactory,
@ -281,11 +281,58 @@ def test_dispatch_provision_portfolio(csp, monkeypatch):
mock.delay.assert_called_once_with(portfolio_id=portfolio.id) mock.delay.assert_called_once_with(portfolio_id=portfolio.id)
def test_do_provision_portfolio(csp, session, portfolio): class TestDoProvisionPortfolio:
def test_portfolio_has_state_machine(self, csp, session, portfolio):
do_provision_portfolio(csp=csp, portfolio_id=portfolio.id) do_provision_portfolio(csp=csp, portfolio_id=portfolio.id)
session.refresh(portfolio) session.refresh(portfolio)
assert portfolio.state_machine assert portfolio.state_machine
def test_sends_email_to_PPOC_on_completion(
self, monkeypatch, csp, portfolio: Portfolio
):
mock = Mock()
monkeypatch.setattr("atst.jobs.send_PPOC_email", mock)
csp._authorize.return_value = None
csp._maybe_raise.return_value = None
sm: PortfolioStateMachine = PortfolioStateMachineFactory.create(
portfolio=portfolio
)
# The stage before "COMPLETED"
sm.state = FSMStates.BILLING_OWNER_CREATED
do_provision_portfolio(csp=csp, portfolio_id=portfolio.id)
# send_PPOC_email was called
assert mock.assert_called_once
def test_send_ppoc_email(monkeypatch, app):
mock = Mock()
monkeypatch.setattr("atst.jobs.send_mail", mock)
ppoc_email = "example@example.com"
user_id = "user_id"
domain_name = "domain"
send_PPOC_email(
{
"password_recovery_email_address": ppoc_email,
"user_id": user_id,
"domain_name": domain_name,
}
)
mock.assert_called_once_with(
recipients=[ppoc_email],
subject=translate("email.portfolio_ready.subject"),
body=translate(
"email.portfolio_ready.body",
{
"password_reset_address": app.config.get("AZURE_LOGIN_URL"),
"username": f"{user_id}@{domain_name}.onmicrosoft.com",
},
),
)
def test_provision_portfolio_create_tenant( def test_provision_portfolio_create_tenant(
csp, session, portfolio, celery_app, celery_worker, monkeypatch csp, session, portfolio, celery_app, celery_worker, monkeypatch