diff --git a/plugins/akamai/api_client_credentials.go b/plugins/akamai/api_client_credentials.go index 87fda0dbf..54f23a5d8 100644 --- a/plugins/akamai/api_client_credentials.go +++ b/plugins/akamai/api_client_credentials.go @@ -73,7 +73,7 @@ func APIClientCredentials() schema.CredentialType { }, DefaultProvisioner: provision.TempFile(configFile, provision.Filename(".edgerc"), - provision.AddArgs( + provision.AppendArgs( "--edgerc", "{{ .Path }}", "--section", "default", ), diff --git a/plugins/mysql/database_credentials.go b/plugins/mysql/database_credentials.go index 432d7c38e..fa880fa82 100644 --- a/plugins/mysql/database_credentials.go +++ b/plugins/mysql/database_credentials.go @@ -43,7 +43,7 @@ func DatabaseCredentials() schema.CredentialType { Optional: true, }, }, - DefaultProvisioner: provision.TempFile(mysqlConfig, provision.Filename("my.cnf"), provision.AddArgs("--defaults-file={{ .Path }}")), + DefaultProvisioner: provision.TempFile(mysqlConfig, provision.Filename("my.cnf"), provision.AppendArgs("--defaults-file={{ .Path }}")), Importer: importer.TryAll( TryMySQLConfigFile("/etc/my.cnf"), TryMySQLConfigFile("/etc/mysql/my.cnf"), diff --git a/sdk/provision/file_provisioner.go b/sdk/provision/file_provisioner.go index 4d5d6041d..a48d1b057 100644 --- a/sdk/provision/file_provisioner.go +++ b/sdk/provision/file_provisioner.go @@ -20,12 +20,20 @@ type FileProvisioner struct { outpathFixed string outpathEnvVar string outdirEnvVar string - setOutpathAsArg bool + argPlacementMode ArgPlacementMode outpathArgTemplates []string } type ItemToFileContents func(in sdk.ProvisionInput) ([]byte, error) +type ArgPlacementMode int + +const ( + NotSet ArgPlacementMode = iota + Prepend + Append +) + // FieldAsFile can be used to store the value of a single field as a file. func FieldAsFile(fieldName sdk.FieldName) ItemToFileContents { return ItemToFileContents(func(in sdk.ProvisionInput) ([]byte, error) { @@ -83,14 +91,30 @@ func SetOutputDirAsEnvVar(envVarName string) FileOption { } } -// AddArgs can be used to add args to the command line. This is useful when the output file path -// should be passed as an arg. The output path is available as "{{ .Path }}" in each arg. +// AppendArgs appends arguments to the command line for a FileProvisioner. +// This is particularly useful when you need to add arguments that reference the output file path. +// The output path is available as "{{ .Path }}" within the provided argument templates. +// For example: +// * `AppendArgs("--log", "{{ .Path }}")` results in `--log /path/to/tempfile`. +// * `AppendArgs("--log={{ .Path }}")` results in `--log=/path/to/tempfile`. +func AppendArgs(argTemplates ...string) FileOption { + return func(p *FileProvisioner) { + p.argPlacementMode = Append + p.outpathArgTemplates = argTemplates + } +} + +// PrependArgs prepends arguments to the command line for a FileProvisioner. +// This is particularly useful when you need to add arguments that reference the output file path. +// The output path is available as "{{ .Path }}" within the provided argument templates. // For example: -// * `AddArgs("--config-file", "{{ .Path }}")` will result in `--config-file /path/to/tempfile`. -// * `AddArgs("--config-file={{ .Path }}")` will result in `--config-file=/path/to/tempfile`. -func AddArgs(argTemplates ...string) FileOption { +// * `PrependArgs("--input", "{{ .Path }}")` results in `--input /path/to/tempfile`. +// * `PrependArgs("--input={{ .Path }}")` results in `--input=/path/to/tempfile`. +// +// The arguments provided are added before any pre-existing arguments in the command line, but after the command itself. +func PrependArgs(argTemplates ...string) FileOption { return func(p *FileProvisioner) { - p.setOutpathAsArg = true + p.argPlacementMode = Prepend p.outpathArgTemplates = argTemplates } } @@ -134,7 +158,7 @@ func (p FileProvisioner) Provision(ctx context.Context, in sdk.ProvisionInput, o } // Add args to specify the output path. - if p.setOutpathAsArg { + if p.argPlacementMode != NotSet { tmplData := struct{ Path string }{ Path: outpath, } @@ -159,7 +183,14 @@ func (p FileProvisioner) Provision(ctx context.Context, in sdk.ProvisionInput, o argsResolved[i] = result.String() } - out.AddArgs(argsResolved...) + switch p.argPlacementMode { + case Append: + out.AppendArgs(argsResolved...) + case Prepend: + out.PrependArgs(argsResolved...) + default: + out.AddError(fmt.Errorf("invalid argument placement mode")) + } } } diff --git a/sdk/provisioner.go b/sdk/provisioner.go index 99883ffae..2a857e791 100644 --- a/sdk/provisioner.go +++ b/sdk/provisioner.go @@ -102,6 +102,36 @@ func (out *ProvisionOutput) AddEnvVar(name string, value string) { out.Environment[name] = value } +// AddArgsAtIndex inserts additional arguments into the command line at the specified index. +// - If position is -1 or greater than the current length, the arguments are appended. +// - If position is 0 or negative, the arguments are prepended. +// - Otherwise, the arguments are inserted at the specified index, shifting existing elements forward. +func (out *ProvisionOutput) AddArgsAtIndex(position int, args ...string) { + if position == -1 || position >= len(out.CommandLine) { + out.CommandLine = append(out.CommandLine, args...) + return + } + + if position <= 0 { + out.CommandLine = append(args, out.CommandLine...) + return + } + + out.CommandLine = append(out.CommandLine[:position], append(args, out.CommandLine[position:]...)...) +} + +// PrependArgs inserts additional arguments at the beginning of the command line, after the first argument. +// This ensures that the first argument (typically the executable name) remains unchanged. +func (out *ProvisionOutput) PrependArgs(args ...string) { + out.AddArgsAtIndex(1, args...) +} + +// AppendArgs appends additional arguments to the end of the command line. +// This ensures that new arguments are always added last, preserving existing order. +func (out *ProvisionOutput) AppendArgs(args ...string) { + out.AddArgsAtIndex(-1, args...) +} + // AddArgs can be used to add additional arguments to the command line of the provision output. func (out *ProvisionOutput) AddArgs(args ...string) { out.CommandLine = append(out.CommandLine, args...) diff --git a/sdk/provisioner_test.go b/sdk/provisioner_test.go index 59c1ee61f..197f2ebbf 100644 --- a/sdk/provisioner_test.go +++ b/sdk/provisioner_test.go @@ -2,6 +2,7 @@ package sdk import ( "encoding/json" + "reflect" "testing" "time" @@ -122,3 +123,60 @@ func TestCacheOperationsPutStruct(t *testing.T) { assert.Equal(t, structData, structResult) } + +func TestProvisionOutputAddArgsAtIndex(t *testing.T) { + tc := []struct { + name string + initial []string + position int + args []string + expected []string + }{ + { + name: "Insert at the beginning", + initial: []string{"arg2", "arg3"}, + position: 0, + args: []string{"arg1"}, + expected: []string{"arg1", "arg2", "arg3"}, + }, + { + name: "Insert in the middle", + initial: []string{"arg1", "arg3"}, + position: 1, + args: []string{"arg2"}, + expected: []string{"arg1", "arg2", "arg3"}, + }, + { + name: "Insert at the end", + initial: []string{"arg1", "arg2"}, + position: -1, + args: []string{"arg3"}, + expected: []string{"arg1", "arg2", "arg3"}, + }, + { + name: "Append at out-of-range index", + initial: []string{"arg1", "arg2"}, + position: 5, + args: []string{"arg3"}, + expected: []string{"arg1", "arg2", "arg3"}, + }, + { + name: "Insert at negative index (should prepend)", + initial: []string{"arg2", "arg3"}, + position: -5, + args: []string{"arg1"}, + expected: []string{"arg1", "arg2", "arg3"}, + }, + } + + for _, tc := range tc { + t.Run(tc.name, func(t *testing.T) { + out := ProvisionOutput{CommandLine: append([]string{}, tc.initial...)} + out.AddArgsAtIndex(tc.position, tc.args...) + + if !reflect.DeepEqual(out.CommandLine, tc.expected) { + t.Errorf("expected %v, got %v", tc.expected, out.CommandLine) + } + }) + } +}