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..73959e9ed --- /dev/null +++ b/src/libraries/go/lib/pkg/version/handler.go @@ -0,0 +1,85 @@ +/* +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 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 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(Service, Version, GitHash) +} + +// 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(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") + _, _ = w.Write(body) + }) +} + +// resolve builds an Info by combining the given service/ver/commit, +// falling back to runtime buildinfo for commit when empty. +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{Service: service, 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..61e979650 --- /dev/null +++ b/src/libraries/go/lib/pkg/version/handler_test.go @@ -0,0 +1,128 @@ +/* +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 + service string + ver string + commit string + wantService string + wantVersion string + wantCommit string + }{ + { + 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", + 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 + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + w := httptest.NewRecorder() + 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")) + + 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) + } + if tc.wantCommit != "" { + assert.Equal(t, tc.wantCommit, got.Commit) + } + assert.NotEmpty(t, got.Commit) + }) + } +} + +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, "/info", 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, "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