Deployers Documentationο
Overviewο
Deployers execute a deployment stage step-by-step against a specific IaC tool or lifecycle script system. They follow a validate β step sequence pattern: call validate_workspace() and validate_environment() before running any steps. All context is bound at construction time β step methods take no arguments.
Available deployers:
Class |
Module |
Backend |
Purpose |
|---|---|---|---|
|
|
β |
Abstract base β step contracts + constructor |
|
|
Terraform CLI |
Runs init β validate β plan β apply via |
|
|
Ansible CLI |
Runs galaxy install β syntax-check β check β apply |
|
|
Docker CLI |
Deploys per-namespace Docker Compose/Stack from build output |
|
|
Helm CLI |
Deploys per-namespace, per-module Helm releases from build output |
|
|
Shell/Python/PS |
Executes lifecycle scripts from the deployment YAML |
BaseDeployerο
Abstract base class all deployers extend. Binds all deployment context in the constructor; step methods call self.deployment_service, self.build_path, etc.
Constructorο
BaseDeployer(
stage: DeploymentStageModel,
deployment_service: DeploymentService,
configuration_service: ConfigurationService,
build_path: Path,
work_path: Path,
verbose: bool = False,
force: bool = False,
)
Abstract interfaceο
Method |
Returns |
Description |
|---|---|---|
|
|
Canonical name (e.g. |
|
|
Ordered step names |
|
|
Verify IaC artefacts / lifecycle exist |
|
|
Verify tool binary / auth |
|
|
Initialise the tool |
|
|
Validate configuration |
|
|
Preview changes |
|
|
Apply changes |
|
|
Tear down resources |
|
|
Preview what destroy would remove |
|
|
Decode the saved plan file |
|
|
Retrieve infrastructure outputs (values only, no sensitivity) |
|
|
Collect outputs split by sensitivity β |
Step constantsο
from strata.deployers.base_deployer import (
STEP_SETUP, STEP_CHECK, STEP_PLAN, STEP_APPLY,
STEP_DESTROY, STEP_PLAN_DESTROY, STEP_SHOW_PLAN, STEP_OUTPUT,
)
Typical call sequenceο
deployer = TerraformDeployer(stage=stage, deployment_service=svc, ...)
ok, msgs = deployer.validate_workspace()
if not ok:
raise RuntimeError(msgs)
ok, msgs = deployer.validate_environment()
if not ok:
raise RuntimeError(msgs)
for step in ["setup", "check", "plan"]:
ok, msgs = getattr(deployer, step)()
if not ok:
break
TerraformDeployerο
Runs a deployment stage using the Terraform CLI (init β validate β plan β apply).
Constructorο
TerraformDeployer(
stage, deployment_service, configuration_service,
build_path, work_path,
verbose=False, force=False,
resolved_values=None, # ResolvedValues β injected as TF_VAR_* env vars during plan/apply/destroy
)
Step β Terraform command mappingο
Step |
Command |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
validate_workspaceο
Resolves the WorkspaceIacModel for the stage (priority: explicit stage.provisioner name β topology lookup β sole provisioner fallback), derives the working directory, and checks that *.tf files exist there. Sets _working_dir and _plan_file for subsequent steps.
validate_environmentο
Looks up the TerraformIntegration instance by name from IntegrationService and calls is_available(). Must be called after validate_workspace().
collect_outputsο
Reads the raw terraform output -json descriptor format to split outputs by sensitivity:
{
"cluster_endpoint": { "value": "https://...", "type": "string", "sensitive": false },
"kubeconfig": { "value": "...", "type": "string", "sensitive": true }
}
sensitive: false(or absent) β returned in the first dict (non_sensitive). The deploy pipeline injects these asTF_VAR_<key>for subsequent stages.sensitive: trueβ returned in the second dict (sensitive). Held in memory only β never written to environment variables.
Note: collect_outputs() runs terraform output -json independently of the output() step method. The output() step strips the descriptor envelope before returning values, making the sensitive flag inaccessible. collect_outputs() reads the raw form to preserve it.
IaC model resolution priorityο
stage.provisionerset β matchworkspace.spec.provisionersby namestage.topologyset β find the topology, match a provisioner whose.provisionertype matchesSingle provisioner workspace β use it unconditionally
Working directoryο
{deployment_build_path}/{iac_model.source.target_path} β falls back to terraform/{iac_model.name} when target_path is unset.
Resolved values (TF_VAR injection)ο
When resolved_values is provided, plan, apply, destroy, and plan_destroy wrap the Terraform call in inject_tf_vars(resolved_values), which temporarily sets TF_VAR_* environment variables for the subprocess.
AnsibleDeployerο
Runs a deployment stage using Ansible (galaxy install β syntax-check β check-mode β apply).
Constructorο
AnsibleDeployer(
stage, deployment_service, configuration_service,
build_path, work_path,
verbose=False, force=False,
resolved_values=None, # ResolvedValues β provides secrets + stage_outputs
solution_controller=None,
)
Step β Ansible command mappingο
Step |
Command |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
Not supported β returns |
|
Not supported β returns |
|
Not supported β returns |
validate_workspaceο
Resolves the WorkspaceIacModel for the stage (same priority as TerraformDeployer: explicit stage.provisioner β topology lookup β sole provisioner fallback). Verifies the source directory exists on disk.
When stage.topology is set, the deployer builds a dynamic inventory from stage outputs (see Topology-based inventory below).
validate_environmentο
Creates a minimal AnsibleIntegration instance and calls ensure_available(). Sets self._ansible for use in subsequent steps.
IaC spec optionsο
Configure per-provisioner behaviour under workspace.spec.provisioners[*].configuration:
Key |
Default |
Description |
|---|---|---|
|
|
Main playbook filename |
|
auto-detect |
Inventory file or directory path |
|
|
Extra variables passed via |
|
|
Secret name for the SSH private key (looked up in |
|
|
Stage output key to extract IPs for dynamic inventory (topology mode) |
Auto-discoveryο
Inventory: looks for
inventory,inventory.yml,hosts.yml,hostsin the working directoryRequirements: looks for
requirements.ymlorcollections/requirements.ymlDestroy playbook: expects
destroy.ymlin the working directory
Strata variable filesο
When AnsibleBuilder runs as part of strata build run, it writes strata_*.yml files into the provisioner build path. The deployer discovers these automatically and passes them to every playbook invocation as -e @file.yml arguments β before any inline extra_vars.
This means playbooks have immediate access to:
# Available without any explicit vars_files block in the playbook:
strata_workspace: # workspace identity, version, environment
strata_providers: # provider config keyed by name
strata_topologies: # topology components and volumes
strata_resources: # all resources keyed by name
strata_<type>: # resources by type (e.g. strata_objectstorage, strata_virtualmachine)
strata_modules: # module properties
strata_namespaces: # namespace definitions
strata_firewalls: # firewall rules
strata_dns_zones: # DNS zone definitions
strata_networks: # network configurations
Variable precedence (high to low):
-e key=value(inline extra_vars fromconfiguration.extra_vars)-e @strata_*.ymlfiles (strata variable files β injected by the deployer)Inventory vars
vars_filesin the playbook
Topology-based inventoryο
When a stage references a topology via stage.topology, the deployer builds a dynamic inventory string from stage outputs rather than using a static inventory file.
The IP address(es) are read from resolved_values.stage_outputs[ip_output_key] where ip_output_key defaults to server_ip (overridable in configuration). The resulting inventory is passed as -i <ip>, to ansible-playbook.
This allows an Ansible stage to immediately follow a Terraform stage that provisions the target hosts:
spec:
stages:
- name: infra
type: terraform
provisioner: tf_hetzner
- name: config
type: ansible
topology: my_topology # matches the topology that tf_hetzner manages
depends_on: [infra] # waits for infra to finish
SSH key managementο
The deployer resolves an SSH private key for remote access using this priority:
resolved_values.secrets[ssh_private_key_secret](default key name:ssh_private_key)os.environ[SSH_PRIVATE_KEY_SECRET.upper()](e.g.SSH_PRIVATE_KEY)
When a key is found, the deployer attempts to load it into a temporary ssh-agent (key lives in agent memory β never written to disk). Ansible picks up the agent automatically via SSH_AUTH_SOCK. If ssh-agent is unavailable, the key is written to a chmod 600 tempfile and passed via --private-key, then deleted immediately after the subprocess exits.
When no key is found, SSH key handling is skipped entirely β Ansible uses its default discovery (agent, ~/.ssh/id_rsa, etc.).
ScriptDeployerο
Executes lifecycle scripts defined in deployment_model.spec.lifecycle. No external binary is required.
Constructorο
ScriptDeployer(
stage, deployment_service, configuration_service,
build_path, work_path,
verbose=False, force=False,
)
Step β lifecycle phase mappingο
Step |
Lifecycle phase |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(not applicable) |
A phase that is not defined in the lifecycle β skip (returns True). A phase with no scripts β skip (returns True).
validate_workspaceο
Checks that deployment_model.spec.lifecycle is set. Returns False with an explanatory message if missing.
validate_environmentο
Always returns (True, []) β no external binary needed.
Script typesο
Extension |
Interpreter |
|---|---|
|
|
|
|
|
|
Environment variables injected into every scriptο
Variable |
Value |
|---|---|
|
|
|
|
|
|
Timeoutο
Each script subprocess has a hard 300-second timeout. Exceeding it returns False with a timeout message.
Script entry typesο
Script entries in the lifecycle phase can be:
Plain string β the path to the script file
ScriptPathModelβ uses the.fileattribute
Deployment YAML for ScriptDeployerο
apiVersion: strata.huybrechts.xyz/v1
kind: deployment
meta:
name: my_deployment
spec:
workspace:
name: my_workspace
file: workspace.yaml
environments:
- environment.yaml
stages:
- name: production
type: script
lifecycle:
deploy_setup:
scripts:
- scripts/setup.sh
deploy_apply:
scripts:
- scripts/apply.sh
deploy_destroy:
scripts:
- scripts/destroy.sh
ComposeDeployerο
Deploys Docker Compose/Stack artifacts produced by ComposeBuilder. For each namespace that has a docker-compose.yml in the build path, ComposeDeployer runs docker stack deploy. Because Docker Stack is the Swarm-mode orchestrator, Docker must be running in Swarm mode (docker swarm init or join an existing swarm) before any steps are executed.
Note:
docker stackcommands require Docker Swarm mode. Runningdocker stack deployagainst a daemon that is not a Swarm manager returns an error.
Step β Docker command mappingο
Step |
Docker command |
|---|---|
|
|
|
Verifies |
|
Parses compose files locally and counts services per namespace (no subprocess) |
|
|
|
|
|
|
|
|
|
Informational β no persisted plan format; returns an explanatory message |
Constructorο
ComposeDeployer(
stage, deployment_service, configuration_service,
build_path, work_path,
verbose=False, force=False,
resolved_values=None, # ResolvedValues β injected as bare KEY env vars during apply
)
validate_workspaceο
Iterates all namespace services from the deployment. For each namespace, looks for {build_path}/{deployment_name}/{namespace}/docker-compose.yml. Namespaces with no compose file are silently skipped β a missing file is not an error at this stage. Only namespaces where the file exists are registered for subsequent steps; if no files are found at all, a warning message is returned (but validation still passes).
validate_environmentο
Calls DockerIntegration.ensure_available(). Sets self._docker for subsequent steps. Fails with an error message if Docker is not on PATH or the daemon is unreachable.
Resolved values (compose env injection)ο
When resolved_values is provided, apply does two things for each namespace:
Writes a
.envfile alongsidedocker-compose.ymlβ consumed bydocker compose upfor local workflows.Injects vars into
os.environviainject_compose_env()for the duration of thedocker stack deploysubprocess β required becausedocker stackdoes not read.envfiles.
Aspect |
Detail |
|---|---|
Key format |
Bare key β no prefix (e.g. |
Merge order |
features β variables β secrets (secrets win on collision) |
Feature booleans |
Lowercased ( |
|
Always written to the namespace directory, even when empty |
Secret values |
Written to |
Logging |
Counts are logged ( |
Deployment YAML exampleο
spec:
stages:
- name: production
provisioner: xyz_swarm
steps: [apply]
Where xyz_swarm references a provisioner with provisioner: compose in the workspace YAML.
See also: DockerIntegration
HelmDeployerο
Deploys per-namespace, per-module Helm releases from build output produced by HelmBuilder. For each module with type: helm, reads values.yaml and meta.yaml from the build path and runs helm upgrade --install.
Step β Helm command mappingο
Step |
Helm command |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
No-op β returns empty dict |
validate_workspaceο
Iterates all namespace/module pairs in the build path. Only processes modules where module.spec.type == ServiceDeployerType.HELM. For each qualifying module, reads values.yaml and meta.yaml from the build output.
validate_environmentο
Calls HelmIntegration.ensure_available().
Chart source resolutionο
meta.yaml carries releaseName and namespace. The chart reference is derived from module.spec.source:
chart_repository + chart_nameβ registry chartrepository + source_pathβ local chart path in the build directory
Deployment YAML exampleο
spec:
stages:
- name: platform
provisioner: xyz_iac
steps: [apply]
Where xyz_iac references a provisioner with provisioner: helm in the workspace YAML.