Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
mkdocs-material-

- name: Install dependencies
run: pip install mkdocs-material mike
run: pip install -r requirements.txt
Comment thread
nlevee marked this conversation as resolved.
Outdated

- name: Lint Markdown files
uses: DavidAnson/markdownlint-cli2-action@ce4853d43830c74c1753b39f3cf40f71c2031eb9 # v23
Expand Down
43 changes: 5 additions & 38 deletions docs/operator-manual/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,45 +57,12 @@ The CLI used to start the different components is implemented using [`cobra`](ht

For more details on how the repository controller works with Git bundles and revisions, see the [Repository Controller documentation](./repository-controller.md).

### The TerraformLayer Controller
### Controller state machines

The status of a `TerraformLayer` is defined using the [conditions standards defined by the community](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties).

3 conditions are defined for a layer:

- `IsPlanArtifactUpToDate`. This condition is used for drift detection. The evaluation is made by compraing the timestamp of the last `terraform plan` which ran and the current date. The timestamp of the last plan is "stored" using an annotation.
- `IsApplyUpToDate`. This condition is used to check if an `apply` needs to run after the last `plan`. Comparison is made by comparing a checksum of the last planned binary and a checksum last applied binary stored in the annotations.
- `IsLastRelevantCommitPlanned`. This condition is used to check if a new commit has been made to the layer and need to be applied. It is evaluated by comparing the commit used for the last `plan`, the last commit which intoduced changes to the layer and the last commit made to the same branch of the repository. Those commits are "stored" as annotations.

With those 3 conditions, we defined 3 states:

- `Idle`. This is the state of a layer if no runner needs be started
- `PlanNeeded`. This is the state of a layer if burrito needs to start a `plan` runner
- `ApplyNeeded`. This is the state of a layer if burrito needs to start an `apply` runner

!!! info
If you use [`dry` remediation strategy](../user-guide/remediation-strategy.md) and an apply is needed, the layer will stay in the `ApplyNeeded` as long as it does not need to enter the `PlanNeeded`.

### The TerraformRun Controller

The status of a `TerraformRun` is also defined using the same [conditions standards defined by the community](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties).

5 conditions are defined for a run:

- `HasStatus`. This condition is used to check if a `TerraformRun` has already been reconciled by the controller.
- `HasReachedRetryLimit`. Used to check if a `TerraformRun` has reached the maximum number of retries.
- `HasSucceeded`. Used to check if a `TerraformRun` has already succeeded (runner pod exited successfully).
- `IsRunning`. Used to check if a `TerraformRun` is currently running by checking the current phase of its associated pod.
- `IsInfailureGracePeriod`. This condition is used to check if a Terraform workflow has already failed. If so, we use an exponential backoff strategy before restarting a runner on the given layer.

With those 5 conditions, we defined 6 states:

- `Initial`. This is the state of a run when it has just been created and has launched its first runner pod.
- `Running`. This is the state of a run if a runner pod is currently running.
- `FailureGracePeriod`. This is the state of a layer if a `plan` or `apply` runner has failed
- `Retrying`. This is an intermediate state of a run if a runner pod has failed and is being restarted (not in failure grace period anymore).
- `Succeeded`. This is one of the two final states a run can have. It means that the runner pod has exited successfully.
- `Failed`. This is the other final state a run can have. It means that the run has failed multiple times and has reached the maximum number of retries.
Each controller drives its resource through a state machine whose status is
expressed using the [conditions standards defined by the community](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties).
For the full state machines of each CRD, the conditions that drive them, and how
the resources interact, see [CRD State Machines](./state-machines.md).

The `TerraformRun` controller also creates and deletes the [Kubernetes leases](https://kubernetes.io/docs/concepts/architecture/leases/) to avoid concurrent use of Terraform on the same layer.

Expand Down
123 changes: 123 additions & 0 deletions docs/operator-manual/state-machines.md
Comment thread
nlevee marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# CRD State Machines

Each Burrito custom resource is driven by a state machine implemented in its
reconciler (`internal/controllers/<crd>/states.go`). Burrito controllers are
**level-based**: on every reconcile the controller *recomputes* the current state
from a set of boolean conditions through an ordered `switch`. There are no stored
transitions — a state's handler performs a side-effect (creating a `TerraformRun`,
a runner pod, an annotation…), and the *next* reconcile observes the changed
conditions and yields the next state.

A practical consequence: some states are **transient triggers**. For example a
`TerraformLayer` in `PlanNeeded` creates a run and, on the following reconcile,
is seen as running and reported as `Idle` again.

## TerraformRepository

The repository controller keeps Git bundles in the datastore in sync with the
remote. It has two states, decided by whether the last sync is too old or failed.

- `Synced` — bundles are up to date; requeue at the repository sync interval.
- `SyncNeeded` — fetch latest revisions per branch, store bundles, and annotate
the affected layers.

```mermaid
stateDiagram-v2
[*] --> Synced
Synced --> SyncNeeded: last sync too old / last sync failed
SyncNeeded --> Synced: all branches synced successfully
SyncNeeded --> SyncNeeded: sync error (requeue)
```

## TerraformLayer

The layer controller watches declared layers and creates `TerraformRun` resources
to plan (drift detection) and, when auto-apply is enabled, to apply. The state is
computed from conditions such as *is a run in flight*, *is a sync scheduled*, *is
the last plan too old*, *is the last relevant commit planned*, *is the apply up to
date*, and *has the last run reached its retry limit*.

Because the first `switch` case is "a run is in flight → `Idle`", a layer with a
running plan/apply is reported as `Idle`. `PlanNeeded` and `ApplyNeeded` are
transient: their handler creates the run and the next reconcile returns to `Idle`.

- `Idle` — nothing to do; requeue at the drift-detection interval.
- `PlanNeeded` — create a plan `TerraformRun`.
- `ApplyNeeded` — create an apply `TerraformRun` (only if auto-apply is enabled).
- `MaxRetriesReached` — the last run exhausted its retries; manual intervention
needed.

```mermaid
stateDiagram-v2
[*] --> Idle
Idle --> PlanNeeded: plan outdated / new relevant commit / sync scheduled
Idle --> ApplyNeeded: plan up to date but apply needed (autoApply on)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I'm not wrong, Idle --> ApplyNeeded is not possible 🤔

The layer should transition from PlanNeeded to ApplyNeeded if the plan contains a drift.

Do you confirm?

PlanNeeded --> Idle: TerraformRun (plan) created — run in flight
ApplyNeeded --> Idle: TerraformRun (apply) created — run in flight

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i'm not wrong, these conditions are not possible, the layer stays in PlanNeeded when a plan is running, and ApplyNeeded when a apply is running

Idle --> MaxRetriesReached: last run exhausted its retries
MaxRetriesReached --> Idle: new relevant commit / retry window reset
```

## TerraformRun

The run controller executes a plan or apply by creating runner pods, and handles
retries with an exponential backoff grace period. `Succeeded` and `Failed` are
terminal.

- `Initial` — freshly created; sets the lease and launches the first runner pod.
- `Running` — a runner pod is currently running.
- `FailureGracePeriod` — the runner failed; wait (exponential backoff) before retry.
- `Retrying` — grace period elapsed and retry limit not reached; launch a new pod.
- `Succeeded` — the runner pod exited successfully (lease released).
- `Failed` — retries exhausted (lease released).

```mermaid
stateDiagram-v2
[*] --> Initial
Initial --> Running: runner pod created
Running --> Succeeded: pod exited successfully
Running --> FailureGracePeriod: pod failed, retry limit not reached
FailureGracePeriod --> Retrying: grace period elapsed
FailureGracePeriod --> Failed: retry limit reached
Retrying --> Running: retry pod created
Succeeded --> [*]
Failed --> [*]
```

## TerraformPullRequest

The pull-request controller discovers layers affected by a PR/MR, waits for their
plans, and posts a comment. Note that "comment up to date" is checked before
"layers still planning", so a PR with an up-to-date comment is reported as `Idle`.

- `DiscoveryNeeded` — a new commit was detected; (re)create the temporary layers
affected by the PR.
- `Planning` — the temporary layers are still planning.
- `CommentNeeded` — plans finished; post/update the PR comment.
- `Idle` — comment is up to date; nothing to do.

```mermaid
stateDiagram-v2
[*] --> DiscoveryNeeded
DiscoveryNeeded --> Planning: temp layers created, commit discovered
Planning --> CommentNeeded: all layers finished planning
CommentNeeded --> Idle: comment posted on PR/MR
Idle --> DiscoveryNeeded: new commit on the PR branch
```

## How the CRDs interact

The resources form a chain: the repository annotates layers with the latest
relevant commit, layers create runs, runs create runner pods, and pods write plan
and apply results back onto the layer's annotations. A pull request creates
temporary layers and reports their plan results as a comment.

```mermaid
flowchart LR
Repo[TerraformRepository] -->|annotates last relevant commit| Layer[TerraformLayer]
Layer -->|creates on plan/apply needed| Run[TerraformRun]
Run -->|creates runner| Pod[Runner Pod]
Pod -->|updates plan/apply annotations| Layer
PR[TerraformPullRequest] -->|creates temp layers| Layer
PR -->|reads plan results, comments| Git[Git provider]
```
1 change: 1 addition & 0 deletions internal/controllers/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ custom resource: `terraformlayer/`, `terraformrun/`, `terraformrepository/`,
- Use structured logging via the `logr.Logger` carried in `ctx` (`log.FromContext(ctx)`).
- Propagate `ctx` and set timeouts on every external call (GitHub, GitLab, Terraform).
- CRD shapes live in `api/v1alpha1`; never edit generated deepcopy code. After API changes run `make manifests && make generate`.
- **State machines are documented.** When you change a reconciler's states or transitions (its `states.go` — the `GetState` switch, state structs, or the conditions in `conditions.go` that drive them), update the matching Mermaid diagram in [`docs/operator-manual/state-machines.md`](../../docs/operator-manual/state-machines.md) so it stays in sync.
8 changes: 7 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ extra:
markdown_extensions:
- codehilite
- admonition
- pymdownx.superfences
- pymdownx.superfences:
# make exceptions to highlighting of code:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:mermaid2.fence_mermaid_custom
- toc:
permalink: true
nav:
Expand All @@ -30,6 +35,7 @@ nav:
- Operator Manual:
- operator-manual/index.md
- operator-manual/architecture.md
- operator-manual/state-machines.md
- operator-manual/user-authentication.md
- operator-manual/repository-controller.md
- Git Authentication:
Expand Down
39 changes: 39 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
babel==2.18.0
backrefs==7.0
beautifulsoup4==4.15.0
certifi==2026.6.17
charset-normalizer==3.4.9
click==8.4.2
colorama==0.4.6
editorconfig==0.17.1
ghp-import==2.1.0
idna==3.18
jinja2==3.1.6
jsbeautifier==2.0.3
markdown==3.10.2
markupsafe==3.0.3
mergedeep==1.3.4
mike==2.2.0
mkdocs==1.6.1
mkdocs-get-deps==0.2.2
mkdocs-material==9.7.6
mkdocs-material-extensions==1.3.1
mkdocs-mermaid2-plugin==1.2.3
packaging==26.2
paginate==0.5.7
pathspec==1.1.1
platformdirs==4.10.0
pygments==2.20.0
pymdown-extensions==11.0.1
pyparsing==3.3.2
python-dateutil==2.9.0.post0
pyyaml==6.0.3
pyyaml-env-tag==1.1
requests==2.34.2
setuptools==83.0.0
six==1.17.0
soupsieve==2.8.4
typing-extensions==4.16.0
urllib3==2.7.0
verspec==0.1.0
watchdog==6.0.0