Skip to content

title: phpboyscout/cicd v0.7 — tofu-module-publish component description: A new component that publishes the repository to the GitLab Terraform Module Registry on release tags, using CI_JOB_TOKEN (own-project write, works on GitLab Free). Lets consumers reference modules as gitlab.com/<ns>/<name>/<system> + version instead of git:: tag refs. status: approved date: 2026-05-27 authors: [Matt Cockayne] tags: [spec, cicd, components, tofu, registry, packages]


Spec: phpboyscout/cicd v0.7 — tofu-module-publish

  • Repository: gitlab.com/phpboyscout/cicd
  • Released as: v0.7.0 (minor — one additive component; no change to existing components).
  • Driver: the terraform-aws-* modules were consumed via git:: tag refs and documented with stale github.com/...?ref=vX source examples. Publishing them to the GitLab Terraform Module Registry gives a versioned, registry-native source address (gitlab.com/<namespace>/<name>/<system> + version) and a one-line registry UI. This component automates that publish on each release tag so a module repo never has to publish by hand.

Decisions

D1 — Publish on release tags, in .post

The job runs only when $CI_COMMIT_TAG matches tag_pattern (default strict semver ^v[0-9]+\.[0-9]+\.[0-9]+$, mirroring tofu-apply). Default stage is the built-in .post, so it runs after — and only on success of — every other job in the tag pipeline (lint, security, validate, pages). No consumer stage wiring is required; tag pipelines already run the full gate (verified on terraform-aws-bootstrap).

D2 — Auth via CI_JOB_TOKEN, header auto-selected

The published artifact is uploaded to PUT …/projects/:id/packages/terraform/modules/:name/:system/:version/file. CI_JOB_TOKEN carries write access to its own project's package registry, so publishing needs no personal/group access token — unlike the jobs-artifacts API, this works on GitLab Free. Following the token convention from v0.5, the token input defaults to "$CI_JOB_TOKEN". Because the package endpoint takes the job token under the JOB-TOKEN header and a PAT under PRIVATE-TOKEN, the job selects the header from the token value (job token → JOB-TOKEN, anything else → PRIVATE-TOKEN), so a PAT override for cross-project publishing still works.

D3 — module_name + module_system

The registry path is <name>/<system>. module_name is required (unique within the top-level namespace); module_system defaults to aws. Keeping the cloud as a separate system segment lets one namespace later hold bootstrap/azure, bootstrap/google, etc. — the same reasoning that dropped the terraform- prefix from the repos.

D4 — Tarball is the tagged tree, publish is idempotent

git archive --format=tar.gz HEAD ships only tracked files (no .git, no .terraform, no untracked state). Re-running the job over an already-published version (a pipeline retry) is treated as success when the registry reports a duplicate, so retries are safe.

Consumers

Repo module_name Source address
terraform-aws-bootstrap bootstrap gitlab.com/phpboyscout/bootstrap/aws
terraform-aws-security-baseline security-baseline gitlab.com/phpboyscout/security-baseline/aws

Both module projects are public, so consumers (incl. the private infra repo) resolve the registry anonymously — no token needed to tofu init. The current versions (bootstrap 0.2.1, security-baseline 0.2.0) were seeded into the registry manually; this component publishes every subsequent tag automatically.

Testing

Self-test is failure-path only — a hermetic SUCCESS test would write a real package version into the registry (junk pollution and duplicate-version races on retry). tests/tofu-module-publish/.gitlab-ci.yml overrides the component's tag-only rule, sets CI_COMMIT_TAG to a dummy via before_script, and supplies an invalid token: the upload returns 401, the script exits 1, and allow_failure.exit_codes tolerates that exact code — any other exit (git archive failure, missing curl, broken case branches, unset-variable surprises) fails the pipeline. The SUCCESS path is exercised live by every terraform-aws-* release tag.