diff --git a/Makefile b/Makefile index 3f76f1d..837d055 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,16 @@ KO := hack/run-tool.sh ko BINDIR := bin/ ATECTL := $(BINDIR)/kubectl-ate +# Version stamping. Override on the make command line to pin +# (e.g. `make VERSION=v0.5.0 build`). +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null || echo unknown) +BUILD_DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) +VERSION_PKG := github.com/agent-substrate/substrate/internal/version +LDFLAGS := -X $(VERSION_PKG).Version=$(VERSION) \ + -X $(VERSION_PKG).Commit=$(COMMIT) \ + -X $(VERSION_PKG).BuildDate=$(BUILD_DATE) + .PHONY: all all: build @@ -34,22 +44,22 @@ build: build-images build-atectl .PHONY: build-images build-images: - $(KO) build ./cmd/ateapi - $(KO) build ./cmd/atelet - $(KO) build ./cmd/podcertcontroller - $(KO) build ./cmd/atenet + $(KO) build --ldflags "$(LDFLAGS)" ./cmd/ateapi + $(KO) build --ldflags "$(LDFLAGS)" ./cmd/atelet + $(KO) build --ldflags "$(LDFLAGS)" ./cmd/podcertcontroller + $(KO) build --ldflags "$(LDFLAGS)" ./cmd/atenet .PHONY: build-atectl build-atectl: - $(GO) build -o $(ATECTL) ./cmd/kubectl-ate + $(GO) build -ldflags "$(LDFLAGS)" -o $(ATECTL) ./cmd/kubectl-ate .PHONY: build-atenet build-atenet: - $(GO) build -o $(BINDIR)/atenet ./cmd/atenet + $(GO) build -ldflags "$(LDFLAGS)" -o $(BINDIR)/atenet ./cmd/atenet .PHONY: build-demos build-demos: - $(KO) build ./demos/counter + $(KO) build --ldflags "$(LDFLAGS)" ./demos/counter .PHONY: test test: diff --git a/cmd/ateapi/main.go b/cmd/ateapi/main.go index 3cb7178..6b5ff51 100644 --- a/cmd/ateapi/main.go +++ b/cmd/ateapi/main.go @@ -32,6 +32,7 @@ import ( "github.com/agent-substrate/substrate/internal/ateinterceptors" "github.com/agent-substrate/substrate/internal/contextlogging" "github.com/agent-substrate/substrate/internal/credbundle" + "github.com/agent-substrate/substrate/internal/version" "github.com/agent-substrate/substrate/pkg/client/clientset/versioned" "github.com/agent-substrate/substrate/pkg/client/informers/externalversions" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" @@ -72,10 +73,16 @@ var ( sessionIDCAPoolFile = flag.String("session-id-ca-pool", "", "The file that contains the CA pool for signing session JWTs") workerpoolCACerts = flag.String("workerpool-ca-certs", "", "The file that contains the CA for verifying workerpool client certificates.") + + showVersion = flag.Bool("version", false, "Print version and exit.") ) func main() { flag.Parse() + if *showVersion { + fmt.Println(version.String()) + return + } ctx := context.Background() slog.SetDefault(slog.New(contextlogging.NewHandler(slog.NewJSONHandler(os.Stdout, nil)))) diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index bd6611d..96d4c71 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -39,6 +39,7 @@ import ( "github.com/agent-substrate/substrate/internal/memorypullcache" "github.com/agent-substrate/substrate/internal/proto/ateletpb" "github.com/agent-substrate/substrate/internal/proto/ateompb" + "github.com/agent-substrate/substrate/internal/version" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/google/go-containerregistry/pkg/authn" @@ -68,10 +69,16 @@ var ( gcpAuthForImagePulls = flag.Bool("gcp-auth-for-image-pulls", true, "Use GCP application default credentials mechanism.") localhostRegistryReplacement = flag.String("localhost-registry-replacement", "", "The replacement registry endpoint for localhost and/or loopback IP addresses, useful for local development. for example kind-registry:5000") + + showVersion = flag.Bool("version", false, "Print version and exit.") ) func main() { flag.Parse() + if *showVersion { + fmt.Println(version.String()) + return + } ctx := context.Background() slog.SetDefault(slog.New(contextlogging.NewHandler(slog.NewJSONHandler(os.Stdout, nil)))) diff --git a/cmd/atenet/internal/app/root.go b/cmd/atenet/internal/app/root.go index 71b9837..34e1f3d 100644 --- a/cmd/atenet/internal/app/root.go +++ b/cmd/atenet/internal/app/root.go @@ -20,13 +20,15 @@ import ( "github.com/agent-substrate/substrate/cmd/atenet/internal/app/dns" "github.com/agent-substrate/substrate/cmd/atenet/internal/app/router" + "github.com/agent-substrate/substrate/internal/version" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ - Use: "atenet", - Short: "atenet is a combined daemon for all networking functionality.", - Long: `atenet is a combined daemon for all networking functionality.`, + Use: "atenet", + Short: "atenet is a combined daemon for all networking functionality.", + Long: `atenet is a combined daemon for all networking functionality.`, + Version: version.String(), } func Execute() { diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index cbc1fe9..bbdee71 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -30,6 +30,7 @@ import ( "github.com/agent-substrate/substrate/internal/ateompath" "github.com/agent-substrate/substrate/internal/contextlogging" "github.com/agent-substrate/substrate/internal/proto/ateompb" + "github.com/agent-substrate/substrate/internal/version" "github.com/hashicorp/go-reap" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" @@ -47,11 +48,17 @@ var ( podNamespace = flag.String("pod-namespace", "", "The namespace of the current pod") podName = flag.String("pod-name", "", "The name of the current pod") + showVersion = flag.Bool("version", false, "Print version and exit.") + reapLock sync.RWMutex ) func main() { flag.Parse() + if *showVersion { + fmt.Println(version.String()) + return + } ctx := context.Background() if err := do(ctx); err != nil { diff --git a/cmd/kubectl-ate/internal/cmd/root.go b/cmd/kubectl-ate/internal/cmd/root.go index c69bd0c..fd1f93b 100644 --- a/cmd/kubectl-ate/internal/cmd/root.go +++ b/cmd/kubectl-ate/internal/cmd/root.go @@ -19,6 +19,8 @@ import ( "os" "github.com/spf13/cobra" + + "github.com/agent-substrate/substrate/internal/version" ) var ( @@ -33,6 +35,7 @@ var rootCmd = &cobra.Command{ Use: "kubectl-ate", Short: "A kubectl plugin for managing Agent Substrate environments", Long: `kubectl ate is a CLI tool to manage Actor and Worker lifecycles in an Agent Substrate.`, + Version: version.String(), SilenceUsage: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if outputFmt != "table" && outputFmt != "json" && outputFmt != "yaml" { diff --git a/cmd/podcertcontroller/main.go b/cmd/podcertcontroller/main.go index 7932fd2..44a2bbd 100644 --- a/cmd/podcertcontroller/main.go +++ b/cmd/podcertcontroller/main.go @@ -24,6 +24,7 @@ package main import ( "context" "flag" + "fmt" "log/slog" "os" "os/signal" @@ -35,6 +36,7 @@ import ( "github.com/agent-substrate/substrate/internal/rendezvous" "github.com/agent-substrate/substrate/internal/servicednssigner" "github.com/agent-substrate/substrate/internal/signercontroller" + "github.com/agent-substrate/substrate/internal/version" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -71,12 +73,18 @@ var ( "", "File that contains the CA pool state for "+podidentitysigner.Name, ) + + showVersion = flag.Bool("version", false, "Print version and exit.") ) func main() { ctx := context.Background() flag.Parse() + if *showVersion { + fmt.Println(version.String()) + return + } slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) var kconfig *rest.Config diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..ba2db0d --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,61 @@ +// 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 +// +// http://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 version exposes build-time identity for substrate binaries. +// Values are set via -ldflags -X at build time; if unset, init() falls +// back to runtime/debug.ReadBuildInfo() so plain `go build` / +// `go install` still produce something meaningful. +package version + +import ( + "fmt" + "runtime" + "runtime/debug" +) + +var ( + Version = "dev" + Commit = "unknown" + BuildDate = "unknown" +) + +func init() { + if Commit != "unknown" { + return + } + info, ok := debug.ReadBuildInfo() + if !ok { + return + } + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + Commit = s.Value + case "vcs.time": + BuildDate = s.Value + case "vcs.modified": + if s.Value == "true" { + Commit += "-dirty" + } + } + } +} + +// String returns a human-readable single-line version summary, +// formatted to sit cleanly after cobra's auto " version " +// prefix. +func String() string { + return fmt.Sprintf("%s commit=%s built=%s %s/%s", + Version, Commit, BuildDate, runtime.GOOS, runtime.GOARCH) +}