Skip to content
Open
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
254 changes: 254 additions & 0 deletions .github/workflows/release-tag-downstream.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
name: Release Tag Downstream Repos
run-name: Tag downstream repos for ${{ inputs.tag || inputs.version }}

on:
workflow_dispatch:
inputs:
version:
description: 'Downstream release version (e.g., "1.22", "1.23")'
required: true
type: string
tag:
description: 'Override tag name (leave empty to auto-derive as v{release-tag} from release config)'
required: false
type: string
default: ''
branch:
description: 'Override branch to tag (leave empty to use release-v{VERSION}.x)'
required: false
type: string
default: ''
repos:
description: |
Comma-separated repo config names to tag (leave empty for all).
Use names from config/downstream/repos/ e.g. "tektoncd-pipeline, operator, pipelines-as-code"
required: false
type: string
default: ''
dry-run:
description: 'Dry run - only print what would be tagged without creating tags'
required: false
type: boolean
default: false

concurrency:
group: tag-downstream-${{ inputs.version }}
cancel-in-progress: true

permissions:
contents: read

jobs:
tag-downstream:
runs-on: ubuntu-latest
steps:
- name: Checkout hack repo
uses: actions/checkout@v6

- name: Install yq
run: |
YQ_VERSION="v4.52.4"
YQ_SHA256="0c4d965ea944b64b8fddaf7f27779ee3034e5693263786506ccd1c120f184e8c"
wget -q "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" -O /tmp/yq
echo "$YQ_SHA256 /tmp/yq" | sha256sum --check --strict
sudo mv /tmp/yq /usr/local/bin/yq
sudo chmod +x /usr/local/bin/yq

- name: Resolve tag, branch, and target repos
id: resolve
env:
VERSION: ${{ inputs.version }}
TAG_OVERRIDE: ${{ inputs.tag }}
BRANCH_OVERRIDE: ${{ inputs.branch }}
TARGET_REPOS: ${{ inputs.repos }}
run: |
set -euo pipefail

REPO_DIR="config/downstream/repos"
RELEASE_YAML="config/downstream/releases/${VERSION}.yaml"

if [[ ! -f "$RELEASE_YAML" ]]; then
echo "ERROR: Release config not found: $RELEASE_YAML"
exit 1
fi

# Determine tag: manual override or auto-derive from release config
if [[ -n "$TAG_OVERRIDE" ]]; then
TAG="$TAG_OVERRIDE"
else
RELEASE_TAG=$(yq '.release-tag' "$RELEASE_YAML")
if [[ -z "$RELEASE_TAG" || "$RELEASE_TAG" == "null" ]]; then
echo "ERROR: No release-tag found in $RELEASE_YAML"
exit 1
fi
TAG="v${RELEASE_TAG}"
fi

# Determine default branch pattern
if [[ -n "$BRANCH_OVERRIDE" ]]; then
BRANCH="$BRANCH_OVERRIDE"
else
BRANCH="release-v${VERSION}.x"
fi

echo "Tag: $TAG"
echo "Branch: $BRANCH"

# Parse target repos filter (comma-separated)
declare -A TARGET_FILTER
if [[ -n "$TARGET_REPOS" ]]; then
IFS=',' read -ra FILTER_LIST <<< "$TARGET_REPOS"
for item in "${FILTER_LIST[@]}"; do
trimmed=$(echo "$item" | xargs)
TARGET_FILTER[$trimmed]=1
done
echo "Filtering to repos: ${!TARGET_FILTER[*]}"
fi

# Resolve unique repo names (multiple configs can map to the same repo, e.g. operator)
NAMES=$(yq '.branches | keys | .[]' "$RELEASE_YAML")
declare -A SEEN_REPOS
UNIQUE_REPOS=()
for NAME in $NAMES; do
REPO_CONFIG="$REPO_DIR/${NAME}.yaml"
if [[ ! -f "$REPO_CONFIG" ]]; then
echo "WARN: config file $REPO_CONFIG not found, skipping $NAME"
continue
fi
REPO=$(yq '.repo // .name' "$REPO_CONFIG")
if [[ -z "${SEEN_REPOS[$REPO]:-}" ]]; then
SEEN_REPOS[$REPO]=1
if [[ ${#TARGET_FILTER[@]} -gt 0 ]] && [[ -z "${TARGET_FILTER[$NAME]:-}" ]] && [[ -z "${TARGET_FILTER[$REPO]:-}" ]]; then
continue
fi
UNIQUE_REPOS+=("$REPO")
fi
done

echo "Resolved ${#UNIQUE_REPOS[@]} unique repos to tag"

# Pass outputs to next step
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
{
echo "repos<<EOF"
printf '%s\n' "${UNIQUE_REPOS[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Create tags on downstream repos
id: create-tags
env:
GH_TOKEN: ${{ secrets.OPENSHIFT_PIPELINES_ROBOT }}
TAG: ${{ steps.resolve.outputs.tag }}
BRANCH: ${{ steps.resolve.outputs.branch }}
REPOS: ${{ steps.resolve.outputs.repos }}
DRY_RUN: ${{ inputs.dry-run }}
run: |
set -euo pipefail

ORG="openshift-pipelines"

echo "Tag: $TAG"
echo "Branch: $BRANCH"
echo "Dry run: $DRY_RUN"
echo "---"

TOTAL=0
OK=0
SKIPPED=0
FAILED=0
RESULTS=""

while IFS= read -r REPO; do
[[ -z "$REPO" ]] && continue
TOTAL=$((TOTAL + 1))

FULL_REPO="$ORG/$REPO"

# Check if branch exists
if ! gh api "repos/$FULL_REPO/branches/$BRANCH" --silent 2>/dev/null; then
echo "SKIP $REPO: branch $BRANCH not found"
RESULTS="${RESULTS}| ${REPO} | ${BRANCH} | ${TAG} | Skipped (no branch) |\n"
SKIPPED=$((SKIPPED + 1))
continue
fi

# Check if tag already exists
if gh api "repos/$FULL_REPO/git/ref/tags/$TAG" --silent 2>/dev/null; then
echo "SKIP $REPO: tag $TAG already exists"
RESULTS="${RESULTS}| ${REPO} | ${BRANCH} | ${TAG} | Skipped (tag exists) |\n"
SKIPPED=$((SKIPPED + 1))
continue
fi

if [[ "$DRY_RUN" == "true" ]]; then
echo "DRY-RUN: Would tag $FULL_REPO@$BRANCH as $TAG"
RESULTS="${RESULTS}| [${REPO}](https://github.com/${FULL_REPO}) | ${BRANCH} | ${TAG} | Dry-run |\n"
OK=$((OK + 1))
continue
fi

# Get the SHA of the branch HEAD
SHA=$(gh api "repos/$FULL_REPO/git/ref/heads/$BRANCH" --jq '.object.sha' 2>/dev/null)

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.

should be reading the commit sha from released snapshot not from the head of the repo.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So in that case, we will need to provide the released snapshot and we should have the logic to find the commit sha of the individual components?

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.

Agree with Pramod's point, user should be able to derive that using the snapshot. I have a script for doing this if you need to reference or plan how to integrate it.

Snapshot: openshift-pipelines-core-1-22-20260630-164711-000-pi
Created:  2026-06-30 22:26 IST

Repo                                               Revision       Components
--------------------------------------------------------------------------------
openshift-pipelines/operator                       a8570514dd8f   3
openshift-pipelines/p12n-console-plugin            11c1ea728bb0   1
openshift-pipelines/p12n-console-plugin-pf5        5f871b8eb87f   1
openshift-pipelines/p12n-manual-approval-gate      f25d0d7642f0   2
openshift-pipelines/p12n-multicluster-proxy-aae    86475aae6abe   1
openshift-pipelines/p12n-opc                       75c5770be7ad   1
openshift-pipelines/p12n-syncer-service            b029f0528ca8   1
openshift-pipelines/p12n-tekton-caches             35d13ca95e59   1
openshift-pipelines/pac-downstream                 bd2efb6bac78   4
openshift-pipelines/serve-tkn-cl                   a510d8905687   1
openshift-pipelines/tekton-kueue                   9e2bc49b0eb2   1
openshift-pipelines/tektoncd-chains                5ce5347c8544   1
openshift-pipelines/tektoncd-cl                    3f9d353d6e2b   1
openshift-pipelines/tektoncd-git-clone             f4ea3d89f9db   1
openshift-pipelines/tektoncd-hub                   410f9709ee1d   2
openshift-pipelines/tektoncd-hub                   ff21b02885dc   1
openshift-pipelines/tektoncd-pipeline              1774b2fdc8f3   8
openshift-pipelines/tektoncd-pruner                68bfea16b601   2
openshift-pipelines/tektoncd-results               23029ec34482   3
openshift-pipelines/tektoncd-triggers              8abaa965abcc   4

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.

Lets not invest more on github workflows. we should go by the plan
that way we can reuse the things we develop

if [[ -z "$SHA" || "$SHA" == "null" ]]; then
echo "FAILED $REPO: could not resolve HEAD SHA for branch $BRANCH"
RESULTS="${RESULTS}| ${REPO} | ${BRANCH} | ${TAG} | Failed (no SHA) |\n"
FAILED=$((FAILED + 1))
continue
fi

# Create the tag
if gh api "repos/$FULL_REPO/git/refs" \
--method POST \
--field ref="refs/tags/$TAG" \
--field sha="$SHA" --silent 2>&1; then
echo "OK $REPO: tagged $TAG at $SHA ($BRANCH)"
TAG_URL="https://github.com/${FULL_REPO}/releases/tag/${TAG}"
RESULTS="${RESULTS}| [${REPO}](https://github.com/${FULL_REPO}) | ${BRANCH} | [${TAG}](${TAG_URL}) | OK |\n"
OK=$((OK + 1))
else
echo "FAILED $REPO: could not create tag $TAG"
RESULTS="${RESULTS}| ${REPO} | ${BRANCH} | ${TAG} | Failed |\n"
FAILED=$((FAILED + 1))
fi
done <<< "$REPOS"

echo "total=$TOTAL" >> "$GITHUB_OUTPUT"
echo "ok=$OK" >> "$GITHUB_OUTPUT"
echo "skipped=$SKIPPED" >> "$GITHUB_OUTPUT"
echo "failed=$FAILED" >> "$GITHUB_OUTPUT"
{
echo "results<<EOF"
echo -e "$RESULTS"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Generate summary
if: always()
env:
TAG: ${{ steps.resolve.outputs.tag }}
BRANCH: ${{ steps.resolve.outputs.branch }}
DRY_RUN: ${{ inputs.dry-run }}
TOTAL: ${{ steps.create-tags.outputs.total }}
OK: ${{ steps.create-tags.outputs.ok }}
SKIPPED: ${{ steps.create-tags.outputs.skipped }}
FAILED: ${{ steps.create-tags.outputs.failed }}
RESULTS: ${{ steps.create-tags.outputs.results }}
run: |
{
echo "## Release Tag Downstream Repos"
echo ""
echo "**Tag:** ${TAG} | **Branch:** ${BRANCH} | **Dry-run:** ${DRY_RUN}"
echo ""
echo "| Repo | Branch | Tag | Status |"
echo "|------|--------|-----|--------|"
echo "$RESULTS"
echo ""
echo "**Total:** ${TOTAL} | **OK:** ${OK} | **Skipped:** ${SKIPPED} | **Failed:** ${FAILED}"
} >> "$GITHUB_STEP_SUMMARY"

if [[ "${FAILED:-0}" -gt 0 ]]; then
echo "::error::$FAILED repos failed to tag"
exit 1
fi