Skip to content

Commit 1169a40

Browse files
vdemeestertekton-robot
authored andcommitted
tekton: automate releases with Pipelines-as-Code
- Replace manual tkn pipeline start with PAC-triggered PipelineRuns - Add weekly cron scan for unreleased commits on release branches - Merge draft release creation into the release pipeline - Simplify release cheat sheet to reflect automated workflow Signed-off-by: Vincent Demeester <vdemeest@redhat.com>
1 parent 6df6dfb commit 1169a40

File tree

5 files changed

+757
-206
lines changed

5 files changed

+757
-206
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
name: Patch Release
2+
3+
"on":
4+
workflow_dispatch:
5+
inputs:
6+
branch:
7+
description: "Release branch (e.g. release-v0.79.x)"
8+
required: true
9+
type: string
10+
version:
11+
description: "Version to release (e.g. v0.79.1)"
12+
required: true
13+
type: string
14+
release_as_latest:
15+
description: "Publish as latest release"
16+
required: false
17+
type: boolean
18+
default: true
19+
schedule:
20+
# Weekly on Thursday at 10:00 UTC
21+
- cron: "0 10 * * 4"
22+
23+
env:
24+
PAC_CONTROLLER_URL: "https://pac.infra.tekton.dev"
25+
PAC_REPOSITORY_NAME: "tektoncd-operator"
26+
# Ignore release branches older than this (major.minor)
27+
MIN_RELEASE_VERSION: "0.70"
28+
29+
jobs:
30+
scan-release-branches:
31+
name: Scan for unreleased commits
32+
if: github.event_name == 'schedule' && github.repository_owner == 'tektoncd'
33+
runs-on: ubuntu-latest
34+
outputs:
35+
matrix: ${{ steps.scan.outputs.matrix }}
36+
has_releases: ${{ steps.scan.outputs.has_releases }}
37+
steps:
38+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
39+
with:
40+
fetch-depth: 0
41+
42+
- name: Scan release branches for new commits
43+
id: scan
44+
run: |
45+
# Determine which release branch is the latest (highest version)
46+
latest_branch=""
47+
latest_major=0
48+
latest_minor=0
49+
for ref in $(git branch -r --list 'origin/release-v*'); do
50+
branch="${ref#origin/}"
51+
# Extract major.minor from release-vX.Y.x
52+
if [[ "$branch" =~ release-v([0-9]+)\.([0-9]+)\.x ]]; then
53+
major="${BASH_REMATCH[1]}"
54+
minor="${BASH_REMATCH[2]}"
55+
if [ "$major" -gt "$latest_major" ] || { [ "$major" -eq "$latest_major" ] && [ "$minor" -gt "$latest_minor" ]; }; then
56+
latest_major=$major
57+
latest_minor=$minor
58+
latest_branch=$branch
59+
fi
60+
fi
61+
done
62+
echo "::notice::Latest release branch: ${latest_branch}"
63+
64+
MIN_MAJOR="${MIN_RELEASE_VERSION%%.*}"
65+
MIN_MINOR="${MIN_RELEASE_VERSION##*.}"
66+
67+
releases=()
68+
for ref in $(git branch -r --list 'origin/release-v*'); do
69+
branch="${ref#origin/}"
70+
71+
# Skip branches older than MIN_RELEASE_VERSION
72+
if [[ "$branch" =~ release-v([0-9]+)\.([0-9]+)\.x ]]; then
73+
major="${BASH_REMATCH[1]}"
74+
minor="${BASH_REMATCH[2]}"
75+
if [ "$major" -lt "$MIN_MAJOR" ] || { [ "$major" -eq "$MIN_MAJOR" ] && [ "$minor" -lt "$MIN_MINOR" ]; }; then
76+
echo "::notice::Branch ${branch} is older than v${MIN_RELEASE_VERSION} — skipping"
77+
continue
78+
fi
79+
fi
80+
81+
# Find the latest tag on this branch
82+
last_tag=$(git describe --tags --abbrev=0 --match 'v*' "$ref" 2>/dev/null || echo "")
83+
if [ -z "$last_tag" ]; then
84+
echo "::notice::Branch ${branch} has no tags — skipping (initial release handled by branch creation)"
85+
continue
86+
fi
87+
88+
# Count commits since last tag
89+
new_commits=$(git rev-list "${last_tag}..${ref}" --count)
90+
if [ "$new_commits" -eq 0 ]; then
91+
echo "::notice::Branch ${branch} has no new commits since ${last_tag}"
92+
continue
93+
fi
94+
95+
# Calculate next patch version: v0.79.0 → v0.79.1
96+
next_version=$(echo "$last_tag" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
97+
98+
# Only the latest release branch publishes as latest
99+
is_latest="false"
100+
if [ "$branch" = "$latest_branch" ]; then
101+
is_latest="true"
102+
fi
103+
104+
echo "::notice::Branch ${branch}: ${new_commits} new commits since ${last_tag} → ${next_version} (latest=${is_latest})"
105+
releases+=("{\"branch\":\"${branch}\",\"version\":\"${next_version}\",\"release_as_latest\":\"${is_latest}\"}")
106+
done
107+
108+
if [ ${#releases[@]} -eq 0 ]; then
109+
echo "matrix=[]" >> "$GITHUB_OUTPUT"
110+
echo "has_releases=false" >> "$GITHUB_OUTPUT"
111+
else
112+
echo "matrix=[$(IFS=,; echo "${releases[*]}")]" >> "$GITHUB_OUTPUT"
113+
echo "has_releases=true" >> "$GITHUB_OUTPUT"
114+
fi
115+
116+
trigger-scanned-releases:
117+
name: "Trigger ${{ matrix.release.version }} (${{ matrix.release.branch }})"
118+
needs: scan-release-branches
119+
if: needs.scan-release-branches.outputs.has_releases == 'true'
120+
runs-on: ubuntu-latest
121+
strategy:
122+
matrix:
123+
release: ${{ fromJson(needs.scan-release-branches.outputs.matrix) }}
124+
max-parallel: 1
125+
steps:
126+
- name: Trigger PAC incoming webhook
127+
env:
128+
PAC_INCOMING_SECRET: ${{ secrets.PAC_INCOMING_SECRET }}
129+
run: |
130+
echo "::notice::Triggering release ${{ matrix.release.version }} on ${{ matrix.release.branch }} (latest=${{ matrix.release.release_as_latest }})"
131+
curl -sf -X POST "${PAC_CONTROLLER_URL}/incoming" \
132+
-H "Content-Type: application/json" \
133+
-d '{
134+
"repository": "'"${PAC_REPOSITORY_NAME}"'",
135+
"branch": "${{ matrix.release.branch }}",
136+
"pipelinerun": "release-patch",
137+
"secret": "'"${PAC_INCOMING_SECRET}"'",
138+
"params": {
139+
"version": "${{ matrix.release.version }}",
140+
"release_as_latest": "${{ matrix.release.release_as_latest }}"
141+
}
142+
}'
143+
echo "Release triggered successfully"
144+
145+
trigger-manual-release:
146+
name: "Trigger ${{ inputs.version }} (${{ inputs.branch }})"
147+
if: github.event_name == 'workflow_dispatch' && github.repository_owner == 'tektoncd'
148+
runs-on: ubuntu-latest
149+
steps:
150+
- name: Validate inputs
151+
run: |
152+
# Validate branch format
153+
if [[ ! "${{ inputs.branch }}" =~ ^release-v[0-9]+\.[0-9]+\.x$ ]]; then
154+
echo "::error::Invalid branch format: ${{ inputs.branch }}. Expected: release-vX.Y.x"
155+
exit 1
156+
fi
157+
# Validate version format
158+
if [[ ! "${{ inputs.version }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
159+
echo "::error::Invalid version format: ${{ inputs.version }}. Expected: vX.Y.Z"
160+
exit 1
161+
fi
162+
163+
- name: Trigger PAC incoming webhook
164+
env:
165+
PAC_INCOMING_SECRET: ${{ secrets.PAC_INCOMING_SECRET }}
166+
run: |
167+
echo "::notice::Triggering release ${{ inputs.version }} on ${{ inputs.branch }} (latest=${{ inputs.release_as_latest }})"
168+
curl -sf -X POST "${PAC_CONTROLLER_URL}/incoming" \
169+
-H "Content-Type: application/json" \
170+
-d '{
171+
"repository": "'"${PAC_REPOSITORY_NAME}"'",
172+
"branch": "${{ inputs.branch }}",
173+
"pipelinerun": "release-patch",
174+
"secret": "'"${PAC_INCOMING_SECRET}"'",
175+
"params": {
176+
"version": "${{ inputs.version }}",
177+
"release_as_latest": "${{ inputs.release_as_latest }}"
178+
}
179+
}'
180+
echo "Release triggered successfully"

.tekton/release-patch.yaml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright 2026 The Tekton Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# This PipelineRun is triggered via PAC incoming webhooks for patch
16+
# releases on existing release branches. It is invoked either manually
17+
# (via GitHub Actions workflow_dispatch) or on a cron schedule when
18+
# new commits are detected on a release branch since the last tag.
19+
#
20+
# The version and release_as_latest parameters are passed dynamically
21+
# through the incoming webhook payload.
22+
---
23+
apiVersion: tekton.dev/v1
24+
kind: PipelineRun
25+
metadata:
26+
name: release-patch
27+
annotations:
28+
pipelinesascode.tekton.dev/on-event: "[incoming]"
29+
pipelinesascode.tekton.dev/on-target-branch: "[release-v*]"
30+
pipelinesascode.tekton.dev/pipeline: "tekton/operator-release-pipeline.yaml"
31+
pipelinesascode.tekton.dev/max-keep-runs: "5"
32+
spec:
33+
pipelineRef:
34+
name: operator-release
35+
taskRunTemplate:
36+
serviceAccountName: release-right-meow
37+
params:
38+
- name: package
39+
value: github.com/tektoncd/operator
40+
- name: repoName
41+
value: operator
42+
- name: components
43+
value: components.yaml
44+
- name: gitRevision
45+
value: "{{ revision }}"
46+
- name: imageRegistry
47+
value: ghcr.io
48+
- name: imageRegistryPath
49+
value: tektoncd/operator
50+
- name: imageRegistryRegions
51+
value: ""
52+
- name: imageRegistryUser
53+
value: tekton-robot
54+
- name: versionTag
55+
value: "{{ version }}"
56+
- name: releaseBucket
57+
value: tekton-releases
58+
- name: releaseAsLatest
59+
value: "{{ release_as_latest }}"
60+
- name: koExtraArgs
61+
value: "--preserve-import-paths"
62+
- name: serviceAccountImagesPath
63+
value: credentials
64+
- name: runTests
65+
value: "true"
66+
- name: platforms
67+
value: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
68+
- name: kubeDistros
69+
value: "kubernetes openshift"
70+
# Auto-detected from git tags when empty
71+
- name: previousReleaseTag
72+
value: ""
73+
- name: releaseName
74+
value: ""
75+
timeouts:
76+
pipeline: 3h0m0s
77+
workspaces:
78+
- name: workarea
79+
volumeClaimTemplate:
80+
spec:
81+
accessModes:
82+
- ReadWriteOnce
83+
resources:
84+
requests:
85+
storage: 5Gi
86+
- name: release-secret
87+
secret:
88+
secretName: oci-release-secret
89+
- name: release-images-secret
90+
secret:
91+
secretName: ghcr-creds
92+
# Enables automatic draft GitHub release creation.
93+
- name: github-secret
94+
secret:
95+
secretName: github-token

.tekton/release.yaml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2026 The Tekton Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# This PipelineRun is triggered by Pipelines-as-Code when a release
16+
# branch (release-v*) is first created. It runs the operator release
17+
# pipeline defined in tekton/operator-release-pipeline.yaml.
18+
#
19+
# The release pipeline builds, publishes images for both kubernetes
20+
# and openshift platforms, uploads artifacts to the release bucket,
21+
# and a draft GitHub release is created separately.
22+
---
23+
apiVersion: tekton.dev/v1
24+
kind: PipelineRun
25+
metadata:
26+
name: release-pipeline
27+
annotations:
28+
pipelinesascode.tekton.dev/on-event: "[push]"
29+
pipelinesascode.tekton.dev/on-target-branch: "[refs/heads/release-v*]"
30+
pipelinesascode.tekton.dev/on-cel-expression: "body.created == true"
31+
pipelinesascode.tekton.dev/pipeline: "tekton/operator-release-pipeline.yaml"
32+
pipelinesascode.tekton.dev/max-keep-runs: "5"
33+
spec:
34+
pipelineRef:
35+
name: operator-release
36+
taskRunTemplate:
37+
serviceAccountName: release-right-meow
38+
params:
39+
- name: package
40+
value: github.com/tektoncd/operator
41+
- name: repoName
42+
value: operator
43+
- name: components
44+
value: components.yaml
45+
- name: gitRevision
46+
value: "{{ revision }}"
47+
- name: imageRegistry
48+
value: ghcr.io
49+
- name: imageRegistryPath
50+
value: tektoncd/operator
51+
- name: imageRegistryRegions
52+
value: ""
53+
- name: imageRegistryUser
54+
value: tekton-robot
55+
# For initial releases, the version is derived from the branch name:
56+
# release-v0.79.x → v0.79.0
57+
- name: versionTag
58+
value: '{{ cel: pac.target_branch.replace("release-", "").replace(".x", ".0") }}'
59+
- name: releaseBucket
60+
value: tekton-releases
61+
- name: releaseAsLatest
62+
value: "true"
63+
- name: koExtraArgs
64+
value: "--preserve-import-paths"
65+
- name: serviceAccountImagesPath
66+
value: credentials
67+
- name: runTests
68+
value: "true"
69+
- name: platforms
70+
value: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
71+
- name: kubeDistros
72+
value: "kubernetes openshift"
73+
# Auto-detected from git tags when empty
74+
- name: previousReleaseTag
75+
value: ""
76+
- name: releaseName
77+
value: ""
78+
timeouts:
79+
pipeline: 3h0m0s
80+
workspaces:
81+
- name: workarea
82+
volumeClaimTemplate:
83+
spec:
84+
accessModes:
85+
- ReadWriteOnce
86+
resources:
87+
requests:
88+
storage: 5Gi
89+
- name: release-secret
90+
secret:
91+
secretName: oci-release-secret
92+
- name: release-images-secret
93+
secret:
94+
secretName: ghcr-creds
95+
# Enables automatic draft GitHub release creation.
96+
- name: github-secret
97+
secret:
98+
secretName: github-token

0 commit comments

Comments
 (0)