From f7797878886e01df1dfed8cb8576e30bd253e0fd Mon Sep 17 00:00:00 2001 From: priyaselvaganesan Date: Mon, 20 Jul 2026 11:33:31 -0700 Subject: [PATCH 1/2] feat(go-lib/version): add Handler and HandlerFor http.Handler Adds a shared HTTP handler to go-lib/pkg/version that serves build metadata as JSON at GET /version. Version and commit are injected at link time via Bazel x_defs (STABLE_VERSION, STABLE_GIT_COMMIT) when built with --stamp. Falls back to Go runtime build info for commit when GitHash is not injected, so local go build still returns a real commit SHA. Response schema: {"version": "v1.2.3", "commit": "abc1234"} Refs: NVCF-10975 --- src/libraries/go/lib/pkg/version/BUILD.bazel | 2 + src/libraries/go/lib/pkg/version/handler.go | 81 ++++++++++++++ .../go/lib/pkg/version/handler_test.go | 105 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 src/libraries/go/lib/pkg/version/handler.go create mode 100644 src/libraries/go/lib/pkg/version/handler_test.go diff --git a/src/libraries/go/lib/pkg/version/BUILD.bazel b/src/libraries/go/lib/pkg/version/BUILD.bazel index 3ba893b52..a7b526d4e 100644 --- a/src/libraries/go/lib/pkg/version/BUILD.bazel +++ b/src/libraries/go/lib/pkg/version/BUILD.bazel @@ -18,6 +18,7 @@ load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "version", srcs = [ + "handler.go", "http.go", "version.go", ], @@ -28,6 +29,7 @@ go_library( go_test( name = "version_test", srcs = [ + "handler_test.go", "http_test.go", "version_test.go", ], diff --git a/src/libraries/go/lib/pkg/version/handler.go b/src/libraries/go/lib/pkg/version/handler.go new file mode 100644 index 000000000..d05bccedc --- /dev/null +++ b/src/libraries/go/lib/pkg/version/handler.go @@ -0,0 +1,81 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +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 + +import ( + "encoding/json" + "net/http" + "runtime/debug" +) + +// Info holds the resolved build version metadata returned by the /version endpoint. +type Info struct { + Version string `json:"version"` + Commit string `json:"commit"` +} + +// Handler returns an http.Handler that serves build version info as JSON. +// Version and Commit are resolved from the package-level ldflags vars +// (Version, GitHash), with buildinfo used as a fallback for Commit +// when GitHash is empty. +func Handler() http.Handler { + return HandlerFor(Version, GitHash) +} + +// HandlerFor returns an http.Handler using the provided version and commit +// values, falling back to buildinfo for commit when commit is empty. +// Use this for services that maintain their own ldflags vars rather than +// pointing directly at this package. +func HandlerFor(ver, commit string) http.Handler { + info := resolve(ver, commit) + body, _ := json.Marshal(info) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(body) + }) +} + +// resolve builds an Info by combining the given ver/commit, +// falling back to runtime buildinfo for commit when empty. +func resolve(ver, commit string) Info { + if commit == "" { + commit = commitFromBuildInfo() + } + if ver == "" { + ver = "unknown" + } + if commit == "" { + commit = "unknown" + } + return Info{Version: ver, Commit: commit} +} + +// commitFromBuildInfo reads the VCS revision from Go's embedded build info +// (available when built inside a git repository without -buildvcs=false). +func commitFromBuildInfo() string { + info, ok := debug.ReadBuildInfo() + if !ok { + return "" + } + for _, s := range info.Settings { + if s.Key == "vcs.revision" { + return s.Value + } + } + return "" +} diff --git a/src/libraries/go/lib/pkg/version/handler_test.go b/src/libraries/go/lib/pkg/version/handler_test.go new file mode 100644 index 000000000..9b753d684 --- /dev/null +++ b/src/libraries/go/lib/pkg/version/handler_test.go @@ -0,0 +1,105 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +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 + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHandlerFor(t *testing.T) { + tests := []struct { + name string + ver string + commit string + wantVersion string + wantCommit string + }{ + { + name: "both set", + ver: "1.2.3", + commit: "abc1234", + wantVersion: "1.2.3", + wantCommit: "abc1234", + }, + { + name: "empty version falls back to unknown", + ver: "", + commit: "abc1234", + wantVersion: "unknown", + wantCommit: "abc1234", + }, + { + name: "empty commit falls back to buildinfo or unknown", + ver: "1.2.3", + commit: "", + wantVersion: "1.2.3", + // commit will be whatever buildinfo provides in the test binary, + // or "unknown" -- just assert it is non-empty string + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/version", nil) + HandlerFor(tc.ver, tc.commit).ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var got Info + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &got)) + + if tc.wantVersion != "" { + assert.Equal(t, tc.wantVersion, got.Version) + } + if tc.wantCommit != "" { + assert.Equal(t, tc.wantCommit, got.Commit) + } + assert.NotEmpty(t, got.Commit) + }) + } +} + +func TestHandler(t *testing.T) { + t.Cleanup(func() { + Version = "" + GitHash = "" + }) + + Version = "2.0.0" + GitHash = "deadbeef" + + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/version", nil) + Handler().ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) + + var got Info + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &got)) + assert.Equal(t, "2.0.0", got.Version) + assert.Equal(t, "deadbeef", got.Commit) +} From d7bc3d23c76bfe7f69098d4807166ba7d5695584 Mon Sep 17 00:00:00 2001 From: priyaselvaganesan Date: Tue, 21 Jul 2026 16:19:19 -0700 Subject: [PATCH 2/2] feat(go-lib/version): add Service field, rename endpoint to GET /info Adds Service string var injected via x_defs so each service identifies itself in the response. Renames from GET /version to GET /info following reviewer feedback that /info is more extensible for future fields. Response schema: {"service":"nvcf-my-service","version":"v1.2.3","commit":"abc1234"} Refs: NVCF-10975 --- src/libraries/go/lib/pkg/version/handler.go | 28 ++++++++------ .../go/lib/pkg/version/handler_test.go | 37 +++++++++++++++---- src/libraries/go/lib/pkg/version/version.go | 1 + 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/libraries/go/lib/pkg/version/handler.go b/src/libraries/go/lib/pkg/version/handler.go index d05bccedc..73959e9ed 100644 --- a/src/libraries/go/lib/pkg/version/handler.go +++ b/src/libraries/go/lib/pkg/version/handler.go @@ -23,26 +23,27 @@ import ( "runtime/debug" ) -// Info holds the resolved build version metadata returned by the /version endpoint. +// Info holds the resolved build metadata returned by the GET /info endpoint. type Info struct { + Service string `json:"service"` Version string `json:"version"` Commit string `json:"commit"` } -// Handler returns an http.Handler that serves build version info as JSON. -// Version and Commit are resolved from the package-level ldflags vars -// (Version, GitHash), with buildinfo used as a fallback for Commit +// Handler returns an http.Handler that serves build info as JSON. +// Service, Version, and Commit are resolved from the package-level ldflags vars +// (Service, Version, GitHash), with buildinfo used as a fallback for Commit // when GitHash is empty. func Handler() http.Handler { - return HandlerFor(Version, GitHash) + return HandlerFor(Service, Version, GitHash) } -// HandlerFor returns an http.Handler using the provided version and commit -// values, falling back to buildinfo for commit when commit is empty. +// HandlerFor returns an http.Handler using the provided service, version, and +// commit values, falling back to buildinfo for commit when commit is empty. // Use this for services that maintain their own ldflags vars rather than // pointing directly at this package. -func HandlerFor(ver, commit string) http.Handler { - info := resolve(ver, commit) +func HandlerFor(service, ver, commit string) http.Handler { + info := resolve(service, ver, commit) body, _ := json.Marshal(info) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -50,19 +51,22 @@ func HandlerFor(ver, commit string) http.Handler { }) } -// resolve builds an Info by combining the given ver/commit, +// resolve builds an Info by combining the given service/ver/commit, // falling back to runtime buildinfo for commit when empty. -func resolve(ver, commit string) Info { +func resolve(service, ver, commit string) Info { if commit == "" { commit = commitFromBuildInfo() } + if service == "" { + service = "unknown" + } if ver == "" { ver = "unknown" } if commit == "" { commit = "unknown" } - return Info{Version: ver, Commit: commit} + return Info{Service: service, Version: ver, Commit: commit} } // commitFromBuildInfo reads the VCS revision from Go's embedded build info diff --git a/src/libraries/go/lib/pkg/version/handler_test.go b/src/libraries/go/lib/pkg/version/handler_test.go index 9b753d684..61e979650 100644 --- a/src/libraries/go/lib/pkg/version/handler_test.go +++ b/src/libraries/go/lib/pkg/version/handler_test.go @@ -30,29 +30,46 @@ import ( func TestHandlerFor(t *testing.T) { tests := []struct { name string + service string ver string commit string + wantService string wantVersion string wantCommit string }{ { - name: "both set", + name: "all set", + service: "nvcf-my-service", ver: "1.2.3", commit: "abc1234", + wantService: "nvcf-my-service", + wantVersion: "1.2.3", + wantCommit: "abc1234", + }, + { + name: "empty service falls back to unknown", + service: "", + ver: "1.2.3", + commit: "abc1234", + wantService: "unknown", wantVersion: "1.2.3", wantCommit: "abc1234", }, { name: "empty version falls back to unknown", + service: "nvcf-my-service", ver: "", commit: "abc1234", + wantService: "nvcf-my-service", wantVersion: "unknown", wantCommit: "abc1234", }, { - name: "empty commit falls back to buildinfo or unknown", - ver: "1.2.3", - commit: "", + name: "empty commit falls back to buildinfo or unknown", + service: "nvcf-my-service", + ver: "1.2.3", + commit: "", + wantService: "nvcf-my-service", wantVersion: "1.2.3", // commit will be whatever buildinfo provides in the test binary, // or "unknown" -- just assert it is non-empty string @@ -62,8 +79,8 @@ func TestHandlerFor(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/version", nil) - HandlerFor(tc.ver, tc.commit).ServeHTTP(w, r) + r := httptest.NewRequest(http.MethodGet, "/info", nil) + HandlerFor(tc.service, tc.ver, tc.commit).ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "application/json", w.Header().Get("Content-Type")) @@ -71,6 +88,9 @@ func TestHandlerFor(t *testing.T) { var got Info require.NoError(t, json.Unmarshal(w.Body.Bytes(), &got)) + if tc.wantService != "" { + assert.Equal(t, tc.wantService, got.Service) + } if tc.wantVersion != "" { assert.Equal(t, tc.wantVersion, got.Version) } @@ -84,15 +104,17 @@ func TestHandlerFor(t *testing.T) { func TestHandler(t *testing.T) { t.Cleanup(func() { + Service = "" Version = "" GitHash = "" }) + Service = "nvcf-my-service" Version = "2.0.0" GitHash = "deadbeef" w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/version", nil) + r := httptest.NewRequest(http.MethodGet, "/info", nil) Handler().ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Code) @@ -100,6 +122,7 @@ func TestHandler(t *testing.T) { var got Info require.NoError(t, json.Unmarshal(w.Body.Bytes(), &got)) + assert.Equal(t, "nvcf-my-service", got.Service) assert.Equal(t, "2.0.0", got.Version) assert.Equal(t, "deadbeef", got.Commit) } diff --git a/src/libraries/go/lib/pkg/version/version.go b/src/libraries/go/lib/pkg/version/version.go index 999c58e8c..e26a4bb6f 100644 --- a/src/libraries/go/lib/pkg/version/version.go +++ b/src/libraries/go/lib/pkg/version/version.go @@ -21,6 +21,7 @@ import "strings" // Following variables are set by CI during compilation. var ( + Service string Version string GitHash string // Dirty is set to "true" if current git working directory has