Implements sections 3-4 of openspec/changes/release-tools-image/tasks.md.
Produces .tekton/release-tools/ containing:
PipelineRuns (PaC):
- on-pull-request-build.yaml — CEL gated to files.any.exists() under
image/release-tools/ or .tekton/release-tools/. Builds + verifies +
SBOM-scans the image; no push.
- on-tag-publish.yaml — triggered on refs/tags/release-tools/v* push.
Pipeline shape (TOCTOU-safe per adversarial review L1):
fetch-source → resolve-tag → build → verify-tools → syft-scan →
push-by-digest → cosign-sign-by-digest → cosign-attach-sbom →
promote-tag (only NOW does vMAJOR.MINOR.PATCH resolve).
Reusable Tasks:
- build-image.yaml — buildah build against image/release-tools/Containerfile
- verify-image-tools.yaml — runs built image, asserts every bundled
tool's --version + the offline-npx + shell-present scenarios
- syft-scan-image.yaml — CycloneDX SBOM via syft (uses release-tools
image itself — dogfooding)
- resolve-release-tools-tag.yaml — strips refs/tags/release-tools/ prefix
- push-image-by-digest.yaml — pushes manifest by digest only, no tag
- cosign-sign-image-by-digest.yaml — signs by digest with
cosign-signing-key (optional: false, fail-closed per spec asymmetry)
- cosign-attach-sbom.yaml — attaches SBOM as cosign attestation
- promote-image-tag.yaml — crane/skopeo tag promotion AFTER sign
Defensive CEL:
- .tekton/on-tag-push-release.yaml — added negative-CEL annotation so a
release-tools/vX.Y.Z tag push CANNOT accidentally fire the forgejo-mcp
release pipeline (adversarial review A1).
Verifier T12 PASS: yaml syntax (all 11 files), pre-commit (gitleaks),
openspec validate release-tools-image — all green.
Registry path is TBD (working assumption quay.io/operate-first/release-tools)
— flagged in pipeline header comments; coordinate with op1st-emea-b4mad
maintainers before first tag-publish run.
Refs: forgejo-mcp-1b4
130 lines
4.2 KiB
YAML
130 lines
4.2 KiB
YAML
apiVersion: tekton.dev/v1
|
||
kind: PipelineRun
|
||
metadata:
|
||
name: on-tag-push-release
|
||
annotations:
|
||
# PipelinesAsCode: trigger on tag pushes matching v* refs.
|
||
# `on-target-branch` is overloaded to match refs/tags/* as well as
|
||
# refs/heads/* when on-event is "push" — PaC resolves the ref via
|
||
# the underlying provider payload (Codeberg/Forgejo).
|
||
pipelinesascode.tekton.dev/on-event: "[push]"
|
||
pipelinesascode.tekton.dev/on-target-branch: "[refs/tags/v*]"
|
||
# T11 defensive CEL (A1 tag-isolation): PaC's glob [refs/tags/v*] may
|
||
# match refs/tags/release-tools/vX.Y.Z on some PaC versions because the
|
||
# glob is applied after stripping refs/tags/ and "release-tools/v1.0.0"
|
||
# starts with a non-v prefix, but belt-and-suspenders ensures this pipeline
|
||
# never fires on a release-tools tag. The expression requires the ref to
|
||
# start with refs/tags/v followed by a digit, which excludes
|
||
# refs/tags/release-tools/... entirely.
|
||
pipelinesascode.tekton.dev/on-cel-expression: |
|
||
event == "push" && target_branch.matches("^refs/tags/v[0-9]")
|
||
pipelinesascode.tekton.dev/task: "[git-clone, .tekton/tasks/goreleaser-release.yaml, .tekton/tasks/cosign-sign-release.yaml, .tekton/tasks/mcpb-pack.yaml]"
|
||
pipelinesascode.tekton.dev/max-keep-runs: "10"
|
||
# Releases are not cancellable — let in-flight runs finish even if
|
||
# another tag arrives.
|
||
pipelinesascode.tekton.dev/cancel-in-progress: "false"
|
||
spec:
|
||
params:
|
||
- name: repo_url
|
||
value: "{{ repo_url }}"
|
||
- name: revision
|
||
value: "{{ revision }}"
|
||
- name: target_branch
|
||
value: "{{ target_branch }}"
|
||
pipelineSpec:
|
||
params:
|
||
- name: repo_url
|
||
- name: revision
|
||
- name: target_branch
|
||
description: |
|
||
PaC passes the matched ref here (e.g. refs/tags/v2.24.0). Stripped
|
||
to the bare tag name (v2.24.0) for downstream tasks.
|
||
workspaces:
|
||
- name: source
|
||
tasks:
|
||
- name: fetch-source
|
||
taskRef:
|
||
name: git-clone
|
||
workspaces:
|
||
- name: output
|
||
workspace: source
|
||
params:
|
||
- name: url
|
||
value: $(params.repo_url)
|
||
- name: revision
|
||
value: $(params.revision)
|
||
|
||
- name: resolve-tag
|
||
runAfter:
|
||
- fetch-source
|
||
params:
|
||
- name: target_branch
|
||
value: $(params.target_branch)
|
||
taskSpec:
|
||
params:
|
||
- name: target_branch
|
||
results:
|
||
- name: tag
|
||
description: Bare tag name (e.g. v2.24.0).
|
||
steps:
|
||
- name: resolve
|
||
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
|
||
script: |
|
||
#!/usr/bin/env sh
|
||
set -eu
|
||
TAG="$(params.target_branch)"
|
||
TAG="${TAG#refs/tags/}"
|
||
echo "Resolved tag: $TAG"
|
||
printf '%s' "$TAG" > "$(results.tag.path)"
|
||
|
||
- name: goreleaser
|
||
runAfter:
|
||
- resolve-tag
|
||
taskRef:
|
||
name: goreleaser-release
|
||
workspaces:
|
||
- name: source
|
||
workspace: source
|
||
params:
|
||
- name: TAG
|
||
value: $(tasks.resolve-tag.results.tag)
|
||
|
||
- name: cosign-sign
|
||
runAfter:
|
||
- goreleaser
|
||
taskRef:
|
||
name: cosign-sign-release
|
||
workspaces:
|
||
- name: source
|
||
workspace: source
|
||
params:
|
||
- name: TAG
|
||
value: $(tasks.resolve-tag.results.tag)
|
||
- name: REPO
|
||
value: goern/forgejo-mcp
|
||
|
||
- name: mcpb-pack
|
||
runAfter:
|
||
- goreleaser
|
||
taskRef:
|
||
name: mcpb-pack
|
||
workspaces:
|
||
- name: source
|
||
workspace: source
|
||
params:
|
||
- name: TAG
|
||
value: $(tasks.resolve-tag.results.tag)
|
||
- name: REPO
|
||
value: goern/forgejo-mcp
|
||
|
||
workspaces:
|
||
- name: source
|
||
volumeClaimTemplate:
|
||
spec:
|
||
accessModes:
|
||
- ReadWriteOnce
|
||
resources:
|
||
requests:
|
||
# Release builds need ~6× CI: cross-compiled binaries +
|
||
# SBOMs + checksums + .mcpb bundles per platform.
|
||
storage: 8Gi
|