@@ -21,7 +21,6 @@ import (
21
21
"github.com/stacklok/toolhive/pkg/registry"
22
22
"github.com/stacklok/toolhive/pkg/secrets"
23
23
"github.com/stacklok/toolhive/pkg/telemetry"
24
- "github.com/stacklok/toolhive/pkg/transport"
25
24
"github.com/stacklok/toolhive/pkg/transport/types"
26
25
)
27
26
@@ -139,7 +138,6 @@ func NewRunConfig() *RunConfig {
139
138
}
140
139
141
140
// NewRunConfigFromFlags creates a new RunConfig with values from command-line flags
142
- // TODO: We may want to use some sort of builder pattern here to make it more readable.
143
141
func NewRunConfigFromFlags (
144
142
ctx context.Context ,
145
143
runtime rt.Runtime ,
@@ -177,132 +175,28 @@ func NewRunConfigFromFlags(
177
175
envVarValidator EnvVarValidator ,
178
176
proxyMode types.ProxyMode ,
179
177
) (* RunConfig , error ) {
180
- // Ensure default values for host and targetHost
181
- if host == "" {
182
- host = transport .LocalhostIPv4
183
- }
184
- if targetHost == "" {
185
- targetHost = transport .LocalhostIPv4
186
- }
187
-
188
- config := & RunConfig {
189
- Runtime : runtime ,
190
- CmdArgs : cmdArgs ,
191
- Name : name ,
192
- Image : imageURL ,
193
- Debug : debug ,
194
- Volumes : volumes ,
195
- Secrets : secretsList ,
196
- AuthzConfigPath : authzConfigPath ,
197
- AuditConfigPath : auditConfigPath ,
198
- PermissionProfileNameOrPath : permissionProfile ,
199
- TargetHost : targetHost ,
200
- ContainerLabels : make (map [string ]string ),
201
- EnvVars : make (map [string ]string ),
202
- Host : host ,
203
- IsolateNetwork : isolateNetwork ,
204
- K8sPodTemplatePatch : k8sPodPatch ,
205
- ProxyMode : proxyMode ,
206
- }
207
-
208
- // Configure audit if enabled
209
- configureAudit (config , enableAudit , auditConfigPath )
210
-
211
- // Configure OIDC if any values are provided
212
- configureOIDC (config , oidcIssuer , oidcAudience , oidcJwksURL , oidcClientID , oidcAllowOpaqueTokens )
213
-
214
- // Configure telemetry if endpoint or metrics port is provided
215
- configureTelemetry (config , otelEndpoint , otelEnablePrometheusMetricsPath , otelServiceName ,
216
- otelSamplingRate , otelHeaders , otelInsecure , otelEnvironmentVariables )
217
-
218
- // When using the CLI validation strategy, this is where the prompting for
219
- // missing environment variables will happen.
220
- processedEnvVars , err := envVarValidator .Validate (ctx , imageMetadata , config , envVars )
221
- if err != nil {
222
- return nil , fmt .Errorf ("failed to validate environment variables: %v" , err )
223
- }
224
-
225
- // Do some final validation which can only be done after everything else is set.
226
- // Apply image metadata overrides if needed.
227
- if err := config .validateConfig (imageMetadata , mcpTransport , port , targetPort ); err != nil {
228
- return nil , fmt .Errorf ("failed to validate run config: %v" , err )
229
- }
230
-
231
- // Now set environment variables with the correct transport and ports resolved
232
- if _ , err := config .WithEnvironmentVariables (processedEnvVars ); err != nil {
233
- return nil , fmt .Errorf ("failed to set environment variables: %v" , err )
234
- }
235
-
236
- return config , nil
237
- }
238
-
239
- // configureAudit sets up audit configuration if enabled
240
- func configureAudit (config * RunConfig , enableAudit bool , auditConfigPath string ) {
241
- if enableAudit && auditConfigPath == "" {
242
- config .AuditConfig = audit .DefaultConfig ()
243
- }
244
- }
245
-
246
- // configureOIDC sets up OIDC configuration if any values are provided
247
- func configureOIDC (config * RunConfig , oidcIssuer , oidcAudience , oidcJwksURL , oidcClientID string , oidcAllowOpaqueTokens bool ) {
248
- if oidcIssuer != "" || oidcAudience != "" || oidcJwksURL != "" || oidcClientID != "" {
249
- config .OIDCConfig = & auth.TokenValidatorConfig {
250
- Issuer : oidcIssuer ,
251
- Audience : oidcAudience ,
252
- JWKSURL : oidcJwksURL ,
253
- ClientID : oidcClientID ,
254
- AllowOpaqueTokens : oidcAllowOpaqueTokens ,
255
- }
256
- }
257
- }
258
-
259
- // configureTelemetry sets up telemetry configuration if endpoint or metrics port is provided
260
- func configureTelemetry (config * RunConfig , otelEndpoint string , otelEnablePrometheusMetricsPath bool ,
261
- otelServiceName string , otelSamplingRate float64 , otelHeaders []string , otelInsecure bool ,
262
- otelEnvironmentVariables []string ) {
263
-
264
- if otelEndpoint == "" && ! otelEnablePrometheusMetricsPath {
265
- return
266
- }
267
-
268
- // Parse headers from key=value format
269
- headers := make (map [string ]string )
270
- for _ , header := range otelHeaders {
271
- parts := strings .SplitN (header , "=" , 2 )
272
- if len (parts ) == 2 {
273
- headers [parts [0 ]] = parts [1 ]
274
- }
275
- }
276
-
277
- // Use provided service name or default
278
- serviceName := otelServiceName
279
- if serviceName == "" {
280
- serviceName = telemetry .DefaultConfig ().ServiceName
281
- }
282
-
283
- // Process environment variables - split comma-separated values
284
- var processedEnvVars []string
285
- for _ , envVarEntry := range otelEnvironmentVariables {
286
- // Split by comma and trim whitespace
287
- envVars := strings .Split (envVarEntry , "," )
288
- for _ , envVar := range envVars {
289
- trimmed := strings .TrimSpace (envVar )
290
- if trimmed != "" {
291
- processedEnvVars = append (processedEnvVars , trimmed )
292
- }
293
- }
294
- }
295
-
296
- config .TelemetryConfig = & telemetry.Config {
297
- Endpoint : otelEndpoint ,
298
- ServiceName : serviceName ,
299
- ServiceVersion : telemetry .DefaultConfig ().ServiceVersion ,
300
- SamplingRate : otelSamplingRate ,
301
- Headers : headers ,
302
- Insecure : otelInsecure ,
303
- EnablePrometheusMetricsPath : otelEnablePrometheusMetricsPath ,
304
- EnvironmentVariables : processedEnvVars ,
305
- }
178
+ return NewRunConfigBuilder ().
179
+ WithRuntime (runtime ).
180
+ WithCmdArgs (cmdArgs ).
181
+ WithName (name ).
182
+ WithImage (imageURL ).
183
+ WithHost (host ).
184
+ WithTargetHost (targetHost ).
185
+ WithDebug (debug ).
186
+ WithVolumes (volumes ).
187
+ WithSecrets (secretsList ).
188
+ WithAuthzConfigPath (authzConfigPath ).
189
+ WithAuditConfigPath (auditConfigPath ).
190
+ WithPermissionProfileNameOrPath (permissionProfile ).
191
+ WithNetworkIsolation (isolateNetwork ).
192
+ WithK8sPodPatch (k8sPodPatch ).
193
+ WithProxyMode (proxyMode ).
194
+ WithTransportAndPorts (mcpTransport , port , targetPort ).
195
+ WithAuditEnabled (enableAudit , auditConfigPath ).
196
+ WithOIDCConfig (oidcIssuer , oidcAudience , oidcJwksURL , oidcClientID , oidcAllowOpaqueTokens ).
197
+ WithTelemetryConfig (otelEndpoint , otelEnablePrometheusMetricsPath , otelServiceName ,
198
+ otelSamplingRate , otelHeaders , otelInsecure , otelEnvironmentVariables ).
199
+ Build (ctx , imageMetadata , envVars , envVarValidator )
306
200
}
307
201
308
202
// WithAuthz adds authorization configuration to the RunConfig
@@ -525,110 +419,3 @@ func (c *RunConfig) ProcessVolumeMounts() error {
525
419
526
420
return nil
527
421
}
528
-
529
- // validateConfig ensures the RunConfig is valid and sets up some of the final
530
- // configuration details which can only be applied after all other flags are added.
531
- // This function also handles setting missing values based on the image metadata (if present).
532
- //
533
- //nolint:gocyclo // This function needs to be refactored to reduce cyclomatic complexity.
534
- func (c * RunConfig ) validateConfig (
535
- imageMetadata * registry.ImageMetadata ,
536
- mcpTransport string ,
537
- port int ,
538
- targetPort int ,
539
- ) error {
540
- var err error
541
-
542
- // The old logic claimed to override the name with the name from the registry
543
- // but didn't. Instead, it used the name passed in from the CLI.
544
- // See: https://github.com/stacklok/toolhive/blob/2873152b62bf61698cbcdd0aba1707a046151e67/cmd/thv/app/run.go#L425
545
- // The following code implements what I believe was the intended behavior:
546
- if c .Name == "" && imageMetadata != nil {
547
- c .Name = imageMetadata .Name
548
- }
549
-
550
- // Check to see if the mcpTransport is defined in the metadata.
551
- // Use this value if it was not set by the user.
552
- // Else, default to stdio.
553
- if mcpTransport == "" {
554
- if imageMetadata != nil && imageMetadata .Transport != "" {
555
- logger .Debugf ("Using registry mcpTransport: %s" , imageMetadata .Transport )
556
- mcpTransport = imageMetadata .Transport
557
- } else {
558
- logger .Debugf ("Defaulting mcpTransport to stdio" )
559
- mcpTransport = types .TransportTypeStdio .String ()
560
- }
561
- }
562
- // Set mcpTransport
563
- if _ , err = c .WithTransport (mcpTransport ); err != nil {
564
- return err
565
- }
566
-
567
- // Use registry target port if not overridden and if the mcpTransport is HTTP-based.
568
- if imageMetadata != nil {
569
- isHTTPServer := mcpTransport == types .TransportTypeSSE .String () ||
570
- mcpTransport == types .TransportTypeStreamableHTTP .String ()
571
- if targetPort == 0 && isHTTPServer && imageMetadata .TargetPort > 0 {
572
- logger .Debugf ("Using registry target port: %d" , imageMetadata .TargetPort )
573
- targetPort = imageMetadata .TargetPort
574
- }
575
- }
576
- // Configure ports and target host
577
- if _ , err = c .WithPorts (port , targetPort ); err != nil {
578
- return err
579
- }
580
-
581
- // If we are missing the permission profile, attempt to load one from the image metadata.
582
- if c .PermissionProfileNameOrPath == "" && imageMetadata != nil {
583
- permProfilePath , err := CreatePermissionProfileFile (c .Name , imageMetadata .Permissions )
584
- if err != nil {
585
- // Just log the error and continue with the default permission profile
586
- logger .Warnf ("Warning: failed to create permission profile file: %v" , err )
587
- } else {
588
- // Update the permission profile path
589
- c .PermissionProfileNameOrPath = permProfilePath
590
- }
591
- }
592
- // Set permission profile (mandatory)
593
- if _ , err = c .ParsePermissionProfile (); err != nil {
594
- return err
595
- }
596
-
597
- // Process volume mounts
598
- if err = c .ProcessVolumeMounts (); err != nil {
599
- return err
600
- }
601
-
602
- // Generate container name if not already set
603
- c .WithContainerName ()
604
-
605
- // Add standard labels
606
- c .WithStandardLabels ()
607
-
608
- // Add authorization configuration if provided
609
- if c .AuthzConfigPath != "" {
610
- authzConfig , err := authz .LoadConfig (c .AuthzConfigPath )
611
- if err != nil {
612
- return fmt .Errorf ("failed to load authorization configuration: %v" , err )
613
- }
614
- c .WithAuthz (authzConfig )
615
- }
616
-
617
- // Add audit configuration if provided
618
- if c .AuditConfigPath != "" {
619
- auditConfig , err := audit .LoadFromFile (c .AuditConfigPath )
620
- if err != nil {
621
- return fmt .Errorf ("failed to load audit configuration: %v" , err )
622
- }
623
- c .WithAudit (auditConfig )
624
- }
625
- // Note: AuditConfig is already set from --enable-audit flag if provided
626
-
627
- // Prepend registry args to command-line args if available
628
- if imageMetadata != nil && len (imageMetadata .Args ) > 0 {
629
- logger .Debugf ("Prepending registry args: %v" , imageMetadata .Args )
630
- c .CmdArgs = append (c .CmdArgs , imageMetadata .Args ... )
631
- }
632
-
633
- return nil
634
- }
0 commit comments