Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/control-plane-services/event-ledger/cmd/api/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ go_library(
go_binary(
name = "api",
embed = [":api_lib"],
x_defs = {
"github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/version.Service": "nvcf-event-ledger",
"github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/version.Version": "{STABLE_VERSION}",
"github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/version.GitHash": "{STABLE_GIT_COMMIT_FULL}",
},
visibility = ["//visibility:public"],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ go_library(
"@com_github_lestrrat_go_jwx_v2//jwk",
"@com_github_nvidia_nvcf_src_libraries_go_lib//pkg/nvkit/auth",
"@com_github_nvidia_nvcf_src_libraries_go_lib//pkg/nvkit/clients",
"@com_github_nvidia_nvcf_src_libraries_go_lib//pkg/version",
"@com_github_spf13_cobra//:cobra",
"@com_github_spf13_viper//:viper",
"@com_github_uptrace_opentelemetry_go_extra_otelzap//:otelzap",
Expand All @@ -47,10 +48,16 @@ alias(

go_test(
name = "startup_test",
srcs = ["root_cmd_test.go"],
srcs = [
"info_test.go",
"root_cmd_test.go",
],
embed = [":startup"],
deps = [
"//src/control-plane-services/event-ledger/cmd/api/service",
"//src/control-plane-services/event-ledger/internal/config",
"@com_github_gorilla_mux//:mux",
"@com_github_nvidia_nvcf_src_libraries_go_lib//pkg/version",
"@com_github_spf13_cobra//:cobra",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
Expand Down
101 changes: 101 additions & 0 deletions src/control-plane-services/event-ledger/cmd/api/startup/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
SPDX-FileCopyrightText: Copyright (c) 2026 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 startup

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

golibversion "github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/version"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/NVIDIA/nvcf/src/control-plane-services/event-ledger/cmd/api/service"
)

// TestRegisterUnauthenticatedRoutes_Info verifies GET /info returns the stamped
// service/version/commit as JSON. It wires /info onto the router before auth
// middleware, so an empty *service.Server is safe as long as /health is not
// exercised.
func TestRegisterUnauthenticatedRoutes_Info(t *testing.T) {
prevService, prevVersion, prevHash := golibversion.Service, golibversion.Version, golibversion.GitHash
golibversion.Service = "nvcf-event-ledger"
golibversion.Version = "test-1.0.0"
golibversion.GitHash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
t.Cleanup(func() {
golibversion.Service = prevService
golibversion.Version = prevVersion
golibversion.GitHash = prevHash
})

// A recording middleware confirms registerUnauthenticatedRoutes actually
// wraps /info with the injected middleware (tracing + logging in production).
mwApplied := false
infoMiddleware := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mwApplied = true
w.Header().Set("X-Info-Middleware", "applied")
h.ServeHTTP(w, r)
})
}

router := mux.NewRouter()
registerUnauthenticatedRoutes(router, &service.Server{}, infoMiddleware)

w := httptest.NewRecorder()
r := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/info", nil)
router.ServeHTTP(w, r)

assert.True(t, mwApplied, "/info should be wrapped with the injected middleware")
assert.Equal(t, "applied", w.Header().Get("X-Info-Middleware"))
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))

var info map[string]string
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &info))
assert.Equal(t, "nvcf-event-ledger", info["service"])
assert.Equal(t, "test-1.0.0", info["version"])
assert.Equal(t, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", info["commit"])
}

// TestRegisterUnauthenticatedRoutes_Info_RejectsNonGET verifies non-GET methods on
// /info return 405 with an Allow: GET header, as enforced by the go-lib handler.
func TestRegisterUnauthenticatedRoutes_Info_RejectsNonGET(t *testing.T) {
router := mux.NewRouter()
registerUnauthenticatedRoutes(router, &service.Server{}, func(h http.Handler) http.Handler { return h })

for _, method := range []string{
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
} {
t.Run(method, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequestWithContext(t.Context(), method, "/info", nil)
router.ServeHTTP(w, r)

assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
assert.Equal(t, http.MethodGet, w.Header().Get("Allow"))
assert.Empty(t, w.Body.String())
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

"github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/nvkit/auth"
"github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/nvkit/clients"
golibversion "github.com/NVIDIA/nvcf/src/libraries/go/lib/pkg/version"
"github.com/golang-jwt/jwt/v5"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
Expand All @@ -53,6 +54,16 @@ import (
"github.com/NVIDIA/nvcf/src/control-plane-services/event-ledger/internal/registrations"
)

// registerUnauthenticatedRoutes registers routes that must be reachable before
// (and regardless of) auth middleware: /health for liveness/readiness probes,
// and /info for build-version discovery. /info is wrapped with tracing and
// request logging via infoMiddleware; /health probes are left uninstrumented to
// avoid span and log spam.
func registerUnauthenticatedRoutes(router *mux.Router, server *service.Server, infoMiddleware func(http.Handler) http.Handler) {
router.HandleFunc("/health", server.Health)
router.Handle("/info", infoMiddleware(golibversion.Handler()))
}

func runService(cfg config.Config) error {
ctx := context.Background()

Expand Down Expand Up @@ -149,16 +160,21 @@ func runService(cfg config.Config) error {
router.Use(middleware.BodyLimitMiddleware(10 * 1024 * 1024)) // 10MB limit
router.Use(metricsMiddleware)

router.HandleFunc("/health", server.Health)

spanNameFormatter := func(operation string, r *http.Request) string {
return r.Method + " " + operation // e.g., "GET /api/resource"
}
authRouter := router.PathPrefix("").Subrouter()
authRouter.Use(otelmux.Middleware("deployment-stages", otelmux.WithSpanNameFormatter(spanNameFormatter)))

// Initialize request logger mw
tracingMW := otelmux.Middleware("deployment-stages", otelmux.WithSpanNameFormatter(spanNameFormatter))
loggerMW := logging.LoggerMiddleware(logger)

// /info runs through tracing and request logging (RED metrics already apply
// on the base router). /health is left uninstrumented to avoid probe span and
// log spam.
registerUnauthenticatedRoutes(router, server, func(h http.Handler) http.Handler {
return tracingMW(loggerMW(h))
})

authRouter := router.PathPrefix("").Subrouter()
authRouter.Use(tracingMW)
authRouter.Use(loggerMW)

// If we're not using Policy, we need to handle scope checks locally in our middleware
Expand Down
2 changes: 1 addition & 1 deletion src/control-plane-services/event-ledger/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ require (
)

require (
github.com/NVIDIA/nvcf/src/libraries/go/lib v0.0.0-20260710034659-973443ac16c3
github.com/NVIDIA/nvcf/src/libraries/go/lib v0.0.0-20260728185909-afca4ec2fb26
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions src/control-plane-services/event-ledger/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/NVIDIA/nvcf/src/libraries/go/lib v0.0.0-20260710034659-973443ac16c3 h1:pBymv2IQia2bswak0wHracVj2kdDXB5xcCgm3BVxfmg=
github.com/NVIDIA/nvcf/src/libraries/go/lib v0.0.0-20260710034659-973443ac16c3/go.mod h1:nj3yBW2weO0qzi1ML45tBqviLa2Y/KG9qCalxQuJVB8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
Expand Down Expand Up @@ -369,3 +367,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
github.com/NVIDIA/nvcf/src/libraries/go/lib v0.0.0-20260728185909-afca4ec2fb26 h1:P9OSmvy6MQPqfzRgaLl0XpvK5UYHAFtNnQQb+yk+6b0=
github.com/NVIDIA/nvcf/src/libraries/go/lib v0.0.0-20260728185909-afca4ec2fb26/go.mod h1:nj3yBW2weO0qzi1ML45tBqviLa2Y/KG9qCalxQuJVB8=
Loading