From 0141a0035fc8e456f1d45aff006c1636cb2c6772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3zes=20L=C3=A1szl=C3=B3=20M=C3=A1t=C3=A9?= Date: Fri, 12 Jun 2026 10:11:04 +0200 Subject: [PATCH 01/10] Add manual patch release workflow for Go KRM functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté Assisted-by: Cursor:composer-2.5 --- .github/workflows/manual-patch-release.yaml | 90 ++++++++ scripts/manual-patch-release.sh | 236 ++++++++++++++++++++ 2 files changed, 326 insertions(+) create mode 100644 .github/workflows/manual-patch-release.yaml create mode 100644 scripts/manual-patch-release.sh diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml new file mode 100644 index 000000000..b98558d72 --- /dev/null +++ b/.github/workflows/manual-patch-release.yaml @@ -0,0 +1,90 @@ +# Copyright 2026 The kpt 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. + +# Manual patch release for functions/go KRM functions. +# +# - Input: comma-separated function names, or "all" (case-insensitive) for every Makefile function +# except utilities in MANUAL_PATCH_EXCLUDE_FROM_ALL in scripts/manual-patch-release.sh (e.g. sleep). +# With "all", functions without a prior functions/go//v* semver tag are skipped with a notice. +# - Resolves the latest functions/go//vMAJOR.MINOR.PATCH tag, bumps patch only. +# - Pushes multi-arch images (make func-push), long + short git tags at github.sha, then +# creates a GitHub Release with GitHub-generated notes (scoped via previous_tag_name) plus +# a container image section. +# - Does not rely on other workflows running after GITHUB_TOKEN pushes (see GitHub docs). + +name: Manual patch release (functions/go) + +on: + workflow_dispatch: + inputs: + functions: + description: >- + Comma-separated function names (e.g. kubeconform,apply-setters), or "all" to patch-release + every Makefile-listed function except excluded utilities (see scripts/manual-patch-release.sh). + Functions with no prior v* semver tag are skipped with a notice when using "all". + required: true + type: string + dry_run: + description: "If true, only print planned versions (no registry, tags, or releases)" + required: false + type: boolean + default: false + +concurrency: + group: manual-patch-release-catalog + cancel-in-progress: false + +permissions: + contents: write + packages: write + +jobs: + release: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Fetch remote tags + run: git fetch --tags origin + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version-file: documentation/go.mod + cache: true + + - name: Setup QEMU + uses: docker/setup-qemu-action@v4 + + - name: Setup Buildx + uses: docker/setup-buildx-action@v4 + + - name: Log in to GHCR + if: ${{ !inputs.dry_run }} + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Patch release (build, tag, release) + env: + MANUAL_PATCH_FUNCTIONS: ${{ inputs.functions }} + MANUAL_PATCH_DRY_RUN: ${{ inputs.dry_run }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SHA: ${{ github.sha }} + run: bash scripts/manual-patch-release.sh diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh new file mode 100644 index 000000000..f7717e719 --- /dev/null +++ b/scripts/manual-patch-release.sh @@ -0,0 +1,236 @@ +#! /usr/bin/env bash + +# Copyright 2026 The kpt 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. + +# Manual patch release for functions/go KRM functions (used by GitHub Actions and runnable locally). +# +# Environment (required unless noted): +# MANUAL_PATCH_FUNCTIONS Comma-separated function names (must match FUNCTIONS in functions/go/Makefile), +# or the single word "all" (case-insensitive) to release every Makefile-listed +# function except keys in MANUAL_PATCH_EXCLUDE_FROM_ALL below. With "all", +# functions with no prior functions/go//vMAJOR.MINOR.PATCH tag are skipped +# with a notice. +# GITHUB_REPOSITORY owner/repo (e.g. kptdev/krm-functions-catalog). +# GITHUB_SHA Commit SHA to tag and release (images built from this tree). +# GH_TOKEN Token for gh api / gh release create (optional in dry-run). +# +# Optional: +# MANUAL_PATCH_DRY_RUN If "true", only print planned versions (no push, tags, or releases). +# +# Prerequisites when not dry-run: docker logged in to GHCR; gh, jq, git, make, Go toolchains as in CI. + +set -euo pipefail + +scripts_dir="$(cd "$(dirname "$0")" && pwd)" +repo_root="$(cd "${scripts_dir}/.." && pwd)" +cd "${repo_root}" + +functions_input="${MANUAL_PATCH_FUNCTIONS:-}" +repo="${GITHUB_REPOSITORY:-}" +sha="${GITHUB_SHA:-}" +dry_run="${MANUAL_PATCH_DRY_RUN:-false}" + +if [[ -z "$functions_input" ]]; then + echo "::error::MANUAL_PATCH_FUNCTIONS is not set" >&2 + exit 1 +fi +if [[ -z "$repo" ]]; then + echo "::error::GITHUB_REPOSITORY is not set" >&2 + exit 1 +fi +if [[ -z "$sha" ]]; then + echo "::error::GITHUB_SHA is not set" >&2 + exit 1 +fi + +# Tags/releases appear as the GitHub Actions bot in the UI. +git config user.email "41898282+github-actions[bot]@users.noreply.github.com" +git config user.name "github-actions[bot]" + +# Same list make func-push uses (see FUNCTIONS in functions/go/Makefile). +mapfile -t allowed < <(awk ' + /^FUNCTIONS :=/ { p = 1; next } + p && /^# Targets for running/ { exit } + p && /^[[:space:]]+[a-z0-9-]+/ { + line = $0 + sub(/^[[:space:]]+/, "", line) + sub(/[[:space:]]*\\$/, "", line) + if (line != "") print line + } +' functions/go/Makefile) + +if [[ ${#allowed[@]} -eq 0 ]]; then + echo "::error::Could not parse FUNCTIONS from functions/go/Makefile" >&2 + exit 1 +fi + +# Keys = function names omitted when MANUAL_PATCH_FUNCTIONS is "all" (no SemVer line / not shipped). +declare -A MANUAL_PATCH_EXCLUDE_FROM_ALL=( + [sleep]=1 +) + +_trim_space() { + local s="$1" + s="${s#"${s%%[![:space:]]*}"}" + s="${s%"${s##*[![:space:]]}"}" + printf '%s' "$s" +} + +is_excluded_from_all() { + [[ -v MANUAL_PATCH_EXCLUDE_FROM_ALL["$1"] ]] +} + +is_allowed() { + local n="$1" + for a in "${allowed[@]}"; do + [[ "$n" == "$a" ]] && return 0 + done + return 1 +} + +outer_trim="$(_trim_space "${functions_input}")" +declare -a functions=() +expand_all=0 + +# Bulk mode: every Makefile function except MANUAL_PATCH_EXCLUDE_FROM_ALL. +if [[ "${outer_trim,,}" == "all" ]]; then + expand_all=1 + for a in "${allowed[@]}"; do + if is_excluded_from_all "$a"; then + continue + fi + if [[ ! -f "functions/go/${a}/metadata.yaml" ]]; then + echo "::error::Missing functions/go/${a}/metadata.yaml" >&2 + exit 1 + fi + functions+=("$a") + done + if [[ ${#functions[@]} -eq 0 ]]; then + echo "::error::No functions left after applying MANUAL_PATCH_EXCLUDE_FROM_ALL" >&2 + exit 1 + fi + mapfile -t functions < <(printf '%s\n' "${functions[@]}" | sort -u) +else + # Explicit list: comma-separated names (strict if missing tags or excluded utility). + IFS=',' read -ra raw_parts <<< "${functions_input}" + for part in "${raw_parts[@]}"; do + fn="$(_trim_space "$part")" + [[ -z "$fn" ]] && continue + if is_excluded_from_all "$fn"; then + echo "::error::Function '${fn}' is not SemVer-released (excluded from catalog patch releases)." >&2 + exit 1 + fi + if ! is_allowed "$fn"; then + echo "::error::Function '$fn' is not in FUNCTIONS in functions/go/Makefile" >&2 + exit 1 + fi + if [[ ! -f "functions/go/${fn}/metadata.yaml" ]]; then + echo "::error::Missing functions/go/${fn}/metadata.yaml" >&2 + exit 1 + fi + functions+=("$fn") + done + if [[ ${#functions[@]} -eq 0 ]]; then + echo "::error::No function names after parsing MANUAL_PATCH_FUNCTIONS" >&2 + exit 1 + fi +fi + +for fn in "${functions[@]}"; do + echo "===== ${fn} =====" + + # Latest strict SemVer long tag for this function (sort -V orders versions correctly). + prev_long="$( + git tag -l "functions/go/${fn}/v*" | + grep -E -- '/v[0-9]+\.[0-9]+\.[0-9]+$' | + sort -V | + tail -n 1 + )" + + if [[ -z "${prev_long}" ]]; then + if [[ "${expand_all}" -eq 1 ]]; then + echo "::notice::Skipping ${fn}: no prior semver tag functions/go/${fn}/vMAJOR.MINOR.PATCH" + continue + fi + echo "::error::No prior semver tag functions/go/${fn}/vMAJOR.MINOR.PATCH; cannot infer next patch." >&2 + exit 1 + fi + + ver="${prev_long##*/}" + v="${ver#v}" + IFS=. read -r major minor patch <<< "${v}" + next_ver="v${major}.${minor}.$((patch + 1))" + long_tag="functions/go/${fn}/${next_ver}" + + echo "Previous: ${prev_long} -> Next: ${long_tag}" + + if [[ "$dry_run" == "true" ]]; then + echo "(dry_run) would func-push, tag, and gh release create for ${long_tag}" + continue + fi + + # Multi-arch push (see go-function-release.sh / docker.sh). + (cd functions/go && make func-push TAG="${next_ver}" CURRENT_FUNCTION="${fn}" DEFAULT_CR="ghcr.io/${repo}") + + if git rev-parse "$long_tag" >/dev/null 2>&1; then + echo "::error::Tag ${long_tag} already exists locally" >&2 + exit 1 + fi + + # Long tag (full path) then short tag (/v…) to match release.yaml behavior. + git tag "$long_tag" "$sha" + git push origin "refs/tags/${long_tag}" + + git fetch origin "refs/tags/${long_tag}" + oid="$(git rev-parse FETCH_HEAD^{})" + short_tag="${fn}/${next_ver}" + git tag -f "$short_tag" "$oid" + git push -f origin "refs/tags/${short_tag}" + + image_base="$(grep '^image:' "functions/go/${fn}/metadata.yaml" | awk '{print $2}' | tr -d '"')" + if [[ -z "$image_base" ]]; then + echo "::error::No image: field in functions/go/${fn}/metadata.yaml" >&2 + exit 1 + fi + image_ref="${image_base}:${next_ver}" + + # GitHub-generated notes between previous_tag_name and target; subshell + trap cleans mktemp on failure. + notes_json="$(gh api "repos/${repo}/releases/generate-notes" \ + -f tag_name="${long_tag}" \ + -f target_commitish="${sha}" \ + -f previous_tag_name="${prev_long}")" + + gen_body="$(printf '%s' "$notes_json" | jq -r .body)" + gen_name="$(printf '%s' "$notes_json" | jq -r .name)" + + ( + notes_file="$(mktemp)" + trap 'rm -f "$notes_file"' EXIT + { + echo "## Container image" + echo + echo '```' + echo "${image_ref}" + echo '```' + echo + printf '%s\n' "${gen_body}" + } >"${notes_file}" + + gh release create "${long_tag}" \ + --repo "${repo}" \ + --title "${gen_name}" \ + --notes-file "${notes_file}" + ) +done From df4bc1aefd259c040b6ee747650d9a0829dbd29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3zes=20L=C3=A1szl=C3=B3=20M=C3=A1t=C3=A9?= Date: Fri, 12 Jun 2026 11:35:56 +0200 Subject: [PATCH 02/10] Simplify manual patch release script and add list-functions target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté Assisted-by: Cursor:composer-2.5 --- .github/workflows/manual-patch-release.yaml | 10 +-- Makefile | 7 +- functions/go/Makefile | 4 ++ scripts/manual-patch-release.sh | 80 +++++---------------- 4 files changed, 30 insertions(+), 71 deletions(-) diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml index b98558d72..3e76dbb60 100644 --- a/.github/workflows/manual-patch-release.yaml +++ b/.github/workflows/manual-patch-release.yaml @@ -14,9 +14,9 @@ # Manual patch release for functions/go KRM functions. # -# - Input: comma-separated function names, or "all" (case-insensitive) for every Makefile function -# except utilities in MANUAL_PATCH_EXCLUDE_FROM_ALL in scripts/manual-patch-release.sh (e.g. sleep). -# With "all", functions without a prior functions/go//v* semver tag are skipped with a notice. +# - Input: comma-separated function names, or "all" (case-insensitive) for every name from +# `make list-functions`. With "all", functions without a prior functions/go//v* semver tag +# are skipped with a notice. # - Resolves the latest functions/go//vMAJOR.MINOR.PATCH tag, bumps patch only. # - Pushes multi-arch images (make func-push), long + short git tags at github.sha, then # creates a GitHub Release with GitHub-generated notes (scoped via previous_tag_name) plus @@ -31,8 +31,8 @@ on: functions: description: >- Comma-separated function names (e.g. kubeconform,apply-setters), or "all" to patch-release - every Makefile-listed function except excluded utilities (see scripts/manual-patch-release.sh). - Functions with no prior v* semver tag are skipped with a notice when using "all". + every function from `make list-functions`. Functions with no prior v* semver tag + are skipped with a notice when using "all". required: true type: string dry_run: diff --git a/Makefile b/Makefile index 6d4af1b96..c3b1aed8a 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,10 @@ NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) help: ## Print this help @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -.PHONY: test unit-test e2e-test push +.PHONY: test unit-test e2e-test push list-functions + +list-functions: ## Print Go KRM function names, one per line + $(MAKE) -C functions/go list-functions unit-test: ## Run unit tests for Go functions cd functions/go && $(MAKE) test -j$(NPROC) @@ -35,7 +38,7 @@ test: unit-test e2e-test ## Run all unit tests and e2e tests # find all subdirectories with a go.mod file in them GO_MOD_DIRS = $(shell find . -name 'go.mod' -not -path './documentation/*' -exec sh -c 'echo "$$(dirname "{}")"' \; ) # NOTE: the above line is complicated for Mac and busybox compatibilty reasons. -# It is meant to be equivalent with this: find . -name 'go.mod' -printf "'%h' " +# It is meant to be equivalent with this: find . -name 'go.mod' -printf "'%h' " .PHONY: tidy tidy: diff --git a/functions/go/Makefile b/functions/go/Makefile index 4fbfedcd4..748acf0a6 100644 --- a/functions/go/Makefile +++ b/functions/go/Makefile @@ -48,6 +48,10 @@ FUNCTIONS := \ upsert-resource \ sleep +.PHONY: list-functions +list-functions: ## Print Go KRM function names (FUNCTIONS), one per line + @printf '%s\n' $(FUNCTIONS) + # Targets for running all function tests FUNCTION_TESTS := $(patsubst %,%-TEST,$(FUNCTIONS)) # Targets for generating all functions docs diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh index f7717e719..20736cc2e 100644 --- a/scripts/manual-patch-release.sh +++ b/scripts/manual-patch-release.sh @@ -17,11 +17,10 @@ # Manual patch release for functions/go KRM functions (used by GitHub Actions and runnable locally). # # Environment (required unless noted): -# MANUAL_PATCH_FUNCTIONS Comma-separated function names (must match FUNCTIONS in functions/go/Makefile), -# or the single word "all" (case-insensitive) to release every Makefile-listed -# function except keys in MANUAL_PATCH_EXCLUDE_FROM_ALL below. With "all", -# functions with no prior functions/go//vMAJOR.MINOR.PATCH tag are skipped -# with a notice. +# MANUAL_PATCH_FUNCTIONS Comma-separated function names (must match output of: make list-functions), +# or the single word "all" (case-insensitive) to release every name from +# `make list-functions`. With "all", functions with no prior +# functions/go//vMAJOR.MINOR.PATCH tag are skipped with a notice. # GITHUB_REPOSITORY owner/repo (e.g. kptdev/krm-functions-catalog). # GITHUB_SHA Commit SHA to tag and release (images built from this tree). # GH_TOKEN Token for gh api / gh release create (optional in dry-run). @@ -55,32 +54,14 @@ if [[ -z "$sha" ]]; then exit 1 fi -# Tags/releases appear as the GitHub Actions bot in the UI. -git config user.email "41898282+github-actions[bot]@users.noreply.github.com" -git config user.name "github-actions[bot]" - -# Same list make func-push uses (see FUNCTIONS in functions/go/Makefile). -mapfile -t allowed < <(awk ' - /^FUNCTIONS :=/ { p = 1; next } - p && /^# Targets for running/ { exit } - p && /^[[:space:]]+[a-z0-9-]+/ { - line = $0 - sub(/^[[:space:]]+/, "", line) - sub(/[[:space:]]*\\$/, "", line) - if (line != "") print line - } -' functions/go/Makefile) +# Same names as FUNCTIONS in functions/go/Makefile (see target list-functions). +mapfile -t allowed < <(make -s list-functions) if [[ ${#allowed[@]} -eq 0 ]]; then - echo "::error::Could not parse FUNCTIONS from functions/go/Makefile" >&2 + echo "::error::make list-functions produced no output" >&2 exit 1 fi -# Keys = function names omitted when MANUAL_PATCH_FUNCTIONS is "all" (no SemVer line / not shipped). -declare -A MANUAL_PATCH_EXCLUDE_FROM_ALL=( - [sleep]=1 -) - _trim_space() { local s="$1" s="${s#"${s%%[![:space:]]*}"}" @@ -88,10 +69,6 @@ _trim_space() { printf '%s' "$s" } -is_excluded_from_all() { - [[ -v MANUAL_PATCH_EXCLUDE_FROM_ALL["$1"] ]] -} - is_allowed() { local n="$1" for a in "${allowed[@]}"; do @@ -104,40 +81,18 @@ outer_trim="$(_trim_space "${functions_input}")" declare -a functions=() expand_all=0 -# Bulk mode: every Makefile function except MANUAL_PATCH_EXCLUDE_FROM_ALL. +# Bulk mode: every function from `make list-functions`. if [[ "${outer_trim,,}" == "all" ]]; then expand_all=1 - for a in "${allowed[@]}"; do - if is_excluded_from_all "$a"; then - continue - fi - if [[ ! -f "functions/go/${a}/metadata.yaml" ]]; then - echo "::error::Missing functions/go/${a}/metadata.yaml" >&2 - exit 1 - fi - functions+=("$a") - done - if [[ ${#functions[@]} -eq 0 ]]; then - echo "::error::No functions left after applying MANUAL_PATCH_EXCLUDE_FROM_ALL" >&2 - exit 1 - fi - mapfile -t functions < <(printf '%s\n' "${functions[@]}" | sort -u) + mapfile -t functions < <(printf '%s\n' "${allowed[@]}" | sort -u) else - # Explicit list: comma-separated names (strict if missing tags or excluded utility). + # Explicit list: comma-separated names (strict if missing tags). IFS=',' read -ra raw_parts <<< "${functions_input}" for part in "${raw_parts[@]}"; do fn="$(_trim_space "$part")" [[ -z "$fn" ]] && continue - if is_excluded_from_all "$fn"; then - echo "::error::Function '${fn}' is not SemVer-released (excluded from catalog patch releases)." >&2 - exit 1 - fi if ! is_allowed "$fn"; then - echo "::error::Function '$fn' is not in FUNCTIONS in functions/go/Makefile" >&2 - exit 1 - fi - if [[ ! -f "functions/go/${fn}/metadata.yaml" ]]; then - echo "::error::Missing functions/go/${fn}/metadata.yaml" >&2 + echo "::error::Function '$fn' is not in output of: make list-functions" >&2 exit 1 fi functions+=("$fn") @@ -199,12 +154,8 @@ for fn in "${functions[@]}"; do git tag -f "$short_tag" "$oid" git push -f origin "refs/tags/${short_tag}" - image_base="$(grep '^image:' "functions/go/${fn}/metadata.yaml" | awk '{print $2}' | tr -d '"')" - if [[ -z "$image_base" ]]; then - echo "::error::No image: field in functions/go/${fn}/metadata.yaml" >&2 - exit 1 - fi - image_ref="${image_base}:${next_ver}" + # Same registry path as make func-push (DEFAULT_CR + function name). + image_ref="ghcr.io/${repo}/${fn}:${next_ver}" # GitHub-generated notes between previous_tag_name and target; subshell + trap cleans mktemp on failure. notes_json="$(gh api "repos/${repo}/releases/generate-notes" \ @@ -213,7 +164,8 @@ for fn in "${functions[@]}"; do -f previous_tag_name="${prev_long}")" gen_body="$(printf '%s' "$notes_json" | jq -r .body)" - gen_name="$(printf '%s' "$notes_json" | jq -r .name)" + # Match existing catalog releases (see scripts/release-krm-functions.sh): " vX.Y.Z". + release_title="${fn} ${next_ver}" ( notes_file="$(mktemp)" @@ -230,7 +182,7 @@ for fn in "${functions[@]}"; do gh release create "${long_tag}" \ --repo "${repo}" \ - --title "${gen_name}" \ + --title "${release_title}" \ --notes-file "${notes_file}" ) done From 7998fa2f60e9249ebfa57d44ea13f53821193d72 Mon Sep 17 00:00:00 2001 From: mozesl-nokia Date: Fri, 12 Jun 2026 13:25:24 +0200 Subject: [PATCH 03/10] Apply suggestions from code review Signed-off-by: mozesl-nokia Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/manual-patch-release.sh | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh index 20736cc2e..1ee556df9 100644 --- a/scripts/manual-patch-release.sh +++ b/scripts/manual-patch-release.sh @@ -111,7 +111,7 @@ for fn in "${functions[@]}"; do git tag -l "functions/go/${fn}/v*" | grep -E -- '/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | - tail -n 1 + tail -n 1 || true )" if [[ -z "${prev_long}" ]]; then @@ -136,13 +136,20 @@ for fn in "${functions[@]}"; do continue fi - # Multi-arch push (see go-function-release.sh / docker.sh). - (cd functions/go && make func-push TAG="${next_ver}" CURRENT_FUNCTION="${fn}" DEFAULT_CR="ghcr.io/${repo}") + # Fail fast before pushing images, to avoid overwriting an existing release. + short_tag="${fn}/${next_ver}" if git rev-parse "$long_tag" >/dev/null 2>&1; then echo "::error::Tag ${long_tag} already exists locally" >&2 exit 1 fi + if git rev-parse "$short_tag" >/dev/null 2>&1; then + echo "::error::Tag ${short_tag} already exists locally" >&2 + exit 1 + fi + + # Multi-arch push (see go-function-release.sh / docker.sh). + (cd functions/go && make func-push TAG="${next_ver}" CURRENT_FUNCTION="${fn}" DEFAULT_CR="ghcr.io/${repo}") # Long tag (full path) then short tag (/v…) to match release.yaml behavior. git tag "$long_tag" "$sha" @@ -151,8 +158,8 @@ for fn in "${functions[@]}"; do git fetch origin "refs/tags/${long_tag}" oid="$(git rev-parse FETCH_HEAD^{})" short_tag="${fn}/${next_ver}" - git tag -f "$short_tag" "$oid" - git push -f origin "refs/tags/${short_tag}" + git tag "$short_tag" "$oid" + git push origin "refs/tags/${short_tag}" # Same registry path as make func-push (DEFAULT_CR + function name). image_ref="ghcr.io/${repo}/${fn}:${next_ver}" From a31ac0dbc91fe900b5b8706be029aff9e08c868e Mon Sep 17 00:00:00 2001 From: mozesl-nokia Date: Mon, 15 Jun 2026 10:49:47 +0200 Subject: [PATCH 04/10] Apply suggestions from code review Signed-off-by: mozesl-nokia Co-authored-by: Aravindhan Ayyanathan --- .github/workflows/manual-patch-release.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml index 3e76dbb60..c1186d075 100644 --- a/.github/workflows/manual-patch-release.yaml +++ b/.github/workflows/manual-patch-release.yaml @@ -56,7 +56,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true @@ -65,10 +65,10 @@ jobs: run: git fetch --tags origin - name: Setup Go - uses: actions/setup-go@v6 + uses: kptdev/porch/.github/actions/setup-go-kpt@main with: go-version-file: documentation/go.mod - cache: true + install-kpt: 'false' - name: Setup QEMU uses: docker/setup-qemu-action@v4 From 3f7533b49c198c28559337053c8a5a4a5d0f62ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3zes=20L=C3=A1szl=C3=B3=20M=C3=A1t=C3=A9?= Date: Thu, 18 Jun 2026 10:16:38 +0200 Subject: [PATCH 05/10] Rely on other workflows to do the bulk of the work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté Assisted-by: Cursor:Auto --- .github/workflows/manual-patch-release.yaml | 38 ++++++------ scripts/manual-patch-release.sh | 64 +++++++++------------ 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml index c1186d075..697c91a84 100644 --- a/.github/workflows/manual-patch-release.yaml +++ b/.github/workflows/manual-patch-release.yaml @@ -18,10 +18,14 @@ # `make list-functions`. With "all", functions without a prior functions/go//v* semver tag # are skipped with a notice. # - Resolves the latest functions/go//vMAJOR.MINOR.PATCH tag, bumps patch only. -# - Pushes multi-arch images (make func-push), long + short git tags at github.sha, then -# creates a GitHub Release with GitHub-generated notes (scoped via previous_tag_name) plus -# a container image section. -# - Does not rely on other workflows running after GITHUB_TOKEN pushes (see GitHub docs). +# - Creates a GitHub Release (and tag at github.sha) via `gh` using repository secret +# MANUAL_PATCH_RELEASE_TOKEN (a PAT or equivalent). GITHUB_TOKEN must not be used for this step, +# or tag push events would not start other workflows (see GitHub Actions docs). +# - Image build/push, short tag, and appending the container image line to release notes are handled +# by workflows triggered on the new tag (e.g. after-tag-with-version.yaml, release.yaml). +# +# Configure MANUAL_PATCH_RELEASE_TOKEN on the repository (classic PAT with repo scope, or +# fine-grained PAT with Contents and Releases write for this repo). name: Manual patch release (functions/go) @@ -36,7 +40,7 @@ on: required: true type: string dry_run: - description: "If true, only print planned versions (no registry, tags, or releases)" + description: "If true, only print planned versions (no releases)" required: false type: boolean default: false @@ -46,14 +50,13 @@ concurrency: cancel-in-progress: false permissions: - contents: write - packages: write + contents: read jobs: release: runs-on: ubuntu-latest env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.MANUAL_PATCH_RELEASE_TOKEN }} steps: - name: Checkout uses: actions/checkout@v6 @@ -64,24 +67,19 @@ jobs: - name: Fetch remote tags run: git fetch --tags origin + - name: Require PAT secret for non dry-run + if: ${{ !inputs.dry_run && secrets.MANUAL_PATCH_RELEASE_TOKEN == '' }} + run: | + echo "::error::MANUAL_PATCH_RELEASE_TOKEN repository secret is not set (required when dry_run is false)" + exit 1 + - name: Setup Go uses: kptdev/porch/.github/actions/setup-go-kpt@main with: go-version-file: documentation/go.mod install-kpt: 'false' - - name: Setup QEMU - uses: docker/setup-qemu-action@v4 - - - name: Setup Buildx - uses: docker/setup-buildx-action@v4 - - - name: Log in to GHCR - if: ${{ !inputs.dry_run }} - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Patch release (build, tag, release) + - name: Patch release (GitHub release only) env: MANUAL_PATCH_FUNCTIONS: ${{ inputs.functions }} MANUAL_PATCH_DRY_RUN: ${{ inputs.dry_run }} diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh index 1ee556df9..002cb0e3d 100644 --- a/scripts/manual-patch-release.sh +++ b/scripts/manual-patch-release.sh @@ -16,19 +16,26 @@ # Manual patch release for functions/go KRM functions (used by GitHub Actions and runnable locally). # +# Creates a GitHub Release (and the semver tag on the server at GITHUB_SHA) via `gh`. Image build, +# short tag creation, and appending the container image to release notes are left to workflows +# that run on tag push (e.g. after-tag-with-version.yaml, release.yaml). Use a PAT for GH_TOKEN in +# CI so those workflows are triggered; GITHUB_TOKEN-created tag/release events do not start them. +# # Environment (required unless noted): # MANUAL_PATCH_FUNCTIONS Comma-separated function names (must match output of: make list-functions), # or the single word "all" (case-insensitive) to release every name from # `make list-functions`. With "all", functions with no prior # functions/go//vMAJOR.MINOR.PATCH tag are skipped with a notice. # GITHUB_REPOSITORY owner/repo (e.g. kptdev/krm-functions-catalog). -# GITHUB_SHA Commit SHA to tag and release (images built from this tree). -# GH_TOKEN Token for gh api / gh release create (optional in dry-run). +# GITHUB_SHA Commit SHA for the new tag and release target. +# GH_TOKEN PAT (or equivalent) for gh api / gh release create — required when not +# in dry-run; must allow creating releases and tags on the repo. # # Optional: -# MANUAL_PATCH_DRY_RUN If "true", only print planned versions (no push, tags, or releases). +# MANUAL_PATCH_DRY_RUN If "true", only print planned versions (no gh calls). # -# Prerequisites when not dry-run: docker logged in to GHCR; gh, jq, git, make, Go toolchains as in CI. +# Prerequisites when not dry-run: gh, jq, git, make, Go as in CI; remote `origin` must exist. +# Prerequisites when dry-run: same except GH_TOKEN not used. set -euo pipefail @@ -54,6 +61,11 @@ if [[ -z "$sha" ]]; then exit 1 fi +if [[ "$dry_run" != "true" ]] && [[ -z "${GH_TOKEN:-}" ]]; then + echo "::error::GH_TOKEN is required when not in dry-run (use a PAT with permission to create releases and tags)" >&2 + exit 1 +fi + # Same names as FUNCTIONS in functions/go/Makefile (see target list-functions). mapfile -t allowed < <(make -s list-functions) @@ -128,41 +140,28 @@ for fn in "${functions[@]}"; do IFS=. read -r major minor patch <<< "${v}" next_ver="v${major}.${minor}.$((patch + 1))" long_tag="functions/go/${fn}/${next_ver}" + short_tag="${fn}/${next_ver}" echo "Previous: ${prev_long} -> Next: ${long_tag}" if [[ "$dry_run" == "true" ]]; then - echo "(dry_run) would func-push, tag, and gh release create for ${long_tag}" + echo "(dry_run) would run: gh release create \"${long_tag}\" --repo \"${repo}\" --target \"${sha}\" --title \"${fn} ${next_ver}\" --notes-file " continue fi - # Fail fast before pushing images, to avoid overwriting an existing release. - short_tag="${fn}/${next_ver}" - - if git rev-parse "$long_tag" >/dev/null 2>&1; then - echo "::error::Tag ${long_tag} already exists locally" >&2 + if [[ -n "$(git ls-remote origin "refs/tags/${long_tag}")" ]]; then + echo "::error::Tag ${long_tag} already exists on origin" >&2 exit 1 fi - if git rev-parse "$short_tag" >/dev/null 2>&1; then - echo "::error::Tag ${short_tag} already exists locally" >&2 + if [[ -n "$(git ls-remote origin "refs/tags/${short_tag}")" ]]; then + echo "::error::Tag ${short_tag} already exists on origin" >&2 exit 1 fi - # Multi-arch push (see go-function-release.sh / docker.sh). - (cd functions/go && make func-push TAG="${next_ver}" CURRENT_FUNCTION="${fn}" DEFAULT_CR="ghcr.io/${repo}") - - # Long tag (full path) then short tag (/v…) to match release.yaml behavior. - git tag "$long_tag" "$sha" - git push origin "refs/tags/${long_tag}" - - git fetch origin "refs/tags/${long_tag}" - oid="$(git rev-parse FETCH_HEAD^{})" - short_tag="${fn}/${next_ver}" - git tag "$short_tag" "$oid" - git push origin "refs/tags/${short_tag}" - - # Same registry path as make func-push (DEFAULT_CR + function name). - image_ref="ghcr.io/${repo}/${fn}:${next_ver}" + if gh release view "${long_tag}" --repo "${repo}" >/dev/null 2>&1; then + echo "::error::Release for tag ${long_tag} already exists" >&2 + exit 1 + fi # GitHub-generated notes between previous_tag_name and target; subshell + trap cleans mktemp on failure. notes_json="$(gh api "repos/${repo}/releases/generate-notes" \ @@ -177,18 +176,11 @@ for fn in "${functions[@]}"; do ( notes_file="$(mktemp)" trap 'rm -f "$notes_file"' EXIT - { - echo "## Container image" - echo - echo '```' - echo "${image_ref}" - echo '```' - echo - printf '%s\n' "${gen_body}" - } >"${notes_file}" + printf '%s\n' "${gen_body}" >"${notes_file}" gh release create "${long_tag}" \ --repo "${repo}" \ + --target "${sha}" \ --title "${release_title}" \ --notes-file "${notes_file}" ) From d2d9718a6b8418d104e0ea9f4ceee596b33f63d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3zes=20L=C3=A1szl=C3=B3=20M=C3=A1t=C3=A9?= Date: Thu, 18 Jun 2026 11:28:42 +0200 Subject: [PATCH 06/10] Remove dependency on go and notes pregeneration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté Assisted-by: Cursor:composer-2.5 --- .github/workflows/manual-patch-release.yaml | 6 --- functions/go/Makefile | 3 +- scripts/manual-patch-release.sh | 46 +++++---------------- 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml index 697c91a84..8534a94f3 100644 --- a/.github/workflows/manual-patch-release.yaml +++ b/.github/workflows/manual-patch-release.yaml @@ -73,12 +73,6 @@ jobs: echo "::error::MANUAL_PATCH_RELEASE_TOKEN repository secret is not set (required when dry_run is false)" exit 1 - - name: Setup Go - uses: kptdev/porch/.github/actions/setup-go-kpt@main - with: - go-version-file: documentation/go.mod - install-kpt: 'false' - - name: Patch release (GitHub release only) env: MANUAL_PATCH_FUNCTIONS: ${{ inputs.functions }} diff --git a/functions/go/Makefile b/functions/go/Makefile index 748acf0a6..e38854181 100644 --- a/functions/go/Makefile +++ b/functions/go/Makefile @@ -13,9 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. SHELL=/bin/bash +GOPATH ?= $(shell go env GOPATH || echo $(HOME)/go) TAG ?= latest DEFAULT_CR ?= ghcr.io/kptdev/krm-functions-catalog -GOBIN := $(shell go env GOPATH)/bin +GOBIN ?= $(GOPATH)/bin GOLANGCI_LINT_VERSION ?= 2.12.2 diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh index 002cb0e3d..95552ffa3 100644 --- a/scripts/manual-patch-release.sh +++ b/scripts/manual-patch-release.sh @@ -28,14 +28,12 @@ # functions/go//vMAJOR.MINOR.PATCH tag are skipped with a notice. # GITHUB_REPOSITORY owner/repo (e.g. kptdev/krm-functions-catalog). # GITHUB_SHA Commit SHA for the new tag and release target. -# GH_TOKEN PAT (or equivalent) for gh api / gh release create — required when not -# in dry-run; must allow creating releases and tags on the repo. +# GH_TOKEN PAT (or equivalent) for gh release create — required when not in dry-run; # # Optional: # MANUAL_PATCH_DRY_RUN If "true", only print planned versions (no gh calls). # -# Prerequisites when not dry-run: gh, jq, git, make, Go as in CI; remote `origin` must exist. -# Prerequisites when dry-run: same except GH_TOKEN not used. +# Prerequisites: gh, git; remote `origin` must exist for non dry-run. set -euo pipefail @@ -140,12 +138,12 @@ for fn in "${functions[@]}"; do IFS=. read -r major minor patch <<< "${v}" next_ver="v${major}.${minor}.$((patch + 1))" long_tag="functions/go/${fn}/${next_ver}" - short_tag="${fn}/${next_ver}" + release_title="${fn} ${next_ver}" echo "Previous: ${prev_long} -> Next: ${long_tag}" if [[ "$dry_run" == "true" ]]; then - echo "(dry_run) would run: gh release create \"${long_tag}\" --repo \"${repo}\" --target \"${sha}\" --title \"${fn} ${next_ver}\" --notes-file " + echo "(dry_run) would run: gh release create \"${long_tag}\" --repo \"${repo}\" --target \"${sha}\" --title \"${release_title}\" --generate-notes --notes-start-tag \"${prev_long}\"" continue fi @@ -153,35 +151,11 @@ for fn in "${functions[@]}"; do echo "::error::Tag ${long_tag} already exists on origin" >&2 exit 1 fi - if [[ -n "$(git ls-remote origin "refs/tags/${short_tag}")" ]]; then - echo "::error::Tag ${short_tag} already exists on origin" >&2 - exit 1 - fi - - if gh release view "${long_tag}" --repo "${repo}" >/dev/null 2>&1; then - echo "::error::Release for tag ${long_tag} already exists" >&2 - exit 1 - fi - - # GitHub-generated notes between previous_tag_name and target; subshell + trap cleans mktemp on failure. - notes_json="$(gh api "repos/${repo}/releases/generate-notes" \ - -f tag_name="${long_tag}" \ - -f target_commitish="${sha}" \ - -f previous_tag_name="${prev_long}")" - - gen_body="$(printf '%s' "$notes_json" | jq -r .body)" - # Match existing catalog releases (see scripts/release-krm-functions.sh): " vX.Y.Z". - release_title="${fn} ${next_ver}" - ( - notes_file="$(mktemp)" - trap 'rm -f "$notes_file"' EXIT - printf '%s\n' "${gen_body}" >"${notes_file}" - - gh release create "${long_tag}" \ - --repo "${repo}" \ - --target "${sha}" \ - --title "${release_title}" \ - --notes-file "${notes_file}" - ) + gh release create "${long_tag}" \ + --repo "${repo}" \ + --target "${sha}" \ + --title "${release_title}" \ + --generate-notes \ + --notes-start-tag "${prev_long}" done From 0e062bc948be7bf0e4ed415b9b2ee827d44dd1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3zes=20L=C3=A1szl=C3=B3=20M=C3=A1t=C3=A9?= Date: Thu, 18 Jun 2026 11:51:06 +0200 Subject: [PATCH 07/10] use app token instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté Assisted-by: Cursor:composer-2.5 --- .github/workflows/manual-patch-release.yaml | 28 ++++++++++++--------- scripts/manual-patch-release.sh | 10 +++++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml index 8534a94f3..8614f898e 100644 --- a/.github/workflows/manual-patch-release.yaml +++ b/.github/workflows/manual-patch-release.yaml @@ -18,14 +18,16 @@ # `make list-functions`. With "all", functions without a prior functions/go//v* semver tag # are skipped with a notice. # - Resolves the latest functions/go//vMAJOR.MINOR.PATCH tag, bumps patch only. -# - Creates a GitHub Release (and tag at github.sha) via `gh` using repository secret -# MANUAL_PATCH_RELEASE_TOKEN (a PAT or equivalent). GITHUB_TOKEN must not be used for this step, -# or tag push events would not start other workflows (see GitHub Actions docs). +# - Creates a GitHub Release (and tag at github.sha) via `gh` using a GitHub App installation +# access token (not GITHUB_TOKEN), so tag push events start other workflows. # - Image build/push, short tag, and appending the container image line to release notes are handled # by workflows triggered on the new tag (e.g. after-tag-with-version.yaml, release.yaml). # -# Configure MANUAL_PATCH_RELEASE_TOKEN on the repository (classic PAT with repo scope, or -# fine-grained PAT with Contents and Releases write for this repo). +# GitHub App setup (org or user app, installed on this repository): +# 1. Create a GitHub App with Repository permissions: Contents (Read and write), Metadata (Read). +# 2. Install the app on this repository (Only select repositories). +# 3. Repository variable: MANUAL_PATCH_RELEASE_APP_ID = App ID (numeric, from app settings). +# 4. Repository secret: MANUAL_PATCH_RELEASE_APP_PRIVATE_KEY = App private key (.pem contents). name: Manual patch release (functions/go) @@ -55,8 +57,6 @@ permissions: jobs: release: runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ secrets.MANUAL_PATCH_RELEASE_TOKEN }} steps: - name: Checkout uses: actions/checkout@v6 @@ -67,11 +67,14 @@ jobs: - name: Fetch remote tags run: git fetch --tags origin - - name: Require PAT secret for non dry-run - if: ${{ !inputs.dry_run && secrets.MANUAL_PATCH_RELEASE_TOKEN == '' }} - run: | - echo "::error::MANUAL_PATCH_RELEASE_TOKEN repository secret is not set (required when dry_run is false)" - exit 1 + - name: Generate GitHub App token + if: ${{ !inputs.dry_run }} + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ vars.MANUAL_PATCH_RELEASE_APP_ID }} + private-key: ${{ secrets.MANUAL_PATCH_RELEASE_APP_PRIVATE_KEY }} + permission-contents: write - name: Patch release (GitHub release only) env: @@ -79,4 +82,5 @@ jobs: MANUAL_PATCH_DRY_RUN: ${{ inputs.dry_run }} GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_SHA: ${{ github.sha }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: bash scripts/manual-patch-release.sh diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh index 95552ffa3..ac5d4302a 100644 --- a/scripts/manual-patch-release.sh +++ b/scripts/manual-patch-release.sh @@ -18,8 +18,9 @@ # # Creates a GitHub Release (and the semver tag on the server at GITHUB_SHA) via `gh`. Image build, # short tag creation, and appending the container image to release notes are left to workflows -# that run on tag push (e.g. after-tag-with-version.yaml, release.yaml). Use a PAT for GH_TOKEN in -# CI so those workflows are triggered; GITHUB_TOKEN-created tag/release events do not start them. +# that run on tag push (e.g. after-tag-with-version.yaml, release.yaml). Use a GitHub App +# installation access token or PAT for GH_TOKEN in CI so those workflows are triggered; +# GITHUB_TOKEN-created tag/release events do not start them. # # Environment (required unless noted): # MANUAL_PATCH_FUNCTIONS Comma-separated function names (must match output of: make list-functions), @@ -28,7 +29,8 @@ # functions/go//vMAJOR.MINOR.PATCH tag are skipped with a notice. # GITHUB_REPOSITORY owner/repo (e.g. kptdev/krm-functions-catalog). # GITHUB_SHA Commit SHA for the new tag and release target. -# GH_TOKEN PAT (or equivalent) for gh release create — required when not in dry-run; +# GH_TOKEN GitHub App installation token or PAT for gh release create — required +# when not in dry-run; must allow creating releases and tags on the repo. # # Optional: # MANUAL_PATCH_DRY_RUN If "true", only print planned versions (no gh calls). @@ -60,7 +62,7 @@ if [[ -z "$sha" ]]; then fi if [[ "$dry_run" != "true" ]] && [[ -z "${GH_TOKEN:-}" ]]; then - echo "::error::GH_TOKEN is required when not in dry-run (use a PAT with permission to create releases and tags)" >&2 + echo "::error::GH_TOKEN is required when not in dry-run (GitHub App installation token or PAT with permission to create releases and tags)" >&2 exit 1 fi From 255cd732956cf0361c3f44dee463da899611465b Mon Sep 17 00:00:00 2001 From: mozesl-nokia Date: Thu, 18 Jun 2026 13:51:43 +0200 Subject: [PATCH 08/10] Apply suggestions from code review Signed-off-by: mozesl-nokia Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/manual-patch-release.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh index ac5d4302a..12be3ddb4 100644 --- a/scripts/manual-patch-release.sh +++ b/scripts/manual-patch-release.sh @@ -1,4 +1,4 @@ -#! /usr/bin/env bash +#!/usr/bin/env bash # Copyright 2026 The kpt Authors # @@ -120,9 +120,8 @@ for fn in "${functions[@]}"; do # Latest strict SemVer long tag for this function (sort -V orders versions correctly). prev_long="$( - git tag -l "functions/go/${fn}/v*" | + git tag -l "functions/go/${fn}/v*" --sort=v:refname | grep -E -- '/v[0-9]+\.[0-9]+\.[0-9]+$' | - sort -V | tail -n 1 || true )" From 3f991d7e86db59d879fcf6cb76edac1acacb4b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3zes=20L=C3=A1szl=C3=B3=20M=C3=A1t=C3=A9?= Date: Tue, 23 Jun 2026 15:08:35 +0200 Subject: [PATCH 09/10] align token generation ID and key with new app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté --- .github/workflows/manual-patch-release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml index 8614f898e..2c84ffe7d 100644 --- a/.github/workflows/manual-patch-release.yaml +++ b/.github/workflows/manual-patch-release.yaml @@ -72,8 +72,8 @@ jobs: id: app-token uses: actions/create-github-app-token@v3 with: - app-id: ${{ vars.MANUAL_PATCH_RELEASE_APP_ID }} - private-key: ${{ secrets.MANUAL_PATCH_RELEASE_APP_PRIVATE_KEY }} + app-id: ${{ secrets.CI_BOT_APP_ID }} + private-key: ${{ secrets.CI_BOT_PRIVATE_KEY }} permission-contents: write - name: Patch release (GitHub release only) From 6f52e57527a376a728fff7621b9205f391979146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B3zes=20L=C3=A1szl=C3=B3=20M=C3=A1t=C3=A9?= Date: Wed, 24 Jun 2026 10:30:48 +0200 Subject: [PATCH 10/10] final adjustments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: Cursor:Opus-4.7 Signed-off-by: Mózes László Máté --- .github/workflows/manual-patch-release.yaml | 7 ++----- scripts/manual-patch-release.sh | 13 ++++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/manual-patch-release.yaml b/.github/workflows/manual-patch-release.yaml index 2c84ffe7d..ede24d8ab 100644 --- a/.github/workflows/manual-patch-release.yaml +++ b/.github/workflows/manual-patch-release.yaml @@ -26,8 +26,8 @@ # GitHub App setup (org or user app, installed on this repository): # 1. Create a GitHub App with Repository permissions: Contents (Read and write), Metadata (Read). # 2. Install the app on this repository (Only select repositories). -# 3. Repository variable: MANUAL_PATCH_RELEASE_APP_ID = App ID (numeric, from app settings). -# 4. Repository secret: MANUAL_PATCH_RELEASE_APP_PRIVATE_KEY = App private key (.pem contents). +# 3. Repository secret: CI_BOT_APP_ID = App ID (numeric, from app settings). +# 4. Repository secret: CI_BOT_PRIVATE_KEY = App private key (.pem contents). name: Manual patch release (functions/go) @@ -64,9 +64,6 @@ jobs: fetch-depth: 0 fetch-tags: true - - name: Fetch remote tags - run: git fetch --tags origin - - name: Generate GitHub App token if: ${{ !inputs.dry_run }} id: app-token diff --git a/scripts/manual-patch-release.sh b/scripts/manual-patch-release.sh index 12be3ddb4..99c91bbcc 100644 --- a/scripts/manual-patch-release.sh +++ b/scripts/manual-patch-release.sh @@ -96,7 +96,7 @@ expand_all=0 # Bulk mode: every function from `make list-functions`. if [[ "${outer_trim,,}" == "all" ]]; then expand_all=1 - mapfile -t functions < <(printf '%s\n' "${allowed[@]}" | sort -u) + functions=("${allowed[@]}") else # Explicit list: comma-separated names (strict if missing tags). IFS=',' read -ra raw_parts <<< "${functions_input}" @@ -109,10 +109,13 @@ else fi functions+=("$fn") done - if [[ ${#functions[@]} -eq 0 ]]; then - echo "::error::No function names after parsing MANUAL_PATCH_FUNCTIONS" >&2 - exit 1 - fi +fi + +mapfile -t functions < <(printf '%s\n' "${functions[@]}" | sort -u) + +if [[ ${#functions[@]} -eq 0 ]]; then + echo "::error::No function names after parsing MANUAL_PATCH_FUNCTIONS" >&2 + exit 1 fi for fn in "${functions[@]}"; do