Skip to content

Commit 7fd6cf7

Browse files
committed
Fix log with cli args
Signed-off-by: David Gageot <david.gageot@docker.com>
1 parent ca5e0ea commit 7fd6cf7

3 files changed

Lines changed: 99 additions & 2 deletions

File tree

taskfile/run_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,72 @@ func TestExpandVarsCallSiteVarBeatsCLIArgsParam(t *testing.T) {
21352135
assert.Equal(t, "op inject -f", expandVars("op inject {{.CLI_ARGS}}", vars, "--from-host"))
21362136
}
21372137

2138+
func TestExpandCLIArgsOnly(t *testing.T) {
2139+
// Only CLI_ARGS is expanded — every other variable is left as-is so logs
2140+
// don't accidentally leak secret values.
2141+
assert.Equal(t, "pytest -n 4 backend/tests",
2142+
expandCLIArgsOnly("pytest -n 4 {{.CLI_ARGS}}", nil, "backend/tests"))
2143+
assert.Equal(t, "pytest -n 4 backend/tests",
2144+
expandCLIArgsOnly("pytest -n 4 ${CLI_ARGS}", nil, "backend/tests"))
2145+
assert.Equal(t, "curl -H Authorization:${TOKEN} arg",
2146+
expandCLIArgsOnly("curl -H Authorization:${TOKEN} {{.CLI_ARGS}}", map[string]string{"TOKEN": "hunter2"}, "arg"))
2147+
// Call-site override beats the cliArgs param.
2148+
assert.Equal(t, "pytest -f",
2149+
expandCLIArgsOnly("pytest {{.CLI_ARGS}}", map[string]string{"CLI_ARGS": "-f"}, "--from-host"))
2150+
}
2151+
2152+
func TestRunnerLogExpandsCLIArgs(t *testing.T) {
2153+
dir := t.TempDir()
2154+
tf := &Config{
2155+
Dir: dir,
2156+
Tasks: map[string]Task{
2157+
"test": {
2158+
Cmds: []Cmd{{Cmd: "poetry run pytest -n 4 {{.CLI_ARGS}}"}},
2159+
},
2160+
},
2161+
DotenvVars: make(map[string]string),
2162+
}
2163+
2164+
runner := newTestRunner(t, tf, dir)
2165+
var stderr strings.Builder
2166+
runner.IO.Stderr = &stderr
2167+
captureExecs(runner)
2168+
2169+
require.NoError(t, runner.Run("test", "backend/tests/views/test_ask.py"))
2170+
2171+
// The log should show the CLI_ARGS expanded — not the raw `{{.CLI_ARGS}}` template.
2172+
log := stderr.String()
2173+
assert.Contains(t, log, "poetry run pytest -n 4 backend/tests/views/test_ask.py")
2174+
assert.NotContains(t, log, "{{.CLI_ARGS}}")
2175+
}
2176+
2177+
func TestRunnerLogDoesNotExpandOtherVars(t *testing.T) {
2178+
// Other ${VAR} / {{.VAR}} references stay un-expanded in the log so we
2179+
// don't accidentally surface secrets, while CLI_ARGS is shown.
2180+
dir := t.TempDir()
2181+
tf := &Config{
2182+
Dir: dir,
2183+
Tasks: map[string]Task{
2184+
"deploy": {
2185+
Env: map[string]string{"TOKEN": "hunter2"},
2186+
Cmds: []Cmd{{Cmd: "deploy --token ${TOKEN} {{.CLI_ARGS}}"}},
2187+
},
2188+
},
2189+
DotenvVars: make(map[string]string),
2190+
}
2191+
2192+
runner := newTestRunner(t, tf, dir)
2193+
var stderr strings.Builder
2194+
runner.IO.Stderr = &stderr
2195+
captureExecs(runner)
2196+
2197+
require.NoError(t, runner.Run("deploy", "--dry-run"))
2198+
2199+
log := stderr.String()
2200+
assert.Contains(t, log, "deploy --token ${TOKEN} --dry-run")
2201+
assert.NotContains(t, log, "hunter2")
2202+
}
2203+
21382204
func TestRunnerLogsToInjectedStderr(t *testing.T) {
21392205
dir := t.TempDir()
21402206
tf := &Config{

taskfile/task_execution.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ func (r *Runner) runCmds(taskName string, cmds []Cmd, vars map[string]string, cl
156156
}
157157

158158
if !silent {
159-
// Log the original command template to avoid leaking expanded secrets.
160-
r.logTask(colorGreen, taskName, cmd.Cmd)
159+
// Log the original command template with CLI_ARGS substituted in,
160+
// so the user sees what they actually passed without leaking other
161+
// expanded variables (which may carry secrets).
162+
r.logTask(colorGreen, taskName, expandCLIArgsOnly(cmd.Cmd, vars, cliArgs))
161163
}
162164

163165
if r.DryRun {

taskfile/vars.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,32 @@ func expandVars(s string, vars map[string]string, cliArgs string) string {
120120
return match
121121
})
122122
}
123+
124+
// expandCLIArgsOnly substitutes only CLI_ARGS in a command string, leaving
125+
// every other ${VAR} and {{.VAR}} reference untouched. It is used to render
126+
// the per-cmd log line: CLI_ARGS comes from the user's invocation and is safe
127+
// to surface, while other variables may carry secrets sourced from env or
128+
// dotenv files and are deliberately not expanded in logs.
129+
//
130+
// CLI_ARGS resolution mirrors expandVars: a value in `vars` (typically a
131+
// call-site `vars: { CLI_ARGS: -f }`) wins over the cliArgs fallback.
132+
func expandCLIArgsOnly(s string, vars map[string]string, cliArgs string) string {
133+
val := cliArgs
134+
if v, ok := vars["CLI_ARGS"]; ok {
135+
val = v
136+
}
137+
138+
s = os.Expand(s, func(key string) string {
139+
if key == "CLI_ARGS" {
140+
return val
141+
}
142+
return "${" + key + "}"
143+
})
144+
145+
return templatePattern.ReplaceAllStringFunc(s, func(match string) string {
146+
if templatePattern.FindStringSubmatch(match)[1] == "CLI_ARGS" {
147+
return val
148+
}
149+
return match
150+
})
151+
}

0 commit comments

Comments
 (0)