From 5c161009fbffa45051379ce4b51bb580632d1e9c Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sun, 10 May 2026 17:31:40 +0530 Subject: [PATCH 1/5] Add automation commands for updating platform versions and CA bundle - Introduced new Makefile targets: `update-quarkus-platform`, `update-springboot-platform`, and `update-ca-bundle` to automate the process of updating respective platform versions in templates. - Updated GitHub Actions workflows for `update-ca-bundle`, `update-quarkus-platform`, and `update-springboot-platform` to utilize Go scripts instead of Node.js for executing updates. - Added new Go scripts for handling the update logic for CA bundle, Quarkus platform, and Spring Boot platform, including PR creation and version checks. - Implemented shared GitHub client functionality for PR management and version retrieval. This change enhances the automation of platform updates and streamlines the CI/CD process for dependency management. --- .github/workflows/update-ca-bundle.yaml | 10 +- .../workflows/update-quarkus-platform.yaml | 9 +- .../workflows/update-springboot-platform.yaml | 9 +- Makefile | 12 + hack/cmd/shared/github.go | 111 +++++++ hack/cmd/update-ca-bundle/main.go | 100 +++++++ hack/cmd/update-quarkus-platform/main.go | 173 +++++++++++ hack/cmd/update-springboot-platform/main.go | 270 ++++++++++++++++++ 8 files changed, 673 insertions(+), 21 deletions(-) create mode 100644 hack/cmd/shared/github.go create mode 100644 hack/cmd/update-ca-bundle/main.go create mode 100644 hack/cmd/update-quarkus-platform/main.go create mode 100644 hack/cmd/update-springboot-platform/main.go diff --git a/.github/workflows/update-ca-bundle.yaml b/.github/workflows/update-ca-bundle.yaml index 56bb9bcbdf..a434a86c59 100644 --- a/.github/workflows/update-ca-bundle.yaml +++ b/.github/workflows/update-ca-bundle.yaml @@ -20,13 +20,9 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: "20" - - name: Install NPM deps. - run: npm install octokit@3.2.1 + - uses: knative/actions/setup-go@main - name: Create PR env: GITHUB_TOKEN: ${{ github.token }} - run: node ./hack/update-ca-bundle.js - + GITHUB_REPOSITORY: ${{ github.repository }} + run: cd hack && go run ./cmd/update-ca-bundle diff --git a/.github/workflows/update-quarkus-platform.yaml b/.github/workflows/update-quarkus-platform.yaml index 11e4284fa0..eb0fe69741 100644 --- a/.github/workflows/update-quarkus-platform.yaml +++ b/.github/workflows/update-quarkus-platform.yaml @@ -21,17 +21,12 @@ jobs: steps: - uses: actions/checkout@v4 - uses: knative/actions/setup-go@main - - uses: actions/setup-node@v4 - with: - node-version: "20" - uses: actions/setup-java@v4 with: java-version: 21 distribution: 'temurin' - - name: Install NPM deps. - run: npm install xml2js@0.6.2 octokit@3.2.1 - name: Create PR env: GITHUB_TOKEN: ${{ github.token }} - run: node ./hack/update-quarkus-platform.js - + GITHUB_REPOSITORY: ${{ github.repository }} + run: cd hack && go run ./cmd/update-quarkus-platform diff --git a/.github/workflows/update-springboot-platform.yaml b/.github/workflows/update-springboot-platform.yaml index feb0b50c5d..6144fe209a 100644 --- a/.github/workflows/update-springboot-platform.yaml +++ b/.github/workflows/update-springboot-platform.yaml @@ -21,17 +21,12 @@ jobs: steps: - uses: actions/checkout@v4 - uses: knative/actions/setup-go@main - - uses: actions/setup-node@v4 - with: - node-version: "20" - uses: actions/setup-java@v4 with: java-version: 21 distribution: 'temurin' - - name: Install NPM deps. - run: npm install xml2js@0.6.2 octokit@3.2.1 yaml@2.4.5 semver@7.6.3 - name: Create PR env: GITHUB_TOKEN: ${{ github.token }} - run: node ./hack/update-springboot-platform.js - + GITHUB_REPOSITORY: ${{ github.repository }} + run: cd hack && go run ./cmd/update-springboot-platform diff --git a/Makefile b/Makefile index e35743e2b0..8a30f172f2 100644 --- a/Makefile +++ b/Makefile @@ -422,6 +422,18 @@ test-hack: __update-builder: # Used in automation cd hack && go run ./cmd/update-builder +.PHONY: update-quarkus-platform +update-quarkus-platform: ## Update Quarkus platform version in templates + cd hack && go run ./cmd/update-quarkus-platform + +.PHONY: update-springboot-platform +update-springboot-platform: ## Update Spring Boot platform version in templates + cd hack && go run ./cmd/update-springboot-platform + +.PHONY: update-ca-bundle +update-ca-bundle: ## Update CA bundle in templates + cd hack && go run ./cmd/update-ca-bundle + .PHONY: setup-githooks setup-githooks: git config --local core.hooksPath .githooks/ diff --git a/hack/cmd/shared/github.go b/hack/cmd/shared/github.go new file mode 100644 index 0000000000..b506986547 --- /dev/null +++ b/hack/cmd/shared/github.go @@ -0,0 +1,111 @@ +package shared + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/google/go-github/v68/github" + "golang.org/x/oauth2" +) + +// NewGHClient constructs an authenticated GitHub client using GITHUB_TOKEN. +func NewGHClient(ctx context.Context) *github.Client { + return github.NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: os.Getenv("GITHUB_TOKEN"), + }))) +} + +// RepoFromEnv splits GITHUB_REPOSITORY ("owner/repo") into owner and repo. +func RepoFromEnv() (owner, repo string, err error) { + v := os.Getenv("GITHUB_REPOSITORY") + if v == "" { + return "", "", fmt.Errorf("GITHUB_REPOSITORY is not set") + } + parts := strings.SplitN(v, "/", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("GITHUB_REPOSITORY has unexpected format: %q", v) + } + return parts[0], parts[1], nil +} + +// PRExists returns true if any open pull request satisfies pred(pr.GetTitle()). +func PRExists(ctx context.Context, client *github.Client, owner, repo string, pred func(string) bool) (bool, error) { + opts := &github.PullRequestListOptions{ + State: "open", + ListOptions: github.ListOptions{ + PerPage: 10, + }, + } + for { + prs, resp, err := client.PullRequests.List(ctx, owner, repo, opts) + if err != nil { + return false, fmt.Errorf("cannot list pull requests: %w", err) + } + for _, pr := range prs { + if pred(pr.GetTitle()) { + return true, nil + } + } + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + return false, nil +} + +// PrepareBranch configures git identity, creates branchName, runs codegen, +// stages filesToAdd, commits with prTitle as the message, and pushes. +func PrepareBranch(ctx context.Context, branchName, prTitle string, filesToAdd []string) error { + steps := [][]string{ + {"git", "config", "user.email", "automation@knative.team"}, + {"git", "config", "user.name", "Knative Automation"}, + {"git", "checkout", "-b", branchName}, + {"make", "generate/zz_filesystem_generated.go"}, + } + for _, args := range steps { + if err := RunCmd(ctx, args[0], args[1:]...); err != nil { + return err + } + } + + addArgs := append([]string{"add"}, filesToAdd...) + if err := RunCmd(ctx, "git", addArgs...); err != nil { + return err + } + + if err := RunCmd(ctx, "git", "commit", "-m", prTitle); err != nil { + return err + } + + return RunCmd(ctx, "git", "push", "--set-upstream", "origin", branchName) +} + +// CreatePR opens a pull request against main with the given title. +// head should be in the form "owner:branchName". +func CreatePR(ctx context.Context, client *github.Client, owner, repo, title, head string) error { + _, _, err := client.PullRequests.Create(ctx, owner, repo, &github.NewPullRequest{ + Title: github.Ptr(title), + Body: github.Ptr(title), + Base: github.Ptr("main"), + Head: github.Ptr(head), + }) + if err != nil { + return fmt.Errorf("cannot create pull request: %w", err) + } + return nil +} + +// RunCmd runs name with args, streaming stdout/stderr to the process output. +func RunCmd(ctx context.Context, name string, args ...string) error { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("command %q failed: %w", name+" "+strings.Join(args, " "), err) + } + return nil +} diff --git a/hack/cmd/update-ca-bundle/main.go b/hack/cmd/update-ca-bundle/main.go new file mode 100644 index 0000000000..7c453e8df7 --- /dev/null +++ b/hack/cmd/update-ca-bundle/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/exec" + "os/signal" + "syscall" + "time" + + "knative.dev/func/hack/cmd/shared" +) + +const ( + caBundlePath = "templates/certs/ca-certificates.crt" + prTitle = "chore: update CA bundle" +) + +func main() { + 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) + }() + + if err := run(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + os.Exit(1) + } + fmt.Println("OK!") +} + +func run(ctx context.Context) error { + owner, repo, err := shared.RepoFromEnv() + if err != nil { + return err + } + ghClient := shared.NewGHClient(ctx) + + exists, err := shared.PRExists(ctx, ghClient, owner, repo, func(title string) bool { + return title == prTitle + }) + if err != nil { + return fmt.Errorf("cannot check for existing PR: %w", err) + } + if exists { + fmt.Println("The PR already exists!") + return nil + } + + if err := shared.RunCmd(ctx, "make", caBundlePath); err != nil { + return fmt.Errorf("cannot update CA bundle: %w", err) + } + + changed, err := hasChanges(ctx) + if err != nil { + return err + } + if !changed { + fmt.Println("The CA bundle is up to date. Nothing to be done.") + return nil + } + + branchName := fmt.Sprintf("update-ca-bundle-%s", time.Now().UTC().Format("2006-01-02")) + + if err := shared.PrepareBranch(ctx, branchName, prTitle, []string{ + "generate/zz_filesystem_generated.go", caBundlePath, + }); err != nil { + return fmt.Errorf("cannot prepare branch: %w", err) + } + + if err := shared.CreatePR(ctx, ghClient, owner, repo, prTitle, fmt.Sprintf("%s:%s", owner, branchName)); err != nil { + return err + } + fmt.Println("The PR has been created!") + return nil +} + +// hasChanges reports whether caBundlePath has uncommitted changes. +// git diff --exit-code exits with 0 when there are no changes, 1 when there are. +func hasChanges(ctx context.Context) (bool, error) { + cmd := exec.CommandContext(ctx, "git", "diff", "--exit-code", "--", caBundlePath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err == nil { + return false, nil + } + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { + return true, nil + } + return false, fmt.Errorf("git diff failed unexpectedly: %w", err) +} diff --git a/hack/cmd/update-quarkus-platform/main.go b/hack/cmd/update-quarkus-platform/main.go new file mode 100644 index 0000000000..8f4e022000 --- /dev/null +++ b/hack/cmd/update-quarkus-platform/main.go @@ -0,0 +1,173 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "os/signal" + "regexp" + "syscall" + + "knative.dev/func/hack/cmd/shared" +) + +const ( + cePomPath = "templates/quarkus/cloudevents/pom.xml" + httpPomPath = "templates/quarkus/http/pom.xml" + + quarkusPlatformAPI = "https://code.quarkus.io/api/platforms" + + quarkusVersionTag = "quarkus.platform.version" + quarkusVersionExpr = `([^<]+)` +) + +var quarkusVersionRe = regexp.MustCompile(quarkusVersionExpr) + +type quarkusPlatformResponse struct { + Platforms []struct { + Streams []struct { + Releases []struct { + QuarkusCoreVersion string `json:"quarkusCoreVersion"` + } `json:"releases"` + } `json:"streams"` + } `json:"platforms"` +} + +func main() { + 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) + }() + + if err := run(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + os.Exit(1) + } + fmt.Println("OK!") +} + +func run(ctx context.Context) error { + latestVersion, err := getLatestQuarkusVersion(ctx) + if err != nil { + return fmt.Errorf("cannot get latest Quarkus platform version: %w", err) + } + fmt.Printf("Latest Quarkus platform version: %s\n", latestVersion) + + ceVersion, err := versionFromPOM(cePomPath) + if err != nil { + return fmt.Errorf("cannot read version from %s: %w", cePomPath, err) + } + httpVersion, err := versionFromPOM(httpPomPath) + if err != nil { + return fmt.Errorf("cannot read version from %s: %w", httpPomPath, err) + } + + if ceVersion == latestVersion && httpVersion == latestVersion { + fmt.Println("Quarkus platform is up-to-date!") + return nil + } + + owner, repo, err := shared.RepoFromEnv() + if err != nil { + return err + } + ghClient := shared.NewGHClient(ctx) + + prTitle := fmt.Sprintf("chore: update Quarkus platform version to %s", latestVersion) + exists, err := shared.PRExists(ctx, ghClient, owner, repo, func(title string) bool { + return title == prTitle + }) + if err != nil { + return fmt.Errorf("cannot check for existing PR: %w", err) + } + if exists { + fmt.Println("The PR already exists!") + return nil + } + + for _, pomPath := range []string{cePomPath, httpPomPath} { + if err := updatePOM(pomPath, latestVersion); err != nil { + return fmt.Errorf("cannot update %s: %w", pomPath, err) + } + } + + smokeCmd := []string{"make", "test-quarkus"} + if err := shared.RunCmd(ctx, smokeCmd[0], smokeCmd[1:]...); err != nil { + return fmt.Errorf("smoke test failed: %w", err) + } + + branchName := fmt.Sprintf("update-quarkus-platform-%s", latestVersion) + if err := shared.PrepareBranch(ctx, branchName, prTitle, []string{ + cePomPath, httpPomPath, "generate/zz_filesystem_generated.go", + }); err != nil { + return fmt.Errorf("cannot prepare branch: %w", err) + } + + if err := shared.CreatePR(ctx, ghClient, owner, repo, prTitle, fmt.Sprintf("%s:%s", owner, branchName)); err != nil { + return err + } + fmt.Println("The PR has been created!") + return nil +} + +func getLatestQuarkusVersion(ctx context.Context) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, quarkusPlatformAPI, nil) + if err != nil { + return "", err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected status %d from %s", resp.StatusCode, quarkusPlatformAPI) + } + + var data quarkusPlatformResponse + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return "", err + } + if len(data.Platforms) == 0 || + len(data.Platforms[0].Streams) == 0 || + len(data.Platforms[0].Streams[0].Releases) == 0 { + return "", fmt.Errorf("unexpected response structure from Quarkus platform API") + } + v := data.Platforms[0].Streams[0].Releases[0].QuarkusCoreVersion + if v == "" { + return "", fmt.Errorf("quarkusCoreVersion is empty in API response") + } + return v, nil +} + +func versionFromPOM(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + m := quarkusVersionRe.FindSubmatch(data) + if len(m) < 2 { + return "", fmt.Errorf("cannot find <%s> in %s", quarkusVersionTag, path) + } + return string(m[1]), nil +} + +func updatePOM(path, newVersion string) error { + data, err := os.ReadFile(path) + if err != nil { + return err + } + updated := quarkusVersionRe.ReplaceAll(data, + []byte(fmt.Sprintf("<%s>%s", quarkusVersionTag, newVersion, quarkusVersionTag))) + return os.WriteFile(path, updated, 0644) +} diff --git a/hack/cmd/update-springboot-platform/main.go b/hack/cmd/update-springboot-platform/main.go new file mode 100644 index 0000000000..ee1b5a28d5 --- /dev/null +++ b/hack/cmd/update-springboot-platform/main.go @@ -0,0 +1,270 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "os/signal" + "regexp" + "strings" + "syscall" + + "github.com/blang/semver/v4" + "gopkg.in/yaml.v3" + + "knative.dev/func/hack/cmd/shared" +) + +const ( + cePomPath = "templates/springboot/cloudevents/pom.xml" + httpPomPath = "templates/springboot/http/pom.xml" + + springBootReleasesAPI = "https://api.github.com/repos/spring-projects/spring-boot/releases/latest" + springCloudBOMURL = "https://raw.githubusercontent.com/spring-io/start.spring.io/main/start-site/src/main/resources/application.yml" + + springCloudVersionTag = "spring-cloud.version" + springCloudVersionExpr = `([^<]+)` +) + +// parentVersionRe matches the version inside the spring-boot-starter-parent block. +var ( + parentVersionRe = regexp.MustCompile(`(spring-boot-starter-parent\s*)([^<]+)()`) + springCloudVersionRe = regexp.MustCompile(springCloudVersionExpr) +) + +type springBootRelease struct { + TagName string `json:"tag_name"` + Draft bool `json:"draft"` +} + +func main() { + 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) + }() + + if err := run(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + os.Exit(1) + } + fmt.Println("OK!") +} + +func run(ctx context.Context) error { + latestVersion, err := getLatestSpringBootVersion(ctx) + if err != nil { + return fmt.Errorf("cannot get latest Spring Boot version: %w", err) + } + if latestVersion == "" { + fmt.Println("Spring Boot platform latest version is not ready to use!") + return nil + } + fmt.Printf("Latest Spring Boot version: %s\n", latestVersion) + + ceVersion, err := parentVersionFromPOM(cePomPath) + if err != nil { + return fmt.Errorf("cannot read version from %s: %w", cePomPath, err) + } + httpVersion, err := parentVersionFromPOM(httpPomPath) + if err != nil { + return fmt.Errorf("cannot read version from %s: %w", httpPomPath, err) + } + + if ceVersion == latestVersion && httpVersion == latestVersion { + fmt.Println("Spring Boot platform is up-to-date!") + return nil + } + + owner, repo, err := shared.RepoFromEnv() + if err != nil { + return err + } + ghClient := shared.NewGHClient(ctx) + + prTitle := fmt.Sprintf("chore: update Springboot platform version to %s", latestVersion) + exists, err := shared.PRExists(ctx, ghClient, owner, repo, func(title string) bool { + return title == prTitle + }) + if err != nil { + return fmt.Errorf("cannot check for existing PR: %w", err) + } + if exists { + fmt.Println("The PR already exists!") + return nil + } + + springCloudVersion, err := getCompatibleSpringCloudVersion(ctx, latestVersion) + if err != nil { + return fmt.Errorf("cannot find compatible spring-cloud version: %w", err) + } + fmt.Printf("Compatible spring-cloud version: %s\n", springCloudVersion) + + for _, pomPath := range []string{cePomPath, httpPomPath} { + if err := updatePOM(pomPath, latestVersion, springCloudVersion); err != nil { + return fmt.Errorf("cannot update %s: %w", pomPath, err) + } + } + + if err := shared.RunCmd(ctx, "make", "test-springboot"); err != nil { + return fmt.Errorf("smoke test failed: %w", err) + } + + branchName := fmt.Sprintf("update-springboot-platform-%s", latestVersion) + if err := shared.PrepareBranch(ctx, branchName, prTitle, []string{ + cePomPath, httpPomPath, "generate/zz_filesystem_generated.go", + }); err != nil { + return fmt.Errorf("cannot prepare branch: %w", err) + } + + if err := shared.CreatePR(ctx, ghClient, owner, repo, prTitle, fmt.Sprintf("%s:%s", owner, branchName)); err != nil { + return err + } + fmt.Println("The PR has been created!") + return nil +} + +func getLatestSpringBootVersion(ctx context.Context) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, springBootReleasesAPI, nil) + if err != nil { + return "", err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected status %d from %s", resp.StatusCode, springBootReleasesAPI) + } + + var release springBootRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return "", err + } + if release.Draft { + return "", nil + } + // Strip any leading alphabetic characters (e.g. "v3.5.12" → "3.5.12") + return strings.TrimLeft(release.TagName, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), nil +} + +func parentVersionFromPOM(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + m := parentVersionRe.FindSubmatch(data) + if len(m) < 4 { + return "", fmt.Errorf("cannot find spring-boot-starter-parent version in %s", path) + } + return string(m[2]), nil +} + +func updatePOM(path, newVersion, newSpringCloudVersion string) error { + data, err := os.ReadFile(path) + if err != nil { + return err + } + + // Replace parent version + updated := parentVersionRe.ReplaceAll(data, []byte("${1}"+newVersion+"${3}")) + + // Replace spring-cloud.version + updated = springCloudVersionRe.ReplaceAll(updated, + []byte(fmt.Sprintf("<%s>%s", springCloudVersionTag, newSpringCloudVersion, springCloudVersionTag))) + + return os.WriteFile(path, updated, 0644) +} + +// springCloudBOM is the minimal structure we need from start.spring.io's application.yml. +type springCloudBOM struct { + Initializr struct { + Env struct { + Boms map[string]struct { + Mappings []struct { + CompatibilityRange string `yaml:"compatibilityRange"` + Version string `yaml:"version"` + } `yaml:"mappings"` + } `yaml:"boms"` + } `yaml:"env"` + } `yaml:"initializr"` +} + +func getCompatibleSpringCloudVersion(ctx context.Context, springBootVersion string) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, springCloudBOMURL, nil) + if err != nil { + return "", err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected status %d from %s", resp.StatusCode, springCloudBOMURL) + } + + var bom springCloudBOM + if err := yaml.NewDecoder(resp.Body).Decode(&bom); err != nil { + return "", fmt.Errorf("cannot decode spring-cloud BOM YAML: %w", err) + } + + sc, ok := bom.Initializr.Env.Boms["spring-cloud"] + if !ok { + return "", fmt.Errorf("spring-cloud entry not found in BOM") + } + + target, err := semver.ParseTolerant(springBootVersion) + if err != nil { + return "", fmt.Errorf("cannot parse Spring Boot version %q: %w", springBootVersion, err) + } + + for _, m := range sc.Mappings { + var begin, end semver.Version + r := m.CompatibilityRange + + if strings.HasPrefix(r, "[") { + // Format: [begin,end) + inner := strings.Trim(r, "[]") + parts := strings.SplitN(inner, ",", 2) + if len(parts) != 2 { + continue + } + begin, err = semver.ParseTolerant(strings.TrimSpace(parts[0])) + if err != nil { + continue + } + end, err = semver.ParseTolerant(strings.TrimSpace(parts[1])) + if err != nil { + continue + } + } else { + // Format: begin (open-ended) + begin, err = semver.ParseTolerant(r) + if err != nil { + continue + } + end, err = semver.ParseTolerant("999.999.999") + if err != nil { + continue + } + } + + if target.GTE(begin) && target.LT(end) { + return m.Version, nil + } + } + + return "", fmt.Errorf("no compatible spring-cloud version found for Spring Boot %s", springBootVersion) +} From a40ba410e606f03996337d299aef64b89492444e Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sun, 10 May 2026 18:21:21 +0530 Subject: [PATCH 2/5] Refactor automation scripts for platform updates and CA bundle management - Updated Makefile targets to streamline the execution of platform updates for Quarkus, Spring Boot, and CA bundle. - Replaced Node.js scripts with Go scripts for better performance and maintainability in GitHub Actions workflows. - Enhanced the update logic for CA bundle, Quarkus platform, and Spring Boot platform, including automated PR creation and version checks. - Removed outdated JavaScript files related to platform updates, consolidating functionality within Go. These changes improve the automation process for dependency management and enhance the CI/CD workflow. --- .github/workflows/update-ca-bundle.yaml | 25 ++- .../workflows/update-quarkus-platform.yaml | 27 ++- .../workflows/update-springboot-platform.yaml | 27 ++- Makefile | 6 +- hack/cmd/shared/github.go | 94 +-------- hack/cmd/update-ca-bundle/main.go | 68 +------ hack/cmd/update-quarkus-platform/main.go | 38 +--- hack/cmd/update-springboot-platform/main.go | 125 +++++------- hack/update-ca-bundle.js | 110 ----------- hack/update-quarkus-platform.js | 137 -------------- hack/update-springboot-platform.js | 179 ------------------ 11 files changed, 125 insertions(+), 711 deletions(-) delete mode 100644 hack/update-ca-bundle.js delete mode 100644 hack/update-quarkus-platform.js delete mode 100644 hack/update-springboot-platform.js diff --git a/.github/workflows/update-ca-bundle.yaml b/.github/workflows/update-ca-bundle.yaml index a434a86c59..5135368931 100644 --- a/.github/workflows/update-ca-bundle.yaml +++ b/.github/workflows/update-ca-bundle.yaml @@ -21,8 +21,23 @@ jobs: steps: - uses: actions/checkout@v4 - uses: knative/actions/setup-go@main - - name: Create PR - env: - GITHUB_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - run: cd hack && go run ./cmd/update-ca-bundle + - name: Update CA bundle + run: go run ./hack/cmd/update-ca-bundle/main.go + - name: Run make generate + run: make generate/zz_filesystem_generated.go + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ github.token }} + commit-message: 'chore: update CA bundle' + title: 'chore: update CA bundle' + body: | + This PR updates the CA bundle in the embedded templates. + + This PR was automatically generated by the [update-ca-bundle workflow](https://github.com/${{ github.repository }}/actions/workflows/update-ca-bundle.yaml). + branch: update-ca-bundle + delete-branch: true + committer: Knative Automation + author: Knative Automation + assignees: gauron99, matejvasek, lkingland + base: main diff --git a/.github/workflows/update-quarkus-platform.yaml b/.github/workflows/update-quarkus-platform.yaml index eb0fe69741..5e860e919f 100644 --- a/.github/workflows/update-quarkus-platform.yaml +++ b/.github/workflows/update-quarkus-platform.yaml @@ -25,8 +25,25 @@ jobs: with: java-version: 21 distribution: 'temurin' - - name: Create PR - env: - GITHUB_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - run: cd hack && go run ./cmd/update-quarkus-platform + - name: Update Quarkus platform version + run: go run ./hack/cmd/update-quarkus-platform/main.go + - name: Run make generate + run: make generate/zz_filesystem_generated.go + - name: Run smoke tests + run: make test-quarkus + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ github.token }} + commit-message: 'chore: update Quarkus platform version' + title: 'chore: update Quarkus platform version' + body: | + This PR updates the Quarkus platform version in the Quarkus scaffolding templates to the latest version. + + This PR was automatically generated by the [update-quarkus-platform workflow](https://github.com/${{ github.repository }}/actions/workflows/update-quarkus-platform.yaml). + branch: update-quarkus-platform + delete-branch: true + committer: Knative Automation + author: Knative Automation + assignees: gauron99, matejvasek, lkingland + base: main diff --git a/.github/workflows/update-springboot-platform.yaml b/.github/workflows/update-springboot-platform.yaml index 6144fe209a..6329c8bfc8 100644 --- a/.github/workflows/update-springboot-platform.yaml +++ b/.github/workflows/update-springboot-platform.yaml @@ -25,8 +25,25 @@ jobs: with: java-version: 21 distribution: 'temurin' - - name: Create PR - env: - GITHUB_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - run: cd hack && go run ./cmd/update-springboot-platform + - name: Update Spring Boot platform version + run: go run ./hack/cmd/update-springboot-platform/main.go + - name: Run make generate + run: make generate/zz_filesystem_generated.go + - name: Run smoke tests + run: make test-springboot + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ github.token }} + commit-message: 'chore: update Spring Boot platform version' + title: 'chore: update Spring Boot platform version' + body: | + This PR updates the Spring Boot platform version in the Spring Boot scaffolding templates to the latest version. + + This PR was automatically generated by the [update-springboot-platform workflow](https://github.com/${{ github.repository }}/actions/workflows/update-springboot-platform.yaml). + branch: update-springboot-platform + delete-branch: true + committer: Knative Automation + author: Knative Automation + assignees: gauron99, matejvasek, lkingland + base: main diff --git a/Makefile b/Makefile index 8a30f172f2..a3a7467d89 100644 --- a/Makefile +++ b/Makefile @@ -424,15 +424,15 @@ __update-builder: # Used in automation .PHONY: update-quarkus-platform update-quarkus-platform: ## Update Quarkus platform version in templates - cd hack && go run ./cmd/update-quarkus-platform + go run ./hack/cmd/update-quarkus-platform/main.go .PHONY: update-springboot-platform update-springboot-platform: ## Update Spring Boot platform version in templates - cd hack && go run ./cmd/update-springboot-platform + go run ./hack/cmd/update-springboot-platform/main.go .PHONY: update-ca-bundle update-ca-bundle: ## Update CA bundle in templates - cd hack && go run ./cmd/update-ca-bundle + go run ./hack/cmd/update-ca-bundle/main.go .PHONY: setup-githooks setup-githooks: diff --git a/hack/cmd/shared/github.go b/hack/cmd/shared/github.go index b506986547..596e8534cd 100644 --- a/hack/cmd/shared/github.go +++ b/hack/cmd/shared/github.go @@ -3,101 +3,15 @@ package shared import ( "context" "fmt" + "net/http" "os" "os/exec" "strings" - - "github.com/google/go-github/v68/github" - "golang.org/x/oauth2" + "time" ) -// NewGHClient constructs an authenticated GitHub client using GITHUB_TOKEN. -func NewGHClient(ctx context.Context) *github.Client { - return github.NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: os.Getenv("GITHUB_TOKEN"), - }))) -} - -// RepoFromEnv splits GITHUB_REPOSITORY ("owner/repo") into owner and repo. -func RepoFromEnv() (owner, repo string, err error) { - v := os.Getenv("GITHUB_REPOSITORY") - if v == "" { - return "", "", fmt.Errorf("GITHUB_REPOSITORY is not set") - } - parts := strings.SplitN(v, "/", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("GITHUB_REPOSITORY has unexpected format: %q", v) - } - return parts[0], parts[1], nil -} - -// PRExists returns true if any open pull request satisfies pred(pr.GetTitle()). -func PRExists(ctx context.Context, client *github.Client, owner, repo string, pred func(string) bool) (bool, error) { - opts := &github.PullRequestListOptions{ - State: "open", - ListOptions: github.ListOptions{ - PerPage: 10, - }, - } - for { - prs, resp, err := client.PullRequests.List(ctx, owner, repo, opts) - if err != nil { - return false, fmt.Errorf("cannot list pull requests: %w", err) - } - for _, pr := range prs { - if pred(pr.GetTitle()) { - return true, nil - } - } - if resp.NextPage == 0 { - break - } - opts.Page = resp.NextPage - } - return false, nil -} - -// PrepareBranch configures git identity, creates branchName, runs codegen, -// stages filesToAdd, commits with prTitle as the message, and pushes. -func PrepareBranch(ctx context.Context, branchName, prTitle string, filesToAdd []string) error { - steps := [][]string{ - {"git", "config", "user.email", "automation@knative.team"}, - {"git", "config", "user.name", "Knative Automation"}, - {"git", "checkout", "-b", branchName}, - {"make", "generate/zz_filesystem_generated.go"}, - } - for _, args := range steps { - if err := RunCmd(ctx, args[0], args[1:]...); err != nil { - return err - } - } - - addArgs := append([]string{"add"}, filesToAdd...) - if err := RunCmd(ctx, "git", addArgs...); err != nil { - return err - } - - if err := RunCmd(ctx, "git", "commit", "-m", prTitle); err != nil { - return err - } - - return RunCmd(ctx, "git", "push", "--set-upstream", "origin", branchName) -} - -// CreatePR opens a pull request against main with the given title. -// head should be in the form "owner:branchName". -func CreatePR(ctx context.Context, client *github.Client, owner, repo, title, head string) error { - _, _, err := client.PullRequests.Create(ctx, owner, repo, &github.NewPullRequest{ - Title: github.Ptr(title), - Body: github.Ptr(title), - Base: github.Ptr("main"), - Head: github.Ptr(head), - }) - if err != nil { - return fmt.Errorf("cannot create pull request: %w", err) - } - return nil -} +// HTTPClient is used for all outbound HTTP requests; has a 30-second timeout. +var HTTPClient = &http.Client{Timeout: 30 * time.Second} // RunCmd runs name with args, streaming stdout/stderr to the process output. func RunCmd(ctx context.Context, name string, args ...string) error { diff --git a/hack/cmd/update-ca-bundle/main.go b/hack/cmd/update-ca-bundle/main.go index 7c453e8df7..002ab11ff0 100644 --- a/hack/cmd/update-ca-bundle/main.go +++ b/hack/cmd/update-ca-bundle/main.go @@ -4,18 +4,13 @@ import ( "context" "fmt" "os" - "os/exec" "os/signal" "syscall" - "time" "knative.dev/func/hack/cmd/shared" ) -const ( - caBundlePath = "templates/certs/ca-certificates.crt" - prTitle = "chore: update CA bundle" -) +const caBundleMakeTarget = "templates/certs/ca-certificates.crt" func main() { ctx, cancel := context.WithCancel(context.Background()) @@ -34,67 +29,8 @@ func main() { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) } - fmt.Println("OK!") } func run(ctx context.Context) error { - owner, repo, err := shared.RepoFromEnv() - if err != nil { - return err - } - ghClient := shared.NewGHClient(ctx) - - exists, err := shared.PRExists(ctx, ghClient, owner, repo, func(title string) bool { - return title == prTitle - }) - if err != nil { - return fmt.Errorf("cannot check for existing PR: %w", err) - } - if exists { - fmt.Println("The PR already exists!") - return nil - } - - if err := shared.RunCmd(ctx, "make", caBundlePath); err != nil { - return fmt.Errorf("cannot update CA bundle: %w", err) - } - - changed, err := hasChanges(ctx) - if err != nil { - return err - } - if !changed { - fmt.Println("The CA bundle is up to date. Nothing to be done.") - return nil - } - - branchName := fmt.Sprintf("update-ca-bundle-%s", time.Now().UTC().Format("2006-01-02")) - - if err := shared.PrepareBranch(ctx, branchName, prTitle, []string{ - "generate/zz_filesystem_generated.go", caBundlePath, - }); err != nil { - return fmt.Errorf("cannot prepare branch: %w", err) - } - - if err := shared.CreatePR(ctx, ghClient, owner, repo, prTitle, fmt.Sprintf("%s:%s", owner, branchName)); err != nil { - return err - } - fmt.Println("The PR has been created!") - return nil -} - -// hasChanges reports whether caBundlePath has uncommitted changes. -// git diff --exit-code exits with 0 when there are no changes, 1 when there are. -func hasChanges(ctx context.Context) (bool, error) { - cmd := exec.CommandContext(ctx, "git", "diff", "--exit-code", "--", caBundlePath) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err == nil { - return false, nil - } - if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 { - return true, nil - } - return false, fmt.Errorf("git diff failed unexpectedly: %w", err) + return shared.RunCmd(ctx, "make", caBundleMakeTarget) } diff --git a/hack/cmd/update-quarkus-platform/main.go b/hack/cmd/update-quarkus-platform/main.go index 8f4e022000..f81bbc9adc 100644 --- a/hack/cmd/update-quarkus-platform/main.go +++ b/hack/cmd/update-quarkus-platform/main.go @@ -52,7 +52,6 @@ func main() { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) } - fmt.Println("OK!") } func run(ctx context.Context) error { @@ -76,46 +75,11 @@ func run(ctx context.Context) error { return nil } - owner, repo, err := shared.RepoFromEnv() - if err != nil { - return err - } - ghClient := shared.NewGHClient(ctx) - - prTitle := fmt.Sprintf("chore: update Quarkus platform version to %s", latestVersion) - exists, err := shared.PRExists(ctx, ghClient, owner, repo, func(title string) bool { - return title == prTitle - }) - if err != nil { - return fmt.Errorf("cannot check for existing PR: %w", err) - } - if exists { - fmt.Println("The PR already exists!") - return nil - } - for _, pomPath := range []string{cePomPath, httpPomPath} { if err := updatePOM(pomPath, latestVersion); err != nil { return fmt.Errorf("cannot update %s: %w", pomPath, err) } } - - smokeCmd := []string{"make", "test-quarkus"} - if err := shared.RunCmd(ctx, smokeCmd[0], smokeCmd[1:]...); err != nil { - return fmt.Errorf("smoke test failed: %w", err) - } - - branchName := fmt.Sprintf("update-quarkus-platform-%s", latestVersion) - if err := shared.PrepareBranch(ctx, branchName, prTitle, []string{ - cePomPath, httpPomPath, "generate/zz_filesystem_generated.go", - }); err != nil { - return fmt.Errorf("cannot prepare branch: %w", err) - } - - if err := shared.CreatePR(ctx, ghClient, owner, repo, prTitle, fmt.Sprintf("%s:%s", owner, branchName)); err != nil { - return err - } - fmt.Println("The PR has been created!") return nil } @@ -124,7 +88,7 @@ func getLatestQuarkusVersion(ctx context.Context) (string, error) { if err != nil { return "", err } - resp, err := http.DefaultClient.Do(req) + resp, err := shared.HTTPClient.Do(req) if err != nil { return "", err } diff --git a/hack/cmd/update-springboot-platform/main.go b/hack/cmd/update-springboot-platform/main.go index ee1b5a28d5..9762f21cca 100644 --- a/hack/cmd/update-springboot-platform/main.go +++ b/hack/cmd/update-springboot-platform/main.go @@ -30,7 +30,7 @@ const ( // parentVersionRe matches the version inside the spring-boot-starter-parent block. var ( - parentVersionRe = regexp.MustCompile(`(spring-boot-starter-parent\s*)([^<]+)()`) + parentVersionRe = regexp.MustCompile(`(spring-boot-starter-parent\s*)([^<]+)()`) springCloudVersionRe = regexp.MustCompile(springCloudVersionExpr) ) @@ -39,6 +39,23 @@ type springBootRelease struct { Draft bool `json:"draft"` } +// springCloudMapping is one entry from the start.spring.io BOM. +type springCloudMapping struct { + CompatibilityRange string `yaml:"compatibilityRange"` + Version string `yaml:"version"` +} + +// springCloudBOM is the minimal structure we need from start.spring.io's application.yml. +type springCloudBOM struct { + Initializr struct { + Env struct { + Boms map[string]struct { + Mappings []springCloudMapping `yaml:"mappings"` + } `yaml:"boms"` + } `yaml:"env"` + } `yaml:"initializr"` +} + func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -56,7 +73,6 @@ func main() { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) } - fmt.Println("OK!") } func run(ctx context.Context) error { @@ -84,24 +100,6 @@ func run(ctx context.Context) error { return nil } - owner, repo, err := shared.RepoFromEnv() - if err != nil { - return err - } - ghClient := shared.NewGHClient(ctx) - - prTitle := fmt.Sprintf("chore: update Springboot platform version to %s", latestVersion) - exists, err := shared.PRExists(ctx, ghClient, owner, repo, func(title string) bool { - return title == prTitle - }) - if err != nil { - return fmt.Errorf("cannot check for existing PR: %w", err) - } - if exists { - fmt.Println("The PR already exists!") - return nil - } - springCloudVersion, err := getCompatibleSpringCloudVersion(ctx, latestVersion) if err != nil { return fmt.Errorf("cannot find compatible spring-cloud version: %w", err) @@ -113,22 +111,6 @@ func run(ctx context.Context) error { return fmt.Errorf("cannot update %s: %w", pomPath, err) } } - - if err := shared.RunCmd(ctx, "make", "test-springboot"); err != nil { - return fmt.Errorf("smoke test failed: %w", err) - } - - branchName := fmt.Sprintf("update-springboot-platform-%s", latestVersion) - if err := shared.PrepareBranch(ctx, branchName, prTitle, []string{ - cePomPath, httpPomPath, "generate/zz_filesystem_generated.go", - }); err != nil { - return fmt.Errorf("cannot prepare branch: %w", err) - } - - if err := shared.CreatePR(ctx, ghClient, owner, repo, prTitle, fmt.Sprintf("%s:%s", owner, branchName)); err != nil { - return err - } - fmt.Println("The PR has been created!") return nil } @@ -137,7 +119,10 @@ func getLatestSpringBootVersion(ctx context.Context) (string, error) { if err != nil { return "", err } - resp, err := http.DefaultClient.Do(req) + if token := os.Getenv("GITHUB_TOKEN"); token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + resp, err := shared.HTTPClient.Do(req) if err != nil { return "", err } @@ -154,8 +139,7 @@ func getLatestSpringBootVersion(ctx context.Context) (string, error) { if release.Draft { return "", nil } - // Strip any leading alphabetic characters (e.g. "v3.5.12" → "3.5.12") - return strings.TrimLeft(release.TagName, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), nil + return strings.TrimPrefix(release.TagName, "v"), nil } func parentVersionFromPOM(path string) (string, error) { @@ -175,37 +159,18 @@ func updatePOM(path, newVersion, newSpringCloudVersion string) error { if err != nil { return err } - - // Replace parent version updated := parentVersionRe.ReplaceAll(data, []byte("${1}"+newVersion+"${3}")) - - // Replace spring-cloud.version updated = springCloudVersionRe.ReplaceAll(updated, []byte(fmt.Sprintf("<%s>%s", springCloudVersionTag, newSpringCloudVersion, springCloudVersionTag))) - return os.WriteFile(path, updated, 0644) } -// springCloudBOM is the minimal structure we need from start.spring.io's application.yml. -type springCloudBOM struct { - Initializr struct { - Env struct { - Boms map[string]struct { - Mappings []struct { - CompatibilityRange string `yaml:"compatibilityRange"` - Version string `yaml:"version"` - } `yaml:"mappings"` - } `yaml:"boms"` - } `yaml:"env"` - } `yaml:"initializr"` -} - func getCompatibleSpringCloudVersion(ctx context.Context, springBootVersion string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, springCloudBOMURL, nil) if err != nil { return "", err } - resp, err := http.DefaultClient.Do(req) + resp, err := shared.HTTPClient.Do(req) if err != nil { return "", err } @@ -225,45 +190,57 @@ func getCompatibleSpringCloudVersion(ctx context.Context, springBootVersion stri return "", fmt.Errorf("spring-cloud entry not found in BOM") } + return resolveSpringCloudVersion(springBootVersion, sc.Mappings) +} + +// resolveSpringCloudVersion finds the spring-cloud version compatible with the +// given springBootVersion by evaluating each mapping's compatibilityRange. +// +// Range format mirrors Maven version ranges: +// - "[begin,end)" — begin inclusive, end exclusive +// - "begin" — begin inclusive, no upper bound +func resolveSpringCloudVersion(springBootVersion string, mappings []springCloudMapping) (string, error) { target, err := semver.ParseTolerant(springBootVersion) if err != nil { return "", fmt.Errorf("cannot parse Spring Boot version %q: %w", springBootVersion, err) } - for _, m := range sc.Mappings { - var begin, end semver.Version + for _, m := range mappings { r := m.CompatibilityRange if strings.HasPrefix(r, "[") { - // Format: [begin,end) - inner := strings.Trim(r, "[]") + // "[begin,end)" — strip the surrounding brackets by index, matching + // the JS original's slice(1,-1), so that the closing ")" is removed + // correctly (strings.Trim would only remove "[" and "]", leaving ")"). + if len(r) < 2 { + continue + } + inner := r[1 : len(r)-1] parts := strings.SplitN(inner, ",", 2) if len(parts) != 2 { continue } - begin, err = semver.ParseTolerant(strings.TrimSpace(parts[0])) + begin, err := semver.ParseTolerant(strings.TrimSpace(parts[0])) if err != nil { continue } - end, err = semver.ParseTolerant(strings.TrimSpace(parts[1])) + end, err := semver.ParseTolerant(strings.TrimSpace(parts[1])) if err != nil { continue } + if target.GTE(begin) && target.LT(end) { + return m.Version, nil + } } else { - // Format: begin (open-ended) - begin, err = semver.ParseTolerant(r) + // open-ended lower bound + begin, err := semver.ParseTolerant(r) if err != nil { continue } - end, err = semver.ParseTolerant("999.999.999") - if err != nil { - continue + if target.GTE(begin) { + return m.Version, nil } } - - if target.GTE(begin) && target.LT(end) { - return m.Version, nil - } } return "", fmt.Errorf("no compatible spring-cloud version found for Spring Boot %s", springBootVersion) diff --git a/hack/update-ca-bundle.js b/hack/update-ca-bundle.js deleted file mode 100644 index dd7b5eee57..0000000000 --- a/hack/update-ca-bundle.js +++ /dev/null @@ -1,110 +0,0 @@ -// const xml2js = require('xml2js'); -const {Octokit} = require("octokit"); -// const {readFile,writeFile} = require('fs/promises'); -const {spawn} = require('node:child_process'); - -const octokit = new Octokit({auth: process.env.GITHUB_TOKEN}); -const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') - -const prExists = async (pred) => { - let page = 1 - const perPage = 10; - - while (true) { - const resp = await octokit.rest.pulls.list({ - owner: owner, - repo: repo, - state: 'open', - per_page: perPage, - page: page - }) - - for (const e of resp.data) { - if (pred(e)) { - return true - } - } - if (resp.data.length < perPage) { - return false - } - page++ - } -} - -/** - * @param script - * @return {Promise} - */ -const runScript = async (script) => { - const subproc = spawn("sh", ["-c", script], {stdio: ['inherit', 'inherit', 'inherit']}) - return new Promise((resolve, reject) => { - subproc.on('exit', code => { - resolve(code) - }) - if (typeof subproc.exitCode === 'number') { - resolve(subproc.exitCode) - } - }) -} - -/** - * @return {Promise} - */ -const updateCA = async () => { - let ec = await runScript('make templates/certs/ca-certificates.crt') - if (ec !== 0) { - throw new Error('cannot update CA bundle') - } - return (await runScript('git diff --exit-code -- templates/certs/ca-certificates.crt')) !== 0 -} - -const prepareBranch = async (branchName, prTitle) => { - const script = `git config user.email "automation@knative.team" && \\ - git config user.name "Knative Automation" && \\ - git checkout -b "${branchName}" && \\ - make generate/zz_filesystem_generated.go && \\ - git add generate/zz_filesystem_generated.go templates/certs/ca-certificates.crt && \\ - git commit -m "${prTitle}" && \\ - git push --set-upstream origin "${branchName}" -` - const ec = await runScript(script) - if (ec !== 0) { - throw new Error("cannot prepare branch: non-zero exit code") - } -} - -const main = async () => { - const prTitle = `chore: update CA bundle` - if (await prExists(({title}) => title === prTitle)) { - console.log("The PR already exists!") - return - } - - const hasUpdated = await updateCA() - if (!hasUpdated) { - console.log('The CA bundle is up to date. Nothing to be done.') - return - } - - const branchName = `update-ca-bundle-${(new Date()).toISOString().split('T')[0]}` - - await prepareBranch(branchName, prTitle) - - await octokit.rest.pulls.create({ - owner: owner, - repo: repo, - title: prTitle, - body: prTitle, - base: 'main', - head: `${owner}:${branchName}`, - }) - console.log("The PR has been created!") - -} - -main().then(value => { - console.log("OK!") -}).catch(reason => { - console.log("ERROR: ", reason) - process.exit(1) -}) diff --git a/hack/update-quarkus-platform.js b/hack/update-quarkus-platform.js deleted file mode 100644 index 1763ed9f6a..0000000000 --- a/hack/update-quarkus-platform.js +++ /dev/null @@ -1,137 +0,0 @@ -const xml2js = require('xml2js'); -const {Octokit} = require("octokit"); -const {readFile,writeFile} = require('fs/promises'); -const {spawn} = require('node:child_process'); - -const cePomPath = "templates/quarkus/cloudevents/pom.xml" -const httpPomPath = "templates/quarkus/http/pom.xml" -const octokit = new Octokit({auth: process.env.GITHUB_TOKEN}); -const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') - -const getLatestPlatform = async () => { - const data = await (await fetch("https://code.quarkus.io/api/platforms")).json() - return data.platforms[0].streams[0].releases[0].quarkusCoreVersion -} - -const prExists = async (pred) => { - - let page = 1 - const perPage = 10; - - while (true) { - const resp = await octokit.rest.pulls.list({ - owner: owner, - repo: repo, - state: 'open', - per_page: perPage, - page: page - }) - - for (const e of resp.data) { - if (pred(e)) { - return true - } - } - if (resp.data.length < perPage) { - return false - } - page++ - } -} - -const parseXML = (text) => new Promise((resolve, reject) => { - xml2js.parseString(text, {}, (err, res) => { - if (err) { - reject(err) - } - resolve(res) - }) -}) - -const platformFromPom = async (pomPath) => { - const pomData = await readFile(pomPath, {encoding: 'utf8'}); - const pom = await parseXML(pomData) - return pom.project.properties[0]['quarkus.platform.version'][0] -} - -const prepareBranch = async (branchName, prTitle) => { - const script = `git config user.email "automation@knative.team" && \\ - git config user.name "Knative Automation" && \\ - git checkout -b "${branchName}" && \\ - make generate/zz_filesystem_generated.go && \\ - git add "${cePomPath}" "${httpPomPath}" generate/zz_filesystem_generated.go && \\ - git commit -m "${prTitle}" && \\ - git push --set-upstream origin "${branchName}" -` - const subproc = spawn("sh", ["-c", script], {stdio: ['inherit', 'inherit', 'inherit']}) - - return new Promise((resolve, reject) => { - subproc.on('exit', code => { - if (code === 0) { - resolve() - return - } - reject(new Error("cannot prepare branch: non-zero exit code")) - }) - }) -} - -const updatePlatformInPom = async (pomPath, newPlatform) => { - const pomData = await readFile(pomPath, {encoding: 'utf8'}); - const newPomData = pomData.replace(new RegExp('[\\w.]+', 'i'), - `${newPlatform}`) - await writeFile(pomPath, newPomData) -} - -const smokeTest = () => { - const subproc = spawn("make", ["test-quarkus"], {stdio: ['inherit', 'inherit', 'inherit']}) - return new Promise((resolve, reject) => { - subproc.on('exit', code => { - if (code === 0) { - resolve() - return - } - reject(new Error("smoke test failed: non-zero exit code")) - }) - }) -} - -const main = async () => { - const latestPlatform = await getLatestPlatform() - const prTitle = `chore: update Quarkus platform version to ${latestPlatform}` - const branchName = `update-quarkus-platform-${latestPlatform}` - const cePlatform = await platformFromPom(cePomPath) - const httpPlatform = await platformFromPom(httpPomPath) - - if (cePlatform === latestPlatform && httpPlatform === latestPlatform) { - console.log("Quarkus platform is up-to-date!") - return - } - - if (await prExists(({title}) => title === prTitle)) { - console.log("The PR already exists!") - return - } - - await updatePlatformInPom(cePomPath, latestPlatform) - await updatePlatformInPom(httpPomPath, latestPlatform) - await smokeTest() - await prepareBranch(branchName, prTitle) - await octokit.rest.pulls.create({ - owner: owner, - repo: repo, - title: prTitle, - body: prTitle, - base: 'main', - head: `${owner}:${branchName}`, - }) - console.log("The PR has been created!") - -} - -main().then(value => { - console.log("OK!") -}).catch(reason => { - console.log("ERROR: ", reason) - process.exit(1) -}) diff --git a/hack/update-springboot-platform.js b/hack/update-springboot-platform.js deleted file mode 100644 index a612341aec..0000000000 --- a/hack/update-springboot-platform.js +++ /dev/null @@ -1,179 +0,0 @@ -const xml2js = require('xml2js'); -const yaml = require('yaml') -const semver = require('semver') -const {Octokit} = require("octokit"); -const {readFile,writeFile} = require('fs/promises'); -const {spawn} = require('node:child_process'); - -const cePomPath = "templates/springboot/cloudevents/pom.xml" -const httpPomPath = "templates/springboot/http/pom.xml" -const octokit = new Octokit({auth: process.env.GITHUB_TOKEN}); -const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') - -const getLatestPlatform = async () => { - const data = await (await fetch("https://api.github.com/repos/spring-projects/spring-boot/releases/latest")).json() - return (data.draft === false) ? data.tag_name.replace(/[A-Za-z]/g, "") : null; -} - -const prExists = async (pred) => { - - let page = 1 - const perPage = 10; - - while (true) { - const resp = await octokit.rest.pulls.list({ - owner: owner, - repo: repo, - state: 'open', - per_page: perPage, - page: page - }) - - for (const e of resp.data) { - if (pred(e)) { - return true - } - } - if (resp.data.length < perPage) { - return false - } - page++ - } -} - -const parseXML = (text) => new Promise((resolve, reject) => { - xml2js.parseString(text, {}, (err, res) => { - if (err) { - reject(err) - } - resolve(res) - }) -}) - -const platformFromPom = async (pomPath) => { - const pomData = await readFile(pomPath, {encoding: 'utf8'}); - const pom = await parseXML(pomData) - return pom.project.parent[0].version[0] -} - -const prepareBranch = async (branchName, prTitle) => { - const script = `git config user.email "automation@knative.team" && \\ - git config user.name "Knative Automation" && \\ - git checkout -b "${branchName}" && \\ - make generate/zz_filesystem_generated.go && \\ - git add "${cePomPath}" "${httpPomPath}" generate/zz_filesystem_generated.go && \\ - git commit -m "${prTitle}" && \\ - git push --set-upstream origin "${branchName}" -` - const subproc = spawn("sh", ["-c", script], {stdio: ['inherit', 'inherit', 'inherit']}) - - return new Promise((resolve, reject) => { - subproc.on('exit', code => { - if (code === 0) { - resolve() - return - } - reject(new Error("cannot prepare branch: non-zero exit code")) - }) - }) -} - -const updatePlatformInPom = async (pomPath, newPlatform) => { - const pomData = await readFile(pomPath, {encoding: 'utf8'}); - const pom = await parseXML(pomData) - pom.project.parent[0].version[0] = newPlatform - - const compatibleSpringCloudVersion = await getCompatibleSpringCloudVersion(newPlatform) - pom.project.properties[0]['spring-cloud.version'] = [compatibleSpringCloudVersion] - - const builder = new xml2js.Builder( { headless: false, renderOpts: { pretty: true } }) - const newPomData = builder.buildObject(pom) + "\n" - await writeFile(pomPath, newPomData) -} - -const getCompatibleSpringCloudVersion = async (newPlatform) => { - const bomUrl = "https://raw.githubusercontent.com/spring-io/start.spring.io/main/start-site/src/main/resources/application.yml" - const data = await (await fetch(bomUrl)).text() - const mappings = yaml.parseAllDocuments(data)[0].toJS() - .initializr - .env - .boms['spring-cloud'] - .mappings - - const newPlatformVersion = semver.parse(newPlatform, {}, true) - for (const {compatibilityRange, version} of mappings) { - let begin, end - if (compatibilityRange.startsWith('[')) { - let [b, e] = compatibilityRange.slice(1, -1).split(',') - begin = semver.parse(b, {}, true) - end = semver.parse(e, {}, true) - } else { - begin = semver.parse(compatibilityRange, {}, true) - end = semver.parse("999.999.999", {}, true) - } - - if (newPlatformVersion.compare(begin) >= 0 && newPlatformVersion.compare(end) < 0) { - return version - } - } - throw new Error("cannot get latest compatible spring-cloud version") -} - -const smokeTest = () => { - const subproc = spawn("make", ["test-springboot"], {stdio: ['inherit', 'inherit', 'inherit']}) - return new Promise((resolve, reject) => { - subproc.on('exit', code => { - if (code === 0) { - resolve() - return - } - reject(new Error("smoke test failed: non-zero exit code")) - }) - }) -} - -const main = async () => { - const latestPlatform = await getLatestPlatform() - - if(latestPlatform === null) { - console.log("Spring Boot platform latest version is not ready to use!") - return - } - - const prTitle = `chore: update Springboot platform version to ${latestPlatform}` - const branchName = `update-springboot-platform-${latestPlatform}` - const cePlatform = await platformFromPom(cePomPath) - const httpPlatform = await platformFromPom(httpPomPath) - - if (cePlatform === latestPlatform && httpPlatform === latestPlatform) { - console.log("Spring Boot platform is up-to-date!") - return - } - - if (await prExists(({title}) => title === prTitle)) { - console.log("The PR already exists!") - return - } - - await updatePlatformInPom(cePomPath, latestPlatform) - await updatePlatformInPom(httpPomPath, latestPlatform) - await smokeTest() - await prepareBranch(branchName, prTitle) - await octokit.rest.pulls.create({ - owner: owner, - repo: repo, - title: prTitle, - body: prTitle, - base: 'main', - head: `${owner}:${branchName}`, - }) - console.log("The PR has been created!") - -} - -main().then(value => { - console.log("OK!") -}).catch(reason => { - console.log("ERROR: ", reason) - process.exit(1) -}) From 8ed0f93687f3b9e9965b0934aaad80041b3b6c68 Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sun, 10 May 2026 18:27:21 +0530 Subject: [PATCH 3/5] fix(workflows): gate smoke tests and PR creation on actual file changes - Pass GITHUB_TOKEN to the Spring Boot update step so GitHub API calls are authenticated (unauthenticated rate limit is 60 req/hr, shared across all Actions runners, causing intermittent 403s on the 4-hour cron) - Add a git-diff check after each Go tool + make generate step; skip smoke tests and the create-pull-request action when no files changed, restoring the early-exit behaviour the original JS scripts had --- .github/workflows/update-ca-bundle.yaml | 9 +++++++++ .github/workflows/update-quarkus-platform.yaml | 10 ++++++++++ .github/workflows/update-springboot-platform.yaml | 12 ++++++++++++ 3 files changed, 31 insertions(+) diff --git a/.github/workflows/update-ca-bundle.yaml b/.github/workflows/update-ca-bundle.yaml index 5135368931..9478c7ccfb 100644 --- a/.github/workflows/update-ca-bundle.yaml +++ b/.github/workflows/update-ca-bundle.yaml @@ -25,7 +25,16 @@ jobs: run: go run ./hack/cmd/update-ca-bundle/main.go - name: Run make generate run: make generate/zz_filesystem_generated.go + - name: Check for changes + id: changes + run: | + if git diff --quiet; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi - name: Create Pull Request + if: steps.changes.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: token: ${{ github.token }} diff --git a/.github/workflows/update-quarkus-platform.yaml b/.github/workflows/update-quarkus-platform.yaml index 5e860e919f..08a7334b9c 100644 --- a/.github/workflows/update-quarkus-platform.yaml +++ b/.github/workflows/update-quarkus-platform.yaml @@ -29,9 +29,19 @@ jobs: run: go run ./hack/cmd/update-quarkus-platform/main.go - name: Run make generate run: make generate/zz_filesystem_generated.go + - name: Check for changes + id: changes + run: | + if git diff --quiet; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi - name: Run smoke tests + if: steps.changes.outputs.changed == 'true' run: make test-quarkus - name: Create Pull Request + if: steps.changes.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: token: ${{ github.token }} diff --git a/.github/workflows/update-springboot-platform.yaml b/.github/workflows/update-springboot-platform.yaml index 6329c8bfc8..3f9301a0c4 100644 --- a/.github/workflows/update-springboot-platform.yaml +++ b/.github/workflows/update-springboot-platform.yaml @@ -26,12 +26,24 @@ jobs: java-version: 21 distribution: 'temurin' - name: Update Spring Boot platform version + env: + GITHUB_TOKEN: ${{ github.token }} run: go run ./hack/cmd/update-springboot-platform/main.go - name: Run make generate run: make generate/zz_filesystem_generated.go + - name: Check for changes + id: changes + run: | + if git diff --quiet; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi - name: Run smoke tests + if: steps.changes.outputs.changed == 'true' run: make test-springboot - name: Create Pull Request + if: steps.changes.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: token: ${{ github.token }} From 4f9a8390af6761241e7e0e31ce483977d0ff8f42 Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sun, 10 May 2026 22:42:58 +0530 Subject: [PATCH 4/5] fix: use package dir in go run and rename shared/github.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch all `go run .../main.go` invocations to `go run ./pkg` in Makefile and the three CI workflows so additional files added to a command package are automatically included - Rename hack/cmd/shared/github.go → shared.go; the file contains a generic HTTP client and command runner, not GitHub-specific helpers --- .github/workflows/update-ca-bundle.yaml | 2 +- .github/workflows/update-quarkus-platform.yaml | 2 +- .github/workflows/update-springboot-platform.yaml | 2 +- Makefile | 6 +++--- hack/cmd/shared/{github.go => shared.go} | 0 5 files changed, 6 insertions(+), 6 deletions(-) rename hack/cmd/shared/{github.go => shared.go} (100%) diff --git a/.github/workflows/update-ca-bundle.yaml b/.github/workflows/update-ca-bundle.yaml index 9478c7ccfb..3f62a986db 100644 --- a/.github/workflows/update-ca-bundle.yaml +++ b/.github/workflows/update-ca-bundle.yaml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v4 - uses: knative/actions/setup-go@main - name: Update CA bundle - run: go run ./hack/cmd/update-ca-bundle/main.go + run: go run ./hack/cmd/update-ca-bundle - name: Run make generate run: make generate/zz_filesystem_generated.go - name: Check for changes diff --git a/.github/workflows/update-quarkus-platform.yaml b/.github/workflows/update-quarkus-platform.yaml index 08a7334b9c..61f60899ea 100644 --- a/.github/workflows/update-quarkus-platform.yaml +++ b/.github/workflows/update-quarkus-platform.yaml @@ -26,7 +26,7 @@ jobs: java-version: 21 distribution: 'temurin' - name: Update Quarkus platform version - run: go run ./hack/cmd/update-quarkus-platform/main.go + run: go run ./hack/cmd/update-quarkus-platform - name: Run make generate run: make generate/zz_filesystem_generated.go - name: Check for changes diff --git a/.github/workflows/update-springboot-platform.yaml b/.github/workflows/update-springboot-platform.yaml index 3f9301a0c4..132cbff167 100644 --- a/.github/workflows/update-springboot-platform.yaml +++ b/.github/workflows/update-springboot-platform.yaml @@ -28,7 +28,7 @@ jobs: - name: Update Spring Boot platform version env: GITHUB_TOKEN: ${{ github.token }} - run: go run ./hack/cmd/update-springboot-platform/main.go + run: go run ./hack/cmd/update-springboot-platform - name: Run make generate run: make generate/zz_filesystem_generated.go - name: Check for changes diff --git a/Makefile b/Makefile index a3a7467d89..7149fbb0a8 100644 --- a/Makefile +++ b/Makefile @@ -424,15 +424,15 @@ __update-builder: # Used in automation .PHONY: update-quarkus-platform update-quarkus-platform: ## Update Quarkus platform version in templates - go run ./hack/cmd/update-quarkus-platform/main.go + go run ./hack/cmd/update-quarkus-platform .PHONY: update-springboot-platform update-springboot-platform: ## Update Spring Boot platform version in templates - go run ./hack/cmd/update-springboot-platform/main.go + go run ./hack/cmd/update-springboot-platform .PHONY: update-ca-bundle update-ca-bundle: ## Update CA bundle in templates - go run ./hack/cmd/update-ca-bundle/main.go + go run ./hack/cmd/update-ca-bundle .PHONY: setup-githooks setup-githooks: diff --git a/hack/cmd/shared/github.go b/hack/cmd/shared/shared.go similarity index 100% rename from hack/cmd/shared/github.go rename to hack/cmd/shared/shared.go From e36f175e988448f0c42df6ea38c9a9173ea47cbd Mon Sep 17 00:00:00 2001 From: Ankitsinghsisodya Date: Sat, 16 May 2026 13:47:19 +0530 Subject: [PATCH 5/5] refactor(workflows): remove change detection from CI workflows - Eliminated the change detection step from the GitHub Actions workflows for updating CA bundle, Quarkus platform, and Spring Boot platform. This simplifies the workflows by directly proceeding to create pull requests without checking for file changes, streamlining the automation process. --- .github/workflows/update-ca-bundle.yaml | 9 --------- .github/workflows/update-quarkus-platform.yaml | 10 ---------- .github/workflows/update-springboot-platform.yaml | 10 ---------- hack/cmd/shared/shared.go | 8 ++++++++ hack/cmd/update-ca-bundle/main.go | 15 ++------------- hack/cmd/update-quarkus-platform/main.go | 15 ++------------- hack/cmd/update-springboot-platform/main.go | 15 ++------------- 7 files changed, 14 insertions(+), 68 deletions(-) diff --git a/.github/workflows/update-ca-bundle.yaml b/.github/workflows/update-ca-bundle.yaml index 3f62a986db..3a89d3444e 100644 --- a/.github/workflows/update-ca-bundle.yaml +++ b/.github/workflows/update-ca-bundle.yaml @@ -25,16 +25,7 @@ jobs: run: go run ./hack/cmd/update-ca-bundle - name: Run make generate run: make generate/zz_filesystem_generated.go - - name: Check for changes - id: changes - run: | - if git diff --quiet; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - fi - name: Create Pull Request - if: steps.changes.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: token: ${{ github.token }} diff --git a/.github/workflows/update-quarkus-platform.yaml b/.github/workflows/update-quarkus-platform.yaml index 61f60899ea..a5b46a4f64 100644 --- a/.github/workflows/update-quarkus-platform.yaml +++ b/.github/workflows/update-quarkus-platform.yaml @@ -29,19 +29,9 @@ jobs: run: go run ./hack/cmd/update-quarkus-platform - name: Run make generate run: make generate/zz_filesystem_generated.go - - name: Check for changes - id: changes - run: | - if git diff --quiet; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - fi - name: Run smoke tests - if: steps.changes.outputs.changed == 'true' run: make test-quarkus - name: Create Pull Request - if: steps.changes.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: token: ${{ github.token }} diff --git a/.github/workflows/update-springboot-platform.yaml b/.github/workflows/update-springboot-platform.yaml index 132cbff167..ab93ab419b 100644 --- a/.github/workflows/update-springboot-platform.yaml +++ b/.github/workflows/update-springboot-platform.yaml @@ -31,19 +31,9 @@ jobs: run: go run ./hack/cmd/update-springboot-platform - name: Run make generate run: make generate/zz_filesystem_generated.go - - name: Check for changes - id: changes - run: | - if git diff --quiet; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - fi - name: Run smoke tests - if: steps.changes.outputs.changed == 'true' run: make test-springboot - name: Create Pull Request - if: steps.changes.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: token: ${{ github.token }} diff --git a/hack/cmd/shared/shared.go b/hack/cmd/shared/shared.go index 596e8534cd..60efff5e89 100644 --- a/hack/cmd/shared/shared.go +++ b/hack/cmd/shared/shared.go @@ -6,13 +6,21 @@ import ( "net/http" "os" "os/exec" + "os/signal" "strings" + "syscall" "time" ) // HTTPClient is used for all outbound HTTP requests; has a 30-second timeout. var HTTPClient = &http.Client{Timeout: 30 * time.Second} +// NotifyContext returns a copy of parent that is canceled on SIGINT or SIGTERM. +// Callers should defer the returned stop function. +func NotifyContext(parent context.Context) (context.Context, context.CancelFunc) { + return signal.NotifyContext(parent, syscall.SIGINT, syscall.SIGTERM) +} + // RunCmd runs name with args, streaming stdout/stderr to the process output. func RunCmd(ctx context.Context, name string, args ...string) error { cmd := exec.CommandContext(ctx, name, args...) diff --git a/hack/cmd/update-ca-bundle/main.go b/hack/cmd/update-ca-bundle/main.go index 002ab11ff0..1687d32e41 100644 --- a/hack/cmd/update-ca-bundle/main.go +++ b/hack/cmd/update-ca-bundle/main.go @@ -4,8 +4,6 @@ import ( "context" "fmt" "os" - "os/signal" - "syscall" "knative.dev/func/hack/cmd/shared" ) @@ -13,17 +11,8 @@ import ( const caBundleMakeTarget = "templates/certs/ca-certificates.crt" func main() { - 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) - }() + ctx, stop := shared.NotifyContext(context.Background()) + defer stop() if err := run(ctx); err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) diff --git a/hack/cmd/update-quarkus-platform/main.go b/hack/cmd/update-quarkus-platform/main.go index f81bbc9adc..e4a822f21d 100644 --- a/hack/cmd/update-quarkus-platform/main.go +++ b/hack/cmd/update-quarkus-platform/main.go @@ -6,9 +6,7 @@ import ( "fmt" "net/http" "os" - "os/signal" "regexp" - "syscall" "knative.dev/func/hack/cmd/shared" ) @@ -36,17 +34,8 @@ type quarkusPlatformResponse struct { } func main() { - 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) - }() + ctx, stop := shared.NotifyContext(context.Background()) + defer stop() if err := run(ctx); err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) diff --git a/hack/cmd/update-springboot-platform/main.go b/hack/cmd/update-springboot-platform/main.go index 9762f21cca..b6fc9fec96 100644 --- a/hack/cmd/update-springboot-platform/main.go +++ b/hack/cmd/update-springboot-platform/main.go @@ -6,10 +6,8 @@ import ( "fmt" "net/http" "os" - "os/signal" "regexp" "strings" - "syscall" "github.com/blang/semver/v4" "gopkg.in/yaml.v3" @@ -57,17 +55,8 @@ type springCloudBOM struct { } func main() { - 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) - }() + ctx, stop := shared.NotifyContext(context.Background()) + defer stop() if err := run(ctx); err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)