fix(updates): select ARM release archive from build variant

ARMv6 and ARMv7 server binaries both report GOARCH=arm. Read the release binary GOARM build setting and fail closed when it does not name a published server archive, rather than offering an ARMv7 update to ARMv6 installations. Cover the architecture mapping and document the installability boundary.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-27 23:29:44 +01:00
parent ab0aa1ef02
commit 18f2dc1f19
3 changed files with 58 additions and 6 deletions
@@ -4048,6 +4048,10 @@ running architecture and tag. A missing exact archive leaves the download URL
empty and the update unavailable; it must not fall back to another component,
architecture or version. This keeps an incomplete release visible as metadata
without presenting an unusable or wrong installation path.
ARMv6 and ARMv7 are separate server archives despite both binaries reporting
`GOARCH=arm`; the updater uses the release binary's embedded `GOARM` build
setting to choose between them, and offers neither if that setting is missing
or unrecognised. It must not assume ARMv7 for every ARM installation.
Malformed metadata stays a hard error and only the typed over-limit condition
may fall through to the feed. Proof:
`internal/updates/issue2282_release_metadata_stream_test.go`.
+29 -6
View File
@@ -18,6 +18,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"runtime/debug"
"sort"
"strconv"
"strings"
@@ -112,12 +113,20 @@ func updateReleaseDownloadPrefix() string {
}
func updateReleaseAssetForRuntime(tagName string) (ReleaseAsset, bool) {
targetArch, ok := map[string]string{
"amd64": "amd64",
"arm64": "arm64",
"arm": "armv7",
"386": "386",
}[runtime.GOARCH]
armVariant := ""
if runtime.GOARCH == "arm" {
// GOARCH alone cannot distinguish the separately published ARMv6 and
// ARMv7 server binaries. Release builds record their GOARM setting.
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "GOARM" {
armVariant = setting.Value
break
}
}
}
}
targetArch, ok := updateReleaseArchitecture(runtime.GOARCH, armVariant)
if !ok {
return ReleaseAsset{}, false
}
@@ -132,6 +141,20 @@ func updateReleaseAssetForRuntime(tagName string) (ReleaseAsset, bool) {
return ReleaseAsset{Name: assetName, BrowserDownloadURL: downloadURL}, true
}
func updateReleaseArchitecture(goarch, goarm string) (string, bool) {
switch goarch {
case "amd64", "arm64", "386":
return goarch, true
case "arm":
// GOARM=5 has no server archive. An absent or unknown variant must not
// silently select ARMv7, which may not run on an ARMv6 host.
if goarm == "6" || goarm == "7" {
return "armv" + goarm, true
}
}
return "", false
}
func updateReleaseAPIPath() string {
return fmt.Sprintf("/repos/%s/releases", updateReleaseRepo())
}
@@ -0,0 +1,25 @@
package updates
import "testing"
func TestUpdateReleaseArchitectureRequiresMatchingARMVariant(t *testing.T) {
for _, tc := range []struct {
goarch, goarm, want string
ok bool
}{
{goarch: "amd64", want: "amd64", ok: true},
{goarch: "arm64", want: "arm64", ok: true},
{goarch: "386", want: "386", ok: true},
{goarch: "arm", goarm: "6", want: "armv6", ok: true},
{goarch: "arm", goarm: "7", want: "armv7", ok: true},
{goarch: "arm", goarm: "5"},
{goarch: "arm"},
{goarch: "arm", goarm: "unknown"},
{goarch: "riscv64"},
} {
got, ok := updateReleaseArchitecture(tc.goarch, tc.goarm)
if got != tc.want || ok != tc.ok {
t.Errorf("updateReleaseArchitecture(%q, %q) = (%q, %t), want (%q, %t)", tc.goarch, tc.goarm, got, ok, tc.want, tc.ok)
}
}
}