From c917a5d4fcf41cc207c5b0ef1adb4ac2f2dff0da Mon Sep 17 00:00:00 2001 From: Pierre Guilleminot Date: Fri, 16 Oct 2026 18:00:15 +0200 Subject: [PATCH] fix(homebrew): avoid redundant formula version Homebrew audit rejects an explicit version when it can infer the same version from the release URL. Read the update offer version from those URLs instead, and update the demo and formula expectations. --- .github/homebrew/bits.rb.erb | 2 -- internal/update/homebrew.go | 22 +++++++++++++++------- internal/update/update_test.go | 13 ++++++------- scripts/ciworkflow_test.go | 4 ++-- scripts/try-update.sh | 2 +- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/.github/homebrew/bits.rb.erb b/.github/homebrew/bits.rb.erb index e0598e7d..a28716cb 100644 --- a/.github/homebrew/bits.rb.erb +++ b/.github/homebrew/bits.rb.erb @@ -8,8 +8,6 @@ class Bits < Formula desc "Datadog Assistant in your terminal" homepage "https://github.com/datadog-labs/bits-cli" - # Shipped bits binaries read this line to offer updates; keep exactly one. - version "<%= values.fetch("version") %>" license "Apache-2.0" on_macos do diff --git a/internal/update/homebrew.go b/internal/update/homebrew.go index 83e0db72..9c475189 100644 --- a/internal/update/homebrew.go +++ b/internal/update/homebrew.go @@ -124,16 +124,24 @@ func detectBackend(executable string) (backend, error) { return h, nil } -// Read only the explicit version declaration in our generated formula. This is -// deliberately not a Ruby parser; unexpected formats fail the check silently. -var formulaVersion = regexp.MustCompile(`(?m)^[\t ]*version[\t ]+"([^"\r\n]+)"[\t ]*\r?$`) +// Read the version from the release download URLs in our generated formula. +// Homebrew infers its formula version from these URLs, so avoid duplicating it +// in an explicit version stanza. This is deliberately not a Ruby parser; +// unexpected formats fail the check silently. +var formulaReleaseVersion = regexp.MustCompile(`(?m)^[\t ]*url[\t ]+"[^"\r\n]*/releases/download/v([^/"\r\n]+)/[^"\r\n]+"[\t ]*\r?$`) func homebrewVersion(raw []byte) (string, error) { - matches := formulaVersion.FindAllSubmatch(raw, -1) - if len(matches) != 1 { - return "", fmt.Errorf("%w: expected one explicit Homebrew version", errInvalidVersion) + matches := formulaReleaseVersion.FindAllSubmatch(raw, -1) + if len(matches) == 0 { + return "", fmt.Errorf("%w: expected one version across Homebrew release URLs", errInvalidVersion) } - latest := "v" + string(matches[0][1]) + version := string(matches[0][1]) + for _, match := range matches[1:] { + if string(match[1]) != version { + return "", fmt.Errorf("%w: mismatched versions across Homebrew release URLs", errInvalidVersion) + } + } + latest := "v" + version if !semver.IsValid(latest) { return "", fmt.Errorf("%w: Homebrew version %q", errInvalidVersion, latest) } diff --git a/internal/update/update_test.go b/internal/update/update_test.go index ef83e136..c9242eb4 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -223,7 +223,7 @@ func TestHomebrewFetch(t *testing.T) { body, want string invalid bool }{ - {"success", 200, ` version "1.3.0"`, "v1.3.0", false}, + {"success", 200, ` url "https://github.com/datadog-labs/bits-cli/releases/download/v1.3.0/bits_1.3.0_linux_amd64.tar.gz"`, "v1.3.0", false}, {"server", 500, "", "", false}, {"missing", 404, "", "", false}, {"redirect", 302, "", "", false}, @@ -394,13 +394,12 @@ func TestHomebrewUpgrade(t *testing.T) { func TestHomebrewVersion(t *testing.T) { for _, tc := range []struct{ name, formula, want string }{ - {"generated format", "class Bits < Formula\n version \"1.2.3\"\n on_macos do\n url \"https://example.com/v9.9.9/bits.tar.gz\"\n end\nend\n", "v1.2.3"}, - {"windows newlines", " version \"1.2.3\"\r\n", "v1.2.3"}, + {"generated format", "class Bits < Formula\n on_macos do\n url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_arm64.tar.gz\"\n url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_amd64.tar.gz\"\n end\nend\n", "v1.2.3"}, + {"windows newlines", " url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_linux_amd64.tar.gz\"\r\n", "v1.2.3"}, {"missing", "class Bits < Formula\nend\n", ""}, - {"comment", " # version \"1.2.3\"\n", ""}, - {"duplicate", " version \"1.2.3\"\n version \"1.2.4\"\n", ""}, - {"expression", " version ENV.fetch(\"VERSION\")\n", ""}, - {"interpolation", " version \"#{version}\"\n", ""}, + {"comment", " # url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_arm64.tar.gz\"\n", ""}, + {"mismatched release URLs", " url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_darwin_arm64.tar.gz\"\n url \"https://github.com/datadog-labs/bits-cli/releases/download/v1.2.4/bits_1.2.4_linux_amd64.tar.gz\"\n", ""}, + {"invalid version", " url \"https://github.com/datadog-labs/bits-cli/releases/download/vnot-a-version/bits.tar.gz\"\n", ""}, } { t.Run(tc.name, func(t *testing.T) { got, err := homebrewVersion([]byte(tc.formula)) diff --git a/scripts/ciworkflow_test.go b/scripts/ciworkflow_test.go index fdd3e80d..6ecf2d6a 100644 --- a/scripts/ciworkflow_test.go +++ b/scripts/ciworkflow_test.go @@ -290,8 +290,8 @@ func TestHomebrewTemplate(t *testing.T) { if err != nil { t.Fatalf("render formula: %v\n%s", err, formula) } - if got := strings.Count(string(formula), "\n version \"1.2.3\"\n"); got != 1 { - t.Fatalf("expected one explicit version declaration, got %d", got) + if strings.Contains(string(formula), "\n version ") { + t.Fatal("formula should let Homebrew infer the version from its release URLs") } for _, platform := range platforms { url := `url "https://github.com/datadog-labs/bits-cli/releases/download/v1.2.3/bits_1.2.3_` + platform + `.tar.gz"` diff --git a/scripts/try-update.sh b/scripts/try-update.sh index 74267086..e2d1cb89 100755 --- a/scripts/try-update.sh +++ b/scripts/try-update.sh @@ -64,7 +64,7 @@ chmod +x "$PREFIX/bin/brew" # Start without a cache so the first launch performs the real check. rm -f "$STATE" -echo "--- serving $URL ($(grep -m1 '^ *version ' "$PREFIX/www/Formula/bits.rb" | xargs))" +echo "--- serving $URL ($(grep -m1 'releases/download/v' "$PREFIX/www/Formula/bits.rb" | sed -E 's|.*releases/download/(v[^/]+)/.*|\1|'))" while true; do echo "--- launching $("$PREFIX/bin/bits" --version)"