Update atst to atat
This commit is contained in:
621
atat/domain/csp/cloud/models.py
Normal file
621
atat/domain/csp/cloud/models.py
Normal file
@@ -0,0 +1,621 @@
|
||||
from enum import Enum
|
||||
from secrets import token_urlsafe
|
||||
from typing import Dict, List, Optional
|
||||
from uuid import uuid4
|
||||
import re
|
||||
|
||||
from pydantic import BaseModel, validator, root_validator
|
||||
|
||||
from .utils import (
|
||||
generate_mail_nickname,
|
||||
generate_user_principal_name,
|
||||
)
|
||||
from atat.utils import snake_to_camel
|
||||
|
||||
|
||||
class AliasModel(BaseModel):
|
||||
"""
|
||||
This provides automatic camel <-> snake conversion for serializing to/from json
|
||||
You can override the alias generation in subclasses by providing a Config that defines
|
||||
a fields property with a dict mapping variables to their cast names, for cases like:
|
||||
* some_url:someURL
|
||||
* user_object_id:objectId
|
||||
"""
|
||||
|
||||
class Config:
|
||||
alias_generator = snake_to_camel
|
||||
allow_population_by_field_name = True
|
||||
|
||||
|
||||
class BaseCSPPayload(AliasModel):
|
||||
tenant_id: str
|
||||
|
||||
|
||||
class TenantCSPPayload(AliasModel):
|
||||
user_id: str
|
||||
password: Optional[str]
|
||||
domain_name: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
country_code: str
|
||||
password_recovery_email_address: str
|
||||
|
||||
|
||||
class TenantCSPResult(AliasModel):
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
user_object_id: str
|
||||
domain_name: str
|
||||
|
||||
tenant_admin_username: Optional[str]
|
||||
tenant_admin_password: Optional[str]
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"user_object_id": "objectId",
|
||||
}
|
||||
|
||||
def dict(self, *args, **kwargs):
|
||||
exclude = {"tenant_admin_username", "tenant_admin_password"}
|
||||
if "exclude" not in kwargs:
|
||||
kwargs["exclude"] = exclude
|
||||
else:
|
||||
kwargs["exclude"].update(exclude)
|
||||
|
||||
return super().dict(*args, **kwargs)
|
||||
|
||||
def get_creds(self):
|
||||
return {
|
||||
"tenant_admin_username": self.tenant_admin_username,
|
||||
"tenant_admin_password": self.tenant_admin_password,
|
||||
"tenant_id": self.tenant_id,
|
||||
}
|
||||
|
||||
|
||||
class BillingProfileAddress(AliasModel):
|
||||
company_name: str
|
||||
address_line_1: str
|
||||
city: str
|
||||
region: str
|
||||
country: str
|
||||
postal_code: str
|
||||
|
||||
|
||||
class BillingProfileCLINBudget(AliasModel):
|
||||
clin_budget: Dict
|
||||
"""
|
||||
"clinBudget": {
|
||||
"amount": 0,
|
||||
"startDate": "2019-12-18T16:47:40.909Z",
|
||||
"endDate": "2019-12-18T16:47:40.909Z",
|
||||
"externalReferenceId": "string"
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class BillingProfileCreationCSPPayload(BaseCSPPayload):
|
||||
tenant_id: str
|
||||
billing_profile_display_name: str
|
||||
billing_account_name: str
|
||||
enabled_azure_plans: Optional[List[str]]
|
||||
address: BillingProfileAddress
|
||||
|
||||
@validator("enabled_azure_plans", pre=True, always=True)
|
||||
def default_enabled_azure_plans(cls, v):
|
||||
"""
|
||||
Normally you'd implement this by setting the field with a value of:
|
||||
dataclasses.field(default_factory=list)
|
||||
but that prevents the object from being correctly pickled, so instead we need
|
||||
to rely on a validator to ensure this has an empty value when not specified
|
||||
"""
|
||||
return v or []
|
||||
|
||||
class Config:
|
||||
fields = {"billing_profile_display_name": "displayName"}
|
||||
|
||||
|
||||
class BillingProfileCreationCSPResult(AliasModel):
|
||||
billing_profile_verify_url: str
|
||||
billing_profile_retry_after: int
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"billing_profile_verify_url": "Location",
|
||||
"billing_profile_retry_after": "Retry-After",
|
||||
}
|
||||
|
||||
|
||||
class BillingProfileVerificationCSPPayload(BaseCSPPayload):
|
||||
billing_profile_verify_url: str
|
||||
|
||||
|
||||
class BillingInvoiceSection(AliasModel):
|
||||
invoice_section_id: str
|
||||
invoice_section_name: str
|
||||
|
||||
class Config:
|
||||
fields = {"invoice_section_id": "id", "invoice_section_name": "name"}
|
||||
|
||||
|
||||
class BillingProfileProperties(AliasModel):
|
||||
address: BillingProfileAddress
|
||||
billing_profile_display_name: str
|
||||
invoice_sections: List[BillingInvoiceSection]
|
||||
|
||||
class Config:
|
||||
fields = {"billing_profile_display_name": "displayName"}
|
||||
|
||||
|
||||
class BillingProfileVerificationCSPResult(AliasModel):
|
||||
billing_profile_id: str
|
||||
billing_profile_name: str
|
||||
billing_profile_properties: BillingProfileProperties
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"billing_profile_id": "id",
|
||||
"billing_profile_name": "name",
|
||||
"billing_profile_properties": "properties",
|
||||
}
|
||||
|
||||
|
||||
class BillingProfileTenantAccessCSPPayload(BaseCSPPayload):
|
||||
tenant_id: str
|
||||
user_object_id: str
|
||||
billing_account_name: str
|
||||
billing_profile_name: str
|
||||
|
||||
|
||||
class BillingProfileTenantAccessCSPResult(AliasModel):
|
||||
billing_role_assignment_id: str
|
||||
billing_role_assignment_name: str
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"billing_role_assignment_id": "id",
|
||||
"billing_role_assignment_name": "name",
|
||||
}
|
||||
|
||||
|
||||
class TaskOrderBillingCreationCSPPayload(BaseCSPPayload):
|
||||
billing_account_name: str
|
||||
billing_profile_name: str
|
||||
|
||||
|
||||
class TaskOrderBillingCreationCSPResult(AliasModel):
|
||||
task_order_billing_verify_url: str
|
||||
task_order_retry_after: int
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"task_order_billing_verify_url": "Location",
|
||||
"task_order_retry_after": "Retry-After",
|
||||
}
|
||||
|
||||
|
||||
class TaskOrderBillingVerificationCSPPayload(BaseCSPPayload):
|
||||
task_order_billing_verify_url: str
|
||||
|
||||
|
||||
class BillingProfileEnabledPlanDetails(AliasModel):
|
||||
enabled_azure_plans: List[Dict]
|
||||
|
||||
|
||||
class TaskOrderBillingVerificationCSPResult(AliasModel):
|
||||
billing_profile_id: str
|
||||
billing_profile_name: str
|
||||
billing_profile_enabled_plan_details: BillingProfileEnabledPlanDetails
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"billing_profile_id": "id",
|
||||
"billing_profile_name": "name",
|
||||
"billing_profile_enabled_plan_details": "properties",
|
||||
}
|
||||
|
||||
|
||||
class BillingInstructionCSPPayload(BaseCSPPayload):
|
||||
initial_clin_amount: float
|
||||
initial_clin_start_date: str
|
||||
initial_clin_end_date: str
|
||||
initial_clin_type: str
|
||||
initial_task_order_id: str
|
||||
billing_account_name: str
|
||||
billing_profile_name: str
|
||||
|
||||
|
||||
class BillingInstructionCSPResult(AliasModel):
|
||||
reported_clin_name: str
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"reported_clin_name": "name",
|
||||
}
|
||||
|
||||
|
||||
class TenantAdminOwnershipCSPPayload(BaseCSPPayload):
|
||||
user_object_id: str
|
||||
|
||||
|
||||
class TenantAdminOwnershipCSPResult(AliasModel):
|
||||
admin_owner_assignment_id: str
|
||||
|
||||
class Config:
|
||||
fields = {"admin_owner_assignment_id": "id"}
|
||||
|
||||
|
||||
class TenantPrincipalOwnershipCSPPayload(BaseCSPPayload):
|
||||
principal_id: str
|
||||
|
||||
|
||||
class TenantPrincipalOwnershipCSPResult(AliasModel):
|
||||
principal_owner_assignment_id: str
|
||||
|
||||
class Config:
|
||||
fields = {"principal_owner_assignment_id": "id"}
|
||||
|
||||
|
||||
class TenantPrincipalAppCSPPayload(BaseCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class TenantPrincipalAppCSPResult(AliasModel):
|
||||
principal_app_id: str
|
||||
principal_app_object_id: str
|
||||
|
||||
class Config:
|
||||
fields = {"principal_app_id": "appId", "principal_app_object_id": "id"}
|
||||
|
||||
|
||||
class TenantPrincipalCSPPayload(BaseCSPPayload):
|
||||
principal_app_id: str
|
||||
|
||||
|
||||
class TenantPrincipalCSPResult(AliasModel):
|
||||
principal_id: str
|
||||
|
||||
class Config:
|
||||
fields = {"principal_id": "id"}
|
||||
|
||||
|
||||
class TenantPrincipalCredentialCSPPayload(BaseCSPPayload):
|
||||
principal_app_id: str
|
||||
principal_app_object_id: str
|
||||
|
||||
|
||||
class TenantPrincipalCredentialCSPResult(AliasModel):
|
||||
principal_client_id: str
|
||||
principal_creds_established: bool
|
||||
|
||||
|
||||
class AdminRoleDefinitionCSPPayload(BaseCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class AdminRoleDefinitionCSPResult(AliasModel):
|
||||
admin_role_def_id: str
|
||||
|
||||
|
||||
class PrincipalAdminRoleCSPPayload(BaseCSPPayload):
|
||||
principal_id: str
|
||||
admin_role_def_id: str
|
||||
|
||||
|
||||
class PrincipalAdminRoleCSPResult(AliasModel):
|
||||
principal_assignment_id: str
|
||||
|
||||
class Config:
|
||||
fields = {"principal_assignment_id": "id"}
|
||||
|
||||
|
||||
AZURE_MGMNT_PATH = "/providers/Microsoft.Management/managementGroups/"
|
||||
|
||||
MANAGEMENT_GROUP_NAME_REGEX = "^[a-zA-Z0-9\-_\(\)\.]+$"
|
||||
|
||||
|
||||
class ManagementGroupCSPPayload(AliasModel):
|
||||
"""
|
||||
:param: management_group_name: Just pass a UUID for this.
|
||||
:param: display_name: This can contain any character and
|
||||
spaces, but should be 90 characters or fewer long.
|
||||
:param: parent_id: This should be the fully qualified Azure ID,
|
||||
i.e. /providers/Microsoft.Management/managementGroups/[management group ID]
|
||||
"""
|
||||
|
||||
tenant_id: str
|
||||
management_group_name: Optional[str]
|
||||
display_name: str
|
||||
parent_id: Optional[str]
|
||||
|
||||
@validator("management_group_name", pre=True, always=True)
|
||||
def supply_management_group_name_default(cls, name):
|
||||
if name:
|
||||
if re.match(MANAGEMENT_GROUP_NAME_REGEX, name) is None:
|
||||
raise ValueError(
|
||||
f"Management group name must match {MANAGEMENT_GROUP_NAME_REGEX}"
|
||||
)
|
||||
|
||||
return name[0:90]
|
||||
else:
|
||||
return str(uuid4())
|
||||
|
||||
@validator("display_name", pre=True, always=True)
|
||||
def enforce_display_name_length(cls, name):
|
||||
return name[0:90]
|
||||
|
||||
@validator("parent_id", pre=True, always=True)
|
||||
def enforce_parent_id_pattern(cls, id_):
|
||||
if id_:
|
||||
if AZURE_MGMNT_PATH not in id_:
|
||||
return f"{AZURE_MGMNT_PATH}{id_}"
|
||||
else:
|
||||
return id_
|
||||
|
||||
|
||||
class ManagementGroupCSPResponse(AliasModel):
|
||||
id: str
|
||||
|
||||
|
||||
class ManagementGroupGetCSPPayload(BaseCSPPayload):
|
||||
management_group_name: str
|
||||
|
||||
|
||||
class ManagementGroupGetCSPResponse(AliasModel):
|
||||
id: str
|
||||
|
||||
|
||||
class ApplicationCSPPayload(ManagementGroupCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class ApplicationCSPResult(ManagementGroupCSPResponse):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupCSPPayload(ManagementGroupCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupCSPResult(ManagementGroupCSPResponse):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupVerificationCSPPayload(ManagementGroupGetCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class InitialMgmtGroupVerificationCSPResult(ManagementGroupGetCSPResponse):
|
||||
pass
|
||||
|
||||
|
||||
class EnvironmentCSPPayload(ManagementGroupCSPPayload):
|
||||
pass
|
||||
|
||||
|
||||
class EnvironmentCSPResult(ManagementGroupCSPResponse):
|
||||
pass
|
||||
|
||||
|
||||
class KeyVaultCredentials(BaseModel):
|
||||
root_sp_client_id: Optional[str]
|
||||
root_sp_key: Optional[str]
|
||||
root_tenant_id: Optional[str]
|
||||
|
||||
tenant_id: Optional[str]
|
||||
|
||||
tenant_admin_username: Optional[str]
|
||||
tenant_admin_password: Optional[str]
|
||||
|
||||
tenant_sp_client_id: Optional[str]
|
||||
tenant_sp_key: Optional[str]
|
||||
|
||||
@root_validator(pre=True)
|
||||
def enforce_admin_creds(cls, values):
|
||||
tenant_id = values.get("tenant_id")
|
||||
username = values.get("tenant_admin_username")
|
||||
password = values.get("tenant_admin_password")
|
||||
if any([username, password]) and not all([tenant_id, username, password]):
|
||||
raise ValueError(
|
||||
"tenant_id, tenant_admin_username, and tenant_admin_password must all be set if any one is"
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
@root_validator(pre=True)
|
||||
def enforce_sp_creds(cls, values):
|
||||
tenant_id = values.get("tenant_id")
|
||||
client_id = values.get("tenant_sp_client_id")
|
||||
key = values.get("tenant_sp_key")
|
||||
if any([client_id, key]) and not all([tenant_id, client_id, key]):
|
||||
raise ValueError(
|
||||
"tenant_id, tenant_sp_client_id, and tenant_sp_key must all be set if any one is"
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
@root_validator(pre=True)
|
||||
def enforce_root_creds(cls, values):
|
||||
sp_creds = [
|
||||
values.get("root_tenant_id"),
|
||||
values.get("root_sp_client_id"),
|
||||
values.get("root_sp_key"),
|
||||
]
|
||||
if any(sp_creds) and not all(sp_creds):
|
||||
raise ValueError(
|
||||
"root_tenant_id, root_sp_client_id, and root_sp_key must all be set if any one is"
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
def merge_credentials(
|
||||
self, new_creds: "KeyVaultCredentials"
|
||||
) -> "KeyVaultCredentials":
|
||||
updated_creds = {k: v for k, v in new_creds.dict().items() if v}
|
||||
old_creds = self.dict()
|
||||
old_creds.update(updated_creds)
|
||||
|
||||
return KeyVaultCredentials(**old_creds)
|
||||
|
||||
|
||||
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):
|
||||
billing_account_name: str
|
||||
billing_profile_name: str
|
||||
|
||||
|
||||
class ProductPurchaseCSPResult(AliasModel):
|
||||
product_purchase_verify_url: str
|
||||
product_purchase_retry_after: int
|
||||
|
||||
class Config:
|
||||
fields = {
|
||||
"product_purchase_verify_url": "Location",
|
||||
"product_purchase_retry_after": "Retry-After",
|
||||
}
|
||||
|
||||
|
||||
class ProductPurchaseVerificationCSPPayload(BaseCSPPayload):
|
||||
product_purchase_verify_url: str
|
||||
|
||||
|
||||
class ProductPurchaseVerificationCSPResult(AliasModel):
|
||||
premium_purchase_date: str
|
||||
|
||||
|
||||
class UserMixin(BaseModel):
|
||||
password: Optional[str]
|
||||
|
||||
@property
|
||||
def user_principal_name(self):
|
||||
return generate_user_principal_name(self.display_name, self.tenant_host_name)
|
||||
|
||||
@property
|
||||
def mail_nickname(self):
|
||||
return generate_mail_nickname(self.display_name)
|
||||
|
||||
@validator("password", pre=True, always=True)
|
||||
def supply_password_default(cls, password):
|
||||
return password or token_urlsafe(16)
|
||||
|
||||
|
||||
class UserCSPPayload(BaseCSPPayload, UserMixin):
|
||||
display_name: str
|
||||
tenant_host_name: str
|
||||
email: str
|
||||
|
||||
|
||||
class UserCSPResult(AliasModel):
|
||||
id: str
|
||||
|
||||
|
||||
class UserRoleCSPPayload(BaseCSPPayload):
|
||||
class Roles(str, Enum):
|
||||
owner = "owner"
|
||||
contributor = "contributor"
|
||||
billing = "billing"
|
||||
|
||||
management_group_id: str
|
||||
role: Roles
|
||||
user_object_id: str
|
||||
|
||||
|
||||
class UserRoleCSPResult(AliasModel):
|
||||
id: str
|
||||
|
||||
|
||||
class QueryColumn(AliasModel):
|
||||
name: str
|
||||
type: str
|
||||
|
||||
|
||||
class CostManagementQueryProperties(AliasModel):
|
||||
columns: List[QueryColumn]
|
||||
rows: List[Optional[list]]
|
||||
|
||||
|
||||
class CostManagementQueryCSPResult(AliasModel):
|
||||
name: str
|
||||
properties: CostManagementQueryProperties
|
||||
|
||||
|
||||
class ReportingCSPPayload(BaseCSPPayload):
|
||||
invoice_section_id: str
|
||||
from_date: str
|
||||
to_date: str
|
||||
|
||||
@root_validator(pre=True)
|
||||
def extract_invoice_section(cls, values):
|
||||
try:
|
||||
values["invoice_section_id"] = values["billing_profile_properties"][
|
||||
"invoice_sections"
|
||||
][0]["invoice_section_id"]
|
||||
return values
|
||||
except (KeyError, IndexError):
|
||||
raise ValueError("Invoice section ID not present in payload")
|
||||
|
||||
|
||||
class BillingOwnerCSPPayload(BaseCSPPayload, UserMixin):
|
||||
"""
|
||||
This class needs to consume data in the shape it's in from the
|
||||
top-level portfolio CSP data, but return it in the shape
|
||||
needed for user provisioning.
|
||||
"""
|
||||
|
||||
display_name = "billing_admin"
|
||||
domain_name: str
|
||||
password_recovery_email_address: str
|
||||
|
||||
@property
|
||||
def tenant_host_name(self):
|
||||
return self.domain_name
|
||||
|
||||
@property
|
||||
def email(self):
|
||||
return self.password_recovery_email_address
|
||||
|
||||
|
||||
class BillingOwnerCSPResult(AliasModel):
|
||||
billing_owner_id: str
|
Reference in New Issue
Block a user