Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLIX · Infrastructure CIInfrastructure CI

Format stage — terraform fmt, ansible-lint format, kubeconform

Intermediate⏱ ~20 mingit

What you'll learn

  • Explain why formatting is a CI gate rather than a local convention
  • Run terraform fmt -check -recursive and read its output
  • Identify the equivalent format tools for Ansible and Kubernetes manifests
  • Recognise the categories of problems format catches and those it ignores

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

The format stage is the first gate every commit crosses. It exists for a reason that is easy to underestimate: code review is expensive, and code review is wasted on whitespace. A pull request that changes a Terraform variable and re-indents three unrelated blocks is a pull request that makes the reviewer hunt for the substantive change. The format stage exists to eliminate that class of pull request before a human ever sees it.

What format catches

The format stage catches four classes of contributor mistake, in increasing order of severity:

  • Inconsistent indentation. Mixed tabs and spaces, misaligned HCL blocks, off-by-two indentation in nested blocks. These changes have no semantic meaning; they are the result of mixing editors or hand-editing config files.
  • Trailing whitespace and stray newlines. Lines ending in spaces, files ending in multiple newlines, BOM markers on UTF-8 files. Same category: no semantic meaning, real review cost.
  • Inconsistent quoting and bracket style. Terraform allows several equivalent syntaxes for the same construct; a project must pick one. The format stage enforces the choice.
  • Misordered arguments. Some tools reorder keys alphabetically; this is a format change, not a semantic change, but it produces large diffs that hide real edits.
flowchart LR
    A[".tf files"] --> B["terraform fmt -check"]
    B --> C{Formatted?}
    C -->|yes| D["exit 0"]
    C -->|no| E["exit non-zero"]
    E --> F["job fails"]
terraform fmt -check -recursive

The -check flag tells Terraform to exit non-zero if any file would be reformatted, without modifying the files. The -recursive flag walks every .tf file under the current directory. The exit code is the only signal the pipeline needs: zero means formatted, non-zero means not.

The Kubernetes and Ansible equivalents

The format-stage tool is different for every IaC language, but the principle is identical.

For Kubernetes manifests the canonical tool is kubeconform, which validates against the Kubernetes OpenAPI schema and reformats YAML to a canonical style. kubeconform is primarily a validator; it will also flag files that are syntactically valid but stylistically inconsistent if the project’s .kubeconform.yaml enforces a style.

For Ansible playbooks the canonical tool is ansible-lint in --format mode, or a YAML formatter such as yamlfix or the YAML Language Server’s format action. ansible-lint runs many checks beyond formatting (it is also the lint-stage tool); the format-specific subset is what the format stage enforces.

For raw YAML configuration files the canonical tool is a YAML formatter configured to the project’s indentation (two spaces is conventional for Kubernetes, Ansible, and most IaC tooling).

What format deliberately ignores

The format stage is deliberately narrow. It does not check whether a variable is used, whether a resource is tagged, whether an IAM policy grants too much, or whether a module is wired correctly. Those checks belong to the lint stage and the security stage. The format stage ignores them by design:

  • Semantic meaning. A reformatter reads tokens, not intent. It cannot tell whether bucket_name = "prod" is correct, only whether it is consistently formatted.
  • Cross-file references. A reformatter does not resolve module.foo.bar to its source module. It does not check whether a referenced variable is defined. Those checks require a graph of the configuration, which is what terraform validate and the lint stage provide.
  • Provider-specific rules. A reformatter does not know that a particular resource type requires a particular argument. It does not enforce that an S3 bucket has versioning or that a security group has a description. Those checks are in the security stage.

The narrowness is the point. The format stage runs in seconds because it does almost nothing. The next stages run in tens of seconds because they do more. The pipeline budgets time per stage according to what the stage costs to run, and the format stage gets the smallest budget because its job is the smallest.

Production discipline

Two rules govern the format stage:

  1. The format stage runs on every commit, not just pull requests. A branch that drifts from the format convention produces a large catch-up diff when it is eventually merged. Catching drift on every push keeps the diff small.
  2. The format stage exits non-zero on failure. A warning that does not fail the job is not a gate. The pipeline must treat format as binary: the file is formatted or it is not, and the build reflects that.

Cross-course references

  • Terraform for Production Sysadmins — Part XXV (QualityGates) covers terraform fmt -check -recursive in the context of a full quality-gate pipeline.
  • Ansible for Production Sysadmins — Part XXXVII (RepoArch) discusses Ansible repository layout, which the format stage assumes.
  • Kubernetes for Production Sysadmins — Parts XXX-XXXII cover manifest validation, of which formatting is the first layer.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer runs `terraform fmt` in their editor on save, so the format stage in CI is redundant. Which production discipline rule refutes this argument?

  2. Q2. The format stage should catch unused variables and missing resource tags.

  3. Q3. Give the command that runs `terraform fmt` as a CI gate without modifying any files, and explain what each flag does.

  4. Q4. Diagnose a pull request whose diff is forty lines but whose substantive change is two lines, and identify which gate should have caught the drift.

    An engineer submits a pull request that changes a Terraform variable from `default = "prod"` to `default = "staging"`. The diff is forty lines. Twenty of those lines are whitespace changes in unrelated resource blocks, twelve are key reordering in a module the engineer did not intend to touch, and eight are the variable change plus its consumers.

Passing score: 75%. Answers are checked in this browser.