Skip to content
Open
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
4 changes: 4 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ node = "24.15.0"
kubectl = "1.36.1"
uv = "0.10.12"
protoc = "29.6"
go = "1.25"
"go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" = "2.12"
"go:google.golang.org/protobuf/cmd/protoc-gen-go" = "1.36.11"
"go:google.golang.org/grpc/cmd/protoc-gen-go-grpc" = "1.6.2"
helm = "4.2.0"
helm-docs = "1.14.2"
skaffold = "2.20.0"
Expand Down
45 changes: 45 additions & 0 deletions sdk/go/Makefile

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems superfluous given our use of mise. Is there a reason to have this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Makefile is intentional as an entry point for Go developers. In the Go ecosystem, mise is essentially unknown, and Makefiles are the standard build tool (most Go projects use make test, make lint, etc.). Since the SDK targets external Go contributors who may never have seen mise, the Makefile picks them up where they are and reduces onboarding friction.

That said, it's a thin convenience layer and I don't feel strongly about it. Happy to drop it if the team prefers mise-only consistency across the repo.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
MISE := $(shell command -v mise 2>/dev/null)

.PHONY: test test-integration lint fmt build ci docs-check

test:
ifndef MISE
$(error mise is not installed. Install from https://mise.jdx.dev)
endif
mise run go:test

test-integration:
ifndef MISE
$(error mise is not installed. Install from https://mise.jdx.dev)
endif
mise run go:test:integration

lint:
ifndef MISE
$(error mise is not installed. Install from https://mise.jdx.dev)
endif
mise run go:lint

fmt:
ifndef MISE
$(error mise is not installed. Install from https://mise.jdx.dev)
endif
mise run go:fmt

build:
ifndef MISE
$(error mise is not installed. Install from https://mise.jdx.dev)
endif
mise run go:build

ci:
ifndef MISE
$(error mise is not installed. Install from https://mise.jdx.dev)
endif
mise run go:ci

docs-check:
ifndef MISE
$(error mise is not installed. Install from https://mise.jdx.dev)
endif
mise run go:docs:check
22 changes: 22 additions & 0 deletions sdk/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/NVIDIA/OpenShell/sdk/go

go 1.24.0

toolchain go1.26.4

require (
github.com/stretchr/testify v1.11.1
golang.org/x/oauth2 v0.35.0
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.33.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
50 changes: 50 additions & 0 deletions sdk/go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
48 changes: 48 additions & 0 deletions sdk/go/openshell/v1/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package v1

import (
"context"

"github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types"
)

// AuthProvider supplies per-RPC credentials. It implements the
// grpc credentials.PerRPCCredentials interface.
type AuthProvider = types.AuthProvider

type noAuth struct{}

// NoAuth returns an AuthProvider that sends no credentials.
func NoAuth() AuthProvider {
return &noAuth{}
}

func (n *noAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
return nil, nil
}

func (n *noAuth) RequireTransportSecurity() bool {
return false
}

type staticToken struct {
token string
}

// StaticToken returns an AuthProvider that sends a fixed Bearer token.
func StaticToken(token string) AuthProvider {
return &staticToken{token: token}
}

func (s *staticToken) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
return map[string]string{
"authorization": "Bearer " + s.token,
}, nil
}

func (s *staticToken) RequireTransportSecurity() bool {
return true
}
80 changes: 80 additions & 0 deletions sdk/go/openshell/v1/auth_extra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package v1

import (
"context"
"errors"
"maps"
"strings"

"github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types"
)

// extraHeadersAuth wraps a base AuthProvider with additional static headers
// that are merged into every GetRequestMetadata call. Extra headers take
// precedence over base headers on key collision (case-insensitive).
type extraHeadersAuth struct {
base types.AuthProvider
headers map[string]string // keys already lowercase, empty values filtered out
}

// WithExtraHeaders wraps base with additional per-RPC headers. Keys are
// normalized to lowercase per HTTP/2 (RFC 9113). Empty-string values are
// silently dropped. The headers map is deep-copied at construction time,
// so later mutations to the caller's map have no effect.
//
// Returns an error if base is nil or if headers is nil, empty, or contains
// only empty-string values.
func WithExtraHeaders(base AuthProvider, headers map[string]string) (AuthProvider, error) {
if base == nil {
return nil, errors.New("base auth provider must not be nil")
}
if len(headers) == 0 {
return nil, errors.New("headers must not be nil or empty")
}

// Deep-copy and normalize: lowercase keys, skip empty values.
normalized := make(map[string]string, len(headers))
for k, v := range headers {
if v == "" {
continue
}
normalized[strings.ToLower(k)] = v
}

if len(normalized) == 0 {
return nil, errors.New("headers must contain at least one non-empty value")
}

return &extraHeadersAuth{
base: base,
headers: normalized,
}, nil
}

// GetRequestMetadata merges base metadata with extra headers. Extra headers
// win on key collision because they are applied after the base metadata.
func (e *extraHeadersAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
baseMD, err := e.base.GetRequestMetadata(ctx, uri...)
if err != nil {
return nil, err
}

// Start with base metadata (may be nil for NoAuth).
// Normalize base keys to lowercase for case-insensitive collision.
merged := make(map[string]string, len(baseMD)+len(e.headers))
for k, v := range baseMD {
merged[strings.ToLower(k)] = v
}
// Extra headers overwrite base on collision.
maps.Copy(merged, e.headers)

return merged, nil
}

// RequireTransportSecurity delegates to the base auth provider.
func (e *extraHeadersAuth) RequireTransportSecurity() bool {
return e.base.RequireTransportSecurity()
}
Loading
Loading