Release workflow¶
Tagged releases are reproducible and GitLab-native. The workflow creates GitLab release metadata and downloadable job artifacts, but it does not publish packages to PyPI or another package registry.
Version source¶
The canonical package version is stored in both:
pyproject.tomlundertool.poetry.versiongitlab_project_audit.__version__
These values must match before a release tag is accepted.
Tag format¶
Release tags use:
For example:
A tag is rejected when:
- it does not use the expected
v...form - it does not match
tool.poetry.version tool.poetry.versionandgitlab_project_audit.__version__differ
Manual release procedure¶
- Update
pyproject.tomlandsrc/gitlab_project_audit/__init__.pyto the same version. - Run the local quality suite.
- Merge the version change to
main. - Create the matching tag on the exact commit:
- The tag pipeline validates the tag/version before package artifacts are trusted.
package-buildcreates the wheel and source distribution.release-artifactsverifies the artifact names, calculates SHA-256 checksums, and writes:release-meta/SHA256SUMSrelease-meta/RELEASE-MANIFEST.jsonrelease-meta/RELEASE-NOTES.mdcreate-gitlab-releasecreates the GitLab release for the same tag and commit.
Release links¶
The GitLab release includes links to:
- the complete release-artifacts archive
SHA256SUMSRELEASE-MANIFEST.json
The manifest binds the release tag and Git commit SHA to the exact distribution artifact hashes.
Credentials¶
No PyPI token or package-registry publishing credential is required.
GitLab release creation runs inside the tagged pipeline and uses the GitLab CI job context.
Local validation¶
You can validate a prospective tag without creating a release:
After building distributions locally:
poetry build
PYTHONPATH=src python -m gitlab_project_audit.release_workflow prepare \
--tag v0.2.0 \
--commit "$(git rev-parse HEAD)" \
--dist-dir dist \
--output-dir release-meta
OCI container image¶
The repository also builds the CLI as an OCI image.
The image uses:
- pinned
python:3.12.12-slim-bookwormbuilder and runtime stages - a wheel built from the repository source
- non-root runtime user
10001:10001 - OCI version, revision, source, and licence labels
gitlab-project-auditas the image entrypoint
Build locally¶
VERSION="$(python - <<'PY'
import tomllib
from pathlib import Path
print(tomllib.loads(Path("pyproject.toml").read_text())["tool"]["poetry"]["version"])
PY
)"
docker build \
--build-arg VERSION="$VERSION" \
--build-arg VCS_REF="$(git rev-parse HEAD)" \
-t "gitlab-project-audit:$VERSION" \
.
Smoke-test the CLI:
Merge-request pipeline¶
Merge requests build the Dockerfile with BuildKit and export an OCI tar artifact. The image jobs
use BuildKit's standard image, so they need a runner with privileged = true in its
config.toml. Rootless BuildKit is no alternative on an unprivileged runner: it needs user
namespaces, which Docker's default seccomp profile blocks.
The merge-request job does not authenticate to the Container Registry and does not push an image.
Release-tag publishing¶
Validated release tags publish exactly one immutable image reference:
For example, tag v0.2.0 publishes:
The container job verifies again that $CI_COMMIT_TAG == v$PACKAGE_VERSION and depends on the
existing release-validate job.
GitLab's built-in per-job registry credentials CI_REGISTRY_USER and
CI_REGISTRY_PASSWORD are used. No personal access token is required.
Latest-tag policy¶
The project intentionally does not publish :latest.
Release tags are immutable/versioned and consumers should pin the exact release they intend to run. If a moving tag is introduced later, that will require an explicit policy change.
CI component release coupling¶
The reusable component in templates/audit.yml is versioned by the same Git tag as the package
and OCI image.
A consumer that includes:
runs:
No separate component version is maintained.