Skip to content

Commit 70509a7

Browse files
committed
feat: wire coder client options into aggregated apiserver
1 parent f80085b commit 70509a7

4 files changed

Lines changed: 150 additions & 13 deletions

File tree

app_dispatch.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
7+
"time"
68

79
ctrl "sigs.k8s.io/controller-runtime"
810

@@ -15,15 +17,40 @@ const supportedAppModes = "controller, aggregated-apiserver, mcp-http"
1517

1618
var (
1719
runControllerApp = controllerapp.Run
18-
runAggregatedAPIServerApp = apiserverapp.Run
19-
runMCPHTTPApp = mcpapp.RunHTTP
20-
setupSignalHandler = ctrl.SetupSignalHandler
20+
runAggregatedAPIServerApp = func(ctx context.Context, opts apiserverapp.Options) error {
21+
return apiserverapp.RunWithOptions(ctx, opts)
22+
}
23+
runMCPHTTPApp = mcpapp.RunHTTP
24+
setupSignalHandler = ctrl.SetupSignalHandler
2125
)
2226

2327
func run(args []string) error {
2428
fs := flag.NewFlagSet("coder-k8s", flag.ContinueOnError)
25-
var appMode string
29+
var (
30+
appMode string
31+
coderURL string
32+
coderSessionToken string
33+
coderRequestTimeout time.Duration
34+
)
2635
fs.StringVar(&appMode, "app", "", "Application mode (controller, aggregated-apiserver, mcp-http)")
36+
fs.StringVar(
37+
&coderSessionToken,
38+
"coder-session-token",
39+
"",
40+
"Admin session token for the backing Coder deployment",
41+
)
42+
fs.StringVar(
43+
&coderURL,
44+
"coder-url",
45+
"",
46+
"Coder deployment URL (fallback when CoderControlPlane status URL is unavailable)",
47+
)
48+
fs.DurationVar(
49+
&coderRequestTimeout,
50+
"coder-request-timeout",
51+
30*time.Second,
52+
"Timeout for Coder SDK API requests",
53+
)
2754
if err := fs.Parse(args); err != nil {
2855
return err
2956
}
@@ -32,7 +59,12 @@ func run(args []string) error {
3259
case "controller":
3360
return runControllerApp(setupSignalHandler())
3461
case "aggregated-apiserver":
35-
return runAggregatedAPIServerApp(setupSignalHandler())
62+
opts := apiserverapp.Options{
63+
CoderURL: coderURL,
64+
CoderSessionToken: coderSessionToken,
65+
CoderRequestTimeout: coderRequestTimeout,
66+
}
67+
return runAggregatedAPIServerApp(setupSignalHandler(), opts)
3668
case "mcp-http":
3769
return runMCPHTTPApp(setupSignalHandler())
3870
case "":

internal/app/apiserverapp/apiserverapp.go

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ package apiserverapp
44
import (
55
"context"
66
"fmt"
7+
"net"
8+
"net/url"
9+
"time"
710

811
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
912
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -23,6 +26,7 @@ import (
2326
"k8s.io/kube-openapi/pkg/validation/spec"
2427

2528
aggregationv1alpha1 "github.com/coder/coder-k8s/api/aggregation/v1alpha1"
29+
"github.com/coder/coder-k8s/internal/aggregated/coder"
2630
"github.com/coder/coder-k8s/internal/aggregated/storage"
2731
)
2832

@@ -32,6 +36,20 @@ const (
3236
serverName = "coder-k8s-aggregated-apiserver"
3337
)
3438

39+
// Options configures aggregated-apiserver bootstrap behavior.
40+
type Options struct {
41+
// SecureServingPort used when Listener is nil. Default: DefaultSecureServingPort.
42+
SecureServingPort int
43+
// Listener allows tests to bind to 127.0.0.1:0.
44+
Listener net.Listener
45+
// CoderURL is an optional fallback URL when CoderControlPlane status has no URL.
46+
CoderURL string
47+
// CoderSessionToken is the admin session token.
48+
CoderSessionToken string
49+
// CoderRequestTimeout for SDK calls. Default 30s.
50+
CoderRequestTimeout time.Duration
51+
}
52+
3553
// NewScheme builds the runtime scheme used by the aggregated API server.
3654
func NewScheme() *runtime.Scheme {
3755
scheme := runtime.NewScheme()
@@ -96,10 +114,17 @@ func NewRecommendedConfig(
96114
}
97115

98116
// NewAPIGroupInfo creates APIGroupInfo for the aggregation.coder.com API group.
99-
func NewAPIGroupInfo(scheme *runtime.Scheme, codecs serializer.CodecFactory) (*genericapiserver.APIGroupInfo, error) {
117+
func NewAPIGroupInfo(
118+
scheme *runtime.Scheme,
119+
codecs serializer.CodecFactory,
120+
provider coder.ClientProvider,
121+
) (*genericapiserver.APIGroupInfo, error) {
100122
if scheme == nil {
101123
return nil, fmt.Errorf("assertion failed: scheme must not be nil")
102124
}
125+
if provider == nil {
126+
return nil, fmt.Errorf("assertion failed: coder client provider must not be nil")
127+
}
103128

104129
parameterCodec := runtime.NewParameterCodec(scheme)
105130
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(
@@ -109,8 +134,8 @@ func NewAPIGroupInfo(scheme *runtime.Scheme, codecs serializer.CodecFactory) (*g
109134
codecs,
110135
)
111136
apiGroupInfo.VersionedResourcesStorageMap[aggregationv1alpha1.SchemeGroupVersion.Version] = map[string]rest.Storage{
112-
"coderworkspaces": storage.NewWorkspaceStorage(),
113-
"codertemplates": storage.NewTemplateStorage(),
137+
"coderworkspaces": storage.NewWorkspaceStorage(provider),
138+
"codertemplates": storage.NewTemplateStorage(provider),
114139
}
115140
return &apiGroupInfo, nil
116141
}
@@ -146,9 +171,48 @@ func NewGenericAPIServer(recommendedConfig *genericapiserver.RecommendedConfig)
146171

147172
// Run starts the aggregated API server application mode.
148173
func Run(ctx context.Context) error {
174+
return RunWithOptions(ctx, Options{})
175+
}
176+
177+
// RunWithOptions starts the aggregated API server application mode.
178+
func RunWithOptions(ctx context.Context, opts Options) error {
149179
if ctx == nil {
150180
return fmt.Errorf("assertion failed: context must not be nil")
151181
}
182+
if opts.CoderURL == "" {
183+
return fmt.Errorf("assertion failed: coder URL must not be empty")
184+
}
185+
if opts.CoderSessionToken == "" {
186+
return fmt.Errorf("assertion failed: coder session token must not be empty")
187+
}
188+
if opts.CoderRequestTimeout < 0 {
189+
return fmt.Errorf("assertion failed: coder request timeout must not be negative")
190+
}
191+
192+
parsedCoderURL, err := url.Parse(opts.CoderURL)
193+
if err != nil {
194+
return fmt.Errorf("parse coder URL %q: %w", opts.CoderURL, err)
195+
}
196+
if parsedCoderURL == nil {
197+
return fmt.Errorf("assertion failed: parsed coder URL must not be nil")
198+
}
199+
200+
requestTimeout := opts.CoderRequestTimeout
201+
if requestTimeout == 0 {
202+
requestTimeout = 30 * time.Second
203+
}
204+
205+
provider, err := coder.NewStaticClientProvider(coder.Config{
206+
CoderURL: parsedCoderURL,
207+
SessionToken: opts.CoderSessionToken,
208+
RequestTimeout: requestTimeout,
209+
})
210+
if err != nil {
211+
return fmt.Errorf("build coder client provider: %w", err)
212+
}
213+
if provider == nil {
214+
return fmt.Errorf("assertion failed: coder client provider is nil after successful construction")
215+
}
152216

153217
scheme := NewScheme()
154218
if scheme == nil {
@@ -157,7 +221,18 @@ func Run(ctx context.Context) error {
157221

158222
codecs := serializer.NewCodecFactory(scheme)
159223
secureServingOptions := genericoptions.NewSecureServingOptions()
160-
secureServingOptions.BindPort = DefaultSecureServingPort
224+
secureServingPort := opts.SecureServingPort
225+
if secureServingPort == 0 {
226+
secureServingPort = DefaultSecureServingPort
227+
}
228+
if secureServingPort < 0 {
229+
return fmt.Errorf("assertion failed: secure serving port must not be negative")
230+
}
231+
secureServingOptions.BindPort = secureServingPort
232+
if opts.Listener != nil {
233+
secureServingOptions.Listener = opts.Listener
234+
secureServingOptions.BindPort = 0
235+
}
161236
secureServingOptions.ServerCert.CertDirectory = ""
162237
secureServingOptions.ServerCert.PairName = ""
163238

@@ -171,7 +246,7 @@ func Run(ctx context.Context) error {
171246
return err
172247
}
173248

174-
apiGroupInfo, err := NewAPIGroupInfo(scheme, codecs)
249+
apiGroupInfo, err := NewAPIGroupInfo(scheme, codecs, provider)
175250
if err != nil {
176251
return fmt.Errorf("build API group info: %w", err)
177252
}

internal/app/apiserverapp/apiserverapp_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"context"
55
"net"
66
"net/http/httptest"
7+
"net/url"
78
"testing"
89

910
"k8s.io/apimachinery/pkg/runtime/schema"
1011
"k8s.io/apimachinery/pkg/runtime/serializer"
1112
genericoptions "k8s.io/apiserver/pkg/server/options"
1213

1314
aggregationv1alpha1 "github.com/coder/coder-k8s/api/aggregation/v1alpha1"
15+
coderhelper "github.com/coder/coder-k8s/internal/aggregated/coder"
1416
)
1517

1618
func TestNewSchemeRegistersAggregationKinds(t *testing.T) {
@@ -67,7 +69,19 @@ func TestInstallAPIGroupRegistersDiscovery(t *testing.T) {
6769
}
6870
defer server.Destroy()
6971

70-
apiGroupInfo, err := NewAPIGroupInfo(scheme, codecs)
72+
coderURL, err := url.Parse("http://localhost:8080")
73+
if err != nil {
74+
t.Fatalf("parse test coder URL: %v", err)
75+
}
76+
provider, err := coderhelper.NewStaticClientProvider(coderhelper.Config{
77+
CoderURL: coderURL,
78+
SessionToken: "test-session-token",
79+
})
80+
if err != nil {
81+
t.Fatalf("build static client provider: %v", err)
82+
}
83+
84+
apiGroupInfo, err := NewAPIGroupInfo(scheme, codecs, provider)
7185
if err != nil {
7286
t.Fatalf("build API group info: %v", err)
7387
}

main_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"errors"
66
"strings"
77
"testing"
8+
"time"
89

910
"k8s.io/apimachinery/pkg/runtime/schema"
1011

1112
coderv1alpha1 "github.com/coder/coder-k8s/api/v1alpha1"
13+
"github.com/coder/coder-k8s/internal/app/apiserverapp"
1214
"github.com/coder/coder-k8s/internal/app/controllerapp"
1315
"github.com/coder/coder-k8s/internal/controller"
1416
)
@@ -102,15 +104,29 @@ func TestRunDispatchesAggregatedAPIServerMode(t *testing.T) {
102104

103105
expectedErr := errors.New("sentinel aggregated-apiserver error")
104106
called := false
105-
runAggregatedAPIServerApp = func(ctx context.Context) error {
107+
runAggregatedAPIServerApp = func(ctx context.Context, opts apiserverapp.Options) error {
106108
called = true
107109
if ctx == nil {
108110
t.Fatal("expected non-nil context passed to aggregated apiserver runner")
109111
}
112+
if got, want := opts.CoderURL, "https://coder.example.com"; got != want {
113+
t.Fatalf("expected coder URL %q, got %q", want, got)
114+
}
115+
if got, want := opts.CoderSessionToken, "test-token"; got != want {
116+
t.Fatalf("expected coder session token %q, got %q", want, got)
117+
}
118+
if got, want := opts.CoderRequestTimeout, 45*time.Second; got != want {
119+
t.Fatalf("expected coder request timeout %v, got %v", want, got)
120+
}
110121
return expectedErr
111122
}
112123

113-
err := run([]string{"--app=aggregated-apiserver"})
124+
err := run([]string{
125+
"--app=aggregated-apiserver",
126+
"--coder-url=https://coder.example.com",
127+
"--coder-session-token=test-token",
128+
"--coder-request-timeout=45s",
129+
})
114130
if !called {
115131
t.Fatal("expected aggregated apiserver runner to be called")
116132
}

0 commit comments

Comments
 (0)