Workspaces
The unit of Terraform execution. One Workspace, one module, one environment.
A Workspace is a namespaced Kubernetes resource that represents a single Terraform module reconciled by Magos. Each Workspace points at one Git source, declares one Terraform version, and is reconciled by the workspace controller into Plan and Apply Jobs that run in their own pods.
Workspaces are passive executors. They do not coordinate with each other, they have no notion of sibling environments, and they only run when an orchestrator (Project or Rollout) grants execution permission.
Minimal Workspace
apiVersion: magosproject.io/v1alpha1
kind: Workspace
metadata:
name: vpc-dev
namespace: default
labels:
env: dev
spec:
projectRef:
name: networking
source:
repoURL: https://github.com/magosproject/demo.git
targetRevision: main
path: .
terraform:
version: "1.14.8"
What the fields do:
projectRef.namebinds the Workspace to aProjectin the same namespace.sourcedescribes the Git module: repository URL, branch/tag/commit, and the path to run Terraform in.terraform.versionselects which Terraform binary the runner pod installs. OpenTofu binaries with matching CLI flags are also accepted; the field is namedterraform.versionfor clarity.metadata.labelsare howRolloutselectors match this Workspace.
Source revisions
source.targetRevision accepts anything Git can resolve: a branch name, a tag, or a full commit SHA. When it is a branch or tag, the RefWatcher controller polls the remote on its interval and writes the resolved SHA back to the Workspace as the magosproject.io/detected-revision annotation. The workspace controller treats annotation changes as a trigger for a fresh reconcile, so a push to main automatically results in a new plan and apply cycle without operator intervention.
status.observedRevision is always the actual commit SHA that was applied. That is the field you key off in alerts and dashboards.
Variables
A Workspace receives Terraform inputs in three ways, in increasing precedence:
spec.terraform.tfvarsPath: a path inside the Git source pointing at a.tfvarsfile.- Project-level
variableSetRef: a list ofVariableSets attached to the parent Project, applied in declaration order. - Workspace-level
variableSetRef: a list ofVariableSets attached directly to this Workspace, applied after the Project's.
Within the combined list of Variable Sets, later entries shadow earlier ones on conflicting variable names. The whole composition reaches Terraform as TF_VAR_<name> environment variables on the Plan and Apply pods, which means it overrides any matching variable defined in tfvarsPath. See Variable Sets for the full rules and the precedence-with-secrets discussion.
Cloud credentials
spec.serviceAccountName sets the Kubernetes ServiceAccount the Plan and Apply Pods run as (default magos-job). Bind that ServiceAccount to a cloud identity — GKE Workload Identity, AWS IRSA, or Azure Workload Identity — and Terraform's provider authenticates with no static keys stored in the cluster. See Cloud Provider Credentials for the per-provider setup.
Phases
Every Workspace progresses through a small state machine. The status.phase field surfaces the current state:
Pending: waiting on orchestration to grant execution permission, or in a reset window between runs.Planning: a Plan Job has been created and is runningterraform plan.Planned: the plan succeeded; the Workspace is either auto-applying or waiting for approval.Applying: an Apply Job has been created and is runningterraform applyagainst the previous plan file.Applied: the run completed successfully. The Workspace stays here until the next reconcile interval, a new revision is detected, or a manual reconcile is requested.Failed: a Job failed, or a controller error prevented progress.ValidationFailed: the plan violated aValidatingPolicyrule; apply was skipped.
status.reason is a CamelCase code (PlanSucceeded, ApplyFailed, AwaitingRollout, PolicyViolation, ...) suitable for keying alerts off. status.message is human-readable.
AutoApply and approval
spec.autoApply controls whether a clean plan auto-applies. With the default true, a successful plan with no policy violations immediately produces an Apply Job. With false, every plan parks the Workspace in Planned and waits for approval. Audit-mode policy warnings always require approval, regardless of autoApply: those plans land in PlannedWithViolations.
Approval is signaled by setting the magosproject.io/approved annotation to the value of status.awaitingApproval.runID. The controller ignores values that do not match the current run, so a stale approval from a previous run can never release a newer plan. The canonical command:
kubectl annotate workspace vpc-prod \
magosproject.io/approved=$(kubectl get workspace vpc-prod -o jsonpath='{.status.awaitingApproval.runID}')
The UI exposes the same action through an "Approve and Apply" button. When the run has policy warnings, the button is gated by a confirmation checkbox so the operator acknowledges what they are letting through.
To abandon a pending plan and re-plan against the latest inputs, set magosproject.io/reconcile-request to a fresh timestamp. While the workspace is gated, that annotation supersedes the gate.
Behavior when something changes during the gate
A new commit or VariableSet rotation arriving during review does not start a new plan by default: the original plan stays available for approval, and the next reconcile after the gate clears picks up the change. Set spec.discardStalePlan: true on the Workspace to abandon the gate as soon as a newer commit or rotation appears, and start fresh against the latest inputs.
Workspace spec edits always discard the gate (the operator's edit is the explicit signal that the previous plan is no longer the one they want). Scheduled drift-detection ticks never discard the gate.
Drift detection and reconcile interval
A successful Applied Workspace is not idle forever. The workspace controller schedules a fresh reconcile at a configurable interval (magosproject.io/reconcile-interval annotation, default three minutes). The scheduled reconcile re-plans the module to detect drift; if the new plan is empty, the Workspace returns to Applied without producing an Apply Job. If the plan has changes, it proceeds through the normal cycle.
This is how Magos catches drift caused by manual cloud-console edits, expired credentials, or external mutations to the underlying provider.
Per-Workspace storage and isolation
For each Workspace, the controller creates a PersistentVolumeClaim named <workspace-name>-data. The PVC is mounted into both Plan and Apply Pods at /workspace-data, which is how the binary plan file produced by the plan phase reaches the apply phase. The PVC is owned by the Workspace via an owner reference, so deleting the Workspace garbage-collects it.
Two Workspaces never share a PVC. There is no shared filesystem across runs of different Workspaces, no shared provider cache, and no shared credentials store. Provider plugins are cached at /workspace-data/plugin-cache inside the PVC, which keeps re-init fast within a single Workspace without leaking across Workspaces.
What a Workspace is not
- A Terraform state backend. State lives wherever the module's
backendblock puts it. - A coordinator. A Workspace has no concept of its siblings. If you need ordering, use a
Rollout. - A long-running process. Each phase is one Job. There is no controller-side
terraformshell holding open.
See Architecture for how Workspaces relate to the rest of the controller fleet, and Variable Sets for the full variable composition story.