metropolis: replace version stamp with product info
This removes the stamped metropolis version library and the associated
stampgo infrastructure, and replaces it with the product info file.
The info is now stored in a separate file in the rootfs, instead of
embedded in the core binary. This has the benefit that the core binary
no longer needs to be relinked when stamping info changes.
The version logging in core/main, and the tconsole are updated to show
some of the additional info from the product info.
Change-Id: Ic5ed0e3598e8da71b96748e8d7abfedff41acd3f
Reviewed-on: https://review.monogon.dev/c/monogon/+/4207
Tested-by: Jenkins CI
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
diff --git a/metropolis/node/core/BUILD.bazel b/metropolis/node/core/BUILD.bazel
index 4e0b3b8..de07e8c 100644
--- a/metropolis/node/core/BUILD.bazel
+++ b/metropolis/node/core/BUILD.bazel
@@ -30,20 +30,19 @@
"//metropolis/node/core/metrics",
"//metropolis/node/core/mgmt",
"//metropolis/node/core/network",
+ "//metropolis/node/core/productinfo",
"//metropolis/node/core/roleserve",
"//metropolis/node/core/rpc/resolver",
"//metropolis/node/core/tconsole",
"//metropolis/node/core/time",
"//metropolis/node/core/update",
"//metropolis/proto/api",
- "//metropolis/version",
"//osbase/bringup",
"//osbase/logtree",
"//osbase/net/dns",
"//osbase/supervisor",
"//osbase/sysctl",
"//osbase/tpm",
- "//version",
"@com_github_cenkalti_backoff_v4//:backoff",
"@com_github_containerd_containerd_v2//client",
"@com_github_containerd_containerd_v2//pkg/namespaces",
diff --git a/metropolis/node/core/main.go b/metropolis/node/core/main.go
index 890a30b..2d80698 100644
--- a/metropolis/node/core/main.go
+++ b/metropolis/node/core/main.go
@@ -17,19 +17,18 @@
"source.monogon.dev/metropolis/node/core/localstorage/declarative"
"source.monogon.dev/metropolis/node/core/metrics"
"source.monogon.dev/metropolis/node/core/network"
+ "source.monogon.dev/metropolis/node/core/productinfo"
"source.monogon.dev/metropolis/node/core/roleserve"
"source.monogon.dev/metropolis/node/core/rpc/resolver"
"source.monogon.dev/metropolis/node/core/tconsole"
timesvc "source.monogon.dev/metropolis/node/core/time"
"source.monogon.dev/metropolis/node/core/update"
- mversion "source.monogon.dev/metropolis/version"
"source.monogon.dev/osbase/bringup"
"source.monogon.dev/osbase/logtree"
"source.monogon.dev/osbase/net/dns"
"source.monogon.dev/osbase/supervisor"
"source.monogon.dev/osbase/sysctl"
"source.monogon.dev/osbase/tpm"
- "source.monogon.dev/version"
)
func main() {
@@ -95,8 +94,17 @@
func root(ctx context.Context) error {
logger := supervisor.Logger(ctx)
- logger.Info("Starting Metropolis node init")
- logger.Infof("Version: %s", version.Semver(mversion.Version))
+ productInfo := productinfo.Get()
+ logger.Infof("Starting %s init", productInfo.Info.Name)
+ logger.Infof("Version: %s", productInfo.VersionString)
+ logger.Infof("Variant: %s", productInfo.Info.Variant)
+ if productInfo.Info.BuildTreeDirty {
+ logger.Warning("Build tree dirty")
+ }
+ if productInfo.Info.CommitHash != "" {
+ logger.Infof("Commit: %s", productInfo.Info.CommitHash)
+ logger.Infof("Commit date: %s", productInfo.HumanCommitDate)
+ }
// Linux kernel default is 4096 which is far too low. Raise it to 1M which
// is what gVisor suggests.
@@ -198,8 +206,15 @@
// Initialize interactive consoles.
interactiveConsoles := []string{"/dev/tty0"}
+ consoleConfig := tconsole.Config{
+ Terminal: tconsole.TerminalLinux,
+ LogTree: supervisor.LogTree(ctx),
+ Network: &networkSvc.Status,
+ Roles: &rs.LocalRoles,
+ CuratorConn: &rs.CuratorConnection,
+ }
for _, c := range interactiveConsoles {
- console, err := tconsole.New(tconsole.TerminalLinux, c, supervisor.LogTree(ctx), &networkSvc.Status, &rs.LocalRoles, &rs.CuratorConnection)
+ console, err := tconsole.New(consoleConfig, c)
if err != nil {
logger.Infof("Failed to initialize interactive console at %s: %v", c, err)
} else {
diff --git a/metropolis/node/core/productinfo/BUILD.bazel b/metropolis/node/core/productinfo/BUILD.bazel
new file mode 100644
index 0000000..c34350e
--- /dev/null
+++ b/metropolis/node/core/productinfo/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "productinfo",
+ srcs = ["productinfo.go"],
+ importpath = "source.monogon.dev/metropolis/node/core/productinfo",
+ visibility = [
+ "//metropolis/node/core:__subpackages__",
+ "//metropolis/node/kubernetes:__subpackages__",
+ ],
+ deps = [
+ "//osbase/oci/osimage",
+ "//version",
+ "//version/spec",
+ "@com_github_coreos_go_semver//semver",
+ "@io_bazel_rules_go//go/runfiles",
+ ],
+)
diff --git a/metropolis/node/core/productinfo/productinfo.go b/metropolis/node/core/productinfo/productinfo.go
new file mode 100644
index 0000000..5d66760
--- /dev/null
+++ b/metropolis/node/core/productinfo/productinfo.go
@@ -0,0 +1,147 @@
+// Copyright The Monogon Project Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+package productinfo
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "regexp"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/bazelbuild/rules_go/go/runfiles"
+ "github.com/coreos/go-semver/semver"
+
+ vpb "source.monogon.dev/version/spec"
+
+ "source.monogon.dev/osbase/oci/osimage"
+ "source.monogon.dev/version"
+)
+
+// ProductInfo is a wrapper of [osimage.ProductInfo] with some additional
+// representations of the same data.
+type ProductInfo struct {
+ // Info is the product info parsed from JSON.
+ Info *osimage.ProductInfo
+ // Version parsed as [vpb.Version]
+ Version *vpb.Version
+ // VersionString is Info.Version with short commit and dirty indicator.
+ VersionString string
+ // HumanCommitDate is Info.CommitDate formatted to be more readable, in UTC.
+ // This is empty if stamping is disabled.
+ HumanCommitDate string
+}
+
+// path to the product info file. This may be replaced by x_defs in tests, and
+// will then be resolved with runfiles.
+var path = "/etc/product-info.json"
+
+// Get returns the product info of the running system.
+var Get = sync.OnceValue(func() *ProductInfo {
+ resolvedPath := path
+ if resolvedPath[0] != '/' {
+ var err error
+ resolvedPath, err = runfiles.Rlocation(resolvedPath)
+ if err != nil {
+ panic(err)
+ }
+ }
+ productInfo, err := read(resolvedPath)
+ if err != nil {
+ panic(err)
+ }
+ return productInfo
+})
+
+// read parses the JSON file at the given path as [osimage.ProductInfo].
+func read(path string) (*ProductInfo, error) {
+ rawProductInfo, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ var productInfo *osimage.ProductInfo
+ err = json.Unmarshal(rawProductInfo, &productInfo)
+ if err != nil {
+ return nil, err
+ }
+
+ specVersion, err := versionFromProductInfo(productInfo)
+ if err != nil {
+ return nil, fmt.Errorf("failed to extract version: %w", err)
+ }
+
+ var humanCommitDate string
+ if productInfo.CommitDate != "" {
+ commitDate, err := time.Parse(time.RFC3339, productInfo.CommitDate)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse commit date: %w", err)
+ }
+ humanCommitDate = commitDate.UTC().Format(time.DateTime)
+ }
+
+ info := &ProductInfo{
+ Info: productInfo,
+ Version: specVersion,
+ VersionString: version.Semver(specVersion),
+ HumanCommitDate: humanCommitDate,
+ }
+ return info, nil
+}
+
+var rePrereleaseCommitOffset = regexp.MustCompile(`^dev([0-9]+)$`)
+
+func versionFromProductInfo(productInfo *osimage.ProductInfo) (*vpb.Version, error) {
+ version := &vpb.Version{}
+
+ if productInfo.CommitHash != "" {
+ if len(productInfo.CommitHash) < 8 {
+ return nil, fmt.Errorf("git commit hash too short")
+ }
+ buildTreeState := vpb.Version_GitInformation_BUILD_TREE_STATE_CLEAN
+ if productInfo.BuildTreeDirty {
+ buildTreeState = vpb.Version_GitInformation_BUILD_TREE_STATE_DIRTY
+ }
+ version.GitInformation = &vpb.Version_GitInformation{
+ CommitHash: productInfo.CommitHash[:8],
+ BuildTreeState: buildTreeState,
+ }
+ }
+
+ if productInfo.Version != "" {
+ v, err := semver.NewVersion(productInfo.Version)
+ if err != nil {
+ return nil, fmt.Errorf("invalid %s version %q: %w", productInfo.ID, productInfo.Version, err)
+ }
+ // Parse prerelease strings (v1.2.3-foo-bar -> [foo, bar])
+ for _, el := range v.PreRelease.Slice() {
+ preCommitOffset := rePrereleaseCommitOffset.FindStringSubmatch(el)
+ switch {
+ case el == "":
+ // Skip empty slices which happens when there's a semver string with no
+ // prerelease data.
+ case el == "nostamp":
+ // Ignore field, we have it from CommitHash.
+ case preCommitOffset != nil:
+ offset, err := strconv.ParseUint(preCommitOffset[1], 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid commit offset value: %w", err)
+ }
+ if version.GitInformation == nil {
+ return nil, fmt.Errorf("have git offset but no git commit")
+ }
+ version.GitInformation.CommitsSinceRelease = offset
+ default:
+ return nil, fmt.Errorf("invalid prerelease string %q (in %q)", el, productInfo.Version)
+ }
+ }
+ version.Release = &vpb.Version_Release{
+ Major: v.Major,
+ Minor: v.Minor,
+ Patch: v.Patch,
+ }
+ }
+ return version, nil
+}
diff --git a/metropolis/node/core/roleserve/BUILD.bazel b/metropolis/node/core/roleserve/BUILD.bazel
index 1fb4719..37ec6d1 100644
--- a/metropolis/node/core/roleserve/BUILD.bazel
+++ b/metropolis/node/core/roleserve/BUILD.bazel
@@ -30,6 +30,7 @@
"//metropolis/node/core/mgmt",
"//metropolis/node/core/network",
"//metropolis/node/core/network/hostsfile",
+ "//metropolis/node/core/productinfo",
"//metropolis/node/core/rpc",
"//metropolis/node/core/rpc/resolver",
"//metropolis/node/core/update",
@@ -38,7 +39,6 @@
"//metropolis/node/kubernetes/pki",
"//metropolis/proto/api",
"//metropolis/proto/common",
- "//metropolis/version",
"//osbase/event",
"//osbase/event/memory",
"//osbase/logtree",
@@ -55,17 +55,23 @@
go_test(
name = "roleserve_test",
srcs = ["worker_statuspush_test.go"],
+ data = [
+ "//metropolis/node:product_info",
+ ],
embed = [":roleserve"],
# TODO: https://github.com/monogon-dev/monogon/issues/250
flaky = True,
+ x_defs = {
+ "source.monogon.dev/metropolis/node/core/productinfo.path": "$(rlocationpath //metropolis/node:product_info )",
+ },
deps = [
"//metropolis/node",
"//metropolis/node/core/consensus",
"//metropolis/node/core/curator",
"//metropolis/node/core/curator/proto/api",
+ "//metropolis/node/core/productinfo",
"//metropolis/proto/common",
"//metropolis/test/util",
- "//metropolis/version",
"//osbase/supervisor",
"@com_github_cenkalti_backoff_v4//:backoff",
"@com_github_google_go_cmp//cmp",
diff --git a/metropolis/node/core/roleserve/worker_statuspush.go b/metropolis/node/core/roleserve/worker_statuspush.go
index 0557b4c..62355bc 100644
--- a/metropolis/node/core/roleserve/worker_statuspush.go
+++ b/metropolis/node/core/roleserve/worker_statuspush.go
@@ -14,7 +14,7 @@
common "source.monogon.dev/metropolis/node"
"source.monogon.dev/metropolis/node/core/network"
- "source.monogon.dev/metropolis/version"
+ "source.monogon.dev/metropolis/node/core/productinfo"
"source.monogon.dev/osbase/event"
"source.monogon.dev/osbase/event/memory"
"source.monogon.dev/osbase/supervisor"
@@ -64,7 +64,7 @@
// workerStatusPushChannels.
func workerStatusPushLoop(ctx context.Context, chans *workerStatusPushChannels) error {
status := cpb.NodeStatus{
- Version: version.Version,
+ Version: productinfo.Get().Version,
BootId: getBootID(ctx),
}
diff --git a/metropolis/node/core/roleserve/worker_statuspush_test.go b/metropolis/node/core/roleserve/worker_statuspush_test.go
index 78c5f9c..40764f2 100644
--- a/metropolis/node/core/roleserve/worker_statuspush_test.go
+++ b/metropolis/node/core/roleserve/worker_statuspush_test.go
@@ -21,8 +21,8 @@
common "source.monogon.dev/metropolis/node"
"source.monogon.dev/metropolis/node/core/consensus"
"source.monogon.dev/metropolis/node/core/curator"
+ "source.monogon.dev/metropolis/node/core/productinfo"
"source.monogon.dev/metropolis/test/util"
- mversion "source.monogon.dev/metropolis/version"
"source.monogon.dev/osbase/supervisor"
ipb "source.monogon.dev/metropolis/node/core/curator/proto/api"
@@ -86,6 +86,9 @@
getBootID = func(ctx context.Context) []byte {
return []byte{1, 2, 3}
}
+
+ productInfo := productinfo.Get()
+
chans := workerStatusPushChannels{
address: make(chan string),
localControlPlane: make(chan *localControlPlane),
@@ -135,7 +138,7 @@
cur.expectReports(t, []*ipb.UpdateNodeStatusRequest{
{NodeId: nodeID, Status: &cpb.NodeStatus{
ExternalAddress: "192.0.2.10",
- Version: mversion.Version,
+ Version: productInfo.Version,
BootId: []byte{1, 2, 3},
}},
})
@@ -146,12 +149,12 @@
cur.expectReports(t, []*ipb.UpdateNodeStatusRequest{
{NodeId: nodeID, Status: &cpb.NodeStatus{
ExternalAddress: "192.0.2.10",
- Version: mversion.Version,
+ Version: productInfo.Version,
BootId: []byte{1, 2, 3},
}},
{NodeId: nodeID, Status: &cpb.NodeStatus{
ExternalAddress: "192.0.2.11",
- Version: mversion.Version,
+ Version: productInfo.Version,
BootId: []byte{1, 2, 3},
}},
})
@@ -168,12 +171,12 @@
cur.expectReports(t, []*ipb.UpdateNodeStatusRequest{
{NodeId: nodeID, Status: &cpb.NodeStatus{
ExternalAddress: "192.0.2.10",
- Version: mversion.Version,
+ Version: productInfo.Version,
BootId: []byte{1, 2, 3},
}},
{NodeId: nodeID, Status: &cpb.NodeStatus{
ExternalAddress: "192.0.2.11",
- Version: mversion.Version,
+ Version: productInfo.Version,
BootId: []byte{1, 2, 3},
}},
{NodeId: nodeID, Status: &cpb.NodeStatus{
@@ -181,12 +184,12 @@
RunningCurator: &cpb.NodeStatus_RunningCurator{
Port: int32(common.CuratorServicePort),
},
- Version: mversion.Version,
+ Version: productInfo.Version,
BootId: []byte{1, 2, 3},
}},
{NodeId: nodeID, Status: &cpb.NodeStatus{
ExternalAddress: "192.0.2.11",
- Version: mversion.Version,
+ Version: productInfo.Version,
BootId: []byte{1, 2, 3},
}},
})
diff --git a/metropolis/node/core/tconsole/BUILD.bazel b/metropolis/node/core/tconsole/BUILD.bazel
index f67e9b9..c551760 100644
--- a/metropolis/node/core/tconsole/BUILD.bazel
+++ b/metropolis/node/core/tconsole/BUILD.bazel
@@ -17,13 +17,12 @@
visibility = ["//visibility:public"],
deps = [
"//metropolis/node/core/network",
+ "//metropolis/node/core/productinfo",
"//metropolis/node/core/roleserve",
"//metropolis/proto/common",
- "//metropolis/version",
"//osbase/event",
"//osbase/logtree",
"//osbase/supervisor",
- "//version",
"@com_github_gdamore_tcell_v2//:tcell",
"@com_github_rivo_uniseg//:uniseg",
],
diff --git a/metropolis/node/core/tconsole/page_status.go b/metropolis/node/core/tconsole/page_status.go
index 7a139ac..482dd6e 100644
--- a/metropolis/node/core/tconsole/page_status.go
+++ b/metropolis/node/core/tconsole/page_status.go
@@ -10,8 +10,7 @@
"github.com/gdamore/tcell/v2"
- mversion "source.monogon.dev/metropolis/version"
- "source.monogon.dev/version"
+ "source.monogon.dev/metropolis/node/core/productinfo"
)
//go:embed build/copyright_line.txt
@@ -49,13 +48,27 @@
splitH := split(c.width, logoWidth, 60)
// Status lines.
+ productInfo := productinfo.Get()
lines := []string{
- fmt.Sprintf("Version: %s", version.Semver(mversion.Version)),
+ fmt.Sprintf("Version: %s", productInfo.VersionString),
+ fmt.Sprintf("Variant: %s", productInfo.Info.Variant),
+ }
+ if productInfo.Info.BuildTreeDirty {
+ lines = append(lines, "Build tree dirty")
+ }
+ if productInfo.Info.CommitHash != "" {
+ lines = append(lines,
+ fmt.Sprintf("Commit: %s", productInfo.Info.CommitHash),
+ fmt.Sprintf("Commit date: %s", productInfo.HumanCommitDate),
+ )
+ }
+ lines = append(lines,
+ "", // Blank line between product info and node info.
fmt.Sprintf("Node ID: %s", d.id),
fmt.Sprintf("CA fingerprint: %s", d.fingerprint),
fmt.Sprintf("Management address: %s", d.netAddr),
fmt.Sprintf("Roles: %s", d.roles),
- }
+ )
// Calculate longest line.
maxLine := 0
for _, l := range lines {
diff --git a/metropolis/node/core/tconsole/standalone/BUILD.bazel b/metropolis/node/core/tconsole/standalone/BUILD.bazel
index bc48d04..e9ed29a 100644
--- a/metropolis/node/core/tconsole/standalone/BUILD.bazel
+++ b/metropolis/node/core/tconsole/standalone/BUILD.bazel
@@ -3,8 +3,14 @@
go_library(
name = "standalone_lib",
srcs = ["main.go"],
+ data = [
+ "//metropolis/node:product_info",
+ ],
importpath = "source.monogon.dev/metropolis/node/core/tconsole/standalone",
visibility = ["//visibility:private"],
+ x_defs = {
+ "source.monogon.dev/metropolis/node/core/productinfo.path": "$(rlocationpath //metropolis/node:product_info )",
+ },
deps = [
"//metropolis/node/core/network",
"//metropolis/node/core/roleserve",
diff --git a/metropolis/node/core/tconsole/standalone/main.go b/metropolis/node/core/tconsole/standalone/main.go
index d9ceb18..eaf2c47 100644
--- a/metropolis/node/core/tconsole/standalone/main.go
+++ b/metropolis/node/core/tconsole/standalone/main.go
@@ -33,8 +33,14 @@
var curV memory.Value[*roleserve.CuratorConnection]
lt := logtree.New()
-
- tc, err := tconsole.New(tconsole.TerminalGeneric, "/proc/self/fd/0", lt, &netV, &rolesV, &curV)
+ config := tconsole.Config{
+ Terminal: tconsole.TerminalGeneric,
+ LogTree: lt,
+ Network: &netV,
+ Roles: &rolesV,
+ CuratorConn: &curV,
+ }
+ tc, err := tconsole.New(config, "/proc/self/fd/0")
if err != nil {
log.Fatalf("tconsole.New: %v", err)
}
@@ -98,7 +104,7 @@
supervisor.Signal(ctx, supervisor.SignalHealthy)
<-ctx.Done()
return ctx.Err()
- }, supervisor.WithExistingLogtree(lt))
+ }, supervisor.WithExistingLogtree(lt), supervisor.WithPropagatePanic)
<-ctx.Done()
tc.Cleanup()
}
diff --git a/metropolis/node/core/tconsole/tconsole.go b/metropolis/node/core/tconsole/tconsole.go
index aa3df7b..eeb4536 100644
--- a/metropolis/node/core/tconsole/tconsole.go
+++ b/metropolis/node/core/tconsole/tconsole.go
@@ -21,12 +21,19 @@
"source.monogon.dev/osbase/supervisor"
)
+type Config struct {
+ Terminal Terminal
+ LogTree *logtree.LogTree
+ Network event.Value[*network.Status]
+ Roles event.Value[*cpb.NodeRoles]
+ CuratorConn event.Value[*roleserve.CuratorConnection]
+}
+
// Console is a Terminal Console (TConsole), a user-interactive informational
// display visible on the TTY of a running Metropolis instance.
type Console struct {
// Quit will be closed when the user press CTRL-C. A new channel will be created
// on each New call.
-
Quit chan struct{}
ttyPath string
tty tcell.Tty
@@ -39,10 +46,8 @@
// constructed dynamically in Run.
activePage int
- reader *logtree.LogReader
- network event.Value[*network.Status]
- roles event.Value[*cpb.NodeRoles]
- curatorConn event.Value[*roleserve.CuratorConnection]
+ config Config
+ reader *logtree.LogReader
}
// New creates a new Console, taking over the TTY at the given path. The given
@@ -51,8 +56,8 @@
//
// network, roles, curatorConn point to various Metropolis subsystems that are
// used to populate the console data.
-func New(terminal Terminal, ttyPath string, lt *logtree.LogTree, network event.Value[*network.Status], roles event.Value[*cpb.NodeRoles], curatorConn event.Value[*roleserve.CuratorConnection]) (*Console, error) {
- reader, err := lt.Read("", logtree.WithChildren(), logtree.WithStream())
+func New(config Config, ttyPath string) (*Console, error) {
+ reader, err := config.LogTree.Read("", logtree.WithChildren(), logtree.WithStream())
if err != nil {
return nil, fmt.Errorf("lt.Read: %w", err)
}
@@ -71,7 +76,7 @@
screen.SetStyle(tcell.StyleDefault)
var pal palette
- switch terminal {
+ switch config.Terminal {
case TerminalLinux:
pal = paletteLinux
tty.Write([]byte(pal.setupLinuxConsole()))
@@ -87,14 +92,11 @@
screen: screen,
width: width,
height: height,
- network: network,
palette: pal,
Quit: make(chan struct{}),
activePage: 0,
+ config: config,
reader: reader,
-
- roles: roles,
- curatorConn: curatorConn,
}, nil
}
@@ -131,13 +133,13 @@
netAddrC := make(chan *network.Status)
rolesC := make(chan *cpb.NodeRoles)
curatorConnC := make(chan *roleserve.CuratorConnection)
- if err := supervisor.Run(ctx, "netpipe", event.Pipe(c.network, netAddrC)); err != nil {
+ if err := supervisor.Run(ctx, "netpipe", event.Pipe(c.config.Network, netAddrC)); err != nil {
return err
}
- if err := supervisor.Run(ctx, "rolespipe", event.Pipe(c.roles, rolesC)); err != nil {
+ if err := supervisor.Run(ctx, "rolespipe", event.Pipe(c.config.Roles, rolesC)); err != nil {
return err
}
- if err := supervisor.Run(ctx, "curatorpipe", event.Pipe(c.curatorConn, curatorConnC)); err != nil {
+ if err := supervisor.Run(ctx, "curatorpipe", event.Pipe(c.config.CuratorConn, curatorConnC)); err != nil {
return err
}
supervisor.Signal(ctx, supervisor.SignalHealthy)
diff --git a/metropolis/node/core/update/BUILD.bazel b/metropolis/node/core/update/BUILD.bazel
index 532b917..0ce75b4 100644
--- a/metropolis/node/core/update/BUILD.bazel
+++ b/metropolis/node/core/update/BUILD.bazel
@@ -11,8 +11,8 @@
deps = [
"//go/logging",
"//metropolis/node/abloader/spec",
+ "//metropolis/node/core/productinfo",
"//metropolis/proto/api",
- "//metropolis/version",
"//osbase/blockdev",
"//osbase/build/mkimage/osimage",
"//osbase/efivarfs",
@@ -20,7 +20,6 @@
"//osbase/kexec",
"//osbase/oci/osimage",
"//osbase/oci/registry",
- "//version",
"@com_github_cenkalti_backoff_v4//:backoff",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//status",
diff --git a/metropolis/node/core/update/e2e/testos/testos.bzl b/metropolis/node/core/update/e2e/testos/testos.bzl
index c59af8c..b0665c0 100644
--- a/metropolis/node/core/update/e2e/testos/testos.bzl
+++ b/metropolis/node/core/update/e2e/testos/testos.bzl
@@ -12,6 +12,7 @@
name = "rootfs_" + variant,
files = {
"/init": ":testos_" + variant,
+ "/etc/product-info.json": ":product_info_" + variant,
"/etc/resolv.conf": "//osbase/net/dns:resolv.conf",
},
fsspecs = [
diff --git a/metropolis/node/core/update/update.go b/metropolis/node/core/update/update.go
index 53bcb96..d620677 100644
--- a/metropolis/node/core/update/update.go
+++ b/metropolis/node/core/update/update.go
@@ -26,7 +26,7 @@
"google.golang.org/protobuf/proto"
"source.monogon.dev/go/logging"
- mversion "source.monogon.dev/metropolis/version"
+ "source.monogon.dev/metropolis/node/core/productinfo"
"source.monogon.dev/osbase/blockdev"
"source.monogon.dev/osbase/build/mkimage/osimage"
"source.monogon.dev/osbase/efivarfs"
@@ -34,7 +34,6 @@
"source.monogon.dev/osbase/kexec"
ociosimage "source.monogon.dev/osbase/oci/osimage"
"source.monogon.dev/osbase/oci/registry"
- "source.monogon.dev/version"
abloaderpb "source.monogon.dev/metropolis/node/abloader/spec"
apb "source.monogon.dev/metropolis/proto/api"
@@ -284,7 +283,7 @@
RetryNotify: func(err error, d time.Duration) {
s.Logger.Warningf("Error while fetching OS image, retrying in %v: %v", d, err)
},
- UserAgent: "MonogonOS/" + version.Semver(mversion.Version),
+ UserAgent: "MonogonOS/" + productinfo.Get().VersionString,
Scheme: imageRef.Scheme,
Host: imageRef.Host,
Repository: imageRef.Repository,