Skip to content

Commit

Permalink
Add system env variable expansion to envVars function (#186)
Browse files Browse the repository at this point in the history
* Add system env variable expansion to envVars function

The function `envVars` has been updated to enable the application to also read system environment variables, in addition to CLI and env file variables. This change includes an update of the function's comment to reflect the new behavior, and the addition of new test cases to validate the feature. CLI variables override env file variables, which in turn override system environment variables if they share the same name.

* add a test for env file with expanded os env

* add docs about os env expansion
  • Loading branch information
umputun authored Mar 24, 2024
1 parent 8dafea0 commit dad40bb
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 12 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ Spot supports the following command-line options:
- `-k`, `--key=`: Specifies the SSH key for connecting to remote hosts. Overrides the key defined in the playbook file.
- `-s`, `--skip=`: Skips the specified commands during the task execution. Providing the `-s` flag multiple times with different command names skips multiple commands.
- `-o`, `--only=`: Runs only the specified commands during the task execution. Providing the `-o` flag multiple times with different command names runs only multiple commands.
- `-e`, `--env=`: Sets the environment variables to be used during the task execution. Providing the `-e` flag multiple times with different environment variables sets multiple environment variables, e.g., `-e VAR1:VALUE1 -e VAR2:VALUE2`.
- `-E`, `--env-file=`: Sets the environment variables from the file to be used during the task execution. The default is env.yml. Can also be set with the environment variable `SPOT_ENV_FILE`.
- `-e`, `--env=`: Sets the environment variables to be used during the task execution. Providing the `-e` flag multiple times with different environment variables sets multiple environment variables, e.g., `-e VAR1:VALUE1 -e VAR2:VALUE2`. Values could be taken from the OS environment variables as well, e.g., `-e VAR1:$ENV_VAR1` or `-e VAR1:${ENV_VAR1}`.
- `-E`, `--env-file=`: Sets the environment variables from the file to be used during the task execution. The file can have values from the OS environment variables as well. The default is env.yml. Can also be set with the environment variable `SPOT_ENV_FILE`.
- `--no-color`: disable the colorized output. It can also be set with the environment variable `SPOT_NO_COLOR`.
- `--dry`: Enables dry-run mode, which prints out the commands to be executed without actually executing them.
- `-v`, `--verbose`: Enables verbose mode, providing more detailed output and error messages during the task execution.
Expand Down
12 changes: 9 additions & 3 deletions cmd/spot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,16 @@ func setAdHocSSH(opts options, pbook *config.PlayBook) (*config.PlayBook, error)
return pbook, nil
}

// envVars returns a map of environment variables from the cli and env file, cli vars override env file vars if duplicated
// envVars returns a map of environment variables from the cli, env file, and system env.
// cli vars override env file vars, which in turn override system env vars if duplicated.
func envVars(vars map[string]string, envFile string) (map[string]string, error) {
res := make(map[string]string)

expandEnv := func(value string) string {
// expand environment variables denoted by $var or ${var}
return os.Expand(value, os.Getenv)
}

// load env file vars
envFileData := struct {
Vars map[string]string `yaml:"vars"`
Expand All @@ -465,13 +471,13 @@ func envVars(vars map[string]string, envFile string) (map[string]string, error)
log.Printf("[WARN] can't parse env file %q: %v", envFile, err)
}
for k, v := range envFileData.Vars {
res[k] = v
res[k] = expandEnv(v)
}
}

// cli vars override env file vars
for k, v := range vars {
res[k] = v
res[k] = expandEnv(v)
}

return res, nil
Expand Down
42 changes: 35 additions & 7 deletions cmd/spot/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,9 @@ func Test_targetsForTask(t *testing.T) {
}

func TestEnvVars(t *testing.T) {
os.Setenv("ENV_VAR", "envValue")
defer os.Unsetenv("ENV_VAR")

tests := []struct {
name string
cliVars map[string]string
Expand All @@ -734,11 +737,17 @@ func TestEnvVars(t *testing.T) {
key1: fileValue1
key2: fileValue2
key3: fileValue3
key4: "${ENV_VAR}"
key5: "${ENV_VAR_NOT_FOUND}"
key6: "$ENV_VAR"
`,
expectedVars: map[string]string{
"key1": "cliValue1",
"key2": "cliValue2",
"key3": "fileValue3",
"key4": "envValue",
"key5": "",
"key6": "envValue",
},
expectedError: false,
},
Expand All @@ -755,34 +764,53 @@ func TestEnvVars(t *testing.T) {
},
expectedError: false,
},
{
name: "system env var replacement",
cliVars: map[string]string{
"key1": "$ENV_VAR",
"key2": "${ENV_VAR}",
"key3": "${ENV_VAR_NOT_FOUND}",
},
envFileData: "",
expectedVars: map[string]string{
"key1": "envValue",
"key2": "envValue",
"key3": "",
},
expectedError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

envFile := "/tmp/env-not-exist.yaml"
if tt.envFileData != "" {
// create a temp file
file, err := os.CreateTemp("", "*.yaml")
if err != nil {
t.Fatalf("could not create temp file: %v", err)
}
defer os.Remove(file.Name())
defer os.Remove(file.Name()) // Clean up

// write data to temp file
if _, err = file.WriteString(tt.envFileData); err != nil {
t.Fatalf("could not write to temp file: %v", err)
}

// close file
if err = file.Close(); err != nil {
t.Fatalf("could not close temp file: %v", err)
}
envFile = file.Name()
}
// get environment variables

actualVars, err := envVars(tt.cliVars, envFile)
assert.Equal(t, tt.expectedError, err != nil)
if err != nil && !tt.expectedError {
t.Errorf("envVars() error = %v, expectedError %v", err, tt.expectedError)
return
}
if err == nil && tt.expectedError {
t.Errorf("envVars() expected error, got none")
return
}

assert.Equal(t, tt.expectedVars, actualVars)
})
}
Expand Down

0 comments on commit dad40bb

Please sign in to comment.