Phase 4a: deployment-provisioning choreography saga
Wire a full tenant deployment as one orchestrated, compensating saga: mark → create droplet → wait active → register in inventory → link to deployment → point DNS → activate. A failure anywhere rolls the whole thing back — droplet destroyed, DNS reverted, deployment moved to cancelled. - New lifecycle state `provisioning`; deployments created via the provision path enter here and only reach `active` once the saga's ActivateDeployment step runs. - Four new steps: MarkDeploymentProvisioning (owns the deployment's failure state), LinkDeploymentResource, PointDeploymentDns, ActivateDeployment. - Provisioning.provision_deployment/2 assembles + starts the saga. - DeploymentController: POST /deployments with provision:true creates in `provisioning` and kicks the saga (202); GET /deployments/:id now returns the provisioning saga + per-step progress. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -148,6 +148,54 @@ defmodule ArcadiaCloud.Provisioning do
|
||||
})
|
||||
end
|
||||
|
||||
alias ArcadiaCloud.Provisioning.Steps
|
||||
|
||||
@doc """
|
||||
Assembles + starts the full deployment-provisioning choreography saga
|
||||
for a deployment that was created in the `provisioning` state.
|
||||
|
||||
Steps: mark → create droplet → wait active → register in inventory →
|
||||
link to deployment → point DNS → activate. A failure anywhere rolls
|
||||
the whole thing back (droplet destroyed, DNS reverted, deployment
|
||||
moved to `cancelled`).
|
||||
|
||||
Required opts: :size, :image. Optional: :region (falls back to the
|
||||
deployment's region), :dns_domain, :dns_record_name (falls back to the
|
||||
deployment slug), :triggered_by.
|
||||
"""
|
||||
def provision_deployment(deployment, opts \\ []) do
|
||||
region = opts[:region] || deployment.region
|
||||
|
||||
inputs = %{
|
||||
droplet_name: opts[:droplet_name] || "dep-#{deployment.slug}",
|
||||
droplet_region: region,
|
||||
droplet_size: opts[:size],
|
||||
droplet_image: opts[:image],
|
||||
droplet_tags: [
|
||||
"deployment:#{deployment.id}",
|
||||
"tenant:#{deployment.tenant_id}"
|
||||
],
|
||||
dns_domain: opts[:dns_domain],
|
||||
dns_record_name: opts[:dns_record_name] || deployment.slug
|
||||
}
|
||||
|
||||
start_saga(%{
|
||||
kind: "provision",
|
||||
deployment_id: deployment.id,
|
||||
triggered_by: opts[:triggered_by] || "manual",
|
||||
step_modules: [
|
||||
Steps.MarkDeploymentProvisioning,
|
||||
Steps.CreateDroplet,
|
||||
Steps.WaitDropletActive,
|
||||
Steps.RegisterDroplet,
|
||||
Steps.LinkDeploymentResource,
|
||||
Steps.PointDeploymentDns,
|
||||
Steps.ActivateDeployment
|
||||
],
|
||||
inputs: inputs
|
||||
})
|
||||
end
|
||||
|
||||
def get_saga(id), do: Repo.get(SagaRun, id)
|
||||
def get_saga!(id), do: Repo.get!(SagaRun, id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user