mirror of https://github.com/knative/func.git
245 lines
6.9 KiB
Go
245 lines
6.9 KiB
Go
// Package main implements a tool for automatically updating component
|
|
// versions for use in the hack/* scripts.
|
|
//
|
|
// Files interacted with:
|
|
// 1. The source-of-truth file at hack/component-versions.json
|
|
// 2. Autogenerated script at hack/component-versions.sh
|
|
//
|
|
// USAGE:
|
|
//
|
|
// This is running on semi-auto basis where versions are being auto bumped via
|
|
// PRs sent to main. Semi-auto because if repo is missing 'owner' or 'repo' fields
|
|
// in the .json it will not be automatically bumped (it has no repo to look).
|
|
// This is intentional for components we dont want autobumped like this.
|
|
// The source-of-truth file is found in this repo @root/hack/component-versions.json
|
|
//
|
|
// ADD NEW/MODIFY COMPONENTS
|
|
//
|
|
// 1. Edit source-of-truth .json file
|
|
// ! If a component is missing "owner" or "repo" it will not be auto-bumped
|
|
// 2. If new component was added:
|
|
// - Edit the autogenerated text just below here 'versionsScriptTemplate'
|
|
// 3. Regenerate using Makefile - find target 'hack-generate-components'
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
github "github.com/google/go-github/v68/github"
|
|
)
|
|
|
|
const (
|
|
fileScript string = "component-versions.sh"
|
|
fileJson string = "component-versions.json"
|
|
|
|
versionsScriptTemplate string = `#!/usr/bin/env bash
|
|
|
|
# AUTOGENERATED FILE - edit versions in ./component-versions.json.
|
|
# If you want to add/modify these components, please read the how-to steps in
|
|
# ./cmd/components/main.go.
|
|
# You can regenerate with "make hack-generate-components".
|
|
|
|
set_versions() {
|
|
# Note: Kubernetes Version node image per Kind releases (full hash is suggested):
|
|
# https://github.com/kubernetes-sigs/kind/releases
|
|
kind_node_version={{.KindNode.Version}}
|
|
|
|
# find source-of-truth in component-versions.json to add/modify components
|
|
knative_serving_version="{{.Serving.Version}}"
|
|
knative_eventing_version="{{.Eventing.Version}}"
|
|
contour_version="{{.Contour.Version}}"
|
|
tekton_version="{{.Tekton.Version}}"
|
|
pac_version="{{.Pac.Version}}"
|
|
}
|
|
`
|
|
)
|
|
|
|
// Individual component info like for "Serving" or "Eventing"
|
|
// If you want to add new component, read the comment at the top of the file!
|
|
type Component struct {
|
|
Version string `json:"version"`
|
|
Owner string `json:"owner,omitempty"`
|
|
Repo string `json:"repo,omitempty"`
|
|
}
|
|
|
|
// make iterable struct
|
|
type ComponentList map[string]*Component
|
|
|
|
func main() {
|
|
// Set up context for possible signal inputs to not disrupt cleanup process.
|
|
// This is not gonna do much for workflows since they finish and shutdown
|
|
// but in case of local testing - dont leave left over resources on disk/RAM.
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
sigs := make(chan os.Signal, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
go func() {
|
|
<-sigs
|
|
cancel()
|
|
<-sigs
|
|
os.Exit(130)
|
|
}()
|
|
|
|
getClient := func(token string) *github.Client {
|
|
if token != "" {
|
|
fmt.Println("client with token")
|
|
return github.NewClient(nil).WithAuthToken(token)
|
|
}
|
|
return github.NewClient(nil)
|
|
}
|
|
client := getClient(os.Getenv("GITHUB_TOKEN"))
|
|
|
|
// Read source-of-truth .json
|
|
componentList, err := readVersions(fileJson)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// update componentList in-situ
|
|
updated, err := update(ctx, client, &componentList)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to update %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if !updated {
|
|
// nothing was updated, nothing to do
|
|
fmt.Println("no newer versions found, re-generating .sh just in case")
|
|
// regenerate .sh to keep up to date if changed
|
|
err = writeScript(componentList, fileScript)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to re-generate script: %v", err)
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Println("all good")
|
|
os.Exit(0)
|
|
}
|
|
|
|
if err := writeFiles(componentList, fileScript, fileJson); err != nil {
|
|
err = fmt.Errorf("failed to write files: %v", err)
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("files updated!")
|
|
}
|
|
|
|
// do the update for each repo defined
|
|
func update(ctx context.Context, client *github.Client, cl *ComponentList) (bool, error) {
|
|
fmt.Println("Getting latest releases")
|
|
updated := false
|
|
for _, c := range *cl {
|
|
if c.Owner == "" || c.Repo == "" {
|
|
//skipping auto updates
|
|
continue
|
|
}
|
|
|
|
newV, err := getLatestVersion(ctx, client, c.Owner, c.Repo)
|
|
if err != nil {
|
|
err = fmt.Errorf("error while getting latest v of %s/%s: %v", c.Owner, c.Repo, err)
|
|
return false, err
|
|
}
|
|
|
|
if c.Version != newV {
|
|
fmt.Printf("bump %v: %v --> %v\n", fmt.Sprintf("%s/%s", c.Owner, c.Repo), c.Version, newV)
|
|
c.Version = newV
|
|
updated = true
|
|
}
|
|
}
|
|
return updated, nil
|
|
}
|
|
|
|
// read (unmarshal) component versions from .json
|
|
func readVersions(file string) (c ComponentList, err error) {
|
|
fmt.Println("Reading versions from source-of-truth")
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = json.Unmarshal(data, &c)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Overwrite the 'source of truth' file - .json and regenerate new script
|
|
// with new versions from 'v'.
|
|
// Arguments 'script' & 'json' are paths to files for autogenerated script and
|
|
// source (json) file respectively.
|
|
func writeFiles(cl ComponentList, script, json string) error {
|
|
fmt.Print("Writing files")
|
|
// write to json
|
|
err := writeSource(cl, json)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write to json: %v", err)
|
|
}
|
|
// write to script file
|
|
err = writeScript(cl, script)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate script: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// write to 'source of truth' .json with updated versions (if pulled latest)
|
|
func writeSource(cl ComponentList, file string) error {
|
|
vB, err := json.MarshalIndent(cl, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("cant Marshal versions: %v", err)
|
|
}
|
|
f, err := os.Create(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
_, err = f.Write(append(vB, '\n')) // append newline for reviewdog
|
|
return err
|
|
}
|
|
|
|
// write the autogenerated script based on 'cl'
|
|
func writeScript(cl ComponentList, file string) error {
|
|
tmpl, err := template.New("versions").Parse(versionsScriptTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f, err := os.Create(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
if err := tmpl.Execute(f, cl); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// get latest version of owner/repo via GH API
|
|
func getLatestVersion(ctx context.Context, client *github.Client, owner string, repo string) (v string, err error) {
|
|
rr, res, err := client.Repositories.GetLatestRelease(ctx, owner, repo)
|
|
if err != nil {
|
|
err = fmt.Errorf("error: request for latest %s release: %v", owner+"/"+repo, err)
|
|
return
|
|
}
|
|
if res.StatusCode < 200 && res.StatusCode > 299 {
|
|
err = fmt.Errorf("error: Return status code of request for latest %s release is %d", owner+"/"+repo, res.StatusCode)
|
|
return
|
|
}
|
|
v = *rr.Name
|
|
if v == "" {
|
|
return "", fmt.Errorf("internal error: returned latest release name is empty for '%s'", repo)
|
|
}
|
|
return v, nil
|
|
}
|