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 viagit::tag refs and documented with stalegithub.com/...?ref=vXsource 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.