From b2570b725cbc35c55ba79da7d43b227156d398d3 Mon Sep 17 00:00:00 2001 From: bwplotka Date: Thu, 21 May 2026 16:27:49 +0100 Subject: [PATCH 01/14] fix: hardening gmpctl Best effort fixes thanks to Gemini security scanning. Signed-off-by: bwplotka --- ops/gmpctl/git.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/ops/gmpctl/git.go b/ops/gmpctl/git.go index 4de71c9dbe..e944d75cae 100644 --- a/ops/gmpctl/git.go +++ b/ops/gmpctl/git.go @@ -16,6 +16,7 @@ package main import ( "fmt" + "os/exec" "strings" ) @@ -49,16 +50,26 @@ func mustFetchAll(dir string) { } } +func mustGetTTY() string { + ttyCmd := exec.Command("tty") + ttyOut, err := ttyCmd.Output() + if err != nil { + panicf(err.Error()) + } + return strings.TrimSpace(string(ttyOut)) +} + func mustCreateSignedTag(dir, tag string) { logf("Creating a signed tag %v...", tag) - // explicit TTY is often needed on Macs. + // Ensure TTY is set for GPG signing. + envs := []string{"GPG_TTY=" + mustGetTTY()} + // TODO(bwplotka): Consider adding v0.x second tag for Prometheus fork (similar to how v0.300 Prometheus releases are structured). // This is to have a little bit cleaner prometheus-engine go.mod version against the fork. if _, err := runCommand( - &cmdOpts{Dir: dir}, - "bash", "-c", - fmt.Sprintf("GPG_TTY=$(tty) git tag -s %v -m %v", tag, tag), + &cmdOpts{Dir: dir, Envs: envs}, + "git", "tag", "-s", tag, "-m", tag, ); err != nil { panicf(err.Error()) } From 8286c98abff497eafbab9a763658a5b6a15cfaf4 Mon Sep 17 00:00:00 2001 From: bwplotka Date: Fri, 29 May 2026 14:37:15 +0100 Subject: [PATCH 02/14] allow main vulnfix Signed-off-by: bwplotka --- ops/gmpctl/gmp.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ops/gmpctl/gmp.go b/ops/gmpctl/gmp.go index 4b3c76a385..ae0f566e1b 100644 --- a/ops/gmpctl/gmp.go +++ b/ops/gmpctl/gmp.go @@ -61,6 +61,8 @@ func projectFromBranch(branch string) (Project, bool) { return Alertmanager, true case PrometheusEngine.BranchRE.MatchString(branch): return PrometheusEngine, true + case strings.Compare(branch, "main") == 0: + return PrometheusEngine, true } return Project{}, false } From cddf865e792e2c2b721ceab7030dd5b8890ee029 Mon Sep 17 00:00:00 2001 From: bwplotka Date: Fri, 29 May 2026 16:11:48 +0100 Subject: [PATCH 03/14] yolo Signed-off-by: bwplotka --- ops/gmpctl/cmd_vulnfix.go | 7 ++++ ops/gmpctl/main.go | 41 ++++++++++++++++++++++ ops/gmpctl/main_test.go | 71 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 ops/gmpctl/main_test.go diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index ef94a61744..b1a4c0097d 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -82,6 +82,13 @@ func vulnfix() error { if *vulnfixSyncDockerfilesFrom { opts = append(opts, "SYNC_DOCKERFILES_FROM=true") } + version, err := detectGoMinorVersion(dir) + if err != nil { + logf("Could not detect Go version from Dockerfile: %v. Using default system Go.", err) + } else { + logf("Detected Go version from Dockerfile: %s", version) + opts = append(opts, "GOTOOLCHAIN=go"+version+".0") + } // TODO(bwplotka): Add NPM vulnfix. if err := runLocalBash(dir, opts, "vulnfix.sh"); err != nil { diff --git a/ops/gmpctl/main.go b/ops/gmpctl/main.go index 602e66823f..f6eb710e17 100644 --- a/ops/gmpctl/main.go +++ b/ops/gmpctl/main.go @@ -22,6 +22,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "gopkg.in/yaml.v3" @@ -33,6 +34,46 @@ var ( gitPreferHTTPS = flag.Bool("git.prefer-https", false, "If true, uses HTTPS protocol instead of git for remote URLs. ") ) +func detectGoMinorVersion(dir string) (string, error) { + var dockerfiles []string + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + name := info.Name() + if name == "third_party" || name == "hack" || name == "ui" || name == "vendor" || name == "node_modules" || name == ".git" { + return filepath.SkipDir + } + return nil + } + if strings.HasPrefix(info.Name(), "Dockerfile") { + dockerfiles = append(dockerfiles, path) + } + return nil + }) + if err != nil { + return "", err + } + if len(dockerfiles) == 0 { + return "", fmt.Errorf("no Dockerfile found in %s", dir) + } + + re := regexp.MustCompile(`(?:google-go\.pkg\.dev/golang|golang):([0-9]+\.[0-9]+)`) + + for _, df := range dockerfiles { + content, err := os.ReadFile(df) + if err != nil { + continue + } + matches := re.FindSubmatch(content) + if len(matches) > 1 { + return string(matches[1]), nil + } + } + return "", fmt.Errorf("could not find golang image in any Dockerfile under %s", dir) +} + type Config struct { // Directory for the gmpctl work, notably for project clones and git worktrees. Directory string `yaml:"dir"` diff --git a/ops/gmpctl/main_test.go b/ops/gmpctl/main_test.go new file mode 100644 index 0000000000..0a4eef899a --- /dev/null +++ b/ops/gmpctl/main_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestDetectGoMinorVersion(t *testing.T) { + for _, tt := range []struct { + name string + files map[string]string + expected string + }{ + { + name: "google-go.pkg.dev image", + files: map[string]string{ + "Dockerfile": ` +FROM --platform=$BUILDPLATFORM google-go.pkg.dev/golang:1.26.2@sha256:1bee769a7a50eea7730ac31f75182ae2614f50a70902407312db390a7c7cb2ff AS buildbase +ARG TARGETOS +`, + }, + expected: "1.26", + }, + { + name: "standard golang image", + files: map[string]string{ + "Dockerfile": ` +FROM golang:1.23.5 AS build +`, + }, + expected: "1.23", + }, + { + name: "skip directories", + files: map[string]string{ + "third_party/Dockerfile": "FROM golang:1.20.0", + "hack/Dockerfile": "FROM golang:1.20.0", + "ui/Dockerfile": "FROM golang:1.20.0", + "vendor/Dockerfile": "FROM golang:1.20.0", + "node_modules/Dockerfile": "FROM golang:1.20.0", + "Dockerfile": "FROM golang:1.24.1", + }, + expected: "1.24", + }, + } { + t.Run(tt.name, func(t *testing.T) { + tempDir, err := os.MkdirTemp("", "gmpctl-test") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.RemoveAll(tempDir) }) + for path, content := range tt.files { + fullPath := filepath.Join(tempDir, path) + if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + version, err := detectGoMinorVersion(tempDir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if version != tt.expected { + t.Errorf("expected version %s, got %s", tt.expected, version) + } + }) + } +} From 1eeacd606320934ce63b2ec49c2e631fe77f023a Mon Sep 17 00:00:00 2001 From: bwplotka Date: Mon, 1 Jun 2026 08:08:35 +0100 Subject: [PATCH 04/14] updates Signed-off-by: bwplotka --- ops/gmpctl/cmd_vulnfix.go | 5 +++ ops/gmpctl/github.go | 67 +++++++++++++++++++++++++++++++++++++++ ops/gmpctl/lib.sh | 14 ++++++-- 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 ops/gmpctl/github.go diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index b1a4c0097d..fc32785796 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -117,6 +117,7 @@ func vulnfix() error { // We are in detached state, so be explicit what to push and from where, by recreating the local prBranch. mustRecreateBranch(dir, prBranch) mustForcePush(dir, prBranch) + mustEnsurePullRequest(dir, branch, prBranch, msg, "Updating Go and image vulnerabilities using"+wrapCode("./gmpctl.sh vulnfix")) } else { return errors.New("aborting") } @@ -126,3 +127,7 @@ func vulnfix() error { } return nil } + +func wrapCode(s string) string { + return "\n```\n" + s + "\n```\n" +} diff --git a/ops/gmpctl/github.go b/ops/gmpctl/github.go new file mode 100644 index 0000000000..d22056d4f7 --- /dev/null +++ b/ops/gmpctl/github.go @@ -0,0 +1,67 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "strings" +) + +type prInfo struct { + URL string `json:"url"` +} + +func mustEnsurePullRequest(dir, baseBranch, headBranch, title, body string) { + logf("Checking for existing pull request for %v...", headBranch) + + // gh pr list --head --base --state open --json url + out, err := runCommand( + &cmdOpts{Dir: dir, HideOutputs: true}, + "gh", "pr", "list", + "--head", headBranch, + "--base", baseBranch, + "--state", "open", + "--json", "url", + ) + if err != nil { + panicf("failed to check existing PR: %v", err) + } + + var prs []prInfo + if err := json.Unmarshal([]byte(out), &prs); err != nil { + panicf("failed to parse gh output: %v, output: %q", err, out) + } + + if len(prs) > 0 { + logf("Pull request already exists: %v", prs[0].URL) + return + } + + logf("Creating pull request from %v to %v...", headBranch, baseBranch) + + // gh pr create --title --body <body> --base <base> --head <head> + prURL, err := runCommand( + &cmdOpts{Dir: dir}, + "gh", "pr", "create", + "--title", title, + "--body", body, + "--base", baseBranch, + "--head", headBranch, + ) + if err != nil { + panicf("failed to create pull request: %v", err) + } + logf("Pull request created: %v", strings.TrimSpace(prURL)) +} diff --git a/ops/gmpctl/lib.sh b/ops/gmpctl/lib.sh index 3e0d4bdaeb..61c091cb70 100755 --- a/ops/gmpctl/lib.sh +++ b/ops/gmpctl/lib.sh @@ -449,10 +449,18 @@ release-lib::manifests_regen() { # Hack: Do the bingo variable swap. This allows injecting our own. # This is faster than running requiring bingo and running bingo get. - cp "${dir}/.bingo/variables.env" "${dir}/.bingo/variables.env.bak" - echo "#!/bin/bash" >"${dir}/.bingo/variables.env" # Clean the file. + # NOTE: Only needed before 0.19. + if [[ -f "${dir}/.bingo/variables.env" ]]; then + cp "${dir}/.bingo/variables.env" "${dir}/.bingo/variables.env.bak" + echo "#!/bin/bash" >"${dir}/.bingo/variables.env" # Clean the file. + fi + # Regenerate. YQ="$(which yq)" HELM="$(which helm)" ADDLICENSE="$(which addlicense)" bash "${dir}/hack/presubmit.sh" manifests - cp "${dir}/.bingo/variables.env.bak" "${dir}/.bingo/variables.env" + + # NOTE: Only needed before 0.19. + if [[ -f "${dir}/.bingo/variables.env.bak" ]]; then + mv "${dir}/.bingo/variables.env.bak" "${dir}/.bingo/variables.env" + fi echo "✅ Manifests regenerated" return 0 From a67acc12e519ddcaa316f306b3237062db2c6a3e Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Mon, 1 Jun 2026 08:59:49 +0100 Subject: [PATCH 05/14] go override Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_vulnfix.go | 19 ++++++++++++------- ops/gmpctl/vulnfix.sh | 14 ++++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index fc32785796..42afed3357 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -26,6 +26,7 @@ var ( vulnfixBranch = vulnfixFlags.String("b", "", "Release branch to work on; Project is auto-detected from this") vulnfixPRBranch = vulnfixFlags.String("pr-branch", "", "(default: $USER/BRANCH-vulnfix) Upstream branch to push to (user-confirmed first).") vulnfixSyncDockerfilesFrom = vulnfixFlags.Bool("sync-dockerfiles-from", false, "Optional branch name to sync Dockerfiles from. Useful when things changed.") + vulnfixGoVersion = vulnfixFlags.String("go-version", "", "Go minor version to use for toolchain and docker images.") ) // Attempt a minimal dependency upgrade to solve fixable vulnerabilities. @@ -74,21 +75,25 @@ func vulnfix() error { // Refresh. mustFetchAll(dir) + goVersion := *vulnfixGoVersion + if goVersion == "" { + goVersion, err = detectGoMinorVersion(dir) + if err != nil { + return fmt.Errorf("could not detect Go version from Dockerfile: %v", err) + } + } + logf("Using Go version: %s", goVersion) + opts := []string{ fmt.Sprintf("DIR=%v", dir), fmt.Sprintf("BRANCH=%v", branch), fmt.Sprintf("PROJECT=%v", proj.Name), + fmt.Sprintf("GO_VERSION=%v", goVersion), + fmt.Sprintf("GOTOOLCHAIN=go" + goVersion + ".0"), } if *vulnfixSyncDockerfilesFrom { opts = append(opts, "SYNC_DOCKERFILES_FROM=true") } - version, err := detectGoMinorVersion(dir) - if err != nil { - logf("Could not detect Go version from Dockerfile: %v. Using default system Go.", err) - } else { - logf("Detected Go version from Dockerfile: %s", version) - opts = append(opts, "GOTOOLCHAIN=go"+version+".0") - } // TODO(bwplotka): Add NPM vulnfix. if err := runLocalBash(dir, opts, "vulnfix.sh"); err != nil { diff --git a/ops/gmpctl/vulnfix.sh b/ops/gmpctl/vulnfix.sh index b1c5fbbc29..4b206fa2b0 100644 --- a/ops/gmpctl/vulnfix.sh +++ b/ops/gmpctl/vulnfix.sh @@ -53,6 +53,11 @@ if [[ -z "${PROJECT}" ]]; then exit 1 fi +if [[ -z "${GO_VERSION}" ]]; then + log_err "GO_VERSION envvar is required." + exit 1 +fi + echo "${DIR}" echo "${SCRIPT_DIR}" @@ -71,18 +76,11 @@ fi # Docker images bumps. -# Get first dockerfile Go version. We will use this version to find minor version to stick to. -go_version=$(release-lib::dockerfile_go_version "${DOCKERFILES[0]}") -if [[ -z "${go_version}" ]]; then - echo "❌ can't find any golang image in ${DOCKERFILES[0]}" - exit 1 -fi - # TODO: git add charts & vendor for old projects? # Update our images. for dockerfile in "${DOCKERFILES[@]}"; do - release-lib::dockerfile_update_image "${dockerfile}" "google-go.pkg.dev/golang" $(echo "${go_version}" | cut -d '.' -f 1-2) + release-lib::dockerfile_update_image "${dockerfile}" "google-go.pkg.dev/golang" $(echo "${GO_VERSION}" | cut -d '.' -f 1-2) release-lib::dockerfile_update_image "${dockerfile}" "gke.gcr.io/gke-distroless/libc" "gke_distroless_" pushd "${DIR}" git add "${dockerfile}" From c213bab5084677a0dcc75c6176b5c0bdb8225458 Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Mon, 1 Jun 2026 10:52:25 +0100 Subject: [PATCH 06/14] fix Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_vulnfix.go | 43 +++++++++++++++++++++++++++++++++++++++ ops/gmpctl/lib.sh | 2 +- ops/gmpctl/main.go | 41 ------------------------------------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index 42afed3357..f75ca8a7a2 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -19,6 +19,9 @@ import ( "flag" "fmt" "os" + "path/filepath" + "regexp" + "strings" ) var ( @@ -133,6 +136,46 @@ func vulnfix() error { return nil } +func detectGoMinorVersion(dir string) (string, error) { + var dockerfiles []string + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + name := info.Name() + if name == "third_party" || name == "ui" || name == "vendor" || name == "node_modules" || name == ".git" { + return filepath.SkipDir + } + return nil + } + if strings.HasPrefix(info.Name(), "Dockerfile") { + dockerfiles = append(dockerfiles, path) + } + return nil + }) + if err != nil { + return "", err + } + if len(dockerfiles) == 0 { + return "", fmt.Errorf("no Dockerfile found in %s", dir) + } + + re := regexp.MustCompile(`(?:google-go\.pkg\.dev/golang|golang):([0-9]+\.[0-9]+)`) + + for _, df := range dockerfiles { + content, err := os.ReadFile(df) + if err != nil { + continue + } + matches := re.FindSubmatch(content) + if len(matches) > 1 { + return string(matches[1]), nil + } + } + return "", fmt.Errorf("could not find golang image in any Dockerfile under %s", dir) +} + func wrapCode(s string) string { return "\n```\n" + s + "\n```\n" } diff --git a/ops/gmpctl/lib.sh b/ops/gmpctl/lib.sh index 61c091cb70..1e5b0a7c5f 100755 --- a/ops/gmpctl/lib.sh +++ b/ops/gmpctl/lib.sh @@ -244,7 +244,7 @@ release-lib::dockerfiles() { log_err "dir arg is required." return 1 fi - find "${dir}" -name "Dockerfile*" | grep -v "${dir}/third_party/" | grep -v "${dir}/hack/" | grep -v "${dir}/ui/" | grep -v "vendor/" | grep -v "node_modules/" + find "${dir}" -name "Dockerfile*" | grep -v "${dir}/third_party/" | grep -v "${dir}/ui/" | grep -v "vendor/" | grep -v "node_modules/" } # Return all images used in a Dockerfile, delimited by new-line. diff --git a/ops/gmpctl/main.go b/ops/gmpctl/main.go index f6eb710e17..602e66823f 100644 --- a/ops/gmpctl/main.go +++ b/ops/gmpctl/main.go @@ -22,7 +22,6 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "strings" "gopkg.in/yaml.v3" @@ -34,46 +33,6 @@ var ( gitPreferHTTPS = flag.Bool("git.prefer-https", false, "If true, uses HTTPS protocol instead of git for remote URLs. ") ) -func detectGoMinorVersion(dir string) (string, error) { - var dockerfiles []string - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - name := info.Name() - if name == "third_party" || name == "hack" || name == "ui" || name == "vendor" || name == "node_modules" || name == ".git" { - return filepath.SkipDir - } - return nil - } - if strings.HasPrefix(info.Name(), "Dockerfile") { - dockerfiles = append(dockerfiles, path) - } - return nil - }) - if err != nil { - return "", err - } - if len(dockerfiles) == 0 { - return "", fmt.Errorf("no Dockerfile found in %s", dir) - } - - re := regexp.MustCompile(`(?:google-go\.pkg\.dev/golang|golang):([0-9]+\.[0-9]+)`) - - for _, df := range dockerfiles { - content, err := os.ReadFile(df) - if err != nil { - continue - } - matches := re.FindSubmatch(content) - if len(matches) > 1 { - return string(matches[1]), nil - } - } - return "", fmt.Errorf("could not find golang image in any Dockerfile under %s", dir) -} - type Config struct { // Directory for the gmpctl work, notably for project clones and git worktrees. Directory string `yaml:"dir"` From c59b772622bb719fa37d4da37d9dbdf8a811adf8 Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Mon, 1 Jun 2026 11:18:30 +0100 Subject: [PATCH 07/14] fix deps Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl.sh | 6 ++++++ ops/gmpctl/cmd_vulnfix.go | 2 +- ops/gmpctl/lib.sh | 7 ------- ops/gmpctl/vulnfix.sh | 5 +---- ops/gmpctl/vulnupdatelist/nvdapi.go | 1 + 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ops/gmpctl.sh b/ops/gmpctl.sh index 794a7a0828..9a45cb7120 100755 --- a/ops/gmpctl.sh +++ b/ops/gmpctl.sh @@ -26,6 +26,12 @@ SCRIPT_DIR="$( pwd -P )" +# Install dependencies in the current context. We switch Go toolchain versions so using go tool might not work. +go install github.com/google/go-containerregistry/cmd/gcrane@latest +go install github.com/mikefarah/yq/v4@latest +go install helm.sh/helm/v3/cmd/helm@latest +go install github.com/google/addlicense@latest + # NOTE gmpctl expects the gmpctl directory to be present on execution for # local bash scripts and configuration. # diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index f75ca8a7a2..5d80552cf0 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -92,7 +92,7 @@ func vulnfix() error { fmt.Sprintf("BRANCH=%v", branch), fmt.Sprintf("PROJECT=%v", proj.Name), fmt.Sprintf("GO_VERSION=%v", goVersion), - fmt.Sprintf("GOTOOLCHAIN=go" + goVersion + ".0"), + fmt.Sprintf("GOTOOLCHAIN=go1.25.0"), // We hardcode toolchain everywhere. } if *vulnfixSyncDockerfilesFrom { opts = append(opts, "SYNC_DOCKERFILES_FROM=true") diff --git a/ops/gmpctl/lib.sh b/ops/gmpctl/lib.sh index 1e5b0a7c5f..da48e10970 100755 --- a/ops/gmpctl/lib.sh +++ b/ops/gmpctl/lib.sh @@ -407,8 +407,6 @@ release-lib::idemp::manifests_bash_image_bump() { return 1 fi - go install github.com/mikefarah/yq/v4@latest - local values_file="${dir}/charts/values.global.yaml" # TODO: Not enough, this has to check actual manifests. local bash_tag=$(yq '.images.bash.tag' "${values_file}") @@ -442,11 +440,6 @@ release-lib::manifests_regen() { return 1 fi - # TODO(bwplotka): Manage deps better. It's getting confusing what bins we should use (worktree bingo? script bingo?). - go install helm.sh/helm/v3/cmd/helm@latest - go install github.com/google/addlicense@latest - go install github.com/mikefarah/yq/v4@latest - # Hack: Do the bingo variable swap. This allows injecting our own. # This is faster than running requiring bingo and running bingo get. # NOTE: Only needed before 0.19. diff --git a/ops/gmpctl/vulnfix.sh b/ops/gmpctl/vulnfix.sh index 4b206fa2b0..14f9565fc9 100644 --- a/ops/gmpctl/vulnfix.sh +++ b/ops/gmpctl/vulnfix.sh @@ -33,9 +33,6 @@ fi source "${SCRIPT_DIR}/lib.sh" -# TODO: Find better way. Go tool grane is tricky as we run in different directory. -go install github.com/google/go-containerregistry/cmd/gcrane@latest - # Also accepts SYNC_DOCKERFILES_FROM. if [[ -z "${DIR}" ]]; then @@ -80,6 +77,7 @@ fi # Update our images. for dockerfile in "${DOCKERFILES[@]}"; do + # TOOD(bwplotka): Bump gcr.io/distroless/static-debian12:nonroot images as well https://github.com/GoogleCloudPlatform/prometheus-engine/pull/1933 release-lib::dockerfile_update_image "${dockerfile}" "google-go.pkg.dev/golang" $(echo "${GO_VERSION}" | cut -d '.' -f 1-2) release-lib::dockerfile_update_image "${dockerfile}" "gke.gcr.io/gke-distroless/libc" "gke_distroless_" pushd "${DIR}" @@ -114,7 +112,6 @@ if [[ "no vulnerabilities" != $(cat "${vuln_file}") ]]; then fi # Check if that helped. - echo "⚠️ This will fail on older branches with vendoring; in this case, simply go to ${DIR}, run 'go mod vendor' and rerun." release-lib::vulnlist "${DIR}" "${vuln_file}" if [[ "no vulnerabilities" != $(cat "${vuln_file}") ]]; then echo "❌ After go mod update some vulnerabilities are still found; go to ${DIR} and resolve it manually (select not reusing the ./vulnlist.txt file) and rerun." diff --git a/ops/gmpctl/vulnupdatelist/nvdapi.go b/ops/gmpctl/vulnupdatelist/nvdapi.go index 3a59085f7c..d91758bb04 100644 --- a/ops/gmpctl/vulnupdatelist/nvdapi.go +++ b/ops/gmpctl/vulnupdatelist/nvdapi.go @@ -111,6 +111,7 @@ func getCVEDetails(apiKey string, osv OSV) CVE { if cveID.ID == "" { return CVE{ID: osv.ID, Severity: "UNKNOWN"} // Fallback to GO ID. } + // TODO: Cache CVS Severity -- lot's of duplicates. sev, err := getCVSSSeverity(apiKey, cveID.ID) if err != nil { slog.Error("failed to find severity", "cve", cveID, "err", err) From 47c800e843f415bafba4a5f196414cd739e6a224 Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Mon, 1 Jun 2026 13:55:47 +0100 Subject: [PATCH 08/14] add semconv sync Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_vulnfix.go | 107 +++++++++++++++++++++++++++++++++++ ops/gmpctl/main_test.go | 116 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index 5d80552cf0..aac38533bb 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -19,6 +19,7 @@ import ( "flag" "fmt" "os" + "os/exec" "path/filepath" "regexp" "strings" @@ -103,6 +104,10 @@ func vulnfix() error { return err } + if err := fixOtelSchemaConflict(dir); err != nil { + return err + } + // TODO: Warn of unstaged files at this point. // Commit if anything is staged. @@ -179,3 +184,105 @@ func detectGoMinorVersion(dir string) (string, error) { func wrapCode(s string) string { return "\n```\n" + s + "\n```\n" } + +// It's a common occurrence that schema import goes off-sync with the go module, fix it. +func fixOtelSchemaConflict(dir string) error { + targetVersion, err := detectSchemaVersion(dir) + if err != nil { + return err + } + if targetVersion == "" { + return nil + } + return replaceOtelImports(dir, targetVersion) +} + +// TODO(bwplotka): AI figured some way, but there's likely a better way to tell? +func detectSchemaVersion(dir string) (string, error) { + tmpFile := filepath.Join(dir, "gmpctl_tmp_schema.go") + tmpCode := `package main + +import ( + "fmt" + "go.opentelemetry.io/otel/sdk/resource" +) + +func main() { + r := resource.Default() + fmt.Print(r.SchemaURL()) +} +` + if err := os.WriteFile(tmpFile, []byte(tmpCode), 0o644); err != nil { + return "", fmt.Errorf("failed to write temp file: %w", err) + } + defer os.Remove(tmpFile) + + cmd := exec.Command("go", "run", "gmpctl_tmp_schema.go") + cmd.Dir = dir + out, err := cmd.Output() + if err != nil { + // If it fails to run, it might be because otel/sdk is not in dependencies, + // or some other issue. We log and ignore to not block the whole pipeline if it's not relevant. + logf("Warning: failed to run temp schema detector: %v", err) + return "", nil + } + + schemaURL := string(out) + if schemaURL == "" { + logf("No schema URL detected from SDK resource") + return "", nil + } + + reVersion := regexp.MustCompile(`([0-9]+\.[0-9]+\.[0-9]+)$`) + matches := reVersion.FindStringSubmatch(schemaURL) + if len(matches) < 2 { + logf("Could not parse version from schema URL: %s", schemaURL) + return "", nil + } + return "v" + matches[1], nil +} + +func replaceOtelImports(dir string, targetVersion string) error { + logf("Detected target OpenTelemetry schema version: %s", targetVersion) + + reImport := regexp.MustCompile(`"go\.opentelemetry\.io/otel/semconv/(v1\.[0-9]+\.[0-9]+)"`) + reSchemaURLUse := regexp.MustCompile(`\.SchemaURL\b`) + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + name := info.Name() + if name == "vendor" || name == "third_party" || name == ".git" { + return filepath.SkipDir + } + return nil + } + if !strings.HasSuffix(info.Name(), ".go") { + return nil + } + + content, err := os.ReadFile(path) + if err != nil { + return err + } + + if !reImport.Match(content) || !reSchemaURLUse.Match(content) { + return nil + } + + newContent := reImport.ReplaceAllFunc(content, func(match []byte) []byte { + return []byte(fmt.Sprintf(`"go.opentelemetry.io/otel/semconv/%s"`, targetVersion)) + }) + + if string(newContent) != string(content) { + logf("Updating OTEL semconv imports to %s in %s", targetVersion, path) + if err := os.WriteFile(path, newContent, 0o644); err != nil { + return fmt.Errorf("failed to write file %s: %w", path, err) + } + } + return nil + }) + return err +} diff --git a/ops/gmpctl/main_test.go b/ops/gmpctl/main_test.go index 0a4eef899a..3c9454505a 100644 --- a/ops/gmpctl/main_test.go +++ b/ops/gmpctl/main_test.go @@ -69,3 +69,119 @@ FROM golang:1.23.5 AS build }) } } + +func TestReplaceOtelImports(t *testing.T) { + for _, tt := range []struct { + name string + files map[string]string + targetVersion string + expected map[string]string + }{ + { + name: "replace import when SchemaURL is used", + files: map[string]string{ + "tracing.go": `package tracing +import ( + semconv "go.opentelemetry.io/otel/semconv/v1.39.0" +) +func init() { + _ = semconv.SchemaURL +} +`, + }, + targetVersion: "v1.40.0", + expected: map[string]string{ + "tracing.go": `package tracing +import ( + semconv "go.opentelemetry.io/otel/semconv/v1.40.0" +) +func init() { + _ = semconv.SchemaURL +} +`, + }, + }, + { + name: "do not replace import when SchemaURL is NOT used", + files: map[string]string{ + "queue.go": `package queue +import ( + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" +) +func init() { + _ = semconv.HTTPResendCount +} +`, + }, + targetVersion: "v1.40.0", + expected: map[string]string{ + "queue.go": `package queue +import ( + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" +) +func init() { + _ = semconv.HTTPResendCount +} +`, + }, + }, + { + name: "replace with alias", + files: map[string]string{ + "tracing.go": `package tracing +import ( + conventions "go.opentelemetry.io/otel/semconv/v1.39.0" +) +func init() { + _ = conventions.SchemaURL +} +`, + }, + targetVersion: "v1.40.0", + expected: map[string]string{ + "tracing.go": `package tracing +import ( + conventions "go.opentelemetry.io/otel/semconv/v1.40.0" +) +func init() { + _ = conventions.SchemaURL +} +`, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + tempDir, err := os.MkdirTemp("", "gmpctl-otel-test") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.RemoveAll(tempDir) }) + for path, content := range tt.files { + fullPath := filepath.Join(tempDir, path) + if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + err = replaceOtelImports(tempDir, tt.targetVersion) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + for path, expectedContent := range tt.expected { + fullPath := filepath.Join(tempDir, path) + content, err := os.ReadFile(fullPath) + if err != nil { + t.Fatal(err) + } + if string(content) != expectedContent { + t.Errorf("file %s: expected content:\n%s\ngot:\n%s", path, expectedContent, string(content)) + } + } + }) + } +} + From 890db03fba8ec0530832f5107a86bf9505110a6a Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Mon, 1 Jun 2026 14:11:16 +0100 Subject: [PATCH 09/14] cache Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/vulnupdatelist/nvdapi.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ops/gmpctl/vulnupdatelist/nvdapi.go b/ops/gmpctl/vulnupdatelist/nvdapi.go index d91758bb04..4b1da64e6a 100644 --- a/ops/gmpctl/vulnupdatelist/nvdapi.go +++ b/ops/gmpctl/vulnupdatelist/nvdapi.go @@ -21,9 +21,15 @@ import ( "log/slog" "net/http" "strings" + "sync" "time" ) +var ( + severityCache = make(map[string]string) + cacheMu sync.RWMutex +) + // NVDResponse is the top-level object for the NVD CVE API. type NVDResponse struct { Vulnerabilities []struct { @@ -42,6 +48,13 @@ type NVDResponse struct { // getCVSSSeverity fetches vulnerability details from the NVD API and returns the CVSS V3 severity. func getCVSSSeverity(apiKey, cveID string) (string, error) { + cacheMu.RLock() + if sev, ok := severityCache[cveID]; ok { + cacheMu.RUnlock() + return sev, nil + } + cacheMu.RUnlock() + // https://nvd.nist.gov/developers/vulnerabilities apiURL := fmt.Sprintf("https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=%s", cveID) @@ -76,10 +89,17 @@ func getCVSSSeverity(apiKey, cveID string) (string, error) { if len(nvdResponse.Vulnerabilities) > 0 { metrics := nvdResponse.Vulnerabilities[0].CVE.Metrics if len(metrics.CVSSMetricV31) > 0 { - return metrics.CVSSMetricV31[0].CVSSData.BaseSeverity, nil + sev := metrics.CVSSMetricV31[0].CVSSData.BaseSeverity + cacheMu.Lock() + severityCache[cveID] = sev + cacheMu.Unlock() + return sev, nil } } + cacheMu.Lock() + severityCache[cveID] = "UNKNOWN" + cacheMu.Unlock() return "UNKNOWN", nil } @@ -111,7 +131,6 @@ func getCVEDetails(apiKey string, osv OSV) CVE { if cveID.ID == "" { return CVE{ID: osv.ID, Severity: "UNKNOWN"} // Fallback to GO ID. } - // TODO: Cache CVS Severity -- lot's of duplicates. sev, err := getCVSSSeverity(apiKey, cveID.ID) if err != nil { slog.Error("failed to find severity", "cve", cveID, "err", err) From ce5cd3641465253b8e3bce6767d659a98b730228 Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Mon, 1 Jun 2026 14:41:14 +0100 Subject: [PATCH 10/14] fix git add Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_vulnfix.go | 11 +++++++---- ops/gmpctl/git.go | 6 ++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index aac38533bb..b942890636 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -108,7 +108,7 @@ func vulnfix() error { return err } - // TODO: Warn of unstaged files at this point. + // TODO: Warn of any unstaged files at this point. // Commit if anything is staged. msg := fmt.Sprintf("google patch[deps]: fix %v vulnerabilities", branch) @@ -248,7 +248,7 @@ func replaceOtelImports(dir string, targetVersion string) error { reImport := regexp.MustCompile(`"go\.opentelemetry\.io/otel/semconv/(v1\.[0-9]+\.[0-9]+)"`) reSchemaURLUse := regexp.MustCompile(`\.SchemaURL\b`) - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } @@ -283,6 +283,9 @@ func replaceOtelImports(dir string, targetVersion string) error { } } return nil - }) - return err + }); err != nil { + return err + } + mustAddAll(dir) + return nil } diff --git a/ops/gmpctl/git.go b/ops/gmpctl/git.go index e944d75cae..e514759cbb 100644 --- a/ops/gmpctl/git.go +++ b/ops/gmpctl/git.go @@ -117,6 +117,12 @@ func mustRecreateBranch(dir, branch string) { } } +func mustAddAll(dir string) { + if _, err := runCommand(&cmdOpts{Dir: dir}, "git", "add", "--all"); err != nil { + panicf("failed to git add all: %v", err) + } +} + func checkoutBranch(dir, branchName string) { if _, err := runCommand(&cmdOpts{Dir: dir}, "git", "checkout", branchName); err != nil { panicf(err.Error()) From c2924e6e36861d3d72ed7f54cd41a03b1f8050ec Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Tue, 2 Jun 2026 17:15:07 +0100 Subject: [PATCH 11/14] tmp Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_release.go | 17 +++++++---------- ops/gmpctl/git.go | 7 ++++--- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/ops/gmpctl/cmd_release.go b/ops/gmpctl/cmd_release.go index 2ce2a9f85d..64d45c3492 100644 --- a/ops/gmpctl/cmd_release.go +++ b/ops/gmpctl/cmd_release.go @@ -83,22 +83,19 @@ func release() error { return err } + // TODO(bwplotka): Check if tag exists. + mustCreateSignedTag(dir, tag) + if !mustIsRemoteUpToDate(dir, branch) { - if confirmf("About to git push state from %q to \"origin/%v\" for %q tag; are you sure?", dir, branch, tag) { - // We are in detached state, so use the HEAD reference. - mustPush(dir, fmt.Sprintf("HEAD:%v", branch)) + if confirmf("About to git push state (HEAD:%v) and tag %q from %q to \"origin\"; are you sure?", branch, tag, dir) { + mustPush(dir, fmt.Sprintf("HEAD:%v", branch), tag) } else { return errors.New("aborting") } - } - - // TODO(bwplotka): Check if tag exists. - mustCreateSignedTag(dir, tag) - if confirmf("About to git push %q tag from %q to \"origin/%v\"; are you sure?", tag, dir, branch) { - mustPush(dir, tag) } else { - return errors.New("aborting") + logf("Nothing to push, everything seems up-to-date!") } + if confirmf("Do you want to remove the %v worktree (recommended)?", dir) { proj.RemoveWorkDir(cfg.Directory, dir) } diff --git a/ops/gmpctl/git.go b/ops/gmpctl/git.go index e514759cbb..855f08bf9e 100644 --- a/ops/gmpctl/git.go +++ b/ops/gmpctl/git.go @@ -95,9 +95,10 @@ func mustIsRemoteUpToDate(dir, branch string) bool { return strings.TrimSpace(localHead) == strings.TrimSpace(remoteHead) } -func mustPush(dir, what string) { - logf("Pushing %v...", what) - if _, err := runCommand(&cmdOpts{Dir: dir}, "git", "push", "origin", what); err != nil { +func mustPush(dir string, what ...string) { + logf("Pushing %v...", strings.Join(what, " ")) + args := append([]string{"git", "push", "origin"}, what...) + if _, err := runCommand(&cmdOpts{Dir: dir}, args...); err != nil { panicf("failed to push: %v", err) } } From 43907f5a3e49d5042af4024482e4c45ca7339602 Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Tue, 2 Jun 2026 17:57:37 +0100 Subject: [PATCH 12/14] fix tagging Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_release.go | 16 +++++++++++----- ops/gmpctl/git.go | 12 +++++------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/ops/gmpctl/cmd_release.go b/ops/gmpctl/cmd_release.go index 64d45c3492..64004e5261 100644 --- a/ops/gmpctl/cmd_release.go +++ b/ops/gmpctl/cmd_release.go @@ -83,17 +83,23 @@ func release() error { return err } - // TODO(bwplotka): Check if tag exists. - mustCreateSignedTag(dir, tag) - if !mustIsRemoteUpToDate(dir, branch) { - if confirmf("About to git push state (HEAD:%v) and tag %q from %q to \"origin\"; are you sure?", branch, tag, dir) { + if confirmf("About to create a signed tag %q and git push state (HEAD:%v) and tag %q from %q to \"origin\"; are you sure?", tag, branch, tag, dir) { + // TODO(bwplotka): Handle local repinning? + mustCreateSignedTag(dir, tag) mustPush(dir, fmt.Sprintf("HEAD:%v", branch), tag) } else { return errors.New("aborting") } } else { - logf("Nothing to push, everything seems up-to-date!") + // Retagging only. This can happen if someone wants to continue the script. + if confirmf("About to create a signed tag %q and push it from %q to \"origin\"; are you sure?", tag, dir) { + // TODO(bwplotka): Handle local repinning? + mustCreateSignedTag(dir, tag) + mustPush(dir, tag) + } else { + return errors.New("aborting") + } } if confirmf("Do you want to remove the %v worktree (recommended)?", dir) { diff --git a/ops/gmpctl/git.go b/ops/gmpctl/git.go index 855f08bf9e..93ad281fe4 100644 --- a/ops/gmpctl/git.go +++ b/ops/gmpctl/git.go @@ -16,7 +16,6 @@ package main import ( "fmt" - "os/exec" "strings" ) @@ -50,20 +49,19 @@ func mustFetchAll(dir string) { } } -func mustGetTTY() string { - ttyCmd := exec.Command("tty") - ttyOut, err := ttyCmd.Output() +func getTTY(dir string) string { + out, err := runCommand(&cmdOpts{Dir: dir}, "tty") if err != nil { - panicf(err.Error()) + return "" } - return strings.TrimSpace(string(ttyOut)) + return strings.TrimSpace(out) } func mustCreateSignedTag(dir, tag string) { logf("Creating a signed tag %v...", tag) // Ensure TTY is set for GPG signing. - envs := []string{"GPG_TTY=" + mustGetTTY()} + envs := []string{"GPG_TTY=" + getTTY(dir)} // TODO(bwplotka): Consider adding v0.x second tag for Prometheus fork (similar to how v0.300 Prometheus releases are structured). // This is to have a little bit cleaner prometheus-engine go.mod version against the fork. From e57eb83fc53f8b77e58453f61c051a6214e9e543 Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Wed, 3 Jun 2026 14:13:25 +0100 Subject: [PATCH 13/14] fix tagging Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_release.go | 5 +---- ops/gmpctl/git.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/ops/gmpctl/cmd_release.go b/ops/gmpctl/cmd_release.go index 64004e5261..0a0e6942d6 100644 --- a/ops/gmpctl/cmd_release.go +++ b/ops/gmpctl/cmd_release.go @@ -83,10 +83,9 @@ func release() error { return err } + mustCreateOrRecreateTag(dir, tag) if !mustIsRemoteUpToDate(dir, branch) { if confirmf("About to create a signed tag %q and git push state (HEAD:%v) and tag %q from %q to \"origin\"; are you sure?", tag, branch, tag, dir) { - // TODO(bwplotka): Handle local repinning? - mustCreateSignedTag(dir, tag) mustPush(dir, fmt.Sprintf("HEAD:%v", branch), tag) } else { return errors.New("aborting") @@ -94,8 +93,6 @@ func release() error { } else { // Retagging only. This can happen if someone wants to continue the script. if confirmf("About to create a signed tag %q and push it from %q to \"origin\"; are you sure?", tag, dir) { - // TODO(bwplotka): Handle local repinning? - mustCreateSignedTag(dir, tag) mustPush(dir, tag) } else { return errors.New("aborting") diff --git a/ops/gmpctl/git.go b/ops/gmpctl/git.go index 93ad281fe4..686a9e0ce6 100644 --- a/ops/gmpctl/git.go +++ b/ops/gmpctl/git.go @@ -73,6 +73,41 @@ func mustCreateSignedTag(dir, tag string) { } } +func mustCreateOrRecreateTag(dir, tag string) { + // Check if the tag exists on origin, and if so, finish + _, errLs := runCommand(&cmdOpts{Dir: dir, HideOutputs: true}, "git", "ls-remote", "--exit-code", "--tags", "origin", "refs/tags/"+tag) + if errLs == nil { + panicf("This tag %v was already pushed, chose a different one!", tag) + } + + // Check if the tag already exists locally. + tagCommit, err := runCommand(&cmdOpts{Dir: dir, HideOutputs: true}, "git", "rev-parse", "--verify", "-q", "refs/tags/"+tag+"^{commit}") + if err == nil { + // Tag exists locally! + headCommit, err := runCommand(&cmdOpts{Dir: dir, HideOutputs: true}, "git", "rev-parse", "HEAD") + if err != nil { + panicf("failed to get HEAD commit: %v", err) + } + + tagCommit = strings.TrimSpace(tagCommit) + headCommit = strings.TrimSpace(headCommit) + + if tagCommit == headCommit { + logf("Tag %q already exists locally and points to HEAD (%s). Skipping recreation.", tag, headCommit) + return + } + logf("Tag %q exists locally but points to commit %s, while HEAD is at %s. Re-tagging...", tag, tagCommit, headCommit) + + // Delete the local tag. + if _, err := runCommand(&cmdOpts{Dir: dir}, "git", "tag", "-d", tag); err != nil { + panicf("failed to delete local tag %s: %v", tag, err) + } + } + + // Create the signed tag. + mustCreateSignedTag(dir, tag) +} + // mustIsRemoteUpToDate returns true if HEAD points to the same commit as // the origin branch func mustIsRemoteUpToDate(dir, branch string) bool { From f678206f8ba7e779e52819c082815bbf0c6725d8 Mon Sep 17 00:00:00 2001 From: bwplotka <bwplotka@google.com> Date: Wed, 3 Jun 2026 15:40:18 +0100 Subject: [PATCH 14/14] fixes Signed-off-by: bwplotka <bwplotka@google.com> --- ops/gmpctl/cmd_vulnfix.go | 15 ++++++++++++--- ops/gmpctl/git.go | 2 +- ops/gmpctl/lib.sh | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ops/gmpctl/cmd_vulnfix.go b/ops/gmpctl/cmd_vulnfix.go index b942890636..e2f7759a50 100644 --- a/ops/gmpctl/cmd_vulnfix.go +++ b/ops/gmpctl/cmd_vulnfix.go @@ -93,19 +93,28 @@ func vulnfix() error { fmt.Sprintf("BRANCH=%v", branch), fmt.Sprintf("PROJECT=%v", proj.Name), fmt.Sprintf("GO_VERSION=%v", goVersion), - fmt.Sprintf("GOTOOLCHAIN=go1.25.0"), // We hardcode toolchain everywhere. + // We hardcode toolchain everywhere for now, until we have deps that require higher version. + // This makes it simpler to maintain dependencies across old versions, forks and tools (e.g. code gen). + fmt.Sprintf("GOTOOLCHAIN=go1.25.0"), } if *vulnfixSyncDockerfilesFrom { opts = append(opts, "SYNC_DOCKERFILES_FROM=true") } + // Update go version in go.mod to what toolchain is set to if it was updated by accident + // otherwise it won't work with our toolchain. + if _, err := runCommand(&cmdOpts{Dir: dir, Envs: opts}, "go", "mod", "edit", "-go=1.25.0"); err != nil { + return fmt.Errorf("failed to update go version in go.mod: %v", err) + } // TODO(bwplotka): Add NPM vulnfix. if err := runLocalBash(dir, opts, "vulnfix.sh"); err != nil { return err } - if err := fixOtelSchemaConflict(dir); err != nil { - return err + if proj.Name != "prometheus-engine" { + if err := fixOtelSchemaConflict(dir); err != nil { + return err + } } // TODO: Warn of any unstaged files at this point. diff --git a/ops/gmpctl/git.go b/ops/gmpctl/git.go index 686a9e0ce6..8a53d4fcc0 100644 --- a/ops/gmpctl/git.go +++ b/ops/gmpctl/git.go @@ -50,7 +50,7 @@ func mustFetchAll(dir string) { } func getTTY(dir string) string { - out, err := runCommand(&cmdOpts{Dir: dir}, "tty") + out, err := runCommand(&cmdOpts{Dir: dir, HideOutputs: true}, "tty") if err != nil { return "" } diff --git a/ops/gmpctl/lib.sh b/ops/gmpctl/lib.sh index da48e10970..c45eb81ecf 100755 --- a/ops/gmpctl/lib.sh +++ b/ops/gmpctl/lib.sh @@ -447,6 +447,8 @@ release-lib::manifests_regen() { cp "${dir}/.bingo/variables.env" "${dir}/.bingo/variables.env.bak" echo "#!/bin/bash" >"${dir}/.bingo/variables.env" # Clean the file. fi + + echo "🔄 Regenerating manifests..." # Regenerate. YQ="$(which yq)" HELM="$(which helm)" ADDLICENSE="$(which addlicense)" bash "${dir}/hack/presubmit.sh" manifests