Merge branch 'staging' into remove-invoiced-from-task-orders
This commit is contained in:
commit
876cddbfe3
@ -3,7 +3,7 @@
|
||||
"files": "^.secrets.baseline$|^.*pgsslrootcert.yml$",
|
||||
"lines": null
|
||||
},
|
||||
"generated_at": "2020-02-10T21:40:38Z",
|
||||
"generated_at": "2020-02-12T18:51:01Z",
|
||||
"plugins_used": [
|
||||
{
|
||||
"base64_limit": 4.5,
|
||||
@ -82,7 +82,7 @@
|
||||
"hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3",
|
||||
"is_secret": false,
|
||||
"is_verified": false,
|
||||
"line_number": 43,
|
||||
"line_number": 44,
|
||||
"type": "Secret Keyword"
|
||||
}
|
||||
],
|
||||
|
@ -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).
|
||||
- `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_TO_BUCKET_NAME`: The Azure blob storage container name for task order uploads
|
||||
- `BLOB_STORAGE_URL`: URL to Azure blob storage container.
|
||||
|
@ -186,6 +186,7 @@ def map_config(config):
|
||||
# with a Beat job once a day)
|
||||
"CELERY_RESULT_EXPIRES": 0,
|
||||
"CELERY_RESULT_EXTENDED": True,
|
||||
"OFFICE_365_DOMAIN": "onmicrosoft.com",
|
||||
"CONTRACT_START_DATE": datetime.strptime(
|
||||
config.get("default", "CONTRACT_START_DATE"), "%Y-%m-%d"
|
||||
).date(),
|
||||
|
@ -327,9 +327,7 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
if result.status_code == 200:
|
||||
result_dict = result.json()
|
||||
tenant_id = result_dict.get("tenantId")
|
||||
tenant_admin_username = (
|
||||
f"{payload.user_id}@{payload.domain_name}.onmicrosoft.com"
|
||||
)
|
||||
tenant_admin_username = f"{payload.user_id}@{payload.domain_name}.{self.config.get('OFFICE_365_DOMAIN')}"
|
||||
self.update_tenant_creds(
|
||||
tenant_id,
|
||||
KeyVaultCredentials(
|
||||
|
@ -4,6 +4,7 @@ from typing import Dict, List, Optional
|
||||
from uuid import uuid4
|
||||
import re
|
||||
|
||||
from flask import current_app as app
|
||||
from pydantic import BaseModel, validator, root_validator
|
||||
|
||||
from atst.utils import snake_to_camel
|
||||
@ -526,7 +527,7 @@ class UserMixin(BaseModel):
|
||||
|
||||
@property
|
||||
def user_principal_name(self):
|
||||
return f"{self.mail_nickname}@{self.tenant_host_name}.onmicrosoft.com"
|
||||
return f"{self.mail_nickname}@{self.tenant_host_name}.{app.config.get('OFFICE_365_DOMAIN')}"
|
||||
|
||||
@property
|
||||
def mail_nickname(self):
|
||||
|
21
atst/jobs.py
21
atst/jobs.py
@ -18,6 +18,7 @@ from atst.domain.environments import Environments
|
||||
from atst.domain.environment_roles import EnvironmentRoles
|
||||
from atst.domain.portfolios import Portfolios
|
||||
from atst.models import CSPRole, JobFailure
|
||||
from atst.models.mixins.state_machines import FSMStates
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.models.utils import claim_for_update, claim_many_for_update
|
||||
from atst.queue import celery
|
||||
@ -177,10 +178,30 @@ def do_work(fn, task, csp, **kwargs):
|
||||
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}.{app.config.get('OFFICE_365_DOMAIN')}",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def do_provision_portfolio(csp: CloudProviderInterface, portfolio_id=None):
|
||||
portfolio = Portfolios.get_for_update(portfolio_id)
|
||||
fsm = Portfolios.get_or_create_state_machine(portfolio)
|
||||
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)
|
||||
|
@ -76,7 +76,7 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
|
||||
@property
|
||||
def is_draft(self):
|
||||
return self.status == Status.DRAFT
|
||||
return self.status == Status.DRAFT or self.status == Status.UNSIGNED
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
|
@ -4,6 +4,7 @@ AZURE_AADP_QTY=5
|
||||
AZURE_ACCOUNT_NAME
|
||||
AZURE_CLIENT_ID
|
||||
AZURE_GRAPH_RESOURCE="https://graph.microsoft.com/"
|
||||
AZURE_LOGIN_URL="https://portal.azure.com/"
|
||||
AZURE_POLICY_LOCATION=policies
|
||||
AZURE_POWERSHELL_CLIENT_ID
|
||||
AZURE_ROLE_DEF_ID_BILLING_READER="fa23ad8b-c56e-40d8-ac0c-ce449e1d2c64"
|
||||
|
@ -55,6 +55,10 @@ export default {
|
||||
return this.step === this.steps - 1
|
||||
},
|
||||
handleSubmit: function(e) {
|
||||
if (this._onLastPage) {
|
||||
this.submitted = true
|
||||
}
|
||||
|
||||
if (!this.validateFields() || !this._onLastPage()) {
|
||||
e.preventDefault()
|
||||
this.next()
|
||||
|
@ -72,7 +72,7 @@ export default {
|
||||
const uploader = await this.getUploader()
|
||||
const response = await uploader.upload(file)
|
||||
if (uploadResponseOkay(response)) {
|
||||
this.attachment = e.target.value
|
||||
this.attachment = file.name
|
||||
this.objectName = uploader.objectName
|
||||
this.$refs.attachmentFilename.value = file.name
|
||||
this.$refs.attachmentObjectName.value = response.objectName
|
||||
|
@ -43,7 +43,6 @@
|
||||
:id="name"
|
||||
:name="name"
|
||||
aria-label="Task Order Upload"
|
||||
v-bind:value="attachment"
|
||||
type="file">
|
||||
<input type="hidden" name="{{ field.filename.name }}" id="{{ field.filename.name }}" ref="attachmentFilename">
|
||||
<input type="hidden" name="{{ field.object_name.name }}" id="{{ field.object_name.name }}" ref="attachmentObjectName" v-bind:value='objectName'>
|
||||
|
@ -134,9 +134,12 @@ def test_UserCSPPayload_mail_nickname():
|
||||
assert payload.mail_nickname == f"han.solo"
|
||||
|
||||
|
||||
def test_UserCSPPayload_user_principal_name():
|
||||
def test_UserCSPPayload_user_principal_name(app):
|
||||
payload = UserCSPPayload(**user_payload)
|
||||
assert payload.user_principal_name == f"han.solo@rebelalliance.onmicrosoft.com"
|
||||
assert (
|
||||
payload.user_principal_name
|
||||
== f"han.solo@rebelalliance.{app.config.get('OFFICE_365_DOMAIN')}"
|
||||
)
|
||||
|
||||
|
||||
def test_UserCSPPayload_password():
|
||||
@ -167,11 +170,11 @@ class TestBillingOwnerCSPPayload:
|
||||
payload = BillingOwnerCSPPayload(**self.user_payload)
|
||||
assert payload.password
|
||||
|
||||
def test_user_principal_name(self):
|
||||
def test_user_principal_name(self, app):
|
||||
payload = BillingOwnerCSPPayload(**self.user_payload)
|
||||
assert (
|
||||
payload.user_principal_name
|
||||
== f"billing_admin@rebelalliance.onmicrosoft.com"
|
||||
== f"billing_admin@rebelalliance.{app.config.get('OFFICE_365_DOMAIN')}"
|
||||
)
|
||||
|
||||
def test_email(self):
|
||||
|
@ -7,8 +7,7 @@ from azure.core.exceptions import AzureError
|
||||
|
||||
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
|
||||
from atst.models import ApplicationRoleStatus, Portfolio, FSMStates
|
||||
|
||||
from atst.jobs import (
|
||||
RecordFailure,
|
||||
@ -24,6 +23,7 @@ from atst.jobs import (
|
||||
do_create_environment,
|
||||
do_create_environment_role,
|
||||
do_create_application,
|
||||
send_PPOC_email,
|
||||
)
|
||||
from tests.factories import (
|
||||
ApplicationFactory,
|
||||
@ -135,11 +135,11 @@ def test_create_application_job_is_idempotent(csp):
|
||||
csp.create_application.assert_not_called()
|
||||
|
||||
|
||||
def test_create_user_job(session, csp):
|
||||
def test_create_user_job(session, csp, app):
|
||||
portfolio = PortfolioFactory.create(
|
||||
csp_data={
|
||||
"tenant_id": str(uuid4()),
|
||||
"domain_name": "rebelalliance.onmicrosoft.com",
|
||||
"domain_name": f"rebelalliance.{app.config.get('OFFICE_365_DOMAIN')}",
|
||||
}
|
||||
)
|
||||
application = ApplicationFactory.create(portfolio=portfolio, cloud_id="321")
|
||||
@ -281,10 +281,57 @@ def test_dispatch_provision_portfolio(csp, monkeypatch):
|
||||
mock.delay.assert_called_once_with(portfolio_id=portfolio.id)
|
||||
|
||||
|
||||
def test_do_provision_portfolio(csp, session, portfolio):
|
||||
do_provision_portfolio(csp=csp, portfolio_id=portfolio.id)
|
||||
session.refresh(portfolio)
|
||||
assert portfolio.state_machine
|
||||
class TestDoProvisionPortfolio:
|
||||
def test_portfolio_has_state_machine(self, csp, session, portfolio):
|
||||
do_provision_portfolio(csp=csp, portfolio_id=portfolio.id)
|
||||
session.refresh(portfolio)
|
||||
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}.{app.config.get('OFFICE_365_DOMAIN')}",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_provision_portfolio_create_tenant(
|
||||
|
@ -83,7 +83,9 @@ errors:
|
||||
email:
|
||||
application_invite: "{inviter_name} has invited you to a JEDI cloud application"
|
||||
portfolio_invite: "{inviter_name} has invited you to a JEDI cloud portfolio"
|
||||
environment_ready: JEDI cloud environment ready
|
||||
portfolio_ready:
|
||||
subject: Portfolio Provisioned
|
||||
body: "Your portfolio has been provisioned.\nVisit {password_reset_address}, and use your username, {username}, to create a password."
|
||||
task_order_sent:
|
||||
subject: "Task Order {to_number}"
|
||||
body: "Task Order number {to_number} updated."
|
||||
|
Loading…
x
Reference in New Issue
Block a user