Skip to content

Commit 7fb68ef

Browse files
authored
drastically simplify the components update (knative#2959)
- now runs in update-deps.sh - only need to modify json and autogen script
1 parent 2e49dd0 commit 7fb68ef

File tree

10 files changed

+422
-541
lines changed

10 files changed

+422
-541
lines changed

.github/workflows/update-builder.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ jobs:
2727
"$HOME/.config/containers/registries.conf"
2828
skopeo login ghcr.io -u gh-action -p "$GITHUB_TOKEN"
2929
docker login ghcr.io -u gh-action -p "$GITHUB_TOKEN"
30-
make wf-update-builder
31-
30+
make __update-builder
31+

.github/workflows/update-knative-hack.yaml

Lines changed: 0 additions & 24 deletions
This file was deleted.

Makefile

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -308,23 +308,16 @@ schema-check: ## Check that func.yaml schema is up-to-date
308308
##@ Hack scripting
309309
######################
310310

311-
### Local section - Can be run locally!
312-
313-
.PHONY: generate-kn-components-local
314-
generate-kn-components-local: ## Generate knative components locally
315-
cd hack && go run ./cmd/update-knative-components "local"
311+
.PHONY: hack-generate-components
312+
hack-generate-components: ## Regenerate components in hack/ dir
313+
cd hack && go run ./cmd/components
316314

317315
.PHONY: test-hack
318316
test-hack:
319317
cd hack && go test ./... -v
320318

321-
### Automated section - This gets run in workflows, scripts etc.
322-
.PHONY: wf-generate-kn-components
323-
wf-generate-kn-components: # Generate kn components - used in automation
324-
cd hack && go run ./cmd/update-knative-components
319+
## This is used by workflows
325320

326321
.PHONY: update-builder
327-
wf-update-builder: # Used in automation
322+
__update-builder: # Used in automation
328323
cd hack && go run ./cmd/update-builder
329-
330-
### end of automation section

hack/cmd/components/main.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// Package main implements a tool for automatically updating component
2+
// versions for use in the hack/* scripts.
3+
//
4+
// Files interacted with:
5+
// 1. The source-of-truth file at hack/component-versions.json
6+
// 2. Autogenerated script at hack/component-versions.sh
7+
//
8+
// USAGE:
9+
//
10+
// This is running on semi-auto basis where versions are being auto bumped via
11+
// PRs sent to main. Semi-auto because if repo is missing 'owner' or 'repo' fields
12+
// in the .json it will not be automatically bumped (it has no repo to look).
13+
// This is intentional for components we dont want autobumped like this.
14+
// The source-of-truth file is found in this repo @root/hack/component-versions.json
15+
//
16+
// ADD NEW/MODIFY COMPONENTS
17+
//
18+
// 1. Edit source-of-truth .json file
19+
// ! If a component is missing "owner" or "repo" it will not be auto-bumped
20+
// 2. If new component was added:
21+
// - Edit the autogenerated text just below here 'versionsScriptTemplate'
22+
// 3. Regenerate using Makefile - find target 'hack-generate-components'
23+
package main
24+
25+
import (
26+
"context"
27+
"encoding/json"
28+
"fmt"
29+
"html/template"
30+
"os"
31+
"os/signal"
32+
"syscall"
33+
34+
github "github.com/google/go-github/v68/github"
35+
)
36+
37+
const (
38+
fileScript string = "component-versions.sh"
39+
fileJson string = "component-versions.json"
40+
41+
versionsScriptTemplate string = `#!/usr/bin/env bash
42+
43+
# AUTOGENERATED FILE - edit versions in ./component-versions.json.
44+
# If you want to add/modify these components, please read the how-to steps in
45+
# ./cmd/components/main.go.
46+
# You can regenerate with "make hack-generate-components".
47+
48+
set_versions() {
49+
# Note: Kubernetes Version node image per Kind releases (full hash is suggested):
50+
# https://github.com/kubernetes-sigs/kind/releases
51+
kind_node_version={{.KindNode.Version}}
52+
53+
# find source-of-truth in component-versions.json to add/modify components
54+
knative_serving_version="{{.Serving.Version}}"
55+
knative_eventing_version="{{.Eventing.Version}}"
56+
contour_version="{{.Contour.Version}}"
57+
tekton_version="{{.Tekton.Version}}"
58+
pac_version="{{.Pac.Version}}"
59+
}
60+
`
61+
)
62+
63+
// Individual component info like for "Serving" or "Eventing"
64+
// If you want to add new component, read the comment at the top of the file!
65+
type Component struct {
66+
Version string `json:"version"`
67+
Owner string `json:"owner,omitempty"`
68+
Repo string `json:"repo,omitempty"`
69+
}
70+
71+
// make iterable struct
72+
type ComponentList map[string]*Component
73+
74+
func main() {
75+
// Set up context for possible signal inputs to not disrupt cleanup process.
76+
// This is not gonna do much for workflows since they finish and shutdown
77+
// but in case of local testing - dont leave left over resources on disk/RAM.
78+
ctx, cancel := context.WithCancel(context.Background())
79+
defer cancel()
80+
sigs := make(chan os.Signal, 1)
81+
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
82+
go func() {
83+
<-sigs
84+
cancel()
85+
<-sigs
86+
os.Exit(130)
87+
}()
88+
89+
getClient := func(token string) *github.Client {
90+
if token != "" {
91+
fmt.Println("client with token")
92+
return github.NewClient(nil).WithAuthToken(token)
93+
}
94+
return github.NewClient(nil)
95+
}
96+
client := getClient(os.Getenv("GITHUB_TOKEN"))
97+
98+
// Read source-of-truth .json
99+
componentList, err := readVersions(fileJson)
100+
if err != nil {
101+
fmt.Fprintln(os.Stderr, err)
102+
os.Exit(1)
103+
}
104+
105+
// update componentList in-situ
106+
updated, err := update(ctx, client, &componentList)
107+
if err != nil {
108+
fmt.Fprintf(os.Stderr, "failed to update %v\n", err)
109+
os.Exit(1)
110+
}
111+
112+
if !updated {
113+
// nothing was updated, nothing to do
114+
fmt.Println("no newer versions found, re-generating .sh just in case")
115+
// regenerate .sh to keep up to date if changed
116+
err = writeScript(componentList, fileScript)
117+
if err != nil {
118+
err = fmt.Errorf("failed to re-generate script: %v", err)
119+
fmt.Fprintln(os.Stderr, err)
120+
os.Exit(1)
121+
}
122+
fmt.Println("all good")
123+
os.Exit(0)
124+
}
125+
126+
if err := writeFiles(componentList, fileScript, fileJson); err != nil {
127+
err = fmt.Errorf("failed to write files: %v", err)
128+
fmt.Fprintln(os.Stderr, err)
129+
os.Exit(1)
130+
}
131+
132+
fmt.Println("files updated!")
133+
}
134+
135+
// do the update for each repo defined
136+
func update(ctx context.Context, client *github.Client, cl *ComponentList) (bool, error) {
137+
fmt.Println("Getting latest releases")
138+
updated := false
139+
for _, c := range *cl {
140+
if c.Owner == "" || c.Repo == "" {
141+
//skipping auto updates
142+
continue
143+
}
144+
145+
newV, err := getLatestVersion(ctx, client, c.Owner, c.Repo)
146+
if err != nil {
147+
err = fmt.Errorf("error while getting latest v of %s/%s: %v", c.Owner, c.Repo, err)
148+
return false, err
149+
}
150+
151+
if c.Version != newV {
152+
fmt.Printf("bump %v: %v --> %v\n", fmt.Sprintf("%s/%s", c.Owner, c.Repo), c.Version, newV)
153+
c.Version = newV
154+
updated = true
155+
}
156+
}
157+
return updated, nil
158+
}
159+
160+
// read (unmarshal) component versions from .json
161+
func readVersions(file string) (c ComponentList, err error) {
162+
fmt.Println("Reading versions from source-of-truth")
163+
data, err := os.ReadFile(file)
164+
if err != nil {
165+
return
166+
}
167+
err = json.Unmarshal(data, &c)
168+
if err != nil {
169+
return
170+
}
171+
return
172+
}
173+
174+
// Overwrite the 'source of truth' file - .json and regenerate new script
175+
// with new versions from 'v'.
176+
// Arguments 'script' & 'json' are paths to files for autogenerated script and
177+
// source (json) file respectively.
178+
func writeFiles(cl ComponentList, script, json string) error {
179+
fmt.Print("Writing files")
180+
// write to json
181+
err := writeSource(cl, json)
182+
if err != nil {
183+
return fmt.Errorf("failed to write to json: %v", err)
184+
}
185+
// write to script file
186+
err = writeScript(cl, script)
187+
if err != nil {
188+
return fmt.Errorf("failed to generate script: %v", err)
189+
}
190+
return nil
191+
}
192+
193+
// write to 'source of truth' .json with updated versions (if pulled latest)
194+
func writeSource(cl ComponentList, file string) error {
195+
vB, err := json.MarshalIndent(cl, "", " ")
196+
if err != nil {
197+
return fmt.Errorf("cant Marshal versions: %v", err)
198+
}
199+
f, err := os.Create(file)
200+
if err != nil {
201+
return err
202+
}
203+
204+
defer f.Close()
205+
206+
_, err = f.Write(append(vB, '\n')) // append newline for reviewdog
207+
return err
208+
}
209+
210+
// write the autogenerated script based on 'cl'
211+
func writeScript(cl ComponentList, file string) error {
212+
tmpl, err := template.New("versions").Parse(versionsScriptTemplate)
213+
if err != nil {
214+
return err
215+
}
216+
f, err := os.Create(file)
217+
if err != nil {
218+
return err
219+
}
220+
defer f.Close()
221+
222+
if err := tmpl.Execute(f, cl); err != nil {
223+
return err
224+
}
225+
return nil
226+
}
227+
228+
// get latest version of owner/repo via GH API
229+
func getLatestVersion(ctx context.Context, client *github.Client, owner string, repo string) (v string, err error) {
230+
rr, res, err := client.Repositories.GetLatestRelease(ctx, owner, repo)
231+
if err != nil {
232+
err = fmt.Errorf("error: request for latest %s release: %v", owner+"/"+repo, err)
233+
return
234+
}
235+
if res.StatusCode < 200 && res.StatusCode > 299 {
236+
err = fmt.Errorf("error: Return status code of request for latest %s release is %d", owner+"/"+repo, res.StatusCode)
237+
return
238+
}
239+
v = *rr.Name
240+
if v == "" {
241+
return "", fmt.Errorf("internal error: returned latest release name is empty for '%s'", repo)
242+
}
243+
return v, nil
244+
}

0 commit comments

Comments
 (0)