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>
49 lines
1.4 KiB
Elixir
49 lines
1.4 KiB
Elixir
defmodule ArcadiaCloud.Provisioning.Steps.ActivateDeployment do
|
|
@moduledoc """
|
|
Final step of the deployment-provisioning choreography: moves the
|
|
deployment from `provisioning` to `active` now that its infra is up,
|
|
registered, linked, and reachable by DNS.
|
|
|
|
Idempotent: `transition_state/3` returns `{:ok, deployment}` when the
|
|
deployment is already `active`.
|
|
|
|
No compensation: this is the last step, so the saga only ever rolls
|
|
back from a step BEFORE this one — meaning this step never ran and the
|
|
deployment is still `provisioning` (cancelled by
|
|
MarkDeploymentProvisioning's compensate).
|
|
"""
|
|
|
|
@behaviour ArcadiaCloud.Provisioning.Step
|
|
|
|
alias ArcadiaCloud.Deployments
|
|
|
|
@impl true
|
|
def name, do: "activate_deployment"
|
|
|
|
@impl true
|
|
def execute(state) do
|
|
with {:ok, deployment} <- fetch_deployment(state) do
|
|
case Deployments.transition_state(deployment, "active",
|
|
reason: "provisioning_complete",
|
|
actor: "saga:#{state.saga_id}"
|
|
) do
|
|
{:ok, _} -> {:ok, state}
|
|
{:error, reason} -> {:error, {:activate_failed, reason}}
|
|
end
|
|
end
|
|
end
|
|
|
|
defp fetch_deployment(state) do
|
|
case state.saga && state.saga.deployment_id do
|
|
nil ->
|
|
{:error, :saga_has_no_deployment}
|
|
|
|
id ->
|
|
case Deployments.get_deployment(id) do
|
|
nil -> {:error, :deployment_not_found}
|
|
deployment -> {:ok, deployment}
|
|
end
|
|
end
|
|
end
|
|
end
|