Merge pull request #1375 from dod-ccpo/azure-subscriptions
Add functionality for creating and verifying subscriptions.
This commit is contained in:
commit
f79af3ad62
@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import re
|
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -9,6 +8,10 @@ from atst.utils import sha256_hex
|
|||||||
from .cloud_provider_interface import CloudProviderInterface
|
from .cloud_provider_interface import CloudProviderInterface
|
||||||
from .exceptions import AuthenticationException
|
from .exceptions import AuthenticationException
|
||||||
from .models import (
|
from .models import (
|
||||||
|
SubscriptionCreationCSPPayload,
|
||||||
|
SubscriptionCreationCSPResult,
|
||||||
|
SubscriptionVerificationCSPPayload,
|
||||||
|
SuscriptionVerificationCSPResult,
|
||||||
AdminRoleDefinitionCSPPayload,
|
AdminRoleDefinitionCSPPayload,
|
||||||
AdminRoleDefinitionCSPResult,
|
AdminRoleDefinitionCSPResult,
|
||||||
ApplicationCSPPayload,
|
ApplicationCSPPayload,
|
||||||
@ -48,10 +51,6 @@ from .models import (
|
|||||||
)
|
)
|
||||||
from .policy import AzurePolicyManager
|
from .policy import AzurePolicyManager
|
||||||
|
|
||||||
SUBSCRIPTION_ID_REGEX = re.compile(
|
|
||||||
"subscriptions\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})",
|
|
||||||
re.I,
|
|
||||||
)
|
|
||||||
|
|
||||||
# This needs to be a fully pathed role definition identifier, not just a UUID
|
# This needs to be a fully pathed role definition identifier, not just a UUID
|
||||||
# TODO: Extract these from sdk msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD
|
# TODO: Extract these from sdk msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD
|
||||||
@ -236,49 +235,6 @@ class AzureCloudProvider(CloudProviderInterface):
|
|||||||
# instead?
|
# instead?
|
||||||
return create_request.result()
|
return create_request.result()
|
||||||
|
|
||||||
def _create_subscription(
|
|
||||||
self,
|
|
||||||
credentials,
|
|
||||||
display_name,
|
|
||||||
billing_profile_id,
|
|
||||||
sku_id,
|
|
||||||
management_group_id,
|
|
||||||
billing_account_name,
|
|
||||||
invoice_section_name,
|
|
||||||
):
|
|
||||||
sub_client = self.sdk.subscription.SubscriptionClient(credentials)
|
|
||||||
|
|
||||||
billing_profile_id = "?" # where do we source this?
|
|
||||||
sku_id = AZURE_SKU_ID
|
|
||||||
# These 2 seem like something that might be worthwhile to allow tiebacks to
|
|
||||||
# TOs filed for the environment
|
|
||||||
billing_account_name = "?" # from TO?
|
|
||||||
invoice_section_name = "?" # from TO?
|
|
||||||
|
|
||||||
body = self.sdk.subscription.models.ModernSubscriptionCreationParameters(
|
|
||||||
display_name=display_name,
|
|
||||||
billing_profile_id=billing_profile_id,
|
|
||||||
sku_id=sku_id,
|
|
||||||
management_group_id=management_group_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# We may also want to create billing sections in the enrollment account
|
|
||||||
sub_creation_operation = sub_client.subscription_factory.create_subscription(
|
|
||||||
billing_account_name, invoice_section_name, body
|
|
||||||
)
|
|
||||||
|
|
||||||
# the resulting object from this process is a link to the new subscription
|
|
||||||
# not a subscription model, so we'll have to unpack the ID
|
|
||||||
new_sub = sub_creation_operation.result()
|
|
||||||
|
|
||||||
subscription_id = self._extract_subscription_id(new_sub.subscription_link)
|
|
||||||
if subscription_id:
|
|
||||||
return subscription_id
|
|
||||||
else:
|
|
||||||
# troublesome error, subscription should exist at this point
|
|
||||||
# but we just don't have a valid ID
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _create_policy_definition(
|
def _create_policy_definition(
|
||||||
self, credentials, subscription_id, management_group_id, properties,
|
self, credentials, subscription_id, management_group_id, properties,
|
||||||
):
|
):
|
||||||
@ -522,6 +478,59 @@ class AzureCloudProvider(CloudProviderInterface):
|
|||||||
else:
|
else:
|
||||||
return self._error(result.json())
|
return self._error(result.json())
|
||||||
|
|
||||||
|
def create_subscription(self, payload: SubscriptionCreationCSPPayload):
|
||||||
|
sp_token = self._get_tenant_principal_token(payload.tenant_id)
|
||||||
|
if sp_token is None:
|
||||||
|
raise AuthenticationException(
|
||||||
|
"Could not resolve token for subscription creation"
|
||||||
|
)
|
||||||
|
|
||||||
|
request_body = {
|
||||||
|
"displayName": payload.display_name,
|
||||||
|
"skuId": AZURE_SKU_ID,
|
||||||
|
"managementGroupId": payload.parent_group_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/invoiceSections/{payload.invoice_section_name}/providers/Microsoft.Subscription/createSubscription?api-version=2019-10-01-preview"
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {sp_token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.sdk.requests.put(url, headers=auth_header, json=request_body)
|
||||||
|
|
||||||
|
if result.status_code in [200, 202]:
|
||||||
|
# 202 has location/retry after headers
|
||||||
|
return SubscriptionCreationCSPResult(**result.headers, **result.json())
|
||||||
|
else:
|
||||||
|
return self._error(result.json())
|
||||||
|
|
||||||
|
def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload):
|
||||||
|
return self.create_subscription(payload)
|
||||||
|
|
||||||
|
def create_subscription_verification(
|
||||||
|
self, payload: SubscriptionVerificationCSPPayload
|
||||||
|
):
|
||||||
|
sp_token = self._get_tenant_principal_token(payload.tenant_id)
|
||||||
|
if sp_token is None:
|
||||||
|
raise AuthenticationException(
|
||||||
|
"Could not resolve token for subscription verification"
|
||||||
|
)
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {sp_token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.sdk.requests.get(
|
||||||
|
payload.subscription_verify_url, headers=auth_header
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.ok:
|
||||||
|
# 202 has location/retry after headers
|
||||||
|
return SuscriptionVerificationCSPResult(**result.json())
|
||||||
|
else:
|
||||||
|
return self._error(result.json())
|
||||||
|
|
||||||
def create_product_purchase(self, payload: ProductPurchaseCSPPayload):
|
def create_product_purchase(self, payload: ProductPurchaseCSPPayload):
|
||||||
sp_token = self._get_root_provisioning_token()
|
sp_token = self._get_root_provisioning_token()
|
||||||
if sp_token is None:
|
if sp_token is None:
|
||||||
@ -930,6 +939,12 @@ class AzureCloudProvider(CloudProviderInterface):
|
|||||||
"tenant_id": self.tenant_id,
|
"tenant_id": self.tenant_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_tenant_principal_token(self, tenant_id):
|
||||||
|
creds = self._source_creds(tenant_id)
|
||||||
|
return self._get_sp_token(
|
||||||
|
creds.tenant_id, creds.tenant_sp_client_id, creds.tenant_sp_key
|
||||||
|
)
|
||||||
|
|
||||||
def _get_elevated_management_token(self, tenant_id):
|
def _get_elevated_management_token(self, tenant_id):
|
||||||
mgmt_token = self._get_tenant_admin_token(
|
mgmt_token = self._get_tenant_admin_token(
|
||||||
tenant_id, self.sdk.cloud.endpoints.resource_manager
|
tenant_id, self.sdk.cloud.endpoints.resource_manager
|
||||||
|
@ -112,9 +112,3 @@ class CloudProviderInterface:
|
|||||||
This may move to be a computed property on the Environment domain object
|
This may move to be a computed property on the Environment domain object
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def create_subscription(self, environment):
|
|
||||||
"""Returns True if a new subscription has been created or raises an
|
|
||||||
exception if an error occurs while creating a subscription.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
@ -31,6 +31,10 @@ from .models import (
|
|||||||
ProductPurchaseVerificationCSPResult,
|
ProductPurchaseVerificationCSPResult,
|
||||||
PrincipalAdminRoleCSPPayload,
|
PrincipalAdminRoleCSPPayload,
|
||||||
PrincipalAdminRoleCSPResult,
|
PrincipalAdminRoleCSPResult,
|
||||||
|
SubscriptionCreationCSPPayload,
|
||||||
|
SubscriptionCreationCSPResult,
|
||||||
|
SubscriptionVerificationCSPPayload,
|
||||||
|
SuscriptionVerificationCSPResult,
|
||||||
TaskOrderBillingCreationCSPPayload,
|
TaskOrderBillingCreationCSPPayload,
|
||||||
TaskOrderBillingCreationCSPResult,
|
TaskOrderBillingCreationCSPResult,
|
||||||
TaskOrderBillingVerificationCSPPayload,
|
TaskOrderBillingVerificationCSPPayload,
|
||||||
@ -113,6 +117,29 @@ class MockCloudProvider(CloudProviderInterface):
|
|||||||
|
|
||||||
return csp_environment_id
|
return csp_environment_id
|
||||||
|
|
||||||
|
def create_subscription(self, payload: SubscriptionCreationCSPPayload):
|
||||||
|
return self.create_subscription_creation(payload)
|
||||||
|
|
||||||
|
def create_subscription_creation(self, payload: SubscriptionCreationCSPPayload):
|
||||||
|
self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION)
|
||||||
|
self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION)
|
||||||
|
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)
|
||||||
|
|
||||||
|
return SubscriptionCreationCSPResult(
|
||||||
|
subscription_verify_url="https://zombo.com", subscription_retry_after=10
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_subscription_verification(
|
||||||
|
self, payload: SubscriptionVerificationCSPPayload
|
||||||
|
):
|
||||||
|
self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION)
|
||||||
|
self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION)
|
||||||
|
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)
|
||||||
|
|
||||||
|
return SuscriptionVerificationCSPResult(
|
||||||
|
subscription_id="subscriptions/60fbbb72-0516-4253-ab18-c92432ba3230"
|
||||||
|
)
|
||||||
|
|
||||||
def create_atat_admin_user(self, auth_credentials, csp_environment_id):
|
def create_atat_admin_user(self, auth_credentials, csp_environment_id):
|
||||||
self._authorize(auth_credentials)
|
self._authorize(auth_credentials)
|
||||||
|
|
||||||
@ -408,11 +435,6 @@ class MockCloudProvider(CloudProviderInterface):
|
|||||||
|
|
||||||
return self._maybe(12)
|
return self._maybe(12)
|
||||||
|
|
||||||
def create_subscription(self, environment):
|
|
||||||
self._maybe_raise(self.UNAUTHORIZED_RATE, GeneralCSPException)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_calculator_url(self):
|
def get_calculator_url(self):
|
||||||
return "https://www.rackspace.com/en-us/calculator"
|
return "https://www.rackspace.com/en-us/calculator"
|
||||||
|
|
||||||
|
@ -408,6 +408,50 @@ class KeyVaultCredentials(BaseModel):
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionCreationCSPPayload(BaseCSPPayload):
|
||||||
|
display_name: str
|
||||||
|
parent_group_id: str
|
||||||
|
billing_account_name: str
|
||||||
|
billing_profile_name: str
|
||||||
|
invoice_section_name: str
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionCreationCSPResult(AliasModel):
|
||||||
|
subscription_verify_url: str
|
||||||
|
subscription_retry_after: int
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
fields = {
|
||||||
|
"subscription_verify_url": "Location",
|
||||||
|
"subscription_retry_after": "Retry-After",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionVerificationCSPPayload(BaseCSPPayload):
|
||||||
|
subscription_verify_url: str
|
||||||
|
|
||||||
|
|
||||||
|
SUBSCRIPTION_ID_REGEX = re.compile(
|
||||||
|
"\/?subscriptions\/([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})",
|
||||||
|
re.I,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SuscriptionVerificationCSPResult(AliasModel):
|
||||||
|
subscription_id: str
|
||||||
|
|
||||||
|
@validator("subscription_id", pre=True, always=True)
|
||||||
|
def enforce_display_name_length(cls, sub_id):
|
||||||
|
sub_id_match = SUBSCRIPTION_ID_REGEX.match(sub_id)
|
||||||
|
if sub_id_match:
|
||||||
|
return sub_id_match.group(1)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
fields = {"subscription_id": "subscriptionLink"}
|
||||||
|
|
||||||
|
|
||||||
class ProductPurchaseCSPPayload(BaseCSPPayload):
|
class ProductPurchaseCSPPayload(BaseCSPPayload):
|
||||||
billing_account_name: str
|
billing_account_name: str
|
||||||
billing_profile_name: str
|
billing_profile_name: str
|
||||||
|
@ -140,7 +140,6 @@ class PortfolioStateMachine(
|
|||||||
|
|
||||||
# Accumulate payload w/ creds
|
# Accumulate payload w/ creds
|
||||||
payload = event.kwargs.get("csp_data")
|
payload = event.kwargs.get("csp_data")
|
||||||
payload["creds"] = event.kwargs.get("creds")
|
|
||||||
|
|
||||||
payload_data_cls = get_stage_csp_class(stage, "payload")
|
payload_data_cls = get_stage_csp_class(stage, "payload")
|
||||||
if not payload_data_cls:
|
if not payload_data_cls:
|
||||||
|
@ -14,6 +14,8 @@ from atst.domain.applications import Applications
|
|||||||
from atst.domain.application_roles import ApplicationRoles
|
from atst.domain.application_roles import ApplicationRoles
|
||||||
from atst.domain.audit_log import AuditLog
|
from atst.domain.audit_log import AuditLog
|
||||||
from atst.domain.csp.cloud.exceptions import GeneralCSPException
|
from atst.domain.csp.cloud.exceptions import GeneralCSPException
|
||||||
|
|
||||||
|
from atst.domain.csp.cloud.models import SubscriptionCreationCSPPayload
|
||||||
from atst.domain.common import Paginator
|
from atst.domain.common import Paginator
|
||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
from atst.domain.invitations import ApplicationInvitations
|
from atst.domain.invitations import ApplicationInvitations
|
||||||
@ -525,6 +527,25 @@ def resend_invite(application_id, application_role_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_subscription_payload(environment) -> SubscriptionCreationCSPPayload:
|
||||||
|
csp_data = environment.application.portfolio.csp_data
|
||||||
|
parent_group_id = environment.cloud_id
|
||||||
|
invoice_section_name = csp_data["billing_profile_properties"]["invoice_sections"][
|
||||||
|
0
|
||||||
|
]["invoice_section_name"]
|
||||||
|
|
||||||
|
display_name = f"{environment.application.name}-{environment.name}"
|
||||||
|
|
||||||
|
return SubscriptionCreationCSPPayload(
|
||||||
|
tenant_id=csp_data.get("tenant_id"),
|
||||||
|
display_name=display_name,
|
||||||
|
parent_group_id=parent_group_id,
|
||||||
|
billing_account_name=csp_data.get("billing_account_name"),
|
||||||
|
billing_profile_name=csp_data.get("billing_profile_name"),
|
||||||
|
invoice_section_name=invoice_section_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@applications_bp.route(
|
@applications_bp.route(
|
||||||
"/environments/<environment_id>/add_subscription", methods=["POST"]
|
"/environments/<environment_id>/add_subscription", methods=["POST"]
|
||||||
)
|
)
|
||||||
@ -533,7 +554,8 @@ def create_subscription(environment_id):
|
|||||||
environment = Environments.get(environment_id)
|
environment = Environments.get(environment_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app.csp.cloud.create_subscription(environment)
|
payload = build_subscription_payload(environment)
|
||||||
|
app.csp.cloud.create_subscription(payload)
|
||||||
flash("environment_subscription_success", name=environment.displayname)
|
flash("environment_subscription_success", name=environment.displayname)
|
||||||
|
|
||||||
except GeneralCSPException:
|
except GeneralCSPException:
|
||||||
|
@ -12,7 +12,6 @@ from atst.domain.csp.cloud.models import (
|
|||||||
AdminRoleDefinitionCSPResult,
|
AdminRoleDefinitionCSPResult,
|
||||||
ApplicationCSPPayload,
|
ApplicationCSPPayload,
|
||||||
ApplicationCSPResult,
|
ApplicationCSPResult,
|
||||||
BaseCSPPayload,
|
|
||||||
BillingInstructionCSPPayload,
|
BillingInstructionCSPPayload,
|
||||||
BillingInstructionCSPResult,
|
BillingInstructionCSPResult,
|
||||||
BillingProfileCreationCSPPayload,
|
BillingProfileCreationCSPPayload,
|
||||||
@ -25,6 +24,10 @@ from atst.domain.csp.cloud.models import (
|
|||||||
ProductPurchaseCSPResult,
|
ProductPurchaseCSPResult,
|
||||||
ProductPurchaseVerificationCSPPayload,
|
ProductPurchaseVerificationCSPPayload,
|
||||||
ProductPurchaseVerificationCSPResult,
|
ProductPurchaseVerificationCSPResult,
|
||||||
|
SubscriptionCreationCSPPayload,
|
||||||
|
SubscriptionCreationCSPResult,
|
||||||
|
SubscriptionVerificationCSPPayload,
|
||||||
|
SuscriptionVerificationCSPResult,
|
||||||
TaskOrderBillingCreationCSPPayload,
|
TaskOrderBillingCreationCSPPayload,
|
||||||
TaskOrderBillingCreationCSPResult,
|
TaskOrderBillingCreationCSPResult,
|
||||||
TaskOrderBillingVerificationCSPPayload,
|
TaskOrderBillingVerificationCSPPayload,
|
||||||
@ -46,40 +49,6 @@ from atst.domain.csp.cloud.models import (
|
|||||||
BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31"
|
BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31"
|
||||||
|
|
||||||
|
|
||||||
def test_create_subscription_succeeds(mock_azure: AzureCloudProvider):
|
|
||||||
environment = EnvironmentFactory.create()
|
|
||||||
|
|
||||||
subscription_id = str(uuid4())
|
|
||||||
|
|
||||||
credentials = mock_azure._get_credential_obj(AUTH_CREDENTIALS)
|
|
||||||
display_name = "Test Subscription"
|
|
||||||
billing_profile_id = str(uuid4())
|
|
||||||
sku_id = str(uuid4())
|
|
||||||
management_group_id = (
|
|
||||||
environment.cloud_id # environment.csp_details.management_group_id?
|
|
||||||
)
|
|
||||||
billing_account_name = (
|
|
||||||
"?" # environment.application.portfilio.csp_details.billing_account.name?
|
|
||||||
)
|
|
||||||
invoice_section_name = "?" # environment.name? or something specific to billing?
|
|
||||||
|
|
||||||
mock_azure.sdk.subscription.SubscriptionClient.return_value.subscription_factory.create_subscription.return_value.result.return_value.subscription_link = (
|
|
||||||
f"subscriptions/{subscription_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
result = mock_azure._create_subscription(
|
|
||||||
credentials,
|
|
||||||
display_name,
|
|
||||||
billing_profile_id,
|
|
||||||
sku_id,
|
|
||||||
management_group_id,
|
|
||||||
billing_account_name,
|
|
||||||
invoice_section_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == subscription_id
|
|
||||||
|
|
||||||
|
|
||||||
def mock_management_group_create(mock_azure, spec_dict):
|
def mock_management_group_create(mock_azure, spec_dict):
|
||||||
mock_azure.sdk.managementgroups.ManagementGroupsAPI.return_value.management_groups.create_or_update.return_value.result.return_value = (
|
mock_azure.sdk.managementgroups.ManagementGroupsAPI.return_value.management_groups.create_or_update.return_value.result.return_value = (
|
||||||
spec_dict
|
spec_dict
|
||||||
@ -123,7 +92,7 @@ def test_create_application_succeeds(mock_azure: AzureCloudProvider):
|
|||||||
tenant_id="1234", display_name=application.name, parent_id=str(uuid4())
|
tenant_id="1234", display_name=application.name, parent_id=str(uuid4())
|
||||||
)
|
)
|
||||||
|
|
||||||
result = mock_azure.create_application(payload)
|
result: ApplicationCSPResult = mock_azure.create_application(payload)
|
||||||
|
|
||||||
assert result.id == "Test Id"
|
assert result.id == "Test Id"
|
||||||
|
|
||||||
@ -686,3 +655,66 @@ def test_create_tenant_principal_ownership(mock_azure: AzureCloudProvider):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert result.principal_owner_assignment_id == "id"
|
assert result.principal_owner_assignment_id == "id"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_subscription_creation(mock_azure: AzureCloudProvider):
|
||||||
|
with patch.object(
|
||||||
|
AzureCloudProvider,
|
||||||
|
"_get_tenant_principal_token",
|
||||||
|
wraps=mock_azure._get_tenant_principal_token,
|
||||||
|
) as _get_tenant_principal_token:
|
||||||
|
_get_tenant_principal_token.return_value = "my fake token"
|
||||||
|
|
||||||
|
mock_result = Mock()
|
||||||
|
mock_result.status_code = 202
|
||||||
|
mock_result.headers = {
|
||||||
|
"Location": "https://verify.me",
|
||||||
|
"Retry-After": 10,
|
||||||
|
}
|
||||||
|
mock_result.json.return_value = {}
|
||||||
|
mock_azure.sdk.requests.put.return_value = mock_result
|
||||||
|
management_group_id = str(uuid4())
|
||||||
|
payload = SubscriptionCreationCSPPayload(
|
||||||
|
**dict(
|
||||||
|
tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435",
|
||||||
|
display_name="application_env_sub1",
|
||||||
|
parent_group_id=management_group_id,
|
||||||
|
billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31",
|
||||||
|
billing_profile_name="KQWI-W2SU-BG7-TGB",
|
||||||
|
invoice_section_name="6HMZ-2HLO-PJA-TGB",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result: SubscriptionCreationCSPResult = mock_azure.create_subscription_creation(
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.subscription_verify_url == "https://verify.me"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_subscription_verification(mock_azure: AzureCloudProvider):
|
||||||
|
with patch.object(
|
||||||
|
AzureCloudProvider,
|
||||||
|
"_get_tenant_principal_token",
|
||||||
|
wraps=mock_azure._get_tenant_principal_token,
|
||||||
|
) as _get_tenant_principal_token:
|
||||||
|
_get_tenant_principal_token.return_value = "my fake token"
|
||||||
|
|
||||||
|
mock_result = Mock()
|
||||||
|
mock_result.ok = True
|
||||||
|
mock_result.json.return_value = {
|
||||||
|
"subscriptionLink": "/subscriptions/60fbbb72-0516-4253-ab18-c92432ba3230"
|
||||||
|
}
|
||||||
|
mock_azure.sdk.requests.get.return_value = mock_result
|
||||||
|
|
||||||
|
payload = SubscriptionVerificationCSPPayload(
|
||||||
|
**dict(
|
||||||
|
tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435",
|
||||||
|
subscription_verify_url="https://verify.me",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result: SuscriptionVerificationCSPResult = mock_azure.create_subscription_verification(
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
assert result.subscription_id == "60fbbb72-0516-4253-ab18-c92432ba3230"
|
||||||
|
@ -1,35 +1,38 @@
|
|||||||
import uuid
|
|
||||||
from flask import url_for, get_flashed_messages
|
|
||||||
from unittest.mock import Mock
|
|
||||||
import datetime
|
import datetime
|
||||||
from werkzeug.datastructures import ImmutableMultiDict
|
import uuid
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from flask import get_flashed_messages, url_for
|
||||||
from tests.factories import *
|
from tests.factories import *
|
||||||
|
from tests.mock_azure import mock_azure
|
||||||
|
from tests.utils import captured_templates
|
||||||
|
from werkzeug.datastructures import ImmutableMultiDict
|
||||||
|
|
||||||
from atst.domain.applications import Applications
|
from atst.database import db
|
||||||
from atst.domain.application_roles import ApplicationRoles
|
from atst.domain.application_roles import ApplicationRoles
|
||||||
|
from atst.domain.applications import Applications
|
||||||
|
from atst.domain.common import Paginator
|
||||||
|
from atst.domain.csp.cloud.azure_cloud_provider import AzureCloudProvider
|
||||||
|
from atst.domain.csp.cloud.exceptions import GeneralCSPException
|
||||||
|
from atst.domain.csp.cloud.models import SubscriptionCreationCSPResult
|
||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
from atst.domain.invitations import ApplicationInvitations
|
from atst.domain.invitations import ApplicationInvitations
|
||||||
from atst.domain.common import Paginator
|
|
||||||
from atst.domain.csp.cloud.exceptions import GeneralCSPException
|
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
from atst.models.application_role import Status as ApplicationRoleStatus
|
|
||||||
from atst.models.environment_role import CSPRole, EnvironmentRole
|
|
||||||
from atst.models.permissions import Permissions
|
|
||||||
from atst.forms.application import EditEnvironmentForm
|
from atst.forms.application import EditEnvironmentForm
|
||||||
from atst.forms.application_member import UpdateMemberForm
|
from atst.forms.application_member import UpdateMemberForm
|
||||||
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
|
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
|
||||||
|
from atst.models.application_role import Status as ApplicationRoleStatus
|
||||||
|
from atst.models.environment_role import CSPRole, EnvironmentRole
|
||||||
|
from atst.models.permissions import Permissions
|
||||||
from atst.routes.applications.settings import (
|
from atst.routes.applications.settings import (
|
||||||
filter_env_roles_form_data,
|
|
||||||
filter_env_roles_data,
|
filter_env_roles_data,
|
||||||
|
filter_env_roles_form_data,
|
||||||
get_environments_obj_for_app,
|
get_environments_obj_for_app,
|
||||||
handle_create_member,
|
handle_create_member,
|
||||||
handle_update_member,
|
handle_update_member,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.utils import captured_templates
|
|
||||||
|
|
||||||
|
|
||||||
def test_updating_application_environments_success(client, user_session):
|
def test_updating_application_environments_success(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
@ -779,10 +782,29 @@ def test_handle_update_member_with_error(set_g, monkeypatch, mock_logger):
|
|||||||
assert mock_logger.messages[-1] == exception
|
assert mock_logger.messages[-1] == exception
|
||||||
|
|
||||||
|
|
||||||
def test_create_subscription_success(client, user_session):
|
def test_create_subscription_success(
|
||||||
|
client, user_session, mock_azure: AzureCloudProvider
|
||||||
|
):
|
||||||
environment = EnvironmentFactory.create()
|
environment = EnvironmentFactory.create()
|
||||||
|
|
||||||
user_session(environment.portfolio.owner)
|
user_session(environment.portfolio.owner)
|
||||||
|
environment.cloud_id = "management/group/id"
|
||||||
|
environment.application.portfolio.csp_data = {
|
||||||
|
"billing_account_name": "xxxx-xxxx-xxx-xxx",
|
||||||
|
"billing_profile_name": "xxxxxxxxxxx:xxxxxxxxxxxxx_xxxxxx",
|
||||||
|
"tenant_id": "xxxxxxxxxxx-xxxxxxxxxx-xxxxxxx-xxxxx",
|
||||||
|
"billing_profile_properties": {
|
||||||
|
"invoice_sections": [{"invoice_section_name": "xxxx-xxxx-xxx-xxx"}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
AzureCloudProvider, "create_subscription", wraps=mock_azure.create_subscription,
|
||||||
|
) as create_subscription:
|
||||||
|
create_subscription.return_value = SubscriptionCreationCSPResult(
|
||||||
|
subscription_verify_url="https://zombo.com", subscription_retry_after=10
|
||||||
|
)
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("applications.create_subscription", environment_id=environment.id),
|
url_for("applications.create_subscription", environment_id=environment.id),
|
||||||
)
|
)
|
||||||
@ -809,6 +831,16 @@ def test_create_subscription_failure(client, user_session, monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
user_session(environment.portfolio.owner)
|
user_session(environment.portfolio.owner)
|
||||||
|
environment.cloud_id = "management/group/id"
|
||||||
|
environment.application.portfolio.csp_data = {
|
||||||
|
"billing_account_name": "xxxx-xxxx-xxx-xxx",
|
||||||
|
"billing_profile_name": "xxxxxxxxxxx:xxxxxxxxxxxxx_xxxxxx",
|
||||||
|
"tenant_id": "xxxxxxxxxxx-xxxxxxxxxx-xxxxxxx-xxxxx",
|
||||||
|
"billing_profile_properties": {
|
||||||
|
"invoice_sections": [{"invoice_section_name": "xxxx-xxxx-xxx-xxx"}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("applications.create_subscription", environment_id=environment.id),
|
url_for("applications.create_subscription", environment_id=environment.id),
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user