Skip to content

Commit 20211f2

Browse files
committed
feat(sdk/go): add gateway client, auth, and fakes (2/3)
Add gateway auto-discovery client, OIDC authentication flows, and fake implementations for testing. Key additions: - Gateway client with CLI config file parsing and auto-discovery - OIDC auth code and device code token flows - Fake client implementations mirroring real validation behavior - Edge client for WebSocket-based gateway connections - Additional converter and type coverage Builds on PR 1/3 (foundation and core clients). Merge PR 1/3 first. Resolves #2044 Signed-off-by: Roland Huß <rhuss@redhat.com>
1 parent 614c8c1 commit 20211f2

187 files changed

Lines changed: 49791 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎sdk/go/Makefile‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
MISE := $(shell command -v mise 2>/dev/null)
2+
3+
.PHONY: test test-integration lint fmt build ci docs-check
4+
5+
test:
6+
ifndef MISE
7+
$(error mise is not installed. Install from https://mise.jdx.dev)
8+
endif
9+
mise run test
10+
11+
test-integration:
12+
ifndef MISE
13+
$(error mise is not installed. Install from https://mise.jdx.dev)
14+
endif
15+
mise run test:integration
16+
17+
lint:
18+
ifndef MISE
19+
$(error mise is not installed. Install from https://mise.jdx.dev)
20+
endif
21+
mise run lint
22+
23+
fmt:
24+
ifndef MISE
25+
$(error mise is not installed. Install from https://mise.jdx.dev)
26+
endif
27+
mise run fmt
28+
29+
build:
30+
ifndef MISE
31+
$(error mise is not installed. Install from https://mise.jdx.dev)
32+
endif
33+
mise run build
34+
35+
ci:
36+
ifndef MISE
37+
$(error mise is not installed. Install from https://mise.jdx.dev)
38+
endif
39+
mise run ci
40+
41+
docs-check:
42+
ifndef MISE
43+
$(error mise is not installed. Install from https://mise.jdx.dev)
44+
endif
45+
mise run docs:check

‎sdk/go/go.mod‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module github.com/NVIDIA/OpenShell/sdk/go
2+
3+
go 1.24.0
4+
5+
require (
6+
github.com/coder/websocket v1.8.15
7+
github.com/stretchr/testify v1.11.1
8+
golang.org/x/oauth2 v0.35.0
9+
google.golang.org/grpc v1.80.0
10+
google.golang.org/protobuf v1.36.11
11+
)
12+
13+
require (
14+
github.com/davecgh/go-spew v1.1.1 // indirect
15+
github.com/pmezard/go-difflib v1.0.0 // indirect
16+
golang.org/x/net v0.49.0 // indirect
17+
golang.org/x/sys v0.41.0 // indirect
18+
golang.org/x/text v0.33.0 // indirect
19+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
20+
gopkg.in/yaml.v3 v3.0.1 // indirect
21+
)

‎sdk/go/go.sum‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
2+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
3+
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
4+
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
5+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
8+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
9+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
10+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
11+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
12+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
13+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
14+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
15+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
16+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
17+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
18+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
19+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
20+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
21+
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
22+
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
23+
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
24+
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
25+
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
26+
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
27+
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
28+
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
29+
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
30+
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
31+
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
32+
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
33+
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
34+
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
35+
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
36+
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
37+
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
38+
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
39+
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
40+
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
41+
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
42+
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
43+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
44+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
45+
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
46+
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
47+
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
48+
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
49+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
50+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
51+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
52+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

‎sdk/go/mise.toml‎

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
[tools]
2+
go = "1.25"
3+
protoc = "29.6"
4+
"go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" = "2.12"
5+
"go:google.golang.org/protobuf/cmd/protoc-gen-go" = "1.36.11"
6+
"go:google.golang.org/grpc/cmd/protoc-gen-go-grpc" = "1.6.2"
7+
8+
[tasks.test]
9+
description = "Run unit tests with coverage"
10+
run = "go test -coverprofile=coverage.out -coverpkg=./openshell/... -race ./..."
11+
12+
[tasks."test:integration"]
13+
description = "Run integration tests"
14+
run = "go test -tags=integration -race ./..."
15+
16+
[tasks.lint]
17+
description = "Run linter"
18+
run = "golangci-lint run ./..."
19+
20+
[tasks.fmt]
21+
description = "Format code"
22+
run = "goimports -w . && go fmt ./..."
23+
24+
[tasks.build]
25+
description = "Build all packages"
26+
run = "go build ./..."
27+
28+
[tasks.ci]
29+
description = "Run full CI pipeline"
30+
depends = ["lint", "build", "test", "proto:check", "docs:check"]
31+
32+
[tasks."docs:check"]
33+
description = "Verify every public package has a docs page"
34+
run = """
35+
#!/usr/bin/env bash
36+
set -euo pipefail
37+
38+
DOCS_DIR="docs/src/api"
39+
SUMMARY="docs/src/SUMMARY.md"
40+
MISSING=0
41+
42+
# Find all public packages with a doc.go (excluding internal, proto, types)
43+
for docfile in openshell/v1/*/doc.go; do
44+
pkg=$(basename "$(dirname "$docfile")")
45+
46+
# Skip internal packages and types (no user-facing docs needed)
47+
case "$pkg" in
48+
internal|types) continue ;;
49+
esac
50+
51+
# Check for matching docs page
52+
if [ ! -f "$DOCS_DIR/$pkg.md" ]; then
53+
echo "MISSING: $DOCS_DIR/$pkg.md (package openshell/v1/$pkg has doc.go but no docs page)"
54+
MISSING=$((MISSING + 1))
55+
fi
56+
57+
# Check for SUMMARY.md entry
58+
if ! grep -q "api/$pkg.md" "$SUMMARY" 2>/dev/null; then
59+
echo "MISSING: SUMMARY.md entry for api/$pkg.md"
60+
MISSING=$((MISSING + 1))
61+
fi
62+
done
63+
64+
if [ "$MISSING" -gt 0 ]; then
65+
echo ""
66+
echo "ERROR: $MISSING documentation gaps found."
67+
echo "Every public package with doc.go needs a docs/src/api/<pkg>.md page"
68+
echo "and a SUMMARY.md entry. See Constitution XIII."
69+
exit 1
70+
fi
71+
72+
echo "Docs check passed: all public packages have documentation."
73+
"""
74+
75+
[tasks."proto:gen"]
76+
description = "Generate Go bindings from proto files"
77+
run = """
78+
#!/usr/bin/env bash
79+
set -euo pipefail
80+
81+
# Check required tools
82+
for tool in protoc protoc-gen-go protoc-gen-go-grpc; do
83+
if ! command -v "$tool" &>/dev/null; then
84+
echo "ERROR: $tool not found. Run 'mise install' to install it."
85+
exit 1
86+
fi
87+
done
88+
89+
MODULE="github.com/NVIDIA/OpenShell/sdk/go"
90+
PROTO_DIR="proto"
91+
92+
# Create output directories
93+
mkdir -p "$PROTO_DIR/openshellv1" "$PROTO_DIR/datamodelv1" "$PROTO_DIR/sandboxv1"
94+
95+
# M flag mappings for Go package paths
96+
M_FLAGS=""
97+
M_FLAGS="$M_FLAGS --go_opt=Mopenshell.proto=$MODULE/proto/openshellv1"
98+
M_FLAGS="$M_FLAGS --go_opt=Mdatamodel.proto=$MODULE/proto/datamodelv1"
99+
M_FLAGS="$M_FLAGS --go_opt=Msandbox.proto=$MODULE/proto/sandboxv1"
100+
M_FLAGS="$M_FLAGS --go-grpc_opt=Mopenshell.proto=$MODULE/proto/openshellv1"
101+
M_FLAGS="$M_FLAGS --go-grpc_opt=Mdatamodel.proto=$MODULE/proto/datamodelv1"
102+
M_FLAGS="$M_FLAGS --go-grpc_opt=Msandbox.proto=$MODULE/proto/sandboxv1"
103+
104+
# Generate Go code for all proto files
105+
# Use module= option to strip the module prefix from the output path,
106+
# so files land in proto/openshellv1/ etc. instead of the full module path
107+
protoc \
108+
--proto_path="$PROTO_DIR" \
109+
--go_out=. \
110+
--go_opt=module=$MODULE \
111+
--go-grpc_out=. \
112+
--go-grpc_opt=module=$MODULE \
113+
$M_FLAGS \
114+
"$PROTO_DIR/openshell.proto" \
115+
"$PROTO_DIR/datamodel.proto" \
116+
"$PROTO_DIR/sandbox.proto"
117+
118+
echo "Proto generation complete."
119+
echo "Generated packages:"
120+
for pkg in openshellv1 datamodelv1 sandboxv1; do
121+
echo " $PROTO_DIR/$pkg/: $(ls "$PROTO_DIR/$pkg/"*.go 2>/dev/null | wc -l | tr -d ' ') files"
122+
done
123+
"""
124+
125+
[tasks."proto:check"]
126+
description = "Verify generated proto files are up to date"
127+
run = """
128+
#!/usr/bin/env bash
129+
set -euo pipefail
130+
131+
# Check required tools
132+
for tool in protoc protoc-gen-go protoc-gen-go-grpc; do
133+
if ! command -v "$tool" &>/dev/null; then
134+
echo "ERROR: $tool not found. Run 'mise install' to install it."
135+
exit 1
136+
fi
137+
done
138+
139+
MODULE="github.com/NVIDIA/OpenShell/sdk/go"
140+
PROTO_DIR="proto"
141+
WORK_DIR=$(mktemp -d)
142+
trap 'rm -rf "$WORK_DIR"' EXIT
143+
144+
# Create output directories in temp
145+
mkdir -p "$WORK_DIR/proto/openshellv1" "$WORK_DIR/proto/datamodelv1" "$WORK_DIR/proto/sandboxv1"
146+
147+
# M flag mappings (same as proto:gen)
148+
M_FLAGS=""
149+
M_FLAGS="$M_FLAGS --go_opt=Mopenshell.proto=$MODULE/proto/openshellv1"
150+
M_FLAGS="$M_FLAGS --go_opt=Mdatamodel.proto=$MODULE/proto/datamodelv1"
151+
M_FLAGS="$M_FLAGS --go_opt=Msandbox.proto=$MODULE/proto/sandboxv1"
152+
M_FLAGS="$M_FLAGS --go-grpc_opt=Mopenshell.proto=$MODULE/proto/openshellv1"
153+
M_FLAGS="$M_FLAGS --go-grpc_opt=Mdatamodel.proto=$MODULE/proto/datamodelv1"
154+
M_FLAGS="$M_FLAGS --go-grpc_opt=Msandbox.proto=$MODULE/proto/sandboxv1"
155+
156+
# Generate to temp directory
157+
protoc \
158+
--proto_path="$PROTO_DIR" \
159+
--go_out="$WORK_DIR" \
160+
--go_opt=module=$MODULE \
161+
--go-grpc_out="$WORK_DIR" \
162+
--go-grpc_opt=module=$MODULE \
163+
$M_FLAGS \
164+
"$PROTO_DIR/openshell.proto" \
165+
"$PROTO_DIR/datamodel.proto" \
166+
"$PROTO_DIR/sandbox.proto"
167+
168+
# Diff against committed files
169+
DIFF_OUTPUT=$(diff -r "$WORK_DIR/proto" "$PROTO_DIR" \
170+
--exclude="*.proto" \
171+
--exclude="UPSTREAM_VERSION" \
172+
2>&1) || true
173+
174+
if [ -n "$DIFF_OUTPUT" ]; then
175+
echo "ERROR: Generated proto files are out of date."
176+
echo "Run 'mise run proto:gen' to regenerate."
177+
echo ""
178+
echo "$DIFF_OUTPUT"
179+
exit 1
180+
fi
181+
182+
echo "Proto check passed: generated files are up to date."
183+
"""
184+
185+
[tasks."proto:clean"]
186+
description = "Remove all generated proto files"
187+
run = """
188+
#!/usr/bin/env bash
189+
set -euo pipefail
190+
191+
PROTO_DIR="proto"
192+
193+
# Remove all generated .pb.go files
194+
find "$PROTO_DIR" -name "*.pb.go" -delete 2>/dev/null || true
195+
196+
# Remove empty generated subdirectories
197+
for dir in "$PROTO_DIR"/*/; do
198+
[ -d "$dir" ] && rmdir "$dir" 2>/dev/null || true
199+
done
200+
201+
echo "Proto clean complete."
202+
"""
203+
204+
[tasks."proto:sync"]
205+
description = "Sync proto files from upstream OpenShell repo"
206+
run = """
207+
#!/usr/bin/env bash
208+
set -euo pipefail
209+
210+
UPSTREAM_PATH="${UPSTREAM_PATH:-../OpenShell/proto}"
211+
212+
if [ ! -d "$UPSTREAM_PATH" ]; then
213+
echo "ERROR: Upstream proto directory not found: $UPSTREAM_PATH"
214+
echo "Set UPSTREAM_PATH env var to the correct location."
215+
exit 1
216+
fi
217+
218+
PROTO_DIR="proto"
219+
mkdir -p "$PROTO_DIR"
220+
221+
PROTO_FILES="openshell.proto datamodel.proto sandbox.proto"
222+
for f in $PROTO_FILES; do
223+
src="$UPSTREAM_PATH/$f"
224+
if [ ! -f "$src" ]; then
225+
echo "ERROR: Proto file not found: $src"
226+
exit 1
227+
fi
228+
cp "$src" "$PROTO_DIR/$f"
229+
echo "Copied $f"
230+
done
231+
232+
# Record upstream commit SHA
233+
UPSTREAM_REPO_DIR="$(cd "$UPSTREAM_PATH/.." && pwd)"
234+
if [ -d "$UPSTREAM_REPO_DIR/.git" ]; then
235+
SHA=$(git -C "$UPSTREAM_REPO_DIR" rev-parse HEAD)
236+
else
237+
SHA="unknown"
238+
fi
239+
echo "$SHA" > "$PROTO_DIR/UPSTREAM_VERSION"
240+
echo "Upstream version: $SHA"
241+
echo "Proto sync complete."
242+
"""

0 commit comments

Comments
 (0)