Skip to content

Commit 333dd2f

Browse files
committed
Add a tool to generate the release name
Signed-off-by: Andrea Frittoli <[email protected]>
1 parent 5b082b1 commit 333dd2f

File tree

2 files changed

+274
-23
lines changed

2 files changed

+274
-23
lines changed

tekton/release-cheat-sheet.md

+37-23
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,54 @@ the pipelines repo, a terminal window and a text editor.
1212

1313
1. [Install kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize) if you haven't already.
1414

15-
1. Ensure the correct version of the release pipeline is installed on the cluster:
15+
1. Select the commit you would like to build the release from (NOTE: the commit is full (40-digit) hash.)
16+
- Select the most recent commit on the ***main branch*** if you are cutting a major or minor release i.e. `x.0.0` or `0.x.0`
17+
- Select the most recent commit on the ***`release-<version number>x` branch***, e.g. [`release-v0.47.x`](https://github.com/tektoncd/pipeline/tree/release-v0.47.x) if you are patching a release i.e. `v0.47.2`.
18+
19+
1. Ensure the correct version of the release pipeline is installed on the cluster.
20+
To do that, the selected commit should be checked-out locally
1621

1722
```bash
1823
kustomize build tekton | kubectl --context dogfooding replace -f -
1924
```
2025

21-
1. Create environment variables for bash scripts in later steps.
26+
1. Choose a name for the new release! The usual pattern is "< cat breed > < famous robot >" e.g. "Ragdoll Norby". For LTS releases, add a suffix "LTS" in the name such as "< cat breed > < famous robot > LTS" e.g. "Ragdoll Norby LTS". Use this command to generate a name that has not yet been used:
2227

2328
```bash
24-
TEKTON_VERSION=# Example: v0.21.0
29+
go run tekton/release_names.go
30+
```
31+
32+
It returns something like:
33+
34+
```json
35+
{
36+
"release_name": "Khao Manee KARR",
37+
"cat_breed_url": "https://en.wikipedia.org/wiki/Khao_Manee",
38+
"robot_url": "https://en.wikipedia.org/wiki/KARR"
39+
}
40+
```
41+
42+
The URLs can be used to find out more about the cat breed and robot selected by the tool.
43+
Previous release names can also be found with the following command:
44+
45+
```bash
46+
curl \
47+
-H "Accept: application/vnd.github.v3+json" \
48+
https://api.github.com/repos/tektoncd/pipeline/releases\?per_page=100 \
49+
| jq ".[].name" | cut -d'"' -f 3 | tr -d '\' | sort -u
2550
```
2651

27-
- Select the commit you would like to build the release from (NOTE: the commit is full (40-digit) hash.)
28-
- Select the most recent commit on the ***main branch*** if you are cutting a major or minor release i.e. `x.0.0` or `0.x.0`
29-
- Select the most recent commit on the ***`release-<version number>x` branch***, e.g. [`release-v0.47.x`](https://github.com/tektoncd/pipeline/tree/release-v0.47.x) if you are patching a release i.e. `v0.47.2`.
52+
1. Create a `release.env` file with environment variables for bash scripts in later steps, and source it:
3053

3154
```bash
32-
TEKTON_RELEASE_GIT_SHA=# SHA of the release to be released
55+
cat <<EOF > release.env
56+
TEKTON_VERSION= # Example: v0.69.0
57+
TEKTON_RELEASE_GIT_SHA= # SHA of the release to be released, e.g. 5b082b1106753e093593d12152c82e1c4b0f37e5
58+
TEKTON_OLD_VERSION= # Example: v0.68.0
59+
TEKTON_RELEASE_NAME="Oriental Longhair Omnibot" # Name of the release
60+
TEKTON_PACKAGE=tektoncd/pipeline
61+
EOF
62+
. ./release.env
3363
```
3464
3565
1. Confirm commit SHA matches what you want to release.
@@ -103,15 +133,6 @@ the pipelines repo, a terminal window and a text editor.
103133
104134
1. The YAMLs are now released! Anyone installing Tekton Pipelines will get the new version. Time to create a new GitHub release announcement:
105135
106-
1. Choose a name for the new release! The usual pattern is "< cat breed > < famous robot >" e.g. "Ragdoll Norby". For LTS releases, add a suffix "LTS" in the name such as "< cat breed > < famous robot > LTS" e.g. "Ragdoll Norby LTS". Browse [the releases page](https://github.com/tektoncd/pipeline/releases) or run this command to check which names have already been used:
107-
108-
```bash
109-
curl \
110-
-H "Accept: application/vnd.github.v3+json" \
111-
https://api.github.com/repos/tektoncd/pipeline/releases\?per_page=100 \
112-
| jq ".[].name"
113-
```
114-
115136
1. Find the Rekor UUID for the release
116137
117138
```bash
@@ -121,13 +142,6 @@ the pipelines repo, a terminal window and a text editor.
121142
echo -e "CONTROLLER_IMAGE_SHA: ${CONTROLLER_IMAGE_SHA}\nREKOR_UUID: ${REKOR_UUID}"
122143
```
123144
124-
1. Create additional environment variables
125-
126-
```bash
127-
TEKTON_OLD_VERSION=# Example: v0.11.1
128-
TEKTON_RELEASE_NAME=# The release name you just chose, e.g.: "Ragdoll Norby"
129-
```
130-
131145
1. Execute the Draft Release Pipeline.
132146
133147
```bash

tekton/release_names.go

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
Copyright 2025 The Tekton Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/*
18+
This utility can be used to generate a new release name in the format:
19+
<cat breed> <robot name>
20+
21+
to be used for a Tekton Pipelines release.
22+
It looks for cat breeds from CatAPIURL and it parses robot names out
23+
of Wikipedia WikiURL. It filters names that have been used already,
24+
based on the GitHub API GitHubReleasesURL
25+
26+
To use, run:
27+
go run release_names.go
28+
29+
30+
Example output:
31+
{
32+
"release_name": "California Spangled Clank",
33+
"cat_breed_url": "https://en.wikipedia.org/wiki/California_Spangled",
34+
"robot_url": "https://en.wikipedia.org/wiki/Clank"
35+
}
36+
*/
37+
38+
package main
39+
40+
import (
41+
"context"
42+
"crypto/rand"
43+
"encoding/json"
44+
"errors"
45+
"fmt"
46+
"io"
47+
"math/big"
48+
"net/http"
49+
"regexp"
50+
"strings"
51+
)
52+
53+
// API Endpoints
54+
const (
55+
CatAPIURL = "https://api.thecatapi.com/v1/breeds"
56+
RobotWikiURL = "https://en.wikipedia.org/wiki/List_of_fictional_robots_and_androids"
57+
WikiURL = "https://en.wikipedia.org/wiki/"
58+
GitHubReleasesURL = "https://api.github.com/repos/tektoncd/pipeline/releases"
59+
)
60+
61+
// Structs to hold API responses
62+
type CatBreed struct {
63+
Name string `json:"name"`
64+
}
65+
66+
type Release struct {
67+
Name string `json:"name"`
68+
}
69+
70+
func httpGet(url string) (*http.Response, error) {
71+
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
72+
if err != nil {
73+
return nil, err
74+
}
75+
return http.DefaultClient.Do(req)
76+
}
77+
78+
// Fetch cat breeds and organize them by first letter
79+
func getCatBreeds() (map[string][]string, error) {
80+
resp, err := httpGet(CatAPIURL)
81+
if err != nil {
82+
return nil, err
83+
}
84+
defer resp.Body.Close()
85+
86+
var breeds []CatBreed
87+
if err := json.NewDecoder(resp.Body).Decode(&breeds); err != nil {
88+
return nil, err
89+
}
90+
91+
catDict := make(map[string][]string)
92+
for _, breed := range breeds {
93+
firstLetter := strings.ToUpper(string(breed.Name[0]))
94+
catDict[firstLetter] = append(catDict[firstLetter], breed.Name)
95+
}
96+
97+
return catDict, nil
98+
}
99+
100+
// Scrape Wikipedia for robot names
101+
func getRobotNames() (map[string][]string, error) {
102+
resp, err := httpGet(RobotWikiURL)
103+
if err != nil {
104+
return nil, err
105+
}
106+
defer resp.Body.Close()
107+
108+
bodyBytes, err := io.ReadAll(resp.Body)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
robotDict := make(map[string][]string)
114+
115+
// Regex to extract robot names from <li><b>Robot Name</b>
116+
re := regexp.MustCompile(`<li>\s*<b>\s*<a[^>]*>([^<]+)</a>\s*</b>`)
117+
matches := re.FindAllStringSubmatch(string(bodyBytes), -1)
118+
119+
for _, match := range matches {
120+
if len(match) > 1 {
121+
name := strings.TrimSpace(match[1])
122+
firstLetter := strings.ToUpper(string(name[0]))
123+
robotDict[firstLetter] = append(robotDict[firstLetter], name)
124+
}
125+
}
126+
127+
return robotDict, nil
128+
}
129+
130+
// Fetch past releases from GitHub
131+
func getPastReleases() (map[string]bool, error) {
132+
resp, err := httpGet(GitHubReleasesURL + "?per_page=100")
133+
if err != nil {
134+
return nil, err
135+
}
136+
defer resp.Body.Close()
137+
138+
var releases []Release
139+
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {
140+
return nil, err
141+
}
142+
143+
pastReleases := make(map[string]bool)
144+
for _, release := range releases {
145+
pastReleases[release.Name] = true
146+
}
147+
148+
return pastReleases, nil
149+
}
150+
151+
func randomElement(array []string) (string, error) {
152+
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(array))))
153+
if err != nil {
154+
return "", err
155+
}
156+
return array[n.Int64()], nil
157+
}
158+
159+
// Generate a unique release name
160+
func generateUniqueTuple() (string, string, error) {
161+
catBreeds, err := getCatBreeds()
162+
if err != nil {
163+
return "", "", err
164+
}
165+
166+
robotNames, err := getRobotNames()
167+
if err != nil {
168+
return "", "", err
169+
}
170+
171+
pastReleases, err := getPastReleases()
172+
if err != nil {
173+
return "", "", err
174+
}
175+
176+
// Find common letters
177+
commonLetters := []string{}
178+
for letter := range catBreeds {
179+
if _, exists := robotNames[letter]; exists {
180+
commonLetters = append(commonLetters, letter)
181+
}
182+
}
183+
184+
if len(commonLetters) == 0 {
185+
return "", "", errors.New("no matching names found")
186+
}
187+
188+
maxAttempts := 10
189+
for range maxAttempts {
190+
chosenLetter, err := randomElement(commonLetters)
191+
if err != nil {
192+
return "", "", err
193+
}
194+
195+
cat, err := randomElement(catBreeds[chosenLetter])
196+
if err != nil {
197+
return "", "", err
198+
}
199+
200+
robot, err := randomElement(robotNames[chosenLetter])
201+
if err != nil {
202+
return "", "", err
203+
}
204+
205+
newName := cat + " " + robot
206+
if !pastReleases[newName] {
207+
return cat, robot, nil
208+
}
209+
}
210+
211+
return "", "", errors.New("could not generate a unique name after multiple attempts")
212+
}
213+
214+
func printJsonError(err error) {
215+
fmt.Println(`{"error": "` + err.Error() + `"}`) //nolint:forbidigo
216+
}
217+
218+
func main() {
219+
cat, robot, err := generateUniqueTuple()
220+
if err != nil {
221+
printJsonError(err)
222+
return
223+
}
224+
225+
output := map[string]string{
226+
"release_name": cat + " " + robot,
227+
"cat_breed_url": WikiURL + strings.ReplaceAll(cat, " ", "_"),
228+
"robot_url": WikiURL + strings.ReplaceAll(robot, " ", "_"),
229+
}
230+
231+
jsonOutput, err := json.MarshalIndent(output, "", " ")
232+
if err != nil {
233+
printJsonError(err)
234+
return
235+
}
236+
fmt.Println(string(jsonOutput)) //nolint:forbidigo
237+
}

0 commit comments

Comments
 (0)