Skip to content
Merged
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 playground/cmd_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ func validateYAMLRecipe(recipe *YAMLRecipe, baseRecipes []Recipe, result *Valida
if serviceConfig.Remove {
result.AddWarning("removing service '%s' from component '%s' - verify names match base recipe", serviceName, componentName)
}
// Validate args and replace_args are mutually exclusive
if len(serviceConfig.Args) > 0 && len(serviceConfig.ReplaceArgs) > 0 {
result.AddError("service '%s' in component '%s': args and replace_args cannot be used together", serviceName, componentName)
}
}
}
}
Expand Down
43 changes: 42 additions & 1 deletion playground/recipe_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,14 @@ type YAMLServiceConfig struct {
// Entrypoint overrides the container entrypoint
Entrypoint string `yaml:"entrypoint,omitempty"`

// Args are the arguments to pass to the service
// Args are the arguments to pass to the service.
// This should be used when ReplaceArgs is not used.
Args []string `yaml:"args,omitempty"`

// ReplaceArgs are the arguments to replace in the service.
// This should be used when Args is not used.
ReplaceArgs []string `yaml:"replace_args,omitempty"`

// Env is a map of environment variables
Env map[string]string `yaml:"env,omitempty"`

Expand Down Expand Up @@ -432,6 +437,9 @@ func applyServiceOverrides(svc *Service, config *YAMLServiceConfig, root *Compon
if len(config.Args) > 0 {
svc.Args = config.Args
}
if len(config.ReplaceArgs) > 0 {
svc.Args = applyReplaceArgs(svc.Args, config.ReplaceArgs)
}
if config.Env != nil {
if svc.Env == nil {
svc.Env = make(map[string]string)
Expand Down Expand Up @@ -470,6 +478,39 @@ func applyServiceOverrides(svc *Service, config *YAMLServiceConfig, root *Compon
}
}

// applyReplaceArgs replaces arguments in the existing args list.
// ReplaceArgs contains flag-value pairs in sequence: ["--flag", "new-value", "--other-flag", "other-value"]
// For each pair, it finds the flag in args and replaces its following value.
func applyReplaceArgs(args, replaceArgs []string) []string {
if len(replaceArgs)%2 != 0 {
slog.Warn("replace_args should contain pairs of flag and value", "count", len(replaceArgs))
}

result := make([]string, len(args))
copy(result, args)

for i := 0; i < len(replaceArgs); i += 2 {
flag := replaceArgs[i]
newValue := replaceArgs[i+1]
result = applyReplacePair(flag, newValue, result)
}

return result
}

// applyReplacePair finds flag in args and replaces the following value with newValue.
func applyReplacePair(flag, newValue string, args []string) []string {
for i := 0; i < len(args); i++ {
if args[i] == flag && i+1 < len(args) {
args[i+1] = newValue
return args
}
}

slog.Warn("replace_args flag not found in service args", "flag", flag)
return args
}

// yamlReleaseToRelease converts a YAMLReleaseConfig to a release struct
func yamlReleaseToRelease(cfg *YAMLReleaseConfig) *release {
return &release{
Expand Down
54 changes: 54 additions & 0 deletions playground/recipe_yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,3 +830,57 @@ func TestApplyDependsOn_UnknownCondition(t *testing.T) {
require.Equal(t, "db", svc.DependsOn[0].Name)
require.Equal(t, DependsOnConditionHealthy, svc.DependsOn[0].Condition)
}

func TestApplyServiceOverrides_WithReplaceArgs(t *testing.T) {
tests := []struct {
name string
args []string
replaceArgs []string
expected []string
}{
{
name: "replace single pair",
args: []string{"--port", "8080", "--host", "localhost"},
replaceArgs: []string{"--port", "9090"},
expected: []string{"--port", "9090", "--host", "localhost"},
},
{
name: "replace multiple pairs",
args: []string{"--port", "8080", "--host", "localhost", "--timeout", "30"},
replaceArgs: []string{"--port", "9090", "--timeout", "60"},
expected: []string{"--port", "9090", "--host", "localhost", "--timeout", "60"},
},
{
name: "replace with subcommand present",
args: []string{"node", "--datadir", "/old", "--port", "8080"},
replaceArgs: []string{"--datadir", "/new", "--port", "9090"},
expected: []string{"node", "--datadir", "/new", "--port", "9090"},
},
{
name: "replace last flag",
args: []string{"--first", "value1", "--second", "value2"},
replaceArgs: []string{"--second", "newvalue2"},
expected: []string{"--first", "value1", "--second", "newvalue2"},
},
{
name: "empty replace args",
args: []string{"--port", "8080"},
replaceArgs: []string{},
expected: []string{"--port", "8080"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
svc := &Service{
Name: "test-svc",
Args: tt.args,
}
config := &YAMLServiceConfig{
ReplaceArgs: tt.replaceArgs,
}
applyServiceOverrides(svc, config, nil)
require.Equal(t, tt.expected, svc.Args)
})
}
}