From 551acd863ef02d94b117b442f656630df6942005 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 19 Feb 2026 13:30:45 +0100 Subject: [PATCH 1/3] feat: implement automated release system for tektoncd projects Add infrastructure for automated release management of Tekton projects, starting with tektoncd/pipeline. Core components: - EventListener with TriggerBindings for release branch events - Per-project TriggerTemplates using git resolver to fetch release pipelines directly from project repos (no pre-installation needed) - scan-release-branches Task for detecting new commits on release branches - release-chatops-router Task for slash commands on tracking issues - manage-release-tracking-issue Task for GitHub issue automation - CronJob for scheduled weekly branch scanning Features: - Dual trigger: GitHub webhooks and cron-based scanning - minVersion filter to scope which release branches are processed - Automatic version detection (compares HEAD vs latest git tag) - ChatOps commands: /release-status, /release-cancel, /release-restart, /release-full, /release-validate - Per-project kustomize overlays (pipeline as first project) Deployment: - Namespace: automated-releases - Kustomize overlays for dogfooding environment - RBAC and ServiceAccount configuration Closes #58 Signed-off-by: Vincent Demeester --- docs/automated-release-deployment.md | 262 ++++++++++++++++++ tekton/catalog/pipelines/release.yaml | 5 + .../bases/automated-release/README.md | 139 ++++++++++ .../automated-release/kustomization.yaml | 24 ++ .../scan-release-branches.yaml | 58 ++++ .../automated-release/cronjob-patch.yaml | 21 ++ .../automated-release/kustomization.yaml | 19 ++ .../automated-release/base/bindings.yaml | 79 ++++++ .../base/chatops-template.yaml | 59 ++++ .../automated-release/base/eventlistener.yaml | 26 ++ .../automated-release/base/kustomization.yaml | 41 +++ .../automated-release/base/rbac.yaml | 68 +++++ .../base/serviceaccount.yaml | 21 ++ .../tasks/manage-release-tracking-issue.yaml | 141 ++++++++++ .../base/tasks/release-chatops-router.yaml | 260 +++++++++++++++++ .../base/tasks/scan-release-branches.yaml | 155 +++++++++++ .../automated-release/base/template.yaml | 71 +++++ .../base/trigger-chatops.yaml | 57 ++++ .../base/trigger-tracking-cron.yaml | 36 +++ .../base/trigger-tracking-webhook.yaml | 57 ++++ .../automated-release/overlays/.gitignore | 2 + .../automated-release/overlays/README.md | 96 +++++++ .../overlays/dogfooding/README.md | 125 +++++++++ .../overlays/dogfooding/kustomization.yaml | 24 ++ .../overlays/pipeline/kustomization.yaml | 24 ++ .../overlays/pipeline/release-template.yaml | 105 +++++++ .../overlays/pipeline/trigger-cron.yaml | 40 +++ .../overlays/pipeline/trigger-webhook.yaml | 60 ++++ tekton/resources/kustomization.yaml | 1 + 29 files changed, 2076 insertions(+) create mode 100644 docs/automated-release-deployment.md create mode 100644 tekton/cronjobs/bases/automated-release/README.md create mode 100644 tekton/cronjobs/bases/automated-release/kustomization.yaml create mode 100644 tekton/cronjobs/bases/automated-release/scan-release-branches.yaml create mode 100644 tekton/cronjobs/dogfooding/automated-release/cronjob-patch.yaml create mode 100644 tekton/cronjobs/dogfooding/automated-release/kustomization.yaml create mode 100644 tekton/resources/automated-release/base/bindings.yaml create mode 100644 tekton/resources/automated-release/base/chatops-template.yaml create mode 100644 tekton/resources/automated-release/base/eventlistener.yaml create mode 100644 tekton/resources/automated-release/base/kustomization.yaml create mode 100644 tekton/resources/automated-release/base/rbac.yaml create mode 100644 tekton/resources/automated-release/base/serviceaccount.yaml create mode 100644 tekton/resources/automated-release/base/tasks/manage-release-tracking-issue.yaml create mode 100644 tekton/resources/automated-release/base/tasks/release-chatops-router.yaml create mode 100644 tekton/resources/automated-release/base/tasks/scan-release-branches.yaml create mode 100644 tekton/resources/automated-release/base/template.yaml create mode 100644 tekton/resources/automated-release/base/trigger-chatops.yaml create mode 100644 tekton/resources/automated-release/base/trigger-tracking-cron.yaml create mode 100644 tekton/resources/automated-release/base/trigger-tracking-webhook.yaml create mode 100644 tekton/resources/automated-release/overlays/.gitignore create mode 100644 tekton/resources/automated-release/overlays/README.md create mode 100644 tekton/resources/automated-release/overlays/dogfooding/README.md create mode 100644 tekton/resources/automated-release/overlays/dogfooding/kustomization.yaml create mode 100644 tekton/resources/automated-release/overlays/pipeline/kustomization.yaml create mode 100644 tekton/resources/automated-release/overlays/pipeline/release-template.yaml create mode 100644 tekton/resources/automated-release/overlays/pipeline/trigger-cron.yaml create mode 100644 tekton/resources/automated-release/overlays/pipeline/trigger-webhook.yaml diff --git a/docs/automated-release-deployment.md b/docs/automated-release-deployment.md new file mode 100644 index 000000000..3e2d675bb --- /dev/null +++ b/docs/automated-release-deployment.md @@ -0,0 +1,262 @@ +# Automated Release System - Deployment Guide + +Quick reference for deploying the automated release system. + +## Quick Start - Test Environment + +```bash +# 1. Create kind cluster +kind create cluster --name tekton-test + +# 2. Install Tekton +kubectl apply -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml +kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml + +# 3. Create namespace +kubectl create namespace automated-releases + +# 4. Create secrets +cd tekton/resources/automated-release/overlays/test +cp secrets-template.yaml secrets.yaml +# Edit secrets.yaml with your GitHub token +kubectl apply -f secrets.yaml + +# 5. Deploy system +cd ../../../../../ # Back to repo root +kubectl apply -k tekton/resources/automated-release/overlays/test +kubectl apply -k tekton/cronjobs/test/automated-release + +# 6. Verify +kubectl get eventlistener,cronjob -n automated-releases +``` + +## Quick Start - Dogfooding Cluster + +```bash +# 1. Connect to dogfooding cluster +kubectl config use-context dogfooding-cluster + +# 2. Verify secrets exist +kubectl get secret -n automated-releases github-token release-secret release-images-secret + +# 3. Deploy +kubectl apply -k tekton/resources/automated-release/overlays/dogfooding +kubectl apply -k tekton/cronjobs/dogfooding/automated-release + +# 4. Verify +kubectl get eventlistener,cronjob -n automated-releases +kubectl get pipelinerun -n automated-releases --watch +``` + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Automated Release System │ +└─────────────────────────────────────────────────────────────┘ + │ + ┌─────────────┼─────────────┐ + │ │ │ + ┌──────▼──────┐ ┌───▼────┐ ┌─────▼──────┐ + │ GitHub │ │ Cron │ │ ChatOps │ + │ Webhooks │ │Scanner │ │ Commands │ + └──────┬──────┘ └───┬────┘ └─────┬──────┘ + │ │ │ + └────────────┼─────────────┘ + │ + ┌────────▼─────────┐ + │ EventListener │ + │ tekton-releases │ + └────────┬─────────┘ + │ + ┌────────────┼────────────┐ + │ │ + ┌──────▼──────┐ ┌───────▼────────┐ + │ TaskRun: │ │ PipelineRun: │ + │ Create/ │ │ Release │ + │ Update │ │ Pipeline │ + │ Issue │ │ │ + └─────────────┘ └────────────────┘ +``` + +## Components Deployed + +### Resources (tekton/resources/automated-release/) +- **EventListener**: `tekton-releases` - Receives webhooks and cron triggers +- **TriggerBindings**: `release-branch-webhook`, `release-branch-cron`, `release-chatops-command` +- **TriggerTemplates**: `release-automation`, `chatops-template` +- **Tasks**: `manage-release-tracking-issue`, `release-chatops-router` +- **ServiceAccount**: `release-automation` with RBAC + +### CronJobs (tekton/cronjobs/) +- **scan-release-branches**: Weekly scanner (Thursday 10:00 UTC) + - Detects release branches + - Calculates next version number + - Triggers validation pipelines + - Creates/updates tracking issues + +## Testing Commands + +### Manual CronJob Trigger +```bash +# Create job from cronjob +kubectl create job --from=cronjob/scan-release-branches-test test-001 -n automated-releases + +# Watch all container logs +kubectl logs -f -n automated-releases job/test-001 -c scan-branches +kubectl logs -f -n automated-releases job/test-001 -c filter-new-commits +kubectl logs -f -n automated-releases job/test-001 -c generate-uuids +kubectl logs -f -n automated-releases job/test-001 -c trigger-releases +``` + +### Manual EventListener Trigger +```bash +# Port-forward EventListener +kubectl port-forward -n automated-releases svc/el-tekton-releases 8080:8080 + +# Send test payload (cron format) +curl -X POST http://localhost:8080 \ + -H "Content-Type: application/json" \ + -d @- << 'EOF' +{ + "buildUUID": "test-manual-001", + "params": { + "release": { + "gitRepository": "https://github.com/tektoncd/pipeline", + "releaseBranch": "release-v0.50.x", + "projectName": "pipeline", + "repositoryFullName": "tektoncd/pipeline", + "releaseMode": "validation-only", + "releaseType": "bugfix", + "releaseVersion": "v0.50.1", + "commitSha": "abc123def456" + } + } +} +EOF +``` + +### Check PipelineRun Status +```bash +# Watch for new PipelineRuns +kubectl get pipelinerun -n automated-releases --watch + +# Get latest PipelineRun +kubectl get pipelinerun -n automated-releases --sort-by=.metadata.creationTimestamp | tail -1 + +# View PipelineRun logs +tkn pipelinerun logs -n automated-releases -f +``` + +### Check GitHub Issue Creation +```bash +# Get TaskRun logs for issue creation +kubectl get taskrun -n automated-releases -l tekton.dev/task=manage-release-tracking-issue + +# View logs +kubectl logs -n automated-releases -l tekton.dev/task=manage-release-tracking-issue +``` + +## Monitoring + +```bash +# Check EventListener health +kubectl get pods -n automated-releases -l eventlistener=tekton-releases + +# View EventListener logs +kubectl logs -n automated-releases -l eventlistener=tekton-releases --tail=100 + +# Check CronJob schedule +kubectl get cronjob -n automated-releases + +# View recent jobs +kubectl get jobs -n automated-releases --sort-by=.metadata.creationTimestamp + +# Monitor all releases +kubectl get pipelinerun,taskrun -n automated-releases -w +``` + +## Troubleshooting + +### EventListener not receiving webhooks + +```bash +# Check service +kubectl get svc -n automated-releases el-tekton-releases + +# Check if EventListener pod is running +kubectl get pods -n automated-releases -l eventlistener=tekton-releases + +# View detailed events +kubectl describe el tekton-releases -n automated-releases +``` + +### CronJob not running + +```bash +# Check if suspended +kubectl get cronjob scan-release-branches-test -n automated-releases -o yaml | grep suspend + +# Check schedule +kubectl get cronjob scan-release-branches-test -n automated-releases -o jsonpath='{.spec.schedule}' + +# Check last execution +kubectl get cronjob scan-release-branches-test -n automated-releases -o jsonpath='{.status.lastScheduleTime}' +``` + +### Pipeline failing + +```bash +# Get failed PipelineRuns +kubectl get pipelinerun -n automated-releases --field-selector status.conditions[0].status=False + +# Get detailed logs +tkn pipelinerun logs -n automated-releases + +# Check task status +kubectl get taskrun -n automated-releases +``` + +### Secret issues + +```bash +# Verify secrets exist +kubectl get secret -n automated-releases github-token release-secret release-images-secret + +# Check secret content (be careful with this!) +kubectl get secret github-token -n automated-releases -o jsonpath='{.data.token}' | base64 -d | wc -c +``` + +## Cleanup + +### Test Environment +```bash +kubectl delete -k tekton/cronjobs/test/automated-release +kubectl delete -k tekton/resources/automated-release/overlays/test +kubectl delete namespace automated-releases +kind delete cluster --name tekton-test +``` + +### Dogfooding Environment +```bash +# Be very careful with this! +kubectl delete -k tekton/cronjobs/dogfooding/automated-release +kubectl delete -k tekton/resources/automated-release/overlays/dogfooding +``` + +## Next Steps + +1. **Test locally** with kind cluster and manual triggers +2. **Deploy to dogfooding** cluster for initial validation +3. **Configure GitHub webhook** to start receiving events +4. **Monitor first runs** and verify issue creation +5. **Expand repositories** once validated (add triggers, operator, etc.) +6. **Implement Phase 5** (Slack bot) for additional ChatOps support + +## Documentation + +- [Base Resources README](tekton/resources/automated-release/base/README.md) - EventListener configuration +- [CronJob README](tekton/cronjobs/bases/automated-release/README.md) - Scanner details +- [Test Overlay README](tekton/resources/automated-release/overlays/test/README.md) - Local testing +- [Dogfooding Overlay README](tekton/resources/automated-release/overlays/dogfooding/README.md) - Production deployment +- [Issue #58](https://github.com/tektoncd/plumbing/issues/58) - Original requirements diff --git a/tekton/catalog/pipelines/release.yaml b/tekton/catalog/pipelines/release.yaml index d906703bc..c56ed4ed2 100644 --- a/tekton/catalog/pipelines/release.yaml +++ b/tekton/catalog/pipelines/release.yaml @@ -45,6 +45,9 @@ spec: - name: runTests description: If set to something other than "true", skip the build and test tasks default: "true" + - name: releaseMode + description: Release mode - "full-release" for complete release or "validation-only" for pre-release checks + default: "full-release" workspaces: - name: workarea description: The workspace where the repo will be cloned. @@ -124,6 +127,8 @@ spec: subPath: git/$(params.subfolder) - name: publish-images runAfter: [build, unit-tests] + when: + - cel: "'$(params.releaseMode)' == 'full-release'" taskRef: resolver: git params: diff --git a/tekton/cronjobs/bases/automated-release/README.md b/tekton/cronjobs/bases/automated-release/README.md new file mode 100644 index 000000000..92cca2900 --- /dev/null +++ b/tekton/cronjobs/bases/automated-release/README.md @@ -0,0 +1,139 @@ +# Automated Release Cron Scanner + +This CronJob scans tektoncd repositories for release branches and triggers validation pipelines when new commits are detected. + +## How It Works + +The CronJob runs weekly (Thursday 10:00 UTC) with 4 stages: + +### 1. Scan Branches (init container) +- Uses `git ls-remote` to find all `release-v*` branches +- Configured repositories via `REPOSITORIES` env var +- Detects next bugfix version number: + - Queries existing tags for each branch (e.g., `v1.10.*`) + - Increments patch version (v1.10.5 → v1.10.6) + - Defaults to `.0` if no tags exist (v1.10.0) +- Outputs: `/shared/branches.txt` (format: `repo:branch:commit:project:version`) + +### 2. Filter New Commits (init container) +- Compares found commits against previously processed ones +- TODO: Implement persistent tracking (ConfigMap/PVC) +- Currently processes all branches (for testing) +- Outputs: `/shared/new_commits.txt` + +### 3. Generate UUIDs (init container) +- Creates unique build ID for each trigger +- Uses Python's uuid module +- Outputs: `/shared/triggers.txt` + +### 4. Trigger Releases (main container) +- POSTs JSON payload to EventListener +- Uses `validation-only` mode (non-intrusive) +- Enforces `bugfix` release type (only patch version increments) +- Includes detected version number in payload +- Expects HTTP 202/201 response +- Configurable via `EVENTLISTENER_URL` env var + +## Configuration + +### Environment Variables + +- `REPOSITORIES`: Space-separated list of repos (default: `tektoncd/pipeline`) +- `EVENTLISTENER_URL`: EventListener endpoint (default: cluster-local service) + +### Schedule + +Default: `0 10 * * 4` (Thursday 10:00 UTC) + +Modify via kustomize patch: +```yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: scan-release-branches +spec: + schedule: "0 2 * * *" # Daily at 2 AM +``` + +## Deployment + +### Base +```bash +kubectl apply -k tekton/cronjobs/bases/automated-release/ +``` + +### With Overlays (Recommended) +```bash +kubectl apply -k tekton/cronjobs/dogfooding/automated-release/ +``` + +## Testing + +### Manual Trigger +```bash +kubectl create job --from=cronjob/scan-release-branches manual-scan-001 -n automated-releases +``` + +### Check Logs +```bash +# Init containers +kubectl logs -n automated-releases job/manual-scan-001 -c scan-branches +kubectl logs -n automated-releases job/manual-scan-001 -c filter-new-commits +kubectl logs -n automated-releases job/manual-scan-001 -c generate-uuids + +# Main container +kubectl logs -n automated-releases job/manual-scan-001 -c trigger-releases +``` + +## Persistent Tracking (TODO) + +Currently, the scanner processes all branches on each run. For production: + +### Option 1: ConfigMap +Store processed commits in a ConfigMap: +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: release-scanner-state +data: + tektoncd-pipeline-release-v0.50.x: "abc123def456" + tektoncd-pipeline-release-v0.51.x: "def456ghi789" +``` + +### Option 2: PersistentVolumeClaim +Mount a PVC and store state in `/state/processed_commits.txt` + +### Option 3: External Service +Query an external API or database for state + +## Integration + +Works with: +- **EventListener**: `tekton-releases` (receives triggers) +- **TriggerBinding**: `release-branch-cron` (parses payload) +- **TriggerTemplate**: `release-automation` (creates PipelineRun) + +## Monitoring + +Check CronJob status: +```bash +kubectl get cronjob scan-release-branches -n automated-releases +kubectl get jobs -n automated-releases -l app.kubernetes.io/component=cron-scanner +``` + +## Troubleshooting + +### No branches found +- Check `REPOSITORIES` env var +- Verify network access to github.com +- Check git ls-remote works: `git ls-remote --heads https://github.com/tektoncd/pipeline` + +### EventListener not responding +- Verify EventListener is running: `kubectl get el -n automated-releases` +- Check service exists: `kubectl get svc el-tekton-releases -n automated-releases` +- Test manually: `curl http://el-tekton-releases.automated-releases.svc.cluster.local:8080` + +### All branches triggering every week +- Implement persistent state tracking (see above) +- Or accept this behavior for weekly validation runs diff --git a/tekton/cronjobs/bases/automated-release/kustomization.yaml b/tekton/cronjobs/bases/automated-release/kustomization.yaml new file mode 100644 index 000000000..d99ee4f94 --- /dev/null +++ b/tekton/cronjobs/bases/automated-release/kustomization.yaml @@ -0,0 +1,24 @@ +# 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. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- scan-release-branches.yaml + +namespace: automated-releases + +labels: +- pairs: + app.kubernetes.io/part-of: tekton-automated-release + app.kubernetes.io/component: cron-scanner diff --git a/tekton/cronjobs/bases/automated-release/scan-release-branches.yaml b/tekton/cronjobs/bases/automated-release/scan-release-branches.yaml new file mode 100644 index 000000000..573a96b89 --- /dev/null +++ b/tekton/cronjobs/bases/automated-release/scan-release-branches.yaml @@ -0,0 +1,58 @@ +# 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. +apiVersion: batch/v1 +kind: CronJob +metadata: + name: scan-release-branches +spec: + schedule: "0 10 * * 4" # Weekly on Thursday at 10:00 UTC + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + spec: + serviceAccountName: release-automation + restartPolicy: Never + containers: + - name: create-taskrun + image: ghcr.io/tektoncd/plumbing/kubectl:latest + command: + - /bin/sh + - -c + - | + kubectl create -f - <= v1.9.x during testing + # Set to "0.0" to process all branches + value: "1.9" diff --git a/tekton/cronjobs/dogfooding/automated-release/kustomization.yaml b/tekton/cronjobs/dogfooding/automated-release/kustomization.yaml new file mode 100644 index 000000000..a3435578c --- /dev/null +++ b/tekton/cronjobs/dogfooding/automated-release/kustomization.yaml @@ -0,0 +1,19 @@ +nameSuffix: -dogfooding +namespace: automated-releases + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../../bases/automated-release + +commonAnnotations: + managed-by: Tekton + +patches: +- path: cronjob-patch.yaml + target: + group: batch + kind: CronJob + name: scan-release-branches + version: v1 diff --git a/tekton/resources/automated-release/base/bindings.yaml b/tekton/resources/automated-release/base/bindings.yaml new file mode 100644 index 000000000..5a6753c0e --- /dev/null +++ b/tekton/resources/automated-release/base/bindings.yaml @@ -0,0 +1,79 @@ +# 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. +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerBinding +metadata: + name: release-branch-webhook +spec: + params: + - name: buildID + value: $(extensions.build-id.id) + - name: gitRepository + value: $(body.repository.html_url) + - name: gitRevision + value: $(body.after) + - name: releaseBranch + value: $(extensions.release_branch) + - name: projectName + value: $(body.repository.name) + - name: repositoryFullName + value: $(body.repository.full_name) + - name: triggerSource + value: $(extensions.trigger_source) + - name: afterCommitSha + value: $(body.after) +--- +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerBinding +metadata: + name: release-branch-cron +spec: + params: + - name: buildID + value: $(body.buildUUID) + - name: gitRepository + value: $(body.params.release.gitRepository) + - name: gitRevision + value: $(body.params.release.releaseBranch) + - name: releaseBranch + value: $(body.params.release.releaseBranch) + - name: projectName + value: $(body.params.release.projectName) + - name: repositoryFullName + value: $(body.params.release.repositoryFullName) + - name: releaseVersion + value: $(body.params.release.releaseVersion) + - name: triggerSource + value: 'cron' + - name: afterCommitSha + value: $(body.params.release.commitSha) +--- +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerBinding +metadata: + name: release-chatops-command +spec: + params: + - name: command + value: $(extensions.command) + - name: issueNumber + value: $(extensions.issue_number) + - name: repositoryFullName + value: $(extensions.repository_full_name) + - name: commentAuthor + value: $(extensions.comment_author) + - name: commentBody + value: $(body.comment.body) + - name: issueTitle + value: $(body.issue.title) diff --git a/tekton/resources/automated-release/base/chatops-template.yaml b/tekton/resources/automated-release/base/chatops-template.yaml new file mode 100644 index 000000000..63a18036c --- /dev/null +++ b/tekton/resources/automated-release/base/chatops-template.yaml @@ -0,0 +1,59 @@ +# 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. +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerTemplate +metadata: + name: release-chatops +spec: + params: + - name: command + description: "ChatOps command (e.g., release-status, release-cancel)" + - name: issueNumber + description: "GitHub issue number" + - name: repositoryFullName + description: "Full repository name (e.g., tektoncd/pipeline)" + - name: commentAuthor + description: "GitHub user who issued the command" + - name: commentBody + description: "Full comment body" + - name: issueTitle + description: "Issue title" + resourcetemplates: + # Route to appropriate task based on command + - apiVersion: tekton.dev/v1 + kind: TaskRun + metadata: + generateName: release-chatops- + labels: + tekton.dev/kind: chatops + release.tekton.dev/command: $(tt.params.command) + release.tekton.dev/issue: $(tt.params.issueNumber) + spec: + taskRef: + name: release-chatops-router + params: + - name: command + value: $(tt.params.command) + - name: issueNumber + value: $(tt.params.issueNumber) + - name: repositoryFullName + value: $(tt.params.repositoryFullName) + - name: commentAuthor + value: $(tt.params.commentAuthor) + - name: issueTitle + value: $(tt.params.issueTitle) + workspaces: + - name: github-secret + secret: + secretName: github-token diff --git a/tekton/resources/automated-release/base/eventlistener.yaml b/tekton/resources/automated-release/base/eventlistener.yaml new file mode 100644 index 000000000..d3d2ecad9 --- /dev/null +++ b/tekton/resources/automated-release/base/eventlistener.yaml @@ -0,0 +1,26 @@ +# 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. + +# EventListener for automated releases. +# Uses namespaceSelector to auto-discover all Triggers in the namespace. +# Per-project Triggers are created by overlays in overlays//. +apiVersion: triggers.tekton.dev/v1beta1 +kind: EventListener +metadata: + name: tekton-releases +spec: + serviceAccountName: release-automation + namespaceSelector: + matchNames: + - automated-releases diff --git a/tekton/resources/automated-release/base/kustomization.yaml b/tekton/resources/automated-release/base/kustomization.yaml new file mode 100644 index 000000000..9355bb6e9 --- /dev/null +++ b/tekton/resources/automated-release/base/kustomization.yaml @@ -0,0 +1,41 @@ +# 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. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +# EventListener (auto-discovers Triggers via namespaceSelector) +- eventlistener.yaml +# TriggerBindings +- bindings.yaml +# Shared TriggerTemplates +- template.yaml +- chatops-template.yaml +# Shared Triggers (tracking issues, chatops) +- trigger-tracking-webhook.yaml +- trigger-tracking-cron.yaml +- trigger-chatops.yaml +# Tasks +- tasks/manage-release-tracking-issue.yaml +- tasks/release-chatops-router.yaml +- tasks/scan-release-branches.yaml +# Auth +- serviceaccount.yaml +- rbac.yaml + +namespace: automated-releases + +labels: +- pairs: + app.kubernetes.io/part-of: tekton-automated-release + app.kubernetes.io/component: automated-release diff --git a/tekton/resources/automated-release/base/rbac.yaml b/tekton/resources/automated-release/base/rbac.yaml new file mode 100644 index 000000000..570abfe67 --- /dev/null +++ b/tekton/resources/automated-release/base/rbac.yaml @@ -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. +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: release-automation-role +rules: +- apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get", "list", "watch"] +- apiGroups: ["triggers.tekton.dev"] + resources: ["eventlisteners", "triggerbindings", "triggertemplates", "triggers", "interceptors"] + verbs: ["get", "list", "watch"] +- apiGroups: ["tekton.dev"] + resources: ["pipelineruns", "pipelineresources", "taskruns"] + verbs: ["create", "get", "list", "watch"] +- apiGroups: [""] + resources: ["serviceaccounts"] + verbs: ["impersonate"] +- apiGroups: ["policy"] + resources: ["podsecuritypolicies"] + resourceNames: ["tekton-triggers"] + verbs: ["use"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: release-automation-rolebinding +subjects: +- kind: ServiceAccount + name: release-automation +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: release-automation-role +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: release-automation-clusterrole +rules: +- apiGroups: ["triggers.tekton.dev"] + resources: ["eventlisteners", "triggerbindings", "triggertemplates", "triggers", "clustertriggerbindings", "clusterinterceptors", "interceptors"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: release-automation-clusterrolebinding +subjects: +- kind: ServiceAccount + name: release-automation +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: release-automation-clusterrole diff --git a/tekton/resources/automated-release/base/serviceaccount.yaml b/tekton/resources/automated-release/base/serviceaccount.yaml new file mode 100644 index 000000000..482232cef --- /dev/null +++ b/tekton/resources/automated-release/base/serviceaccount.yaml @@ -0,0 +1,21 @@ +# 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. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: release-automation +secrets: +- name: release-secret +- name: release-images-secret +- name: github-token diff --git a/tekton/resources/automated-release/base/tasks/manage-release-tracking-issue.yaml b/tekton/resources/automated-release/base/tasks/manage-release-tracking-issue.yaml new file mode 100644 index 000000000..5030f1c54 --- /dev/null +++ b/tekton/resources/automated-release/base/tasks/manage-release-tracking-issue.yaml @@ -0,0 +1,141 @@ +# 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. +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: manage-release-tracking-issue +spec: + description: Create or update a GitHub issue to track automated release activities + params: + - name: repositoryFullName + description: "Full repository name (e.g., tektoncd/pipeline)" + type: string + - name: releaseBranch + description: "Release branch name (e.g., release-v0.50.x)" + type: string + - name: buildID + description: "Build UUID for tracking" + type: string + - name: releaseVersion + description: "Release version number (e.g., v1.10.0)" + type: string + default: "" + - name: commitSha + description: "Commit SHA being released" + type: string + workspaces: + - name: github-secret + description: GitHub token for API access + steps: + - name: check-or-create-issue + image: docker.io/alpine:3.21 + script: | + #!/bin/sh + set -e + + apk add --no-cache github-cli > /dev/null 2>&1 + + export GH_TOKEN=$(cat $(workspaces.github-secret.path)/GITHUB_TOKEN) + + REPO="$(params.repositoryFullName)" + BRANCH="$(params.releaseBranch)" + BUILD_ID="$(params.buildID)" + VERSION="$(params.releaseVersion)" + COMMIT="$(params.commitSha)" + + # Determine issue title and search term + if [ -n "${VERSION}" ]; then + ISSUE_TITLE="[Automated Release] ${VERSION}" + SEARCH_TERM="${VERSION}" + RELEASE_ID="${VERSION}" + else + ISSUE_TITLE="[Automated Release] ${BRANCH}" + SEARCH_TERM="${BRANCH}" + RELEASE_ID="${BRANCH}" + fi + + echo "Managing release tracking issue for ${REPO}/${RELEASE_ID}" + + # Search for existing open issue for this release + ISSUE_NUMBER=$(gh issue list \ + --repo "${REPO}" \ + --label "area/release" \ + --state open \ + --search "in:title [Automated Release] ${SEARCH_TERM}" \ + --json number \ + --jq '.[0].number // empty') + + if [ -z "$ISSUE_NUMBER" ]; then + echo "Creating new release tracking issue..." + + # Create issue from template using printf + TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + + # Build status lines + STATUS_VERSION="" + if [ -n "${VERSION}" ]; then + STATUS_VERSION="- **Version**: ${VERSION}" + fi + STATUS_BRANCH="- **Branch**: ${BRANCH}" + STATUS_COMMIT="- **Commit**: ${COMMIT}" + STATUS_BUILD="- **Build ID**: ${BUILD_ID}" + STATUS_TIME="- **Triggered**: ${TIMESTAMP}" + + BODY=$(printf '%s\n\n%s\n\n%s\n%s\n%s\n%s\n%s\n%s\n\n%s\n\n%s\n\n%s\n%s\n%s\n%s\n%s\n\n%s\n\n%s\n\n%s' \ + "# Automated Release Tracking: ${RELEASE_ID}" \ + "This issue tracks automated release activities for the \`${RELEASE_ID}\` release." \ + "## Current Status" \ + "${STATUS_VERSION}" \ + "${STATUS_BRANCH}" \ + "${STATUS_COMMIT}" \ + "${STATUS_BUILD}" \ + "${STATUS_TIME}" \ + "## Release Commands" \ + "You can control the release process using these slash commands:" \ + "- \`/release-status\` - Check current release pipeline status" \ + "- \`/release-cancel\` - Cancel the current release" \ + "- \`/release-restart\` - Restart the release pipeline" \ + "- \`/release-full\` - Trigger a full release (build, test, publish)" \ + "- \`/release-validate\` - Run validation checks only" \ + "**Note**: These commands require write access to the repository." \ + "## Activity Log" \ + "- Build ${BUILD_ID} started at ${TIMESTAMP}") + + ISSUE_URL=$(gh issue create \ + --repo "${REPO}" \ + --title "${ISSUE_TITLE}" \ + --label "area/release" \ + --body "${BODY}") + ISSUE_NUMBER=$(echo "${ISSUE_URL}" | grep -oE '[0-9]+$') + + echo "✓ Created issue #${ISSUE_NUMBER}" + else + echo "Found existing issue #${ISSUE_NUMBER}, adding update comment..." + + # Append update to existing issue + TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + COMMENT=$(printf '%s\n%s\n%s\n%s' \ + "**Update**: New release triggered" \ + "- **Commit**: ${COMMIT}" \ + "- **Build ID**: ${BUILD_ID}" \ + "- **Time**: ${TIMESTAMP}") + + gh issue comment "${ISSUE_NUMBER}" \ + --repo "${REPO}" \ + --body "${COMMENT}" + + echo "✓ Updated issue #${ISSUE_NUMBER}" + fi + + echo "Issue tracking: https://github.com/${REPO}/issues/${ISSUE_NUMBER}" diff --git a/tekton/resources/automated-release/base/tasks/release-chatops-router.yaml b/tekton/resources/automated-release/base/tasks/release-chatops-router.yaml new file mode 100644 index 000000000..6d04888c4 --- /dev/null +++ b/tekton/resources/automated-release/base/tasks/release-chatops-router.yaml @@ -0,0 +1,260 @@ +# 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. +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: release-chatops-router +spec: + description: Routes release ChatOps commands to appropriate handlers + params: + - name: command + description: "ChatOps command (e.g., release-status)" + type: string + - name: issueNumber + description: "GitHub issue number" + type: string + - name: repositoryFullName + description: "Full repository name" + type: string + - name: commentAuthor + description: "GitHub user who issued the command" + type: string + - name: issueTitle + description: "Issue title (contains branch/version)" + type: string + - name: releaseNamespace + description: "Namespace where releases run" + type: string + default: "automated-releases" + - name: eventListenerURL + description: "EventListener URL for triggering releases" + type: string + default: "http://el-tekton-releases.automated-releases.svc.cluster.local:8080" + workspaces: + - name: github-secret + description: GitHub token for API access + steps: + - name: extract-release-info + image: docker.io/alpine:3.19 + script: | + #!/bin/sh + set -e + + ISSUE_TITLE="$(params.issueTitle)" + REPO="$(params.repositoryFullName)" + + echo "Extracting release info from: ${ISSUE_TITLE}" + + # Extract release branch from issue title + # Title formats: + # "[Automated Release] release-v0.50.x" + # "[Automated Release] v0.50.1" + BRANCH=$(echo "${ISSUE_TITLE}" | grep -oE 'release-v[0-9]+\.[0-9]+\.x' || true) + VERSION=$(echo "${ISSUE_TITLE}" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true) + + if [ -z "${BRANCH}" ] && [ -z "${VERSION}" ]; then + echo "Error: Could not extract release branch or version from title" + echo "" > $(results.releaseBranch.path) + echo "" > $(results.releaseVersion.path) + echo "" > $(results.projectName.path) + exit 0 + fi + + # If we have a version but no branch, derive branch from version + if [ -z "${BRANCH}" ] && [ -n "${VERSION}" ]; then + # v0.50.1 -> release-v0.50.x + MAJOR_MINOR=$(echo "${VERSION}" | sed -E 's/v([0-9]+\.[0-9]+)\.[0-9]+/\1/') + BRANCH="release-v${MAJOR_MINOR}.x" + fi + + # Extract project name from repo (tektoncd/pipeline -> pipeline) + PROJECT=$(echo "${REPO}" | cut -d/ -f2) + + echo "Branch: ${BRANCH}" + echo "Version: ${VERSION}" + echo "Project: ${PROJECT}" + + echo -n "${BRANCH}" > $(results.releaseBranch.path) + echo -n "${VERSION}" > $(results.releaseVersion.path) + echo -n "${PROJECT}" > $(results.projectName.path) + - name: route-command + image: docker.io/rancher/kubectl:v1.31.3 + env: + - name: GH_TOKEN + valueFrom: + secretKeyRef: + name: github-token + key: GITHUB_TOKEN + script: | + #!/bin/bash + set -e + + COMMAND="$(params.command)" + ISSUE_NUM="$(params.issueNumber)" + REPO="$(params.repositoryFullName)" + AUTHOR="$(params.commentAuthor)" + NAMESPACE="$(params.releaseNamespace)" + EL_URL="$(params.eventListenerURL)" + + BRANCH=$(cat $(results.releaseBranch.path)) + VERSION=$(cat $(results.releaseVersion.path)) + PROJECT=$(cat $(results.projectName.path)) + + echo "Processing command: ${COMMAND}" + echo "Issue: ${REPO}#${ISSUE_NUM}" + echo "Author: ${AUTHOR}" + echo "Branch: ${BRANCH}" + echo "Version: ${VERSION}" + echo "Project: ${PROJECT}" + + # Helper function to post GitHub comment + post_comment() { + local body="$1" + curl -s -X POST \ + -H "Authorization: token ${GH_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${REPO}/issues/${ISSUE_NUM}/comments" \ + -d "{\"body\": \"${body}\"}" + } + + # Helper function to build release payload + build_payload() { + local mode="$1" + local uuid="$2" + printf '{"buildUUID":"%s","params":{"release":{"gitRepository":"https://github.com/%s","releaseBranch":"%s","projectName":"%s","repositoryFullName":"%s","releaseMode":"%s","releaseType":"bugfix","releaseVersion":"%s","commitSha":"HEAD"}}}' \ + "${uuid}" "${REPO}" "${BRANCH}" "${PROJECT}" "${REPO}" "${mode}" "${VERSION}" + } + + # Helper function to trigger release + trigger_release() { + local mode="$1" + local uuid="$2" + local payload=$(build_payload "${mode}" "${uuid}") + + curl -s -w "\n%{http_code}" -X POST \ + -H "Content-Type: application/json" \ + -d "${payload}" \ + "${EL_URL}" + } + + if [ -z "${BRANCH}" ]; then + echo "Error: Could not determine release branch" + post_comment ":x: Error: Could not determine release branch from issue title" + exit 1 + fi + + # Route to appropriate handler + case "${COMMAND}" in + release-status) + echo "Checking release status..." + + # Query PipelineRuns with matching labels + PIPELINERUNS=$(kubectl get pipelinerun -n "${NAMESPACE}" \ + -l "release.tekton.dev/branch=${BRANCH}" \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.conditions[0].reason}{"\t"}{.status.conditions[0].message}{"\n"}{end}' 2>/dev/null || echo "") + + if [ -z "${PIPELINERUNS}" ]; then + post_comment ":information_source: **Release Status for \`${BRANCH}\`**\\n\\nNo PipelineRuns found for this release branch." + else + # Format status table + STATUS_TABLE=":information_source: **Release Status for \\\`${BRANCH}\\\`**\\n\\n| PipelineRun | Status | Message |\\n|-------------|--------|---------|" + while IFS=$'\t' read -r name reason message; do + if [ -n "${name}" ]; then + # Truncate message if too long + short_msg=$(echo "${message}" | head -c 50) + STATUS_TABLE="${STATUS_TABLE}\\n| \\\`${name}\\\` | ${reason} | ${short_msg} |" + fi + done <<< "${PIPELINERUNS}" + post_comment "${STATUS_TABLE}" + fi + ;; + + release-cancel) + echo "Cancelling release..." + + # Find running PipelineRuns + RUNNING=$(kubectl get pipelinerun -n "${NAMESPACE}" \ + -l "release.tekton.dev/branch=${BRANCH}" \ + --field-selector=status.conditions[0].reason=Running \ + -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "") + + if [ -z "${RUNNING}" ]; then + post_comment ":warning: No running PipelineRun found for \`${BRANCH}\` to cancel." + else + # Cancel by patching the spec + kubectl patch pipelinerun "${RUNNING}" -n "${NAMESPACE}" \ + --type=json \ + -p='[{"op": "add", "path": "/spec/status", "value": "Cancelled"}]' + + post_comment ":stop_sign: Cancelled PipelineRun \`${RUNNING}\` for \`${BRANCH}\`.\\n\\nRequested by @${AUTHOR}" + fi + ;; + + release-restart) + echo "Restarting release (validation-only)..." + + BUILD_UUID=$(cat /proc/sys/kernel/random/uuid) + RESPONSE=$(trigger_release "validation-only" "${BUILD_UUID}") + HTTP_CODE=$(echo "${RESPONSE}" | tail -1) + + if [ "${HTTP_CODE}" = "202" ] || [ "${HTTP_CODE}" = "201" ]; then + post_comment ":arrows_counterclockwise: Restarted release validation for \`${BRANCH}\`.\\n\\n- **Build ID**: \`${BUILD_UUID}\`\\n- **Mode**: validation-only\\n- **Requested by**: @${AUTHOR}" + else + post_comment ":x: Failed to trigger release restart (HTTP ${HTTP_CODE})" + fi + ;; + + release-full) + echo "Triggering full release..." + + BUILD_UUID=$(cat /proc/sys/kernel/random/uuid) + RESPONSE=$(trigger_release "full-release" "${BUILD_UUID}") + HTTP_CODE=$(echo "${RESPONSE}" | tail -1) + + if [ "${HTTP_CODE}" = "202" ] || [ "${HTTP_CODE}" = "201" ]; then + post_comment ":rocket: Triggered **full release** for \`${BRANCH}\`.\\n\\n- **Build ID**: \`${BUILD_UUID}\`\\n- **Mode**: full-release\\n- **Version**: ${VERSION}\\n- **Requested by**: @${AUTHOR}\\n\\n:warning: This will build, test, and publish release artifacts." + else + post_comment ":x: Failed to trigger full release (HTTP ${HTTP_CODE})" + fi + ;; + + release-validate) + echo "Triggering validation..." + + BUILD_UUID=$(cat /proc/sys/kernel/random/uuid) + RESPONSE=$(trigger_release "validation-only" "${BUILD_UUID}") + HTTP_CODE=$(echo "${RESPONSE}" | tail -1) + + if [ "${HTTP_CODE}" = "202" ] || [ "${HTTP_CODE}" = "201" ]; then + post_comment ":mag: Triggered **validation** for \`${BRANCH}\`.\\n\\n- **Build ID**: \`${BUILD_UUID}\`\\n- **Mode**: validation-only\\n- **Requested by**: @${AUTHOR}\\n\\nThis will run tests without publishing artifacts." + else + post_comment ":x: Failed to trigger validation (HTTP ${HTTP_CODE})" + fi + ;; + + *) + echo "Unknown command: ${COMMAND}" + post_comment ":x: Unknown command: \`/${COMMAND}\`\\n\\nAvailable commands:\\n- \`/release-status\` - Check pipeline status\\n- \`/release-cancel\` - Cancel running release\\n- \`/release-restart\` - Restart validation\\n- \`/release-full\` - Trigger full release\\n- \`/release-validate\` - Run validation only" + exit 1 + ;; + esac + + echo "Command processed successfully" + results: + - name: releaseBranch + description: Extracted release branch + - name: releaseVersion + description: Extracted release version + - name: projectName + description: Extracted project name diff --git a/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml b/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml new file mode 100644 index 000000000..fb8e6412b --- /dev/null +++ b/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml @@ -0,0 +1,155 @@ +# 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. +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: scan-release-branches +spec: + description: >- + Scans tektoncd repositories for release branches with new commits + since the last release tag. Only triggers a bugfix release when + the branch HEAD differs from the tagged commit. + params: + - name: repositories + description: Space-separated list of repositories to scan + type: string + default: "tektoncd/pipeline" + - name: eventListenerURL + description: EventListener URL for triggering releases + type: string + default: "http://el-tekton-releases.automated-releases.svc.cluster.local:8080" + - name: minVersion + description: >- + Minimum major.minor version to consider (e.g., "0.68"). + Branches below this version are skipped. Set to "0.0" to + process all branches. + type: string + default: "0.0" + steps: + - name: scan-and-trigger + image: docker.io/alpine/git:latest + script: | + #!/bin/sh + set -e + + # Install curl + apk add --no-cache curl + + REPOS="$(params.repositories)" + EL_URL="$(params.eventListenerURL)" + MIN_VERSION="$(params.minVersion)" + + # Convert major.minor to a comparable integer (major * 1000 + minor) + version_to_int() { + echo "$1" | awk -F. '{ printf "%d", $1 * 1000 + $2 }' + } + MIN_VERSION_INT=$(version_to_int "${MIN_VERSION}") + + echo "Scanning repositories: ${REPOS}" + echo "EventListener URL: ${EL_URL}" + echo "Minimum version: ${MIN_VERSION}" + echo "" + + for REPO in ${REPOS}; do + echo "=== Scanning ${REPO} ===" + + PROJECT=$(echo ${REPO} | cut -d/ -f2) + + # Fetch all refs (branches + tags) in one call + ALL_REFS=$(git ls-remote --heads --tags --refs https://github.com/${REPO} 2>/dev/null) + + # Get all release-vX.Y.x branches + echo "${ALL_REFS}" | grep -E 'refs/heads/release-v[0-9]+\.[0-9]+\.x$' | \ + while read BRANCH_SHA REF; do + BRANCH=$(echo ${REF} | sed 's|refs/heads/||') + + # Extract major.minor version from branch (release-v1.10.x -> 1.10) + VERSION_PREFIX=$(echo ${BRANCH} | sed -E 's/release-v([0-9]+\.[0-9]+)\.x/\1/') + + # Check minimum version + BRANCH_VERSION_INT=$(version_to_int "${VERSION_PREFIX}") + if [ "${BRANCH_VERSION_INT}" -lt "${MIN_VERSION_INT}" ]; then + echo " ${BRANCH}: below minimum version ${MIN_VERSION}, skipping" + continue + fi + + # Find the latest tag for this version series + LATEST_TAG=$(echo "${ALL_REFS}" | \ + grep -E "refs/tags/v${VERSION_PREFIX}\.[0-9]+$" | \ + sed 's|.*/v||' | \ + sort -V | \ + tail -1 || echo "") + + if [ -z "${LATEST_TAG}" ]; then + # No tag exists yet — this branch has never been released. + # The initial release is handled by the webhook trigger on + # branch creation, not by the cron scanner. + echo " ${BRANCH}: no tags yet, skipping (initial release via webhook)" + continue + fi + + # Get the commit SHA of the latest tag + TAG_SHA=$(echo "${ALL_REFS}" | grep "refs/tags/v${LATEST_TAG}$" | awk '{print $1}') + + if [ "${BRANCH_SHA}" = "${TAG_SHA}" ]; then + echo " ${BRANCH}: HEAD matches v${LATEST_TAG}, no new commits" + continue + fi + + # New commits exist — compute next version + PATCH=$(echo ${LATEST_TAG} | cut -d. -f3) + NEXT_PATCH=$((PATCH + 1)) + NEXT_VERSION="v${VERSION_PREFIX}.${NEXT_PATCH}" + + echo " ${BRANCH}: new commits since v${LATEST_TAG} → ${NEXT_VERSION}" + + # Generate UUID for build + BUILD_UUID=$(cat /proc/sys/kernel/random/uuid) + + # Trigger EventListener + PAYLOAD=$(cat </dev/null || echo "000") + + HTTP_CODE=$(echo "${RESPONSE}" | tail -1) + + if [ "${HTTP_CODE}" = "202" ] || [ "${HTTP_CODE}" = "201" ]; then + echo " ✓ Triggered (Build ID: ${BUILD_UUID})" + else + echo " ✗ Failed (HTTP ${HTTP_CODE})" + fi + done + + echo "" + done + + echo "Scan complete" diff --git a/tekton/resources/automated-release/base/template.yaml b/tekton/resources/automated-release/base/template.yaml new file mode 100644 index 000000000..df70c2625 --- /dev/null +++ b/tekton/resources/automated-release/base/template.yaml @@ -0,0 +1,71 @@ +# 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 TriggerTemplate handles release tracking issues only. +# The actual release PipelineRun is created by per-project TriggerTemplates +# in overlays//, which use the git resolver to fetch the release +# pipeline from the project's own repository and branch. +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerTemplate +metadata: + name: release-tracking +spec: + params: + - name: buildID + description: Build UUID for tracking + - name: gitRepository + description: Git repository URL + - name: gitRevision + description: Git revision (branch name) + - name: releaseBranch + description: Release branch name (e.g., release-v0.50.x) + - name: projectName + description: Project name (e.g., pipeline, triggers) + - name: repositoryFullName + description: Full repository name (e.g., tektoncd/pipeline) + - name: releaseVersion + description: Release version number (e.g., v1.10.0) + default: "" + - name: triggerSource + description: Trigger source (webhook or cron) + - name: afterCommitSha + description: Commit SHA that triggered the release + resourcetemplates: + - apiVersion: tekton.dev/v1 + kind: TaskRun + metadata: + generateName: release-tracking-issue- + labels: + tekton.dev/kind: release-automation + release.tekton.dev/project: $(tt.params.projectName) + release.tekton.dev/branch: $(tt.params.releaseBranch) + release.tekton.dev/trigger: $(tt.params.triggerSource) + spec: + taskRef: + name: manage-release-tracking-issue + params: + - name: repositoryFullName + value: $(tt.params.repositoryFullName) + - name: releaseBranch + value: $(tt.params.releaseBranch) + - name: buildID + value: $(tt.params.buildID) + - name: releaseVersion + value: $(tt.params.releaseVersion) + - name: commitSha + value: $(tt.params.afterCommitSha) + workspaces: + - name: github-secret + secret: + secretName: github-token diff --git a/tekton/resources/automated-release/base/trigger-chatops.yaml b/tekton/resources/automated-release/base/trigger-chatops.yaml new file mode 100644 index 000000000..821b2443a --- /dev/null +++ b/tekton/resources/automated-release/base/trigger-chatops.yaml @@ -0,0 +1,57 @@ +# 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. + +# Trigger for ChatOps slash commands on release tracking issues. +apiVersion: triggers.tekton.dev/v1beta1 +kind: Trigger +metadata: + name: release-chatops +spec: + interceptors: + - name: "Validate GitHub payload and filter on eventType" + ref: + name: "github" + kind: ClusterInterceptor + params: + - name: "secretRef" + value: + secretName: release-webhook + secretKey: secret + - name: "eventTypes" + value: + - "issue_comment" + - name: "Filter release ChatOps commands" + ref: + name: "cel" + kind: ClusterInterceptor + params: + - name: "filter" + value: >- + body.action == 'created' && + body.issue.labels.exists(l, l.name == 'automated-release') && + body.comment.body.startsWith('/release-') + - name: "overlays" + value: + - key: command + expression: "body.comment.body.split(' ')[0].substring(1)" + - key: issue_number + expression: "string(body.issue.number)" + - key: repository_full_name + expression: "body.repository.full_name" + - key: comment_author + expression: "body.comment.user.login" + bindings: + - ref: release-chatops-command + template: + ref: release-chatops diff --git a/tekton/resources/automated-release/base/trigger-tracking-cron.yaml b/tekton/resources/automated-release/base/trigger-tracking-cron.yaml new file mode 100644 index 000000000..d98533cf9 --- /dev/null +++ b/tekton/resources/automated-release/base/trigger-tracking-cron.yaml @@ -0,0 +1,36 @@ +# 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. + +# Trigger for creating/updating release tracking issues on cron scan. +# Fires for all tektoncd projects when the cron scanner detects new commits. +apiVersion: triggers.tekton.dev/v1beta1 +kind: Trigger +metadata: + name: release-tracking-cron +spec: + interceptors: + - name: "Filter cron triggers" + ref: + name: "cel" + kind: ClusterInterceptor + params: + - name: "filter" + value: >- + 'buildUUID' in body && + 'params' in body && + 'release' in body.params + bindings: + - ref: release-branch-cron + template: + ref: release-tracking diff --git a/tekton/resources/automated-release/base/trigger-tracking-webhook.yaml b/tekton/resources/automated-release/base/trigger-tracking-webhook.yaml new file mode 100644 index 000000000..1e70e21cc --- /dev/null +++ b/tekton/resources/automated-release/base/trigger-tracking-webhook.yaml @@ -0,0 +1,57 @@ +# 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. + +# Trigger for creating/updating release tracking issues on webhook push. +# Fires for all tektoncd projects on release branch pushes. +apiVersion: triggers.tekton.dev/v1beta1 +kind: Trigger +metadata: + name: release-tracking-webhook +spec: + interceptors: + - name: "Validate GitHub payload and filter on eventType" + ref: + name: "github" + kind: ClusterInterceptor + params: + - name: "secretRef" + value: + secretName: release-webhook + secretKey: secret + - name: "eventTypes" + value: + - "push" + - name: "Filter release branches" + ref: + name: "cel" + kind: ClusterInterceptor + params: + - name: "filter" + value: >- + body.repository.full_name.startsWith('tektoncd/') && + body.ref.matches('refs/heads/release-v[0-9]+\\.[0-9]+\\.x') + - name: "overlays" + value: + - key: release_branch + expression: "body.ref.split('/')[2]" + - key: trigger_source + expression: "'webhook'" + - name: "Add a build ID" + ref: + name: "build-id" + kind: ClusterInterceptor + bindings: + - ref: release-branch-webhook + template: + ref: release-tracking diff --git a/tekton/resources/automated-release/overlays/.gitignore b/tekton/resources/automated-release/overlays/.gitignore new file mode 100644 index 000000000..20cdc276a --- /dev/null +++ b/tekton/resources/automated-release/overlays/.gitignore @@ -0,0 +1,2 @@ +# Ignore actual secrets (keep templates only) +secrets.yaml diff --git a/tekton/resources/automated-release/overlays/README.md b/tekton/resources/automated-release/overlays/README.md new file mode 100644 index 000000000..ba847525d --- /dev/null +++ b/tekton/resources/automated-release/overlays/README.md @@ -0,0 +1,96 @@ +# Automated Release System - Overlays + +This directory contains kustomize overlays for per-project and per-environment configuration. + +## Architecture + +The automated release system uses per-project overlays following the same +pattern as the nightly-release system. Each project gets its own: + +- **TriggerTemplate** — defines the PipelineRun spec with the git resolver + pointing to the project's release pipeline in its own repository/branch +- **Trigger (webhook)** — filters GitHub push events for the specific project +- **Trigger (cron)** — filters cron scanner events for the specific project + +The **EventListener** uses `namespaceSelector` to auto-discover all Triggers +in the namespace. Shared resources (bindings, tracking issue triggers, tasks, +ChatOps) live in `base/`. + +## Per-Project Overlays + +### pipeline/ +Release automation for `tektoncd/pipeline`. +- Pipeline: `tekton/release-pipeline.yaml` (fetched via git resolver) +- Registry: ghcr.io/tektoncd/pipeline +- Status: **Active** + +### triggers/ (planned) +Release automation for `tektoncd/triggers`. +- Pipeline: `tekton/release-pipeline.yaml` + +### chains/ (planned) +Release automation for `tektoncd/chains`. +- Pipeline: `release/release-pipeline.yaml` (note: `release/` dir, not `tekton/`) + +### operator/ (planned) +Release automation for `tektoncd/operator`. +- Pipeline: `tekton/operator-release-pipeline.yaml` (different filename) +- Extra params: `kubeDistros`, `components` + +### dashboard/ (planned) +Release automation for `tektoncd/dashboard`. +- Pipeline: `tekton/release-pipeline.yaml` +- Note: Uses gcr.io default, own prerelease-checks, extra build task + +## Environment Overlays + +### dogfooding/ +Production deployment to Oracle dogfooding cluster. +Includes `base/` and all active per-project overlays. + +**Deploy**: +```bash +kubectl apply -k tekton/resources/automated-release/overlays/dogfooding +kubectl apply -k tekton/cronjobs/dogfooding/automated-release +``` + +## Adding a New Project + +To add support for a new tektoncd project: + +1. Create the overlay directory: + ```bash + mkdir tekton/resources/automated-release/overlays/ + ``` + +2. Create the three files (use `pipeline/` as a template): + - `kustomization.yaml` — references the three resource files + - `release-template.yaml` — TriggerTemplate with git resolver + project-specific params + - `trigger-webhook.yaml` — Trigger filtering on project name for webhooks + - `trigger-cron.yaml` — Trigger filtering on project name for cron + +3. Key things to customize per project: + - `pipelineRef` git resolver: URL, pathInRepo + - PipelineRun params: package, repoName, imageRegistryPath, releaseBucket + - Secret names (if different) + +4. Add the overlay to `dogfooding/kustomization.yaml`: + ```yaml + resources: + - ../../base + - ../pipeline + - ../ # Add here + ``` + +5. Add the repository to the scan-release-branches CronJob: + ```yaml + env: + - name: REPOSITORIES + value: "tektoncd/pipeline tektoncd/" + ``` + +## Related + +- [Base resources](../base/) — Shared Tekton resources +- [Nightly release overlays](../../nightly-release/overlays/) — Similar pattern +- [Issue #58](https://github.com/tektoncd/plumbing/issues/58) — Feature request diff --git a/tekton/resources/automated-release/overlays/dogfooding/README.md b/tekton/resources/automated-release/overlays/dogfooding/README.md new file mode 100644 index 000000000..c6387e443 --- /dev/null +++ b/tekton/resources/automated-release/overlays/dogfooding/README.md @@ -0,0 +1,125 @@ +# Dogfooding Environment Deployment + +Deploy the automated release system to the Oracle dogfooding cluster. + +## Prerequisites + +1. **Access to dogfooding cluster**: + ```bash + kubectl config use-context dogfooding-cluster + ``` + +2. **Namespace** (likely already exists): + ```bash + kubectl get namespace tekton-releases + ``` + +3. **Secrets** (should already exist in dogfooding cluster): + - `github-token` - GitHub API access for issue management + - `release-secret` - GCS service account for releases + - `release-images-secret` - Container registry credentials + + Verify: + ```bash + kubectl get secret -n tekton-releases github-token + kubectl get secret -n tekton-releases release-secret + kubectl get secret -n tekton-releases release-images-secret + ``` + +## Deploy + +```bash +# From repository root +kubectl apply -k tekton/resources/automated-release/overlays/dogfooding +kubectl apply -k tekton/cronjobs/dogfooding/automated-release +``` + +## Verify Deployment + +```bash +# Check EventListener +kubectl get eventlistener -n tekton-releases +kubectl describe el tekton-releases -n tekton-releases + +# Check CronJob +kubectl get cronjob -n tekton-releases scan-release-branches-dogfooding + +# Check next scheduled run +kubectl get cronjob scan-release-branches-dogfooding -n tekton-releases -o jsonpath='{.status.lastScheduleTime}' + +# Get EventListener service +kubectl get svc -n tekton-releases el-tekton-releases +``` + +## Configure GitHub Webhook + +1. Go to https://github.com/tektoncd/pipeline/settings/hooks +2. Add webhook: + - **Payload URL**: `http://el-tekton-releases.tekton-releases.svc.cluster.local:8080` (or public ingress URL) + - **Content type**: `application/json` + - **Events**: Just the `push` event + - **Active**: ✓ + +3. Test delivery with a test push to a release branch + +## Monitor + +```bash +# Watch for triggered PipelineRuns +kubectl get pipelinerun -n tekton-releases --watch + +# Check CronJob history +kubectl get jobs -n tekton-releases -l app.kubernetes.io/component=cron-scanner + +# View CronJob logs +kubectl logs -n tekton-releases \ + -l app.kubernetes.io/component=cron-scanner \ + -c trigger-releases --tail=100 +``` + +## Troubleshooting + +### EventListener not receiving webhooks + +```bash +# Check EventListener pod +kubectl get pods -n tekton-releases -l eventlistener=tekton-releases + +# View EventListener logs +kubectl logs -n tekton-releases -l eventlistener=tekton-releases +``` + +### CronJob not running + +```bash +# Check if suspended +kubectl get cronjob scan-release-branches-dogfooding -n tekton-releases -o jsonpath='{.spec.suspend}' + +# Manually trigger for testing +kubectl create job --from=cronjob/scan-release-branches-dogfooding manual-test-001 -n tekton-releases +``` + +### Issue creation failing + +```bash +# Check github-token secret +kubectl get secret github-token -n tekton-releases -o jsonpath='{.data.token}' | base64 -d + +# Check TaskRun logs +kubectl logs -n tekton-releases -l tekton.dev/task=manage-release-tracking-issue +``` + +## Expanding to More Repositories + +Edit the cronjob patch to add more repositories: + +```yaml +# tekton/cronjobs/dogfooding/automated-release/cronjob-patch.yaml +- name: REPOSITORIES + value: "tektoncd/pipeline tektoncd/triggers tektoncd/operator" +``` + +Then reapply: +```bash +kubectl apply -k tekton/cronjobs/dogfooding/automated-release +``` diff --git a/tekton/resources/automated-release/overlays/dogfooding/kustomization.yaml b/tekton/resources/automated-release/overlays/dogfooding/kustomization.yaml new file mode 100644 index 000000000..5356644f1 --- /dev/null +++ b/tekton/resources/automated-release/overlays/dogfooding/kustomization.yaml @@ -0,0 +1,24 @@ +# Dogfooding environment overlay for automated-release system. +# Deploy to Oracle dogfooding cluster. +# Includes the base (shared resources) and all per-project overlays. +namespace: automated-releases + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- ../../base +# Per-project release triggers and templates +- ../pipeline +# Future: +# - ../triggers +# - ../chains +# - ../operator +# - ../dashboard + +labels: +- pairs: + environment: dogfooding + +commonAnnotations: + managed-by: Tekton diff --git a/tekton/resources/automated-release/overlays/pipeline/kustomization.yaml b/tekton/resources/automated-release/overlays/pipeline/kustomization.yaml new file mode 100644 index 000000000..9462b3ba5 --- /dev/null +++ b/tekton/resources/automated-release/overlays/pipeline/kustomization.yaml @@ -0,0 +1,24 @@ +# 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. +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +# Project-specific release resources for tektoncd/pipeline. +# These are included by the top-level kustomization alongside the base. +resources: +- release-template.yaml +- trigger-webhook.yaml +- trigger-cron.yaml + +namespace: automated-releases diff --git a/tekton/resources/automated-release/overlays/pipeline/release-template.yaml b/tekton/resources/automated-release/overlays/pipeline/release-template.yaml new file mode 100644 index 000000000..50baf1104 --- /dev/null +++ b/tekton/resources/automated-release/overlays/pipeline/release-template.yaml @@ -0,0 +1,105 @@ +# 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. + +# TriggerTemplate for tektoncd/pipeline releases. +# Uses the git resolver to fetch the release pipeline directly from +# the project's release branch — no pre-installation needed. +apiVersion: triggers.tekton.dev/v1beta1 +kind: TriggerTemplate +metadata: + name: pipeline-release-automation +spec: + params: + - name: buildID + description: Build UUID for tracking + - name: gitRepository + description: Git repository URL (e.g., https://github.com/tektoncd/pipeline) + - name: gitRevision + description: Git revision (commit SHA) + - name: releaseBranch + description: Release branch name (e.g., release-v1.9.x) + - name: projectName + description: Project name (e.g., pipeline) + - name: repositoryFullName + description: Full repository name (e.g., tektoncd/pipeline) + - name: releaseVersion + description: Release version number (e.g., v1.9.1) + default: "" + - name: triggerSource + description: Trigger source (webhook or cron) + - name: afterCommitSha + description: Commit SHA that triggered the release + resourcetemplates: + - apiVersion: tekton.dev/v1 + kind: PipelineRun + metadata: + generateName: pipeline-release- + labels: + tekton.dev/kind: release + release.tekton.dev/project: pipeline + release.tekton.dev/branch: $(tt.params.releaseBranch) + release.tekton.dev/trigger: $(tt.params.triggerSource) + spec: + # Fetch the release pipeline from the project's own release branch. + # This ensures we always use the pipeline definition that matches + # the branch being released, including its publish task, precheck, etc. + pipelineRef: + resolver: git + params: + - name: url + value: https://github.com/tektoncd/pipeline + - name: revision + value: $(tt.params.releaseBranch) + - name: pathInRepo + value: tekton/release-pipeline.yaml + params: + - name: package + value: github.com/tektoncd/pipeline + - name: repoName + value: pipeline + - name: gitRevision + value: $(tt.params.afterCommitSha) + - name: imageRegistry + value: ghcr.io + - name: imageRegistryPath + value: tektoncd/pipeline + - name: imageRegistryRegions + value: "" + - name: imageRegistryUser + value: tekton-robot + - name: versionTag + value: $(tt.params.releaseVersion) + - name: releaseBucket + value: tekton-releases + - name: serviceAccountImagesPath + value: credentials + - name: koExtraArgs + value: "" + timeouts: + pipeline: 3h0m0s + workspaces: + - name: workarea + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + - name: release-secret + secret: + secretName: oci-release-secret + - name: release-images-secret + secret: + secretName: ghcr-creds diff --git a/tekton/resources/automated-release/overlays/pipeline/trigger-cron.yaml b/tekton/resources/automated-release/overlays/pipeline/trigger-cron.yaml new file mode 100644 index 000000000..6ac7f3135 --- /dev/null +++ b/tekton/resources/automated-release/overlays/pipeline/trigger-cron.yaml @@ -0,0 +1,40 @@ +# 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. + +# Trigger for tektoncd/pipeline cron-based releases. +# Fires when the scan-release-branches task detects new commits +# on pipeline release branches. +apiVersion: triggers.tekton.dev/v1beta1 +kind: Trigger +metadata: + name: pipeline-release-cron + labels: + release.tekton.dev/project: pipeline +spec: + interceptors: + - name: "Filter pipeline cron triggers" + ref: + name: "cel" + kind: ClusterInterceptor + params: + - name: "filter" + value: >- + 'buildUUID' in body && + 'params' in body && + 'release' in body.params && + body.params.release.projectName == 'pipeline' + bindings: + - ref: release-branch-cron + template: + ref: pipeline-release-automation diff --git a/tekton/resources/automated-release/overlays/pipeline/trigger-webhook.yaml b/tekton/resources/automated-release/overlays/pipeline/trigger-webhook.yaml new file mode 100644 index 000000000..1be6af323 --- /dev/null +++ b/tekton/resources/automated-release/overlays/pipeline/trigger-webhook.yaml @@ -0,0 +1,60 @@ +# 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. + +# Trigger for tektoncd/pipeline webhook-based releases. +# Fires only on branch creation (initial push) of release branches. +apiVersion: triggers.tekton.dev/v1beta1 +kind: Trigger +metadata: + name: pipeline-release-webhook + labels: + release.tekton.dev/project: pipeline +spec: + interceptors: + - name: "Validate GitHub payload" + ref: + name: "github" + kind: ClusterInterceptor + params: + - name: "secretRef" + value: + secretName: release-webhook + secretKey: secret + - name: "eventTypes" + value: + - "push" + - name: "Filter pipeline release branches" + ref: + name: "cel" + kind: ClusterInterceptor + params: + - name: "filter" + value: >- + body.repository.full_name == 'tektoncd/pipeline' && + body.ref.matches('refs/heads/release-v[0-9]+\\.[0-9]+\\.x') && + body.created == true + - name: "overlays" + value: + - key: release_branch + expression: "body.ref.split('/')[2]" + - key: trigger_source + expression: "'webhook'" + - name: "Add a build ID" + ref: + name: "build-id" + kind: ClusterInterceptor + bindings: + - ref: release-branch-webhook + template: + ref: pipeline-release-automation diff --git a/tekton/resources/kustomization.yaml b/tekton/resources/kustomization.yaml index df1168ff8..8af290356 100644 --- a/tekton/resources/kustomization.yaml +++ b/tekton/resources/kustomization.yaml @@ -10,5 +10,6 @@ resources: - nightly-release - nightly-tests - branch-protection +- automated-release apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization From 4cd9bef4e2744a29631216e3324e6a8edeb01e99 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 19 Feb 2026 16:50:01 +0100 Subject: [PATCH 2/3] feat: set releaseAsLatest for patch releases on latest branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Query GitHub releases API during scan to determine which release branch corresponds to the current latest release. Patch releases on that branch get releaseAsLatest=true so the bucket latest/ path is updated. - Scan task: detect latest release, compare major.minor with each branch - Add github-secret workspace to scan task for API access - Pass releaseAsLatest through binding → template → PipelineRun param Signed-off-by: Vincent Demeester --- .../scan-release-branches.yaml | 4 +++ .../automated-release/base/bindings.yaml | 2 ++ .../base/tasks/scan-release-branches.yaml | 32 +++++++++++++++++-- .../automated-release/base/template.yaml | 3 ++ .../overlays/pipeline/release-template.yaml | 5 +++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/tekton/cronjobs/bases/automated-release/scan-release-branches.yaml b/tekton/cronjobs/bases/automated-release/scan-release-branches.yaml index 573a96b89..da15048d1 100644 --- a/tekton/cronjobs/bases/automated-release/scan-release-branches.yaml +++ b/tekton/cronjobs/bases/automated-release/scan-release-branches.yaml @@ -48,6 +48,10 @@ spec: value: "${EVENTLISTENER_URL}" - name: minVersion value: "${MIN_VERSION}" + workspaces: + - name: github-secret + secret: + secretName: github-token EOF env: - name: REPOSITORIES diff --git a/tekton/resources/automated-release/base/bindings.yaml b/tekton/resources/automated-release/base/bindings.yaml index 5a6753c0e..8b2b08059 100644 --- a/tekton/resources/automated-release/base/bindings.yaml +++ b/tekton/resources/automated-release/base/bindings.yaml @@ -54,6 +54,8 @@ spec: value: $(body.params.release.repositoryFullName) - name: releaseVersion value: $(body.params.release.releaseVersion) + - name: releaseAsLatest + value: $(body.params.release.releaseAsLatest) - name: triggerSource value: 'cron' - name: afterCommitSha diff --git a/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml b/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml index fb8e6412b..969080747 100644 --- a/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml +++ b/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml @@ -36,6 +36,10 @@ spec: process all branches. type: string default: "0.0" + workspaces: + - name: github-secret + description: Secret containing GITHUB_TOKEN for querying latest release + optional: true steps: - name: scan-and-trigger image: docker.io/alpine/git:latest @@ -43,13 +47,18 @@ spec: #!/bin/sh set -e - # Install curl - apk add --no-cache curl + # Install curl and GitHub CLI + apk add --no-cache curl github-cli > /dev/null 2>&1 REPOS="$(params.repositories)" EL_URL="$(params.eventListenerURL)" MIN_VERSION="$(params.minVersion)" + # Read GitHub token from workspace if available + if [ -f $(workspaces.github-secret.path)/GITHUB_TOKEN ]; then + export GH_TOKEN=$(cat $(workspaces.github-secret.path)/GITHUB_TOKEN) + fi + # Convert major.minor to a comparable integer (major * 1000 + minor) version_to_int() { echo "$1" | awk -F. '{ printf "%d", $1 * 1000 + $2 }' @@ -66,6 +75,17 @@ spec: PROJECT=$(echo ${REPO} | cut -d/ -f2) + # Determine the latest release version to set releaseAsLatest correctly. + # Patch releases on the latest branch should update "latest" in the bucket. + LATEST_RELEASE_MINOR="" + if [ -n "${GH_TOKEN:-}" ]; then + LATEST_RELEASE_TAG=$(gh release list --repo "${REPO}" --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "") + if [ -n "${LATEST_RELEASE_TAG}" ]; then + LATEST_RELEASE_MINOR=$(echo "${LATEST_RELEASE_TAG}" | sed -E 's/^v([0-9]+\.[0-9]+)\..*/\1/') + echo " Latest release: ${LATEST_RELEASE_TAG} (minor: ${LATEST_RELEASE_MINOR})" + fi + fi + # Fetch all refs (branches + tags) in one call ALL_REFS=$(git ls-remote --heads --tags --refs https://github.com/${REPO} 2>/dev/null) @@ -114,6 +134,13 @@ spec: echo " ${BRANCH}: new commits since v${LATEST_TAG} → ${NEXT_VERSION}" + # Determine if this branch is the latest release branch + RELEASE_AS_LATEST="false" + if [ "${VERSION_PREFIX}" = "${LATEST_RELEASE_MINOR}" ]; then + RELEASE_AS_LATEST="true" + echo " → will update latest (matches current latest release)" + fi + # Generate UUID for build BUILD_UUID=$(cat /proc/sys/kernel/random/uuid) @@ -128,6 +155,7 @@ spec: "projectName": "${PROJECT}", "repositoryFullName": "${REPO}", "releaseVersion": "${NEXT_VERSION}", + "releaseAsLatest": "${RELEASE_AS_LATEST}", "commitSha": "${BRANCH_SHA}" } } diff --git a/tekton/resources/automated-release/base/template.yaml b/tekton/resources/automated-release/base/template.yaml index df70c2625..c526c93da 100644 --- a/tekton/resources/automated-release/base/template.yaml +++ b/tekton/resources/automated-release/base/template.yaml @@ -41,6 +41,9 @@ spec: description: Trigger source (webhook or cron) - name: afterCommitSha description: Commit SHA that triggered the release + - name: releaseAsLatest + description: Whether to publish this release as latest + default: "false" resourcetemplates: - apiVersion: tekton.dev/v1 kind: TaskRun diff --git a/tekton/resources/automated-release/overlays/pipeline/release-template.yaml b/tekton/resources/automated-release/overlays/pipeline/release-template.yaml index 50baf1104..42ac42640 100644 --- a/tekton/resources/automated-release/overlays/pipeline/release-template.yaml +++ b/tekton/resources/automated-release/overlays/pipeline/release-template.yaml @@ -40,6 +40,9 @@ spec: description: Trigger source (webhook or cron) - name: afterCommitSha description: Commit SHA that triggered the release + - name: releaseAsLatest + description: Whether to publish this release as latest + default: "false" resourcetemplates: - apiVersion: tekton.dev/v1 kind: PipelineRun @@ -82,6 +85,8 @@ spec: value: $(tt.params.releaseVersion) - name: releaseBucket value: tekton-releases + - name: releaseAsLatest + value: $(tt.params.releaseAsLatest) - name: serviceAccountImagesPath value: credentials - name: koExtraArgs From 4272d23a3265bbd45069fd0495af24e3d96b5d66 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 23 Feb 2026 07:47:57 +0100 Subject: [PATCH 3/3] feat: pass previousVersion and github-secret for draft release creation Add previousVersion to the scan-release-branches payload and propagate it through bindings and TriggerTemplates to the pipeline PipelineRun as the previousReleaseTag param. Also wire the github-token secret as the github-secret workspace so the release pipeline can create draft GitHub releases automatically. --- tekton/resources/automated-release/base/bindings.yaml | 2 ++ .../base/tasks/scan-release-branches.yaml | 1 + tekton/resources/automated-release/base/template.yaml | 3 +++ .../overlays/pipeline/release-template.yaml | 10 ++++++++++ 4 files changed, 16 insertions(+) diff --git a/tekton/resources/automated-release/base/bindings.yaml b/tekton/resources/automated-release/base/bindings.yaml index 8b2b08059..4fc7b67c9 100644 --- a/tekton/resources/automated-release/base/bindings.yaml +++ b/tekton/resources/automated-release/base/bindings.yaml @@ -54,6 +54,8 @@ spec: value: $(body.params.release.repositoryFullName) - name: releaseVersion value: $(body.params.release.releaseVersion) + - name: previousVersion + value: $(body.params.release.previousVersion) - name: releaseAsLatest value: $(body.params.release.releaseAsLatest) - name: triggerSource diff --git a/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml b/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml index 969080747..36925de31 100644 --- a/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml +++ b/tekton/resources/automated-release/base/tasks/scan-release-branches.yaml @@ -155,6 +155,7 @@ spec: "projectName": "${PROJECT}", "repositoryFullName": "${REPO}", "releaseVersion": "${NEXT_VERSION}", + "previousVersion": "v${LATEST_TAG}", "releaseAsLatest": "${RELEASE_AS_LATEST}", "commitSha": "${BRANCH_SHA}" } diff --git a/tekton/resources/automated-release/base/template.yaml b/tekton/resources/automated-release/base/template.yaml index c526c93da..a02ee3d68 100644 --- a/tekton/resources/automated-release/base/template.yaml +++ b/tekton/resources/automated-release/base/template.yaml @@ -37,6 +37,9 @@ spec: - name: releaseVersion description: Release version number (e.g., v1.10.0) default: "" + - name: previousVersion + description: Previous release version tag (e.g., v1.9.0) for changelog generation + default: "" - name: triggerSource description: Trigger source (webhook or cron) - name: afterCommitSha diff --git a/tekton/resources/automated-release/overlays/pipeline/release-template.yaml b/tekton/resources/automated-release/overlays/pipeline/release-template.yaml index 42ac42640..061835012 100644 --- a/tekton/resources/automated-release/overlays/pipeline/release-template.yaml +++ b/tekton/resources/automated-release/overlays/pipeline/release-template.yaml @@ -36,6 +36,9 @@ spec: - name: releaseVersion description: Release version number (e.g., v1.9.1) default: "" + - name: previousVersion + description: Previous release version tag (e.g., v1.9.0) for changelog generation + default: "" - name: triggerSource description: Trigger source (webhook or cron) - name: afterCommitSha @@ -54,6 +57,8 @@ spec: release.tekton.dev/branch: $(tt.params.releaseBranch) release.tekton.dev/trigger: $(tt.params.triggerSource) spec: + taskRunTemplate: + serviceAccountName: release-automation # Fetch the release pipeline from the project's own release branch. # This ensures we always use the pipeline definition that matches # the branch being released, including its publish task, precheck, etc. @@ -87,6 +92,8 @@ spec: value: tekton-releases - name: releaseAsLatest value: $(tt.params.releaseAsLatest) + - name: previousReleaseTag + value: $(tt.params.previousVersion) - name: serviceAccountImagesPath value: credentials - name: koExtraArgs @@ -108,3 +115,6 @@ spec: - name: release-images-secret secret: secretName: ghcr-creds + - name: github-secret + secret: + secretName: github-token