forgejo-mcp/.tekton/release-tools/tasks/push-image-by-digest.yaml
Christoph Görn 748de5102d
fix: 🐛 push image version tag directly, never delete a staging tag
On Forgejo/Codeberg the container registry deletes the underlying
MANIFEST when any of its tags is deleted. promote-image-tag's
`skopeo delete :build-tmp` cleanup therefore wiped the :vX.Y.Z and
:latest tags that shared the same digest — only the separate cosign
.sig/.att manifests survived. This silently broke the v2.27.0 image
publish (first release to run the delete step added in f026f8c).

Fix (shared by the forgejo-mcp and release-tools publish pipelines):
- push-image-by-digest pushes :vX.Y.Z directly (new TAG param); no
  :build-tmp staging tag is ever created.
- promote-image-tag only ADDS :latest (stable releases); it no longer
  copies :vX.Y.Z or deletes any tag.
- new delete-tag-on-failure task runs in `finally`, gated on
  attach-sbom not succeeding, to remove the briefly-unsigned :vX.Y.Z
  so the fail-closed "never advertise unsigned" guarantee is kept.

Both PipelineRuns validated with `oc create --dry-run=server`.

Refs: forgejo-mcp-9r4
2026-06-02 17:37:45 +02:00

103 lines
4.4 KiB
YAML

apiVersion: tekton.dev/v1
kind: Task
metadata:
name: release-tools-push-image-by-digest
annotations:
tekton.dev/displayName: "push release-tools image to registry by digest (no tag)"
spec:
description: |
Push the built image to the registry under its version tag (vX.Y.Z) and
record the resulting manifest digest for downstream sign/attest.
# bd forgejo-mcp-9r4: the earlier design pushed an ephemeral :build-tmp tag
# and deleted it after promotion. On Forgejo/Codeberg, deleting ANY tag
# deletes the underlying manifest — so removing :build-tmp also removed the
# :vX.Y.Z and :latest tags that shared the same digest (only the separate
# cosign .sig/.att manifests survived). We now push :vX.Y.Z directly with
# no staging tag. cosign-sign + attach-sbom operate on the digest in place;
# promote-image-tag adds :latest only after both succeed. The tag is
# briefly unsigned (~1 min) until cosign-sign runs; a signing failure
# deletes the unsigned :vX.Y.Z via the pipeline's finally cleanup.
params:
- name: IMAGE
description: |
Local image tag to push (e.g. release-tools:build-tmp). Must already
exist in the buildah store in the workspace.
# Path A (decided 2026-05-25): codeberg.org/operate-first/release-tools
# Registry path working assumption: codeberg.org/operate-first/release-tools
type: string
- name: REGISTRY_IMAGE
description: |
Full registry path without tag (e.g. codeberg.org/operate-first/release-tools).
# Path A (decided 2026-05-25): codeberg.org/operate-first/release-tools
# Working assumption: codeberg.org/operate-first/release-tools
type: string
default: "codeberg.org/operate-first/release-tools"
- name: TAG
description: Human-readable version tag to push (e.g. v1.0.0).
type: string
- name: STORAGE_DRIVER
description: buildah storage driver.
type: string
default: vfs
workspaces:
- name: source
mountPath: /workspace/source
- name: registry-credentials
mountPath: /workspace/registry-creds
optional: false
results:
- name: IMAGE_DIGEST
description: sha256 digest of the pushed manifest (e.g. sha256:abc123...).
- name: IMAGE_URL
description: Registry URL without tag or digest (e.g. codeberg.org/operate-first/release-tools). Used by Tekton Chains to bind image to SLSA provenance.
- name: IMAGE_REPO_DIGEST
description: Full digest reference (e.g. codeberg.org/operate-first/release-tools@sha256:abc123).
steps:
- name: push-by-digest
image: quay.io/buildah/stable:latest
securityContext:
privileged: true
workingDir: $(workspaces.source.path)
script: |
#!/usr/bin/env bash
set -euo pipefail
# Configure registry auth
REGISTRY_AUTH_FILE="$(workspaces.registry-credentials.path)/.dockerconfigjson"
if [ ! -f "${REGISTRY_AUTH_FILE}" ]; then
REGISTRY_AUTH_FILE="$(workspaces.registry-credentials.path)/config.json"
fi
# Use same workspace store as build-image task.
BUILDAH_STORE="$(workspaces.source.path)/.buildah"
LOCAL_IMAGE="localhost/$(params.IMAGE)"
REGISTRY_IMAGE="$(params.REGISTRY_IMAGE)"
TAG="$(params.TAG)"
DEST="${REGISTRY_IMAGE}:${TAG}"
echo "Pushing ${LOCAL_IMAGE} to docker://${DEST} ..."
# Push directly to the version tag — NO ephemeral :build-tmp staging
# tag (bd forgejo-mcp-9r4: deleting a staging tag on Forgejo/Codeberg
# deletes the shared manifest and takes the real tags with it). The
# tag is briefly unsigned until cosign-sign runs next; a signing
# failure removes it via the pipeline's finally cleanup. --digestfile
# still records the manifest digest so sign/attest can pin by digest.
buildah --root "${BUILDAH_STORE}" push \
--storage-driver="$(params.STORAGE_DRIVER)" \
--authfile="${REGISTRY_AUTH_FILE}" \
--digestfile=/tmp/pushed-digest \
"${LOCAL_IMAGE}" \
"docker://${DEST}" \
2>&1 | tee /dev/stderr
DIGEST=$(cat /tmp/pushed-digest)
REPO_DIGEST="${REGISTRY_IMAGE}@${DIGEST}"
printf '%s' "${DIGEST}" > "$(results.IMAGE_DIGEST.path)"
printf '%s' "${REGISTRY_IMAGE}" > "$(results.IMAGE_URL.path)"
printf '%s' "${REPO_DIGEST}" > "$(results.IMAGE_REPO_DIGEST.path)"
echo "Pushed by digest: ${REPO_DIGEST}"