From 4c8ab04fb787e41f5e76683bc8f88831784c1c54 Mon Sep 17 00:00:00 2001 From: Filippo Morelli Date: Mon, 7 Sep 2026 22:15:11 +0000 Subject: [PATCH 1/6] Pin the uv base image to a patched trixie release --- cookiecutter.json | 1 + {{cookiecutter.project_dirname}}/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cookiecutter.json b/cookiecutter.json index 0d7a662..f7d84e2 100755 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -13,6 +13,7 @@ "postgres_create_database": "true", "use_vault": "false", "python_version": "3.14", + "uv_version": "0.12.10", "minos_service_image": "registry.gitlab.com/20tab-open/minos/service:latest", "opentofu_component_version": "3.11.0", "opentofu_version": "1.10.6", diff --git a/{{cookiecutter.project_dirname}}/Dockerfile b/{{cookiecutter.project_dirname}}/Dockerfile index 854491c..e30ccf3 100644 --- a/{{cookiecutter.project_dirname}}/Dockerfile +++ b/{{cookiecutter.project_dirname}}/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/astral-sh/uv:python{{ cookiecutter.python_version }}-bookworm-slim AS base +FROM ghcr.io/astral-sh/uv:{{ cookiecutter.uv_version }}-python{{ cookiecutter.python_version }}-trixie-slim AS base LABEL company="20tab" project="{{ cookiecutter.project_slug }}" service="{{ cookiecutter.service_slug }}" stage="base" From e4c5c1e9b0e2c700e806c55ad2df506f7cc232d1 Mon Sep 17 00:00:00 2001 From: Filippo Morelli Date: Mon, 7 Sep 2026 22:15:11 +0000 Subject: [PATCH 2/6] Keep the checks in the check script and the coverage in the test script --- {{cookiecutter.project_dirname}}/justfile | 2 +- {{cookiecutter.project_dirname}}/scripts/check.sh | 1 + {{cookiecutter.project_dirname}}/scripts/test.sh | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_dirname}}/justfile b/{{cookiecutter.project_dirname}}/justfile index 0c3637b..3d3c0b1 100644 --- a/{{cookiecutter.project_dirname}}/justfile +++ b/{{cookiecutter.project_dirname}}/justfile @@ -76,7 +76,7 @@ showoutdated: uv tree --all-groups --outdated | grep --color=always "(latest:.*)" || true # Run the test suite -test *ARGS: +test *ARGS: check ./scripts/test.sh {% raw %}{{ARGS}}{% endraw %} # Upgrade all libraries diff --git a/{{cookiecutter.project_dirname}}/scripts/check.sh b/{{cookiecutter.project_dirname}}/scripts/check.sh index 2194f0d..fc2b669 100755 --- a/{{cookiecutter.project_dirname}}/scripts/check.sh +++ b/{{cookiecutter.project_dirname}}/scripts/check.sh @@ -6,3 +6,4 @@ uv run ruff format --check . uv run ruff check . uv run mypy . uv run bandit -c pyproject.toml --quiet --recursive . +uvx uv-secure --no-check-uv-tool --ignore-unfixed uv.lock diff --git a/{{cookiecutter.project_dirname}}/scripts/test.sh b/{{cookiecutter.project_dirname}}/scripts/test.sh index f5f06f3..33179ca 100755 --- a/{{cookiecutter.project_dirname}}/scripts/test.sh +++ b/{{cookiecutter.project_dirname}}/scripts/test.sh @@ -2,6 +2,9 @@ set -uo pipefail -./scripts/check.sh -./scripts/coverage.sh "$@" -./scripts/report.sh +status=0 + +./scripts/coverage.sh "$@" || status=1 +./scripts/report.sh || status=1 + +exit "${status}" From df82a403cd7677b9cfd9c98f36a11acd9bc023f6 Mon Sep 17 00:00:00 2001 From: Filippo Morelli Date: Mon, 7 Sep 2026 22:15:11 +0000 Subject: [PATCH 3/6] Add the Check stage, per-environment registries and the layer cache --- .../.gitlab-ci.yml | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/{{cookiecutter.project_dirname}}/.gitlab-ci.yml b/{{cookiecutter.project_dirname}}/.gitlab-ci.yml index 99e3be8..1fc7215 100644 --- a/{{cookiecutter.project_dirname}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_dirname}}/.gitlab-ci.yml @@ -6,7 +6,27 @@ opentofu_version: {{ cookiecutter.opentofu_version }} no_plan: true +workflow: + rules: + - if: $CI_COMMIT_BRANCH == "develop" + variables: + IMAGE_REPO: development + IMAGE_REF: ${CI_COMMIT_SHA} + - if: $CI_COMMIT_BRANCH == "main" + variables: + IMAGE_REPO: staging + IMAGE_REF: ${CI_COMMIT_SHA} + - if: $CI_COMMIT_TAG + variables: + IMAGE_REPO: production + IMAGE_REF: v${CI_COMMIT_TAG} + - when: always + variables: + IMAGE_REPO: feature + IMAGE_REF: ${CI_COMMIT_SHA} + stages: + - Check - Build - Test - Pact-verify @@ -17,7 +37,7 @@ stages: - Sentry variables: - IMAGE_TAG: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA} + IMAGE_TAG: ${CI_REGISTRY_IMAGE}/${IMAGE_REPO}:${IMAGE_REF} BUILDAH_FORMAT: docker BUILDAH_IMAGE: quay.io/buildah/stable:latest BUILDAH_ISOLATION: chroot @@ -26,6 +46,7 @@ variables: DOCKER_BUILDKIT: 1 PROJECT_SLUG: {{ cookiecutter.project_slug }} SERVICE_SLUG: {{ cookiecutter.service_slug }} + TEST_RUNNER_TAG: saas-linux-small-amd64 VERSION_BEFORE_REF: ${CI_COMMIT_BEFORE_SHA} VAULT_ROLE: service-gitlab-job @@ -124,22 +145,61 @@ sentry_release_production: - <<: *sentry-rule - *production-rules +# [Check] +# ----------------------------------------------------------------------------- + +.check: + stage: Check + image: ghcr.io/astral-sh/uv:{{ cookiecutter.uv_version }}-python{{ cookiecutter.python_version }}-trixie-slim + before_script: + - uv sync --frozen --only-group test --no-install-project + rules: + - if: $CI_PIPELINE_SOURCE == "push" + +lint: + extends: + - .check + script: + - uv run --no-sync ruff format --check . + - uv run --no-sync ruff check . + +security: + extends: + - .check + script: + - uv run --no-sync bandit -c pyproject.toml --quiet --recursive . + +audit: + extends: + - .check + script: + - uvx uv-secure --no-check-uv-tool --ignore-unfixed uv.lock + # [Build] # ----------------------------------------------------------------------------- build: stage: Build image: ${BUILDAH_IMAGE} + rules: + - <<: *pipeline-push-rule + - when: always + variables: + STORAGE_DRIVER: overlay before_script: - echo "${CI_REGISTRY_PASSWORD}" | buildah login "${CI_REGISTRY}" --username "${CI_REGISTRY_USER}" --password-stdin - | - TAGS="--tag=${IMAGE_TAG}" - if [ -n "$CI_COMMIT_TAG" ]; then - TAGS="$TAGS --tag=${CI_REGISTRY_IMAGE}:v${CI_COMMIT_TAG} --tag=${CI_REGISTRY_IMAGE}:latest" + IMAGE_TAGS="${IMAGE_TAG}" + if [ -n "$CI_COMMIT_TAG" ]; then IMAGE_TAGS="$IMAGE_TAGS ${CI_REGISTRY_IMAGE}/${IMAGE_REPO}:${CI_COMMIT_SHA}"; fi + BUILD_TAGS="" + for image_tag in $IMAGE_TAGS; do BUILD_TAGS="$BUILD_TAGS --tag=$image_tag"; done + CACHE_ARGS="--cache-from=${CI_REGISTRY_IMAGE}/cache" + if [ "$CI_COMMIT_BRANCH" = "develop" ] || [ "$CI_COMMIT_BRANCH" = "main" ]; then + CACHE_ARGS="$CACHE_ARGS --cache-to=${CI_REGISTRY_IMAGE}/cache" fi script: - - buildah bud --format="${BUILDAH_FORMAT}" --target=remote --layers $TAGS . - - buildah push --all "${IMAGE_TAG}" + - buildah bud --format="${BUILDAH_FORMAT}" --target=remote --layers $CACHE_ARGS $BUILD_TAGS . + - for image_tag in $IMAGE_TAGS; do buildah push "$image_tag"; done after_script: - buildah logout ${CI_REGISTRY} @@ -162,9 +222,12 @@ test: extends: - .test stage: Test + tags: + - ${TEST_RUNNER_TAG} rules: - if: $CI_PIPELINE_SOURCE == "push" script: + - uv run mypy . - ./scripts/test.sh coverage: '/^TOTAL.*\s+(\d+\%)$/' artifacts: @@ -352,7 +415,7 @@ deploy_production: .rollback: extends: .deploy variables: - TF_VAR_image: "${CI_REGISTRY_IMAGE}:${CI_COMMIT_BEFORE_SHA}" + TF_VAR_image: "${CI_REGISTRY_IMAGE}/${IMAGE_REPO}:${CI_COMMIT_BEFORE_SHA}" rollback_development: extends: From ea916c7d1a39f8746c99839e7ba4657027c679ca Mon Sep 17 00:00:00 2001 From: Filippo Morelli Date: Mon, 7 Sep 2026 22:15:11 +0000 Subject: [PATCH 4/6] Expose the uv version and the test runner tag to the scaffolding --- bootstrap/constants.py | 8 ++++++++ bootstrap/runner.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/bootstrap/constants.py b/bootstrap/constants.py index 6e5aa45..0dcb08c 100644 --- a/bootstrap/constants.py +++ b/bootstrap/constants.py @@ -78,3 +78,11 @@ # Python PYTHON_VERSION_DEFAULT = "3.14" + +# uv + +UV_VERSION = "0.12.10" + +# CI + +TEST_RUNNER_TAG_DEFAULT = "saas-linux-small-amd64" diff --git a/bootstrap/runner.py b/bootstrap/runner.py index 447b187..5b84791 100644 --- a/bootstrap/runner.py +++ b/bootstrap/runner.py @@ -27,6 +27,8 @@ STAGE_ENV_NAME, STAGE_ENV_SLUG, TERRAFORM_BACKEND_TFC, + TEST_RUNNER_TAG_DEFAULT, + UV_VERSION, ) from bootstrap.exceptions import BootstrapError from bootstrap.helpers import format_gitlab_variable, format_tfvar @@ -76,6 +78,8 @@ class Runner: gitlab_namespace_path: str | None = None gitlab_token: str | None = None python_version: str = PYTHON_VERSION_DEFAULT + uv_version: str = UV_VERSION + test_runner_tag: str = TEST_RUNNER_TAG_DEFAULT minos_service_image: str = MINOS_SERVICE_IMAGE opentofu_component_version: str = OPENTOFU_COMPONENT_VERSION opentofu_version: str = OPENTOFU_VERSION @@ -146,6 +150,9 @@ def register_gitlab_project_variables(self, *args): def collect_gitlab_variables(self): """Collect the GitLab group and project variables.""" + self.register_gitlab_project_variables( + ("TEST_RUNNER_TAG", self.test_runner_tag, False, False) + ) if self.sentry_dsn: self.register_gitlab_project_variables( ("SENTRY_ORG", self.sentry_org), @@ -243,6 +250,7 @@ def init_service(self): ), "use_valkey": self.use_valkey and "true" or "false", "use_vault": self.vault_url and "true" or "false", + "uv_version": self.uv_version, }, output_dir=self.output_dir, no_input=True, From f03ebfa78f4c75070bda563d3b534df434ead6e6 Mon Sep 17 00:00:00 2001 From: Filippo Morelli Date: Mon, 7 Sep 2026 22:17:43 +0000 Subject: [PATCH 5/6] Set the container cleanup policy on the created project --- tofu/gitlab/main.tf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tofu/gitlab/main.tf b/tofu/gitlab/main.tf index cb5f30c..0919474 100644 --- a/tofu/gitlab/main.tf +++ b/tofu/gitlab/main.tf @@ -58,6 +58,15 @@ resource "gitlab_project" "main" { namespace_id = data.gitlab_group.main.id initialize_with_readme = false shared_runners_enabled = true + + container_expiration_policy { + enabled = true + cadence = "1d" + keep_n = 10 + older_than = "7d" + name_regex_delete = ".*" + name_regex_keep = "^v.*" + } } resource "null_resource" "init_repo" { From 867f564bc3e6fa2c5179d345f2a40e5ec5d2b0ac Mon Sep 17 00:00:00 2001 From: Filippo Morelli Date: Tue, 8 Sep 2026 15:17:51 +0000 Subject: [PATCH 6/6] Record the real deploy start time on Sentry --- {{cookiecutter.project_dirname}}/.gitlab-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/{{cookiecutter.project_dirname}}/.gitlab-ci.yml b/{{cookiecutter.project_dirname}}/.gitlab-ci.yml index 1fc7215..5716b31 100644 --- a/{{cookiecutter.project_dirname}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_dirname}}/.gitlab-ci.yml @@ -499,11 +499,9 @@ create-version-tag_production: - .sentry stage: Sentry script: - - RELEASE_START=$(date -d "${CI_PIPELINE_CREATED_AT}" +%s) - - RELEASE_END=$(date +%s) - > sentry-cli releases deploys "${CI_COMMIT_SHA}" - new --env "${ENV_NAME}" --time $((RELEASE_END-RELEASE_START)) + new --env "${ENV_NAME}" --started "${CI_PIPELINE_CREATED_AT}" sentry_success_development: extends: