Skip to content

Commit 61b44fa

Browse files
mozesl-nokiaCopilotaravindtga
authored
Create GitHub action for bumping patch versions (#1259)
* Add manual patch release workflow for Go KRM functions Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> Assisted-by: Cursor:composer-2.5 * Simplify manual patch release script and add list-functions target Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> Assisted-by: Cursor:composer-2.5 * Apply suggestions from code review Signed-off-by: mozesl-nokia <laszlo.mozes@nokia.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Signed-off-by: mozesl-nokia <laszlo.mozes@nokia.com> Co-authored-by: Aravindhan Ayyanathan <aravindhan.a@est.tech> * Rely on other workflows to do the bulk of the work Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> Assisted-by: Cursor:Auto * Remove dependency on go and notes pregeneration Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> Assisted-by: Cursor:composer-2.5 * use app token instead Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> Assisted-by: Cursor:composer-2.5 * Apply suggestions from code review Signed-off-by: mozesl-nokia <laszlo.mozes@nokia.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * align token generation ID and key with new app Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> * final adjustments Assisted-by: Cursor:Opus-4.7 Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> --------- Signed-off-by: Mózes László Máté <laszlo.mozes@nokia.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Aravindhan Ayyanathan <aravindhan.a@est.tech>
1 parent 2eb6685 commit 61b44fa

4 files changed

Lines changed: 259 additions & 3 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Copyright 2026 The kpt Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Manual patch release for functions/go KRM functions.
16+
#
17+
# - Input: comma-separated function names, or "all" (case-insensitive) for every name from
18+
# `make list-functions`. With "all", functions without a prior functions/go/<name>/v* semver tag
19+
# are skipped with a notice.
20+
# - Resolves the latest functions/go/<name>/vMAJOR.MINOR.PATCH tag, bumps patch only.
21+
# - Creates a GitHub Release (and tag at github.sha) via `gh` using a GitHub App installation
22+
# access token (not GITHUB_TOKEN), so tag push events start other workflows.
23+
# - Image build/push, short tag, and appending the container image line to release notes are handled
24+
# by workflows triggered on the new tag (e.g. after-tag-with-version.yaml, release.yaml).
25+
#
26+
# GitHub App setup (org or user app, installed on this repository):
27+
# 1. Create a GitHub App with Repository permissions: Contents (Read and write), Metadata (Read).
28+
# 2. Install the app on this repository (Only select repositories).
29+
# 3. Repository secret: CI_BOT_APP_ID = App ID (numeric, from app settings).
30+
# 4. Repository secret: CI_BOT_PRIVATE_KEY = App private key (.pem contents).
31+
32+
name: Manual patch release (functions/go)
33+
34+
on:
35+
workflow_dispatch:
36+
inputs:
37+
functions:
38+
description: >-
39+
Comma-separated function names (e.g. kubeconform,apply-setters), or "all" to patch-release
40+
every function from `make list-functions`. Functions with no prior v* semver tag
41+
are skipped with a notice when using "all".
42+
required: true
43+
type: string
44+
dry_run:
45+
description: "If true, only print planned versions (no releases)"
46+
required: false
47+
type: boolean
48+
default: false
49+
50+
concurrency:
51+
group: manual-patch-release-catalog
52+
cancel-in-progress: false
53+
54+
permissions:
55+
contents: read
56+
57+
jobs:
58+
release:
59+
runs-on: ubuntu-latest
60+
steps:
61+
- name: Checkout
62+
uses: actions/checkout@v6
63+
with:
64+
fetch-depth: 0
65+
fetch-tags: true
66+
67+
- name: Generate GitHub App token
68+
if: ${{ !inputs.dry_run }}
69+
id: app-token
70+
uses: actions/create-github-app-token@v3
71+
with:
72+
app-id: ${{ secrets.CI_BOT_APP_ID }}
73+
private-key: ${{ secrets.CI_BOT_PRIVATE_KEY }}
74+
permission-contents: write
75+
76+
- name: Patch release (GitHub release only)
77+
env:
78+
MANUAL_PATCH_FUNCTIONS: ${{ inputs.functions }}
79+
MANUAL_PATCH_DRY_RUN: ${{ inputs.dry_run }}
80+
GITHUB_REPOSITORY: ${{ github.repository }}
81+
GITHUB_SHA: ${{ github.sha }}
82+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
83+
run: bash scripts/manual-patch-release.sh

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)
2222
help: ## Print this help
2323
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
2424

25-
.PHONY: test unit-test e2e-test push
25+
.PHONY: test unit-test e2e-test push list-functions
26+
27+
list-functions: ## Print Go KRM function names, one per line
28+
$(MAKE) -C functions/go list-functions
2629

2730
unit-test: ## Run unit tests for Go functions
2831
cd functions/go && $(MAKE) test -j$(NPROC)
@@ -35,7 +38,7 @@ test: unit-test e2e-test ## Run all unit tests and e2e tests
3538
# find all subdirectories with a go.mod file in them
3639
GO_MOD_DIRS = $(shell find . -name 'go.mod' -not -path './documentation/*' -exec sh -c 'echo "$$(dirname "{}")"' \; )
3740
# NOTE: the above line is complicated for Mac and busybox compatibilty reasons.
38-
# It is meant to be equivalent with this: find . -name 'go.mod' -printf "'%h' "
41+
# It is meant to be equivalent with this: find . -name 'go.mod' -printf "'%h' "
3942

4043
.PHONY: tidy
4144
tidy:

functions/go/Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
SHELL=/bin/bash
16+
GOPATH ?= $(shell go env GOPATH || echo $(HOME)/go)
1617
TAG ?= latest
1718
DEFAULT_CR ?= ghcr.io/kptdev/krm-functions-catalog
18-
GOBIN := $(shell go env GOPATH)/bin
19+
GOBIN ?= $(GOPATH)/bin
1920

2021
GOLANGCI_LINT_VERSION ?= 2.12.2
2122

@@ -48,6 +49,10 @@ FUNCTIONS := \
4849
upsert-resource \
4950
sleep
5051

52+
.PHONY: list-functions
53+
list-functions: ## Print Go KRM function names (FUNCTIONS), one per line
54+
@printf '%s\n' $(FUNCTIONS)
55+
5156
# Targets for running all function tests
5257
FUNCTION_TESTS := $(patsubst %,%-TEST,$(FUNCTIONS))
5358
# Targets for generating all functions docs

scripts/manual-patch-release.sh

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright 2026 The kpt Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
# Manual patch release for functions/go KRM functions (used by GitHub Actions and runnable locally).
18+
#
19+
# Creates a GitHub Release (and the semver tag on the server at GITHUB_SHA) via `gh`. Image build,
20+
# short tag creation, and appending the container image to release notes are left to workflows
21+
# that run on tag push (e.g. after-tag-with-version.yaml, release.yaml). Use a GitHub App
22+
# installation access token or PAT for GH_TOKEN in CI so those workflows are triggered;
23+
# GITHUB_TOKEN-created tag/release events do not start them.
24+
#
25+
# Environment (required unless noted):
26+
# MANUAL_PATCH_FUNCTIONS Comma-separated function names (must match output of: make list-functions),
27+
# or the single word "all" (case-insensitive) to release every name from
28+
# `make list-functions`. With "all", functions with no prior
29+
# functions/go/<name>/vMAJOR.MINOR.PATCH tag are skipped with a notice.
30+
# GITHUB_REPOSITORY owner/repo (e.g. kptdev/krm-functions-catalog).
31+
# GITHUB_SHA Commit SHA for the new tag and release target.
32+
# GH_TOKEN GitHub App installation token or PAT for gh release create — required
33+
# when not in dry-run; must allow creating releases and tags on the repo.
34+
#
35+
# Optional:
36+
# MANUAL_PATCH_DRY_RUN If "true", only print planned versions (no gh calls).
37+
#
38+
# Prerequisites: gh, git; remote `origin` must exist for non dry-run.
39+
40+
set -euo pipefail
41+
42+
scripts_dir="$(cd "$(dirname "$0")" && pwd)"
43+
repo_root="$(cd "${scripts_dir}/.." && pwd)"
44+
cd "${repo_root}"
45+
46+
functions_input="${MANUAL_PATCH_FUNCTIONS:-}"
47+
repo="${GITHUB_REPOSITORY:-}"
48+
sha="${GITHUB_SHA:-}"
49+
dry_run="${MANUAL_PATCH_DRY_RUN:-false}"
50+
51+
if [[ -z "$functions_input" ]]; then
52+
echo "::error::MANUAL_PATCH_FUNCTIONS is not set" >&2
53+
exit 1
54+
fi
55+
if [[ -z "$repo" ]]; then
56+
echo "::error::GITHUB_REPOSITORY is not set" >&2
57+
exit 1
58+
fi
59+
if [[ -z "$sha" ]]; then
60+
echo "::error::GITHUB_SHA is not set" >&2
61+
exit 1
62+
fi
63+
64+
if [[ "$dry_run" != "true" ]] && [[ -z "${GH_TOKEN:-}" ]]; then
65+
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
66+
exit 1
67+
fi
68+
69+
# Same names as FUNCTIONS in functions/go/Makefile (see target list-functions).
70+
mapfile -t allowed < <(make -s list-functions)
71+
72+
if [[ ${#allowed[@]} -eq 0 ]]; then
73+
echo "::error::make list-functions produced no output" >&2
74+
exit 1
75+
fi
76+
77+
_trim_space() {
78+
local s="$1"
79+
s="${s#"${s%%[![:space:]]*}"}"
80+
s="${s%"${s##*[![:space:]]}"}"
81+
printf '%s' "$s"
82+
}
83+
84+
is_allowed() {
85+
local n="$1"
86+
for a in "${allowed[@]}"; do
87+
[[ "$n" == "$a" ]] && return 0
88+
done
89+
return 1
90+
}
91+
92+
outer_trim="$(_trim_space "${functions_input}")"
93+
declare -a functions=()
94+
expand_all=0
95+
96+
# Bulk mode: every function from `make list-functions`.
97+
if [[ "${outer_trim,,}" == "all" ]]; then
98+
expand_all=1
99+
functions=("${allowed[@]}")
100+
else
101+
# Explicit list: comma-separated names (strict if missing tags).
102+
IFS=',' read -ra raw_parts <<< "${functions_input}"
103+
for part in "${raw_parts[@]}"; do
104+
fn="$(_trim_space "$part")"
105+
[[ -z "$fn" ]] && continue
106+
if ! is_allowed "$fn"; then
107+
echo "::error::Function '$fn' is not in output of: make list-functions" >&2
108+
exit 1
109+
fi
110+
functions+=("$fn")
111+
done
112+
fi
113+
114+
mapfile -t functions < <(printf '%s\n' "${functions[@]}" | sort -u)
115+
116+
if [[ ${#functions[@]} -eq 0 ]]; then
117+
echo "::error::No function names after parsing MANUAL_PATCH_FUNCTIONS" >&2
118+
exit 1
119+
fi
120+
121+
for fn in "${functions[@]}"; do
122+
echo "===== ${fn} ====="
123+
124+
# Latest strict SemVer long tag for this function (sort -V orders versions correctly).
125+
prev_long="$(
126+
git tag -l "functions/go/${fn}/v*" --sort=v:refname |
127+
grep -E -- '/v[0-9]+\.[0-9]+\.[0-9]+$' |
128+
tail -n 1 || true
129+
)"
130+
131+
if [[ -z "${prev_long}" ]]; then
132+
if [[ "${expand_all}" -eq 1 ]]; then
133+
echo "::notice::Skipping ${fn}: no prior semver tag functions/go/${fn}/vMAJOR.MINOR.PATCH"
134+
continue
135+
fi
136+
echo "::error::No prior semver tag functions/go/${fn}/vMAJOR.MINOR.PATCH; cannot infer next patch." >&2
137+
exit 1
138+
fi
139+
140+
ver="${prev_long##*/}"
141+
v="${ver#v}"
142+
IFS=. read -r major minor patch <<< "${v}"
143+
next_ver="v${major}.${minor}.$((patch + 1))"
144+
long_tag="functions/go/${fn}/${next_ver}"
145+
release_title="${fn} ${next_ver}"
146+
147+
echo "Previous: ${prev_long} -> Next: ${long_tag}"
148+
149+
if [[ "$dry_run" == "true" ]]; then
150+
echo "(dry_run) would run: gh release create \"${long_tag}\" --repo \"${repo}\" --target \"${sha}\" --title \"${release_title}\" --generate-notes --notes-start-tag \"${prev_long}\""
151+
continue
152+
fi
153+
154+
if [[ -n "$(git ls-remote origin "refs/tags/${long_tag}")" ]]; then
155+
echo "::error::Tag ${long_tag} already exists on origin" >&2
156+
exit 1
157+
fi
158+
159+
gh release create "${long_tag}" \
160+
--repo "${repo}" \
161+
--target "${sha}" \
162+
--title "${release_title}" \
163+
--generate-notes \
164+
--notes-start-tag "${prev_long}"
165+
done

0 commit comments

Comments
 (0)