Skip to content

Commit 3f7a568

Browse files
committed
drastically simplify the components update
- now runs in update-deps.sh - only need to modify json and autogen script
1 parent e06397d commit 3f7a568

File tree

9 files changed

+424
-540
lines changed

9 files changed

+424
-540
lines changed

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

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

Makefile

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -308,23 +308,17 @@ 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

321+
## TODO: gauron99 -- include this in update-deps as well
326322
.PHONY: update-builder
327-
wf-update-builder: # Used in automation
328-
cd hack && go run ./cmd/update-builder
329-
330-
### end of automation section
323+
__update-builder: # Used in automation
324+
cd hack && go run ./cmd/builder

hack/cmd/components/main.go

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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+
client := &github.Client{}
90+
// if running locally you might not have token, still works for public repo
91+
// you might hit a gh api rate limit
92+
if os.Getenv("GITHUB_TOKEN") != "" {
93+
fmt.Println("client with token")
94+
client = github.NewClient(nil).WithAuthToken(os.Getenv("GITHUB_TOKEN"))
95+
} else {
96+
fmt.Println("client without token")
97+
client = github.NewClient(nil)
98+
}
99+
100+
// Read source-of-truth .json
101+
componentList, err := readVersions(fileJson)
102+
if err != nil {
103+
fmt.Fprintln(os.Stderr, err)
104+
os.Exit(1)
105+
}
106+
107+
// update componentList in-situ
108+
updated, err := update(ctx, client, &componentList)
109+
if err != nil {
110+
fmt.Fprintf(os.Stderr, "failed to update %v\n", err)
111+
os.Exit(1)
112+
}
113+
114+
if !updated {
115+
// nothing was updated, nothing to do
116+
fmt.Println("no newer versions found, re-generating .sh just in case")
117+
// regenerate .sh to keep up to date if changed
118+
err = writeScript(componentList, fileScript)
119+
if err != nil {
120+
err = fmt.Errorf("failed to re-generate script: %v", err)
121+
fmt.Fprintln(os.Stderr, err)
122+
os.Exit(1)
123+
}
124+
fmt.Println("all good")
125+
os.Exit(0)
126+
}
127+
128+
if err := writeFiles(componentList, fileScript, fileJson); err != nil {
129+
err = fmt.Errorf("failed to write files: %v", err)
130+
fmt.Fprintln(os.Stderr, err)
131+
os.Exit(1)
132+
}
133+
134+
fmt.Println("files updated!")
135+
}
136+
137+
// do the update for each repo defined
138+
func update(ctx context.Context, client *github.Client, cl *ComponentList) (bool, error) {
139+
fmt.Println("Getting latest releases")
140+
updated := false
141+
for _, c := range *cl {
142+
if c.Owner == "" || c.Repo == "" {
143+
//skipping auto updates
144+
continue
145+
}
146+
147+
newV, err := getLatestVersion(ctx, client, c.Owner, c.Repo)
148+
if err != nil {
149+
err = fmt.Errorf("error while getting latest v of %s/%s: %v", c.Owner, c.Repo, err)
150+
return false, err
151+
}
152+
153+
if c.Version != newV {
154+
fmt.Printf("bump %v: %v --> %v\n", fmt.Sprintf("%s/%s", c.Owner, c.Repo), c.Version, newV)
155+
c.Version = newV
156+
updated = true
157+
}
158+
}
159+
return updated, nil
160+
}
161+
162+
// read (unmarshal) component versions from .json
163+
func readVersions(file string) (c ComponentList, err error) {
164+
fmt.Println("Reading versions from source-of-truth")
165+
data, err := os.ReadFile(file)
166+
if err != nil {
167+
return
168+
}
169+
err = json.Unmarshal(data, &c)
170+
if err != nil {
171+
return
172+
}
173+
return
174+
}
175+
176+
// Overwrite the 'source of truth' file - .json and regenerate new script
177+
// with new versions from 'v'.
178+
// Arguments 'script' & 'json' are paths to files for autogenerated script and
179+
// source (json) file respectively.
180+
func writeFiles(cl ComponentList, script, json string) error {
181+
fmt.Print("Writing files")
182+
// write to json
183+
err := writeSource(cl, json)
184+
if err != nil {
185+
return fmt.Errorf("failed to write to json: %v", err)
186+
}
187+
// write to script file
188+
err = writeScript(cl, script)
189+
if err != nil {
190+
return fmt.Errorf("failed to generate script: %v", err)
191+
}
192+
return nil
193+
}
194+
195+
// write to 'source of truth' .json with updated versions (if pulled latest)
196+
func writeSource(cl ComponentList, file string) error {
197+
vB, err := json.MarshalIndent(cl, "", " ")
198+
if err != nil {
199+
return fmt.Errorf("cant Marshal versions: %v", err)
200+
}
201+
f, err := os.Create(file)
202+
if err != nil {
203+
return err
204+
}
205+
206+
defer f.Close()
207+
208+
_, err = f.Write(append(vB, '\n')) // append newline for reviewdog
209+
return err
210+
}
211+
212+
// write the autogenerated script based on 'cl'
213+
func writeScript(cl ComponentList, file string) error {
214+
tmpl, err := template.New("versions").Parse(versionsScriptTemplate)
215+
if err != nil {
216+
return err
217+
}
218+
f, err := os.Create(file)
219+
if err != nil {
220+
return err
221+
}
222+
defer f.Close()
223+
224+
if err := tmpl.Execute(f, cl); err != nil {
225+
return err
226+
}
227+
return nil
228+
}
229+
230+
// get latest version of owner/repo via GH API
231+
func getLatestVersion(ctx context.Context, client *github.Client, owner string, repo string) (v string, err error) {
232+
rr, res, err := client.Repositories.GetLatestRelease(ctx, owner, repo)
233+
if err != nil {
234+
err = fmt.Errorf("error: request for latest %s release: %v", owner+"/"+repo, err)
235+
return
236+
}
237+
if res.StatusCode < 200 && res.StatusCode > 299 {
238+
err = fmt.Errorf("error: Return status code of request for latest %s release is %d", owner+"/"+repo, res.StatusCode)
239+
return
240+
}
241+
v = *rr.Name
242+
if v == "" {
243+
return "", fmt.Errorf("internal error: returned latest release name is empty for '%s'", repo)
244+
}
245+
return v, nil
246+
}

0 commit comments

Comments
 (0)