Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 258 additions & 0 deletions .github/workflows/patch-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
name: Patch Release

"on":
workflow_dispatch:
inputs:
branch:
description: "Release branch (e.g. release-v0.45.x)"
required: true
type: string
version:
description: "Version to release (e.g. v0.45.1)"
required: true
type: string
release_as_latest:
description: "Publish as latest release"
required: false
type: boolean
default: true
schedule:
# Weekly on Thursday at 10:00 UTC
- cron: "0 10 * * 4"

permissions: {}

env:
PAC_CONTROLLER_URL: "https://pac.infra.tekton.dev"
PAC_REPOSITORY_NAME: "tektoncd-cli"
# Ignore release branches older than this (major.minor)
MIN_RELEASE_VERSION: "0.40"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vdemeester we may want to include v0.37.x as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah initially I set this high enough to not be flooded by the pipelineruns.. but we can definitely lower this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add that later.


jobs:
scan-release-branches:
name: Scan for unreleased commits
if: github.event_name == 'schedule' && github.repository_owner == 'tektoncd'
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.scan.outputs.matrix }}
has_releases: ${{ steps.scan.outputs.has_releases }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false

- name: Scan release branches for new commits
id: scan
run: |
# Determine which release branch is the latest (highest version)
latest_branch=""
latest_major=0
latest_minor=0
for ref in $(git branch -r --list 'origin/release-v*'); do
branch="${ref#origin/}"
# Extract major.minor from release-vX.Y.x
if [[ "$branch" =~ release-v([0-9]+)\.([0-9]+)\.x ]]; then
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"
if [ "$major" -gt "$latest_major" ] || { [ "$major" -eq "$latest_major" ] && [ "$minor" -gt "$latest_minor" ]; }; then
latest_major=$major
latest_minor=$minor
latest_branch=$branch
fi
fi
done
echo "::notice::Latest release branch: ${latest_branch}"

MIN_MAJOR="${MIN_RELEASE_VERSION%%.*}"
MIN_MINOR="${MIN_RELEASE_VERSION##*.}"

releases=()
for ref in $(git branch -r --list 'origin/release-v*'); do
branch="${ref#origin/}"

# Only process release-vX.Y.x branches; skip old-style release-vX.Y.Z
if [[ ! "$branch" =~ release-v([0-9]+)\.([0-9]+)\.x ]]; then
continue
fi
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"

# Skip branches older than MIN_RELEASE_VERSION
if [ "$major" -lt "$MIN_MAJOR" ] || { [ "$major" -eq "$MIN_MAJOR" ] && [ "$minor" -lt "$MIN_MINOR" ]; }; then
echo "::notice::Branch ${branch} is older than v${MIN_RELEASE_VERSION} — skipping"
continue
fi

# Find the latest tag on this branch
last_tag=$(git describe --tags --abbrev=0 --match 'v*' "$ref" 2>/dev/null || echo "")
if [ -z "$last_tag" ]; then
echo "::notice::Branch ${branch} has no tags — skipping (initial release handled by branch creation)"
continue
fi

# Count commits since last tag
new_commits=$(git rev-list "${last_tag}..${ref}" --count)
if [ "$new_commits" -eq 0 ]; then
echo "::notice::Branch ${branch} has no new commits since ${last_tag}"
continue
fi

# Calculate next patch version: v0.44.0 → v0.44.1
next_version=$(echo "$last_tag" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')

# Read tool versions from the branch
go_version=$(git show "${ref}:go.mod" | grep "^go " | awk '{ print $2 }')
golangci_version=$(git show "${ref}:tools/go.mod" | grep golangci-lint | awk '{ print $3 }')

# Only the latest release branch publishes as latest
is_latest="false"
if [ "$branch" = "$latest_branch" ]; then
is_latest="true"
fi

echo "::notice::Branch ${branch}: ${new_commits} new commits since ${last_tag} → ${next_version} (latest=${is_latest}, go=${go_version}, lint=${golangci_version})"
releases+=("{\"branch\":\"${branch}\",\"version\":\"${next_version}\",\"release_as_latest\":\"${is_latest}\",\"go_version\":\"${go_version}\",\"golangci_lint_version\":\"${golangci_version}\"}")
done

if [ ${#releases[@]} -eq 0 ]; then
echo "matrix=[]" >> "$GITHUB_OUTPUT"
echo "has_releases=false" >> "$GITHUB_OUTPUT"
else
echo "matrix=[$(IFS=,; echo "${releases[*]}")]" >> "$GITHUB_OUTPUT"
echo "has_releases=true" >> "$GITHUB_OUTPUT"
fi

create-tag-and-trigger:
name: "Release ${{ matrix.release.version }} (${{ matrix.release.branch }})"
needs: scan-release-branches
if: needs.scan-release-branches.outputs.has_releases == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
matrix:
release: ${{ fromJson(needs.scan-release-branches.outputs.matrix) }}
max-parallel: 1
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ matrix.release.branch }}

- name: Create and push version tag
env:
RELEASE_VERSION: ${{ matrix.release.version }}
run: |
# Write version to VERSION file (goreleaser reads from git tag,
# but the Makefile uses this file)
echo "${RELEASE_VERSION#v}" > VERSION
git config user.name "tekton-robot"
git config user.email "tekton-robot@tektoncd.com"
git add VERSION
git commit -sm "New version ${RELEASE_VERSION}" || echo "No VERSION changes"
git tag -a "${RELEASE_VERSION}" -m "Release ${RELEASE_VERSION}"
git push origin "${RELEASE_VERSION}"
git push origin "HEAD:${{ matrix.release.branch }}"

- name: Trigger PAC incoming webhook
env:
PAC_INCOMING_SECRET: ${{ secrets.PAC_INCOMING_SECRET }}
RELEASE_BRANCH: ${{ matrix.release.branch }}
RELEASE_VERSION: ${{ matrix.release.version }}
RELEASE_AS_LATEST: ${{ matrix.release.release_as_latest }}
GO_VERSION: ${{ matrix.release.go_version }}
GOLANGCI_LINT_VERSION: ${{ matrix.release.golangci_lint_version }}
run: |
echo "::notice::Triggering release ${RELEASE_VERSION} on ${RELEASE_BRANCH} (latest=${RELEASE_AS_LATEST})"
curl -sf -X POST "${PAC_CONTROLLER_URL}/incoming" \
-H "Content-Type: application/json" \
-d '{
"repository": "'"${PAC_REPOSITORY_NAME}"'",
"branch": "'"${RELEASE_BRANCH}"'",
"pipelinerun": "release-patch",
"secret": "'"${PAC_INCOMING_SECRET}"'",
"params": {
"version": "'"${RELEASE_VERSION}"'",
"release_as_latest": "'"${RELEASE_AS_LATEST}"'",
"go_version": "'"${GO_VERSION}"'",
"golangci_lint_version": "'"${GOLANGCI_LINT_VERSION}"'"
}
}'
echo "✅ Release triggered successfully"

trigger-manual-release:
name: "Trigger ${{ inputs.version }} (${{ inputs.branch }})"
if: github.event_name == 'workflow_dispatch' && github.repository_owner == 'tektoncd'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.branch }}

- name: Validate inputs
env:
INPUT_BRANCH: ${{ inputs.branch }}
INPUT_VERSION: ${{ inputs.version }}
run: |
# Validate branch format
if [[ ! "${INPUT_BRANCH}" =~ ^release-v[0-9]+\.[0-9]+\.x$ ]]; then
echo "::error::Invalid branch format: ${INPUT_BRANCH}. Expected: release-vX.Y.x"
exit 1
fi
# Validate version format
if [[ ! "${INPUT_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Invalid version format: ${INPUT_VERSION}. Expected: vX.Y.Z"
exit 1
fi

- name: Read tool versions
id: versions
run: |
GO_VERSION=$(head -3 go.mod | grep "^go " | awk '{ print $2 }')
GOLANGCI_VERSION=$(grep golangci-lint tools/go.mod | awk '{ print $3 }')
echo "go_version=${GO_VERSION}" >> "$GITHUB_OUTPUT"
echo "golangci_lint_version=${GOLANGCI_VERSION}" >> "$GITHUB_OUTPUT"
echo "::notice::Go version: ${GO_VERSION}, golangci-lint: ${GOLANGCI_VERSION}"

- name: Create and push version tag
env:
INPUT_VERSION: ${{ inputs.version }}
INPUT_BRANCH: ${{ inputs.branch }}
run: |
# Write version to VERSION file
echo "${INPUT_VERSION#v}" > VERSION
git config user.name "tekton-robot"
git config user.email "tekton-robot@tektoncd.com"
git add VERSION
git commit -sm "New version ${INPUT_VERSION}" || echo "No VERSION changes"
git tag -a "${INPUT_VERSION}" -m "Release ${INPUT_VERSION}"
git push origin "${INPUT_VERSION}"
git push origin "HEAD:${INPUT_BRANCH}"

- name: Trigger PAC incoming webhook
env:
PAC_INCOMING_SECRET: ${{ secrets.PAC_INCOMING_SECRET }}
INPUT_BRANCH: ${{ inputs.branch }}
INPUT_VERSION: ${{ inputs.version }}
INPUT_RELEASE_AS_LATEST: ${{ inputs.release_as_latest }}
GO_VERSION: ${{ steps.versions.outputs.go_version }}
GOLANGCI_LINT_VERSION: ${{ steps.versions.outputs.golangci_lint_version }}
run: |
echo "::notice::Triggering release ${INPUT_VERSION} on ${INPUT_BRANCH} (latest=${INPUT_RELEASE_AS_LATEST})"
curl -sf -X POST "${PAC_CONTROLLER_URL}/incoming" \
-H "Content-Type: application/json" \
-d '{
"repository": "'"${PAC_REPOSITORY_NAME}"'",
"branch": "'"${INPUT_BRANCH}"'",
"pipelinerun": "release-patch",
"secret": "'"${PAC_INCOMING_SECRET}"'",
"params": {
"version": "'"${INPUT_VERSION}"'",
"release_as_latest": "'"${INPUT_RELEASE_AS_LATEST}"'",
"go_version": "'"${GO_VERSION}"'",
"golangci_lint_version": "'"${GOLANGCI_LINT_VERSION}"'"
}
}'
echo "✅ Release triggered successfully"
68 changes: 68 additions & 0 deletions .tekton/release-patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright 2026 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This PipelineRun is triggered via PAC incoming webhooks for patch
# releases on existing release branches. It is invoked either manually
# (via GitHub Actions workflow_dispatch) or on a cron schedule when
# new commits are detected on a release branch since the last tag.
#
# The version, go_version, and golangci_lint_version parameters are
# passed dynamically through the incoming webhook payload.
#
# Before triggering this PipelineRun, the caller (patch-release.yaml
# workflow) must have created and pushed the version tag (e.g. v0.45.1)
# so that goreleaser can detect it.
---
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: release-patch
annotations:
pipelinesascode.tekton.dev/on-event: "[incoming]"
pipelinesascode.tekton.dev/on-target-branch: "[release-v*]"
pipelinesascode.tekton.dev/pipeline: "tekton/release-pipeline.yaml"
pipelinesascode.tekton.dev/max-keep-runs: "5"
pipelinesascode.tekton.dev/task: "[git-clone, golangci-lint, golang-test, golang-build, goreleaser]"
pipelinesascode.tekton.dev/task-1: "tekton/get-version.yaml"
spec:
taskRunTemplate:
serviceAccountName: release
pipelineRef:
name: cli-release-pipeline
params:
- name: url
value: "{{ repo_url }}"
- name: revision
value: "{{ version }}"
- name: package
value: github.com/tektoncd/cli
- name: github-token-secret
value: bot-token-github
- name: github-token-secret-key
value: bot-token
- name: golangci-lint-version
value: "{{ golangci_lint_version }}"
- name: go-version
value: "{{ go_version }}"
timeouts:
pipeline: 2h0m0s
workspaces:
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
File renamed without changes.
2 changes: 1 addition & 1 deletion tekton/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ if ! kubectl -n ${TARGET_NAMESPACE} get secret ${SECRET_NAME} -o name >/dev/null
kubectl -n ${TARGET_NAMESPACE} create secret generic ${SECRET_NAME} --from-literal=bot-token=${github_token}
fi

kubectl -n ${TARGET_NAMESPACE} apply -f ./tekton/release-pipeline.yml
kubectl -n ${TARGET_NAMESPACE} apply -f ./tekton/release-pipeline.yaml

sleep 2

Expand Down
2 changes: 1 addition & 1 deletion tekton/rpmbuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dnf install tektoncd-cli
USAGE
=====

Same as when you use the [release.pipeline.yaml](../release-pipeline.yml), you
Same as when you use the [release.pipeline.yaml](../release-pipeline.yaml), you
need to have a PipelineResource for your git repository. See
[here](../release-pipeline-run.yml) for an example.

Expand Down
Loading