Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/update-builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ jobs:
"$HOME/.config/containers/registries.conf"
skopeo login ghcr.io -u gh-action -p "$GITHUB_TOKEN"
docker login ghcr.io -u gh-action -p "$GITHUB_TOKEN"
make wf-update-builder
make __update-builder

24 changes: 0 additions & 24 deletions .github/workflows/update-knative-hack.yaml

This file was deleted.

17 changes: 5 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -308,23 +308,16 @@ schema-check: ## Check that func.yaml schema is up-to-date
##@ Hack scripting
######################

### Local section - Can be run locally!

.PHONY: generate-kn-components-local
generate-kn-components-local: ## Generate knative components locally
cd hack && go run ./cmd/update-knative-components "local"
.PHONY: hack-generate-components
hack-generate-components: ## Regenerate components in hack/ dir
cd hack && go run ./cmd/components

.PHONY: test-hack
test-hack:
cd hack && go test ./... -v

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

.PHONY: update-builder
wf-update-builder: # Used in automation
__update-builder: # Used in automation
cd hack && go run ./cmd/update-builder

### end of automation section
244 changes: 244 additions & 0 deletions hack/cmd/components/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// 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
}
Loading
Loading