Skip to content

Commit 4259eef

Browse files
authored
Consistently use maps instead of slices for env vars (#1398)
This change is intended to allow env vars to be specified to the API as a map of string to string. Some refactoring was made to apply this change consistently throughout the codebase. Now, the CLI is responsible for converting from a list of strings to the map. Also replace a manual mock with one generated by mockgen.
1 parent 6b74b4b commit 4259eef

File tree

12 files changed

+248
-154
lines changed

12 files changed

+248
-154
lines changed

cmd/thv-proxyrunner/app/run.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/spf13/cobra"
99

1010
"github.com/stacklok/toolhive/pkg/container"
11+
"github.com/stacklok/toolhive/pkg/environment"
1112
"github.com/stacklok/toolhive/pkg/logger"
1213
"github.com/stacklok/toolhive/pkg/registry"
1314
"github.com/stacklok/toolhive/pkg/runner"
@@ -237,6 +238,12 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
237238

238239
var imageMetadata *registry.ImageMetadata
239240

241+
// Parse environment variables from slice to map
242+
envVarsMap, err := environment.ParseEnvironmentVariables(runEnv)
243+
if err != nil {
244+
return fmt.Errorf("failed to parse environment variables: %v", err)
245+
}
246+
240247
// Initialize a new RunConfig with values from command-line flags
241248
runConfig, err := runner.NewRunConfigBuilder().
242249
WithRuntime(rt).
@@ -261,7 +268,7 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
261268
WithTelemetryConfig(finalOtelEndpoint, runOtelEnablePrometheusMetricsPath, runOtelServiceName,
262269
finalOtelSamplingRate, runOtelHeaders, runOtelInsecure, finalOtelEnvironmentVariables).
263270
WithToolsFilter(runToolsFilter).
264-
Build(ctx, imageMetadata, runEnv, envVarValidator)
271+
Build(ctx, imageMetadata, envVarsMap, envVarValidator)
265272
if err != nil {
266273
return fmt.Errorf("failed to create RunConfig: %v", err)
267274
}

cmd/thv/app/mcp_serve_helpers.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ func buildServerConfig(
6666
builder = builder.WithTransportAndPorts(transport, 0, 0)
6767

6868
// Prepare environment variables
69-
envVarsList := prepareEnvironmentVariables(imageMetadata, args.Env)
69+
envVars := prepareEnvironmentVariables(imageMetadata, args.Env)
7070

7171
// Build the configuration
7272
envVarValidator := &runner.DetachedEnvVarValidator{}
73-
return builder.Build(ctx, imageMetadata, envVarsList, envVarValidator)
73+
return builder.Build(ctx, imageMetadata, envVars, envVarValidator)
7474
}
7575

7676
// configureTransport sets up transport configuration from metadata
@@ -88,7 +88,7 @@ func configureTransport(builder *runner.RunConfigBuilder, imageMetadata *registr
8888
}
8989

9090
// prepareEnvironmentVariables merges default and user environment variables
91-
func prepareEnvironmentVariables(imageMetadata *registry.ImageMetadata, userEnv map[string]string) []string {
91+
func prepareEnvironmentVariables(imageMetadata *registry.ImageMetadata, userEnv map[string]string) map[string]string {
9292
envVarsMap := make(map[string]string)
9393

9494
// Add default environment variables from metadata
@@ -105,13 +105,7 @@ func prepareEnvironmentVariables(imageMetadata *registry.ImageMetadata, userEnv
105105
envVarsMap[k] = v
106106
}
107107

108-
// Convert map to []string format
109-
var envVarsList []string
110-
for k, v := range envVarsMap {
111-
envVarsList = append(envVarsList, fmt.Sprintf("%s=%s", k, v))
112-
}
113-
114-
return envVarsList
108+
return envVarsMap
115109
}
116110

117111
// saveAndRunServer saves the configuration and runs the server

cmd/thv/app/run_flags.go

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
cfg "github.com/stacklok/toolhive/pkg/config"
1010
"github.com/stacklok/toolhive/pkg/container"
1111
"github.com/stacklok/toolhive/pkg/container/runtime"
12+
"github.com/stacklok/toolhive/pkg/environment"
1213
"github.com/stacklok/toolhive/pkg/ignore"
1314
"github.com/stacklok/toolhive/pkg/networking"
1415
"github.com/stacklok/toolhive/pkg/process"
@@ -184,16 +185,16 @@ func AddRunFlags(cmd *cobra.Command, config *RunFlags) {
184185
// BuildRunnerConfig creates a runner.RunConfig from the configuration
185186
func BuildRunnerConfig(
186187
ctx context.Context,
187-
runConfig *RunFlags,
188+
runFlags *RunFlags,
188189
serverOrImage string,
189190
cmdArgs []string,
190191
debugMode bool,
191192
cmd *cobra.Command,
192193
) (*runner.RunConfig, error) {
193194
// Validate the host flag
194-
validatedHost, err := ValidateAndNormaliseHostFlag(runConfig.Host)
195+
validatedHost, err := ValidateAndNormaliseHostFlag(runFlags.Host)
195196
if err != nil {
196-
return nil, fmt.Errorf("invalid host: %s", runConfig.Host)
197+
return nil, fmt.Errorf("invalid host: %s", runFlags.Host)
197198
}
198199

199200
// Get OIDC flags
@@ -212,15 +213,15 @@ func BuildRunnerConfig(
212213
// Get OTEL flag values with config fallbacks
213214
config := cfg.GetConfig()
214215
finalOtelEndpoint, finalOtelSamplingRate, finalOtelEnvironmentVariables := getTelemetryFromFlags(cmd, config,
215-
runConfig.OtelEndpoint, runConfig.OtelSamplingRate, runConfig.OtelEnvironmentVariables)
216+
runFlags.OtelEndpoint, runFlags.OtelSamplingRate, runFlags.OtelEnvironmentVariables)
216217

217218
// Create container runtime
218219
rt, err := container.NewFactory().Create(ctx)
219220
if err != nil {
220221
return nil, fmt.Errorf("failed to create container runtime: %v", err)
221222
}
222223

223-
// Select an env var validation strategy depending on how the CLI is run:
224+
// Select an envVars var validation strategy depending on how the CLI is run:
224225
// If we have called the CLI directly, we use the CLIEnvVarValidator.
225226
// If we are running in detached mode, or the CLI is wrapped by the K8s operator,
226227
// we use the DetachedEnvVarValidator.
@@ -241,52 +242,58 @@ func BuildRunnerConfig(
241242
// Take the MCP server we were supplied and either fetch the image, or
242243
// build it from a protocol scheme. If the server URI refers to an image
243244
// in our trusted registry, we will also fetch the image metadata.
244-
imageURL, imageMetadata, err = retriever.GetMCPServer(ctx, serverOrImage, runConfig.CACertPath, runConfig.VerifyImage)
245+
imageURL, imageMetadata, err = retriever.GetMCPServer(ctx, serverOrImage, runFlags.CACertPath, runFlags.VerifyImage)
245246
if err != nil {
246247
return nil, fmt.Errorf("failed to find or create the MCP server %s: %v", serverOrImage, err)
247248
}
248249
}
249250

250251
// Validate proxy mode early
251-
if !types.IsValidProxyMode(runConfig.ProxyMode) {
252-
if runConfig.ProxyMode == "" {
253-
runConfig.ProxyMode = types.ProxyModeSSE.String() // default to SSE for backward compatibility
252+
if !types.IsValidProxyMode(runFlags.ProxyMode) {
253+
if runFlags.ProxyMode == "" {
254+
runFlags.ProxyMode = types.ProxyModeSSE.String() // default to SSE for backward compatibility
254255
} else {
255-
return nil, fmt.Errorf("invalid value for --proxy-mode: %s", runConfig.ProxyMode)
256+
return nil, fmt.Errorf("invalid value for --proxy-mode: %s", runFlags.ProxyMode)
256257
}
257258
}
258259

260+
// Parse the environment variables from a list of strings to a map.
261+
envVars, err := environment.ParseEnvironmentVariables(runFlags.Env)
262+
if err != nil {
263+
return nil, fmt.Errorf("failed to parse environment variables: %v", err)
264+
}
265+
259266
// Initialize a new RunConfig with values from command-line flags
260267
return runner.NewRunConfigBuilder().
261268
WithRuntime(rt).
262269
WithCmdArgs(cmdArgs).
263-
WithName(runConfig.Name).
270+
WithName(runFlags.Name).
264271
WithImage(imageURL).
265272
WithHost(validatedHost).
266-
WithTargetHost(runConfig.TargetHost).
273+
WithTargetHost(runFlags.TargetHost).
267274
WithDebug(debugMode).
268-
WithVolumes(runConfig.Volumes).
269-
WithSecrets(runConfig.Secrets).
270-
WithAuthzConfigPath(runConfig.AuthzConfig).
271-
WithAuditConfigPath(runConfig.AuditConfig).
272-
WithPermissionProfileNameOrPath(runConfig.PermissionProfile).
273-
WithNetworkIsolation(runConfig.IsolateNetwork).
274-
WithK8sPodPatch(runConfig.K8sPodPatch).
275-
WithProxyMode(types.ProxyMode(runConfig.ProxyMode)).
276-
WithTransportAndPorts(runConfig.Transport, runConfig.ProxyPort, runConfig.TargetPort).
277-
WithAuditEnabled(runConfig.EnableAudit, runConfig.AuditConfig).
278-
WithLabels(runConfig.Labels).
279-
WithGroup(runConfig.Group).
275+
WithVolumes(runFlags.Volumes).
276+
WithSecrets(runFlags.Secrets).
277+
WithAuthzConfigPath(runFlags.AuthzConfig).
278+
WithAuditConfigPath(runFlags.AuditConfig).
279+
WithPermissionProfileNameOrPath(runFlags.PermissionProfile).
280+
WithNetworkIsolation(runFlags.IsolateNetwork).
281+
WithK8sPodPatch(runFlags.K8sPodPatch).
282+
WithProxyMode(types.ProxyMode(runFlags.ProxyMode)).
283+
WithTransportAndPorts(runFlags.Transport, runFlags.ProxyPort, runFlags.TargetPort).
284+
WithAuditEnabled(runFlags.EnableAudit, runFlags.AuditConfig).
285+
WithLabels(runFlags.Labels).
286+
WithGroup(runFlags.Group).
280287
WithOIDCConfig(oidcIssuer, oidcAudience, oidcJwksURL, oidcIntrospectionURL, oidcClientID, oidcClientSecret,
281-
runConfig.ThvCABundle, runConfig.JWKSAuthTokenFile, runConfig.ResourceURL, runConfig.JWKSAllowPrivateIP).
282-
WithTelemetryConfig(finalOtelEndpoint, runConfig.OtelEnablePrometheusMetricsPath, runConfig.OtelServiceName,
283-
finalOtelSamplingRate, runConfig.OtelHeaders, runConfig.OtelInsecure, finalOtelEnvironmentVariables).
284-
WithToolsFilter(runConfig.ToolsFilter).
288+
runFlags.ThvCABundle, runFlags.JWKSAuthTokenFile, runFlags.ResourceURL, runFlags.JWKSAllowPrivateIP).
289+
WithTelemetryConfig(finalOtelEndpoint, runFlags.OtelEnablePrometheusMetricsPath, runFlags.OtelServiceName,
290+
finalOtelSamplingRate, runFlags.OtelHeaders, runFlags.OtelInsecure, finalOtelEnvironmentVariables).
291+
WithToolsFilter(runFlags.ToolsFilter).
285292
WithIgnoreConfig(&ignore.Config{
286-
LoadGlobal: runConfig.IgnoreGlobally,
287-
PrintOverlays: runConfig.PrintOverlays,
293+
LoadGlobal: runFlags.IgnoreGlobally,
294+
PrintOverlays: runFlags.PrintOverlays,
288295
}).
289-
Build(ctx, imageMetadata, runConfig.Env, envVarValidator)
296+
Build(ctx, imageMetadata, envVars, envVarValidator)
290297
}
291298

292299
// getOidcFromFlags extracts OIDC configuration from command flags

docs/server/docs.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.yaml

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/api/v1/workloads.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ type createRequest struct {
562562
// Port to expose from the container
563563
TargetPort int `json:"target_port"`
564564
// Environment variables to set in the container
565-
EnvVars []string `json:"env_vars"`
565+
EnvVars map[string]string `json:"env_vars"`
566566
// Secret parameters to inject
567567
Secrets []secrets.SecretParameter `json:"secrets"`
568568
// Volume mounts

pkg/runner/config.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -242,19 +242,14 @@ func (c *RunConfig) WithPorts(proxyPort, targetPort int) (*RunConfig, error) {
242242
return c, nil
243243
}
244244

245-
// WithEnvironmentVariables parses and sets environment variables
246-
func (c *RunConfig) WithEnvironmentVariables(envVarStrings []string) (*RunConfig, error) {
247-
envVars, err := environment.ParseEnvironmentVariables(envVarStrings)
248-
if err != nil {
249-
return c, fmt.Errorf("failed to parse environment variables: %v", err)
250-
}
251-
245+
// WithEnvironmentVariables sets environment variables
246+
func (c *RunConfig) WithEnvironmentVariables(envVars map[string]string) (*RunConfig, error) {
252247
// Initialize EnvVars if it's nil
253248
if c.EnvVars == nil {
254249
c.EnvVars = make(map[string]string)
255250
}
256251

257-
// Merge the parsed environment variables with existing ones
252+
// Merge the provided environment variables with existing ones
258253
for key, value := range envVars {
259254
c.EnvVars[key] = value
260255
}

pkg/runner/config_builder.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,12 @@ func (b *RunConfigBuilder) WithIgnoreConfig(ignoreConfig *ignore.Config) *RunCon
297297
}
298298

299299
// Build creates the final RunConfig instance with validation and processing
300-
func (b *RunConfigBuilder) Build(ctx context.Context, imageMetadata *registry.ImageMetadata,
301-
envVars []string, envVarValidator EnvVarValidator) (*RunConfig, error) {
300+
func (b *RunConfigBuilder) Build(
301+
ctx context.Context,
302+
imageMetadata *registry.ImageMetadata,
303+
envVars map[string]string,
304+
envVarValidator EnvVarValidator,
305+
) (*RunConfig, error) {
302306
// When using the CLI validation strategy, this is where the prompting for
303307
// missing environment variables will happen.
304308
processedEnvVars, err := envVarValidator.Validate(ctx, imageMetadata, b.config, envVars)

0 commit comments

Comments
 (0)