state machine integration wip
This commit is contained in:
@@ -10,6 +10,9 @@ class StageStates(Enum):
|
||||
class AzureStages(Enum):
|
||||
TENANT = "tenant"
|
||||
BILLING_PROFILE = "billing profile"
|
||||
BILLING_PROFILE_TENANT_ACCESS = "billing profile tenant access"
|
||||
TASK_ORDER_BILLING = "task order billing"
|
||||
BILLING_INSTRUCTION = "billing instruction"
|
||||
|
||||
|
||||
def _build_csp_states(csp_stages):
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
from random import choice, choices
|
||||
import re
|
||||
import string
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, Enum as SQLAEnum
|
||||
from sqlalchemy.orm import relationship, reconstructor
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
@@ -17,6 +21,16 @@ import atst.models.mixins as mixins
|
||||
from atst.models.mixins.state_machines import FSMStates, AzureStages, _build_transitions
|
||||
|
||||
|
||||
def make_password():
|
||||
return choice(string.ascii_letters) + "".join(
|
||||
choices(string.ascii_letters + string.digits + string.punctuation, k=15)
|
||||
)
|
||||
|
||||
|
||||
def fetch_portfolio_creds(portfolio):
|
||||
return dict(username="mock-cloud", password="shh")
|
||||
|
||||
|
||||
@add_state_features(Tags)
|
||||
class StateMachineWithTags(Machine):
|
||||
pass
|
||||
@@ -73,57 +87,49 @@ class PortfolioStateMachine(
|
||||
return getattr(FSMStates, self.state)
|
||||
return self.state
|
||||
|
||||
def trigger_next_transition(self):
|
||||
def trigger_next_transition(self, **kwargs):
|
||||
state_obj = self.machine.get_state(self.state)
|
||||
|
||||
if state_obj.is_system:
|
||||
if self.current_state in (FSMStates.UNSTARTED, FSMStates.STARTING):
|
||||
# call the first trigger availabe for these two system states
|
||||
trigger_name = self.machine.get_triggers(self.current_state.name)[0]
|
||||
self.trigger(trigger_name)
|
||||
self.trigger(trigger_name, **kwargs)
|
||||
|
||||
elif self.current_state == FSMStates.STARTED:
|
||||
# get the first trigger that starts with 'create_'
|
||||
create_trigger = self._get_first_stage_create_trigger()
|
||||
if create_trigger:
|
||||
self.trigger(create_trigger)
|
||||
self.trigger(create_trigger, **kwargs)
|
||||
else:
|
||||
self.fail_stage(stage)
|
||||
|
||||
elif state_obj.is_IN_PROGRESS:
|
||||
pass
|
||||
|
||||
# elif state_obj.is_TENANT:
|
||||
# pass
|
||||
# elif state_obj.is_BILLING_PROFILE:
|
||||
# pass
|
||||
elif state_obj.is_CREATED:
|
||||
triggers = self.machine.get_triggers(state_obj.name)
|
||||
self.trigger(triggers[-1], **kwargs)
|
||||
|
||||
# @with_payload
|
||||
def after_in_progress_callback(self, event):
|
||||
stage = self.current_state.name.split("_IN_PROGRESS")[0].lower()
|
||||
if stage == "tenant":
|
||||
payload = dict( # nosec
|
||||
creds={"username": "mock-cloud", "pass": "shh"},
|
||||
user_id="123",
|
||||
password="123",
|
||||
domain_name="123",
|
||||
first_name="john",
|
||||
last_name="doe",
|
||||
country_code="US",
|
||||
password_recovery_email_address="password@email.com",
|
||||
)
|
||||
elif stage == "billing_profile":
|
||||
payload = dict(creds={"username": "mock-cloud", "pass": "shh"},)
|
||||
|
||||
# Accumulate payload w/ creds
|
||||
payload = event.kwargs.get("csp_data")
|
||||
payload["creds"] = event.kwargs.get("creds")
|
||||
|
||||
payload_data_cls = get_stage_csp_class(stage, "payload")
|
||||
if not payload_data_cls:
|
||||
print("could not resolve payload data class")
|
||||
self.fail_stage(stage)
|
||||
try:
|
||||
payload_data = payload_data_cls(**payload)
|
||||
except PydanticValidationError as exc:
|
||||
print("Payload Validation Error:")
|
||||
print(exc.json())
|
||||
print("got")
|
||||
print(payload)
|
||||
self.fail_stage(stage)
|
||||
|
||||
# TODO: Determine best place to do this, maybe @reconstructor
|
||||
csp = event.kwargs.get("csp")
|
||||
if csp is not None:
|
||||
self.csp = AzureCSP(app).cloud
|
||||
@@ -132,7 +138,8 @@ class PortfolioStateMachine(
|
||||
|
||||
for attempt in range(5):
|
||||
try:
|
||||
response = getattr(self.csp, "create_" + stage)(payload_data)
|
||||
func_name = f"create_{stage}"
|
||||
response = getattr(self.csp, func_name)(payload_data)
|
||||
except (ConnectionException, UnknownServerException) as exc:
|
||||
print("caught exception. retry", attempt)
|
||||
continue
|
||||
@@ -140,14 +147,17 @@ class PortfolioStateMachine(
|
||||
break
|
||||
else:
|
||||
# failed all attempts
|
||||
print("failed")
|
||||
self.fail_stage(stage)
|
||||
|
||||
if self.portfolio.csp_data is None:
|
||||
self.portfolio.csp_data = {}
|
||||
self.portfolio.csp_data[stage + "_data"] = response
|
||||
self.portfolio.csp_data.update(response)
|
||||
db.session.add(self.portfolio)
|
||||
db.session.commit()
|
||||
|
||||
# store any updated creds, if necessary
|
||||
|
||||
self.finish_stage(stage)
|
||||
|
||||
def is_csp_data_valid(self, event):
|
||||
@@ -156,16 +166,23 @@ class PortfolioStateMachine(
|
||||
if self.portfolio.csp_data is None or not isinstance(
|
||||
self.portfolio.csp_data, dict
|
||||
):
|
||||
print("no csp data")
|
||||
return False
|
||||
|
||||
stage = self.current_state.name.split("_IN_PROGRESS")[0].lower()
|
||||
stage_data = self.portfolio.csp_data.get(stage + "_data")
|
||||
stage_data = self.portfolio.csp_data
|
||||
cls = get_stage_csp_class(stage, "result")
|
||||
if not cls:
|
||||
return False
|
||||
|
||||
try:
|
||||
cls(**stage_data)
|
||||
dc = cls(**stage_data)
|
||||
if getattr(dc, "get_creds", None) is not None:
|
||||
new_creds = dc.get_creds()
|
||||
# TODO: how/where to store these
|
||||
# TODO: credential schema
|
||||
# self.store_creds(self.portfolio, new_creds)
|
||||
|
||||
except PydanticValidationError as exc:
|
||||
print(exc.json())
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user