Skip to content

Commit 399380a

Browse files
committed
Add config flag support to installers
1 parent 7483c85 commit 399380a

File tree

5 files changed

+193
-3
lines changed

5 files changed

+193
-3
lines changed

cmd/state-installer/cmd.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type Params struct {
4848
activateDefault *project.Namespaced
4949
showVersion bool
5050
nonInteractive bool
51+
configSettings []string
5152
}
5253

5354
func newParams() *Params {
@@ -194,6 +195,11 @@ func main() {
194195
Value: &params.showVersion,
195196
},
196197
{Name: "non-interactive", Shorthand: "n", Hidden: true, Value: &params.nonInteractive}, // don't prompt
198+
{
199+
Name: "config-set",
200+
Description: "Set config values in 'key=value' or 'key value' format, supports comma-separated pairs, can be specified multiple times",
201+
Value: &params.configSettings,
202+
},
197203
// The remaining flags are for backwards compatibility (ie. we don't want to error out when they're provided)
198204
{Name: "channel", Hidden: true, Value: &garbageString},
199205
{Name: "bbb", Shorthand: "b", Hidden: true, Value: &garbageString},
@@ -335,6 +341,11 @@ func execute(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher,
335341
return err
336342
}
337343
storeInstallSource(params.sourceInstaller)
344+
345+
if err := applyConfigSettings(cfg, params.configSettings); err != nil {
346+
return errs.Wrap(err, "Failed to apply config settings")
347+
}
348+
338349
return postInstallEvents(out, cfg, an, params)
339350
}
340351

@@ -501,3 +512,52 @@ func assertCompatibility() error {
501512

502513
return nil
503514
}
515+
516+
func applyConfigSettings(cfg *config.Instance, configSettings []string) error {
517+
for _, settingGroup := range configSettings {
518+
// Split on commas first to handle multiple pairs in one flag value
519+
pairs := strings.Split(settingGroup, ",")
520+
for _, pair := range pairs {
521+
pair = strings.TrimSpace(pair)
522+
if pair == "" {
523+
continue // Skip empty pairs
524+
}
525+
if err := applyConfigSetting(cfg, pair); err != nil {
526+
return errs.Wrap(err, "Failed to apply config setting: %s", pair)
527+
}
528+
}
529+
}
530+
return nil
531+
}
532+
533+
func applyConfigSetting(cfg *config.Instance, setting string) error {
534+
var key, valueStr string
535+
536+
if strings.Contains(setting, "=") {
537+
parts := strings.SplitN(setting, "=", 2)
538+
if len(parts) == 2 {
539+
key = strings.TrimSpace(parts[0])
540+
valueStr = strings.TrimSpace(parts[1])
541+
}
542+
} else {
543+
parts := strings.Fields(setting)
544+
if len(parts) >= 2 {
545+
key = parts[0]
546+
valueStr = strings.Join(parts[1:], " ")
547+
}
548+
}
549+
550+
if key == "" || valueStr == "" {
551+
return locale.NewInputError("err_config_invalid_format", "Config setting must be in 'key=value' or 'key value' format: {{.V0}}", setting)
552+
}
553+
554+
// Store the raw string value without type validation since config options
555+
// are not yet registered in the installer context
556+
err := cfg.Set(key, valueStr)
557+
if err != nil {
558+
return locale.WrapError(err, "err_config_set", "Could not set value {{.V0}} for key {{.V1}}", valueStr, key)
559+
}
560+
561+
logging.Debug("Config setting applied: %s=%s", key, valueStr)
562+
return nil
563+
}

installers/install.ps1

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,29 @@ function getopt([string] $opt, [string] $default, [string[]] $arr)
4141
return $default
4242
}
4343

44+
# Collect all instances of a flag (for flags that can appear multiple times)
45+
function getopt_all([string] $opt, [string[]] $arr)
46+
{
47+
$result = @()
48+
for ($i = 0; $i -lt $arr.Length; $i++)
49+
{
50+
$arg = $arr[$i]
51+
if ($arg -eq $opt -and ($i + 1) -lt $arr.Length)
52+
{
53+
$value = $arr[$i + 1]
54+
if ($value -and -not $value.StartsWith("-"))
55+
{
56+
$result += $opt
57+
$result += $value
58+
}
59+
}
60+
}
61+
return $result
62+
}
63+
4464
$script:CHANNEL = getopt "-b" $script:CHANNEL $args
4565
$script:VERSION = getopt "-v" $script:VERSION $args
66+
$script:CONFIG_SET_ARGS = getopt_all "--config-set" $args
4667

4768
function download([string] $url, [string] $out)
4869
{
@@ -269,7 +290,7 @@ $PSDefaultParameterValues['*:Encoding'] = 'utf8'
269290
# Run the installer.
270291
$env:ACTIVESTATE_SESSION_TOKEN = $script:SESSION_TOKEN_VALUE
271292
setShellOverride
272-
& $exePath $args --source-installer="install.ps1"
293+
& $exePath $args $script:CONFIG_SET_ARGS --source-installer="install.ps1"
273294
$success = $?
274295
if (Test-Path env:ACTIVESTATE_SESSION_TOKEN)
275296
{

installers/install.sh

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,30 @@ getopt() {
3636
echo $default
3737
}
3838

39+
# Collect all instances of a flag (for flags that can appear multiple times)
40+
getopt_all() {
41+
opt=$1; shift
42+
result=""
43+
i=0
44+
for arg in $@; do
45+
i=$((i + 1))
46+
if [ "${arg}" = "$opt" ]; then
47+
value=$(echo "$@" | cut -d' ' -f$(($i + 1)))
48+
if [ -n "$value" ] && [ "${value#-}" = "$value" ]; then # ensure value doesn't start with -
49+
if [ -n "$result" ]; then
50+
result="$result $opt $value"
51+
else
52+
result="$opt $value"
53+
fi
54+
fi
55+
fi
56+
done
57+
echo $result
58+
}
59+
3960
CHANNEL=$(getopt "-b" "$CHANNEL" $@)
4061
VERSION=$(getopt "-v" "$VERSION" $@)
62+
CONFIG_SET_ARGS=$(getopt_all "--config-set" $@)
4163

4264
if [ -z "${TERM}" ] || [ "${TERM}" = "dumb" ]; then
4365
OUTPUT_OK=""
@@ -205,7 +227,7 @@ progress_done
205227
echo ""
206228

207229
# Run the installer.
208-
ACTIVESTATE_SESSION_TOKEN=$SESSION_TOKEN_VALUE $INSTALLERTMPDIR/$INSTALLERNAME$BINARYEXT "$@" --source-installer="install.sh"
230+
ACTIVESTATE_SESSION_TOKEN=$SESSION_TOKEN_VALUE $INSTALLERTMPDIR/$INSTALLERNAME$BINARYEXT "$@" $CONFIG_SET_ARGS --source-installer="install.sh"
209231

210232
# Remove temp files
211233
rm -r $INSTALLERTMPDIR

internal/testhelpers/e2e/session.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ type Session struct {
6666
ignoreLogErrors bool
6767
cfg *config.Instance
6868
cache keyCache
69-
cfg *config.Instance
7069
}
7170

7271
type keyCache map[string]string

test/integration/install_scripts_int_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,94 @@ func (suite *InstallScriptsIntegrationTestSuite) assertCorrectVersion(ts *e2e.Se
281281
}
282282
}
283283

284+
func (suite *InstallScriptsIntegrationTestSuite) TestInstall_ConfigSet() {
285+
suite.OnlyRunForTags(tagsuite.InstallScripts)
286+
ts := e2e.New(suite.T(), false)
287+
defer ts.Close()
288+
289+
baseUrl := "https://state-tool.s3.amazonaws.com/update/state/"
290+
scriptBaseName := "install."
291+
if runtime.GOOS != "windows" {
292+
scriptBaseName += "sh"
293+
} else {
294+
scriptBaseName += "ps1"
295+
}
296+
scriptUrl := baseUrl + constants.ChannelName + "/" + scriptBaseName
297+
298+
b, err := httputil.GetDirect(scriptUrl)
299+
suite.Require().NoError(err)
300+
script := filepath.Join(ts.Dirs.Work, scriptBaseName)
301+
suite.Require().NoError(fileutils.WriteFile(script, b))
302+
303+
installDir := filepath.Join(ts.Dirs.Work, "install")
304+
args := []string{script}
305+
args = append(args, "-t", installDir)
306+
args = append(args, "-n") // non-interactive
307+
args = append(args, "-b", constants.ChannelName)
308+
309+
args = append(args, "--config-set", "analytics.enabled=false")
310+
args = append(args, "--config-set", "output.format json") // space-separated format
311+
args = append(args, "--config-set", "test.key1=value1,test.key2=value2") // comma-separated format
312+
313+
appInstallDir := filepath.Join(ts.Dirs.Work, "app")
314+
suite.NoError(fileutils.Mkdir(appInstallDir))
315+
316+
cmd := "bash"
317+
opts := []e2e.SpawnOptSetter{
318+
e2e.OptArgs(args...),
319+
e2e.OptAppendEnv(constants.DisableRuntime + "=false"),
320+
e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.AppInstallDirOverrideEnvVarName, appInstallDir)),
321+
e2e.OptAppendEnv(fmt.Sprintf("%s=FOO", constants.OverrideSessionTokenEnvVarName)),
322+
e2e.OptAppendEnv(fmt.Sprintf("%s=false", constants.DisableActivateEventsEnvVarName)),
323+
}
324+
if runtime.GOOS == "windows" {
325+
cmd = "powershell.exe"
326+
opts = append(opts,
327+
e2e.OptAppendEnv("SHELL="),
328+
e2e.OptAppendEnv(constants.OverrideShellEnvVarName+"="),
329+
)
330+
}
331+
cp := ts.SpawnCmdWithOpts(cmd, opts...)
332+
cp.Expect("Preparing Installer for State Tool Package Manager")
333+
if runtime.GOOS == "windows" {
334+
cp.Expect("Continuing because the '--force' flag is set") // admin prompt
335+
}
336+
cp.Expect("Installation Complete", e2e.RuntimeSourcingTimeoutOpt)
337+
338+
cp.SendLine("exit")
339+
cp.ExpectExitCode(0)
340+
341+
stateExec, err := installation.StateExecFromDir(installDir)
342+
suite.NoError(err)
343+
suite.FileExists(stateExec)
344+
345+
suite.verifyConfigValue(ts, stateExec, "analytics.enabled", "false")
346+
suite.verifyConfigValue(ts, stateExec, "output.format", "json")
347+
suite.verifyConfigValue(ts, stateExec, "test.key1", "value1")
348+
suite.verifyConfigValue(ts, stateExec, "test.key2", "value2")
349+
350+
suite.verifyConfigValueDirect(ts, "analytics.enabled", "false")
351+
suite.verifyConfigValueDirect(ts, "output.format", "json")
352+
suite.verifyConfigValueDirect(ts, "test.key1", "value1")
353+
suite.verifyConfigValueDirect(ts, "test.key2", "value2")
354+
}
355+
356+
func (suite *InstallScriptsIntegrationTestSuite) verifyConfigValue(ts *e2e.Session, stateExec, key, expectedValue string) {
357+
cp := ts.SpawnCmd(stateExec, "config", "get", key)
358+
cp.ExpectExitCode(0)
359+
output := strings.TrimSpace(cp.StrippedSnapshot())
360+
suite.Equal(expectedValue, output, "Config value for key %s should be %s, got %s", key, expectedValue, output)
361+
}
362+
363+
func (suite *InstallScriptsIntegrationTestSuite) verifyConfigValueDirect(ts *e2e.Session, key, expectedValue string) {
364+
cfg, err := config.NewCustom(ts.Dirs.Config, singlethread.New(), true)
365+
suite.Require().NoError(err)
366+
defer cfg.Close()
367+
368+
actualValue := cfg.GetString(key)
369+
suite.Equal(expectedValue, actualValue, "Config value for key %s should be %s, got %s (via config library)", key, expectedValue, actualValue)
370+
}
371+
284372
func (suite *InstallScriptsIntegrationTestSuite) assertAnalytics(ts *e2e.Session) {
285373
// Verify analytics reported a non-empty sessionToken.
286374
sessionTokenFound := false

0 commit comments

Comments
 (0)