Skip to content

Commit af04688

Browse files
ostermanclaude
andauthored
fix: Make --chdir flag work with terraform/helmfile/packer commands (#1767)
* fix: Make --chdir flag work with terraform/helmfile/packer commands This commit fixes the --chdir flag to work correctly with commands that have DisableFlagParsing=true (terraform, helmfile, packer). The issue was that Cobra doesn't parse flags when DisableFlagParsing=true, but PersistentPreRun runs before the command's Run function where flags would be manually parsed. This meant --chdir was not processed before atmos.yaml was loaded. The fix adds parseChdirFromArgs() which manually parses --chdir or -C from os.Args, supporting all formats: --chdir=path, -C=path, -Cpath, --chdir path, -C path. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * test: Add comprehensive tests for parseChdirFromArgs function Add 14 test cases covering all argument parsing formats: - --chdir=path, --chdir path - -C=path, -C path, -Cpath - Relative paths, tilde expansion - Edge cases (missing values, multiple flags, empty args) This improves test coverage for the chdir flag parsing logic that is critical for terraform/helmfile/packer commands. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent 0a7cb9d commit af04688

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

cmd/root.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,52 @@ var profilerServer *profiler.Server
6262
// logFileHandle holds the opened log file for the lifetime of the program.
6363
var logFileHandle *os.File
6464

65+
// parseChdirFromArgs manually parses --chdir or -C flag from os.Args.
66+
// This is needed for commands with DisableFlagParsing=true (terraform, helmfile, packer)
67+
// where Cobra doesn't parse flags before PersistentPreRun is called.
68+
func parseChdirFromArgs() string {
69+
args := os.Args
70+
for i := 0; i < len(args); i++ {
71+
arg := args[i]
72+
73+
// Check for --chdir=value format.
74+
if strings.HasPrefix(arg, "--chdir=") {
75+
return strings.TrimPrefix(arg, "--chdir=")
76+
}
77+
78+
// Check for -C=value format.
79+
if strings.HasPrefix(arg, "-C=") {
80+
return strings.TrimPrefix(arg, "-C=")
81+
}
82+
83+
// Check for -C<value> format (concatenated, e.g., -C../foo).
84+
if strings.HasPrefix(arg, "-C") && len(arg) > 2 {
85+
return arg[2:]
86+
}
87+
88+
// Check for --chdir value or -C value format (next arg is the value).
89+
if arg == "--chdir" || arg == "-C" {
90+
if i+1 < len(args) {
91+
return args[i+1]
92+
}
93+
}
94+
}
95+
return ""
96+
}
97+
6598
// processChdirFlag processes the --chdir flag and ATMOS_CHDIR environment variable,
6699
// changing the working directory before any other operations.
67100
// Precedence: --chdir flag > ATMOS_CHDIR environment variable.
68101
func processChdirFlag(cmd *cobra.Command) error {
102+
// Try to get chdir from parsed flags first (works when DisableFlagParsing=false).
69103
chdir, _ := cmd.Flags().GetString("chdir")
104+
105+
// If flag parsing is disabled (terraform/helmfile/packer commands), manually parse os.Args.
106+
// This is necessary because Cobra doesn't parse flags when DisableFlagParsing=true,
107+
// but PersistentPreRun runs before the command's Run function where flags would be manually parsed.
108+
if chdir == "" {
109+
chdir = parseChdirFromArgs()
110+
}
70111
// If flag is not set, check environment variable.
71112
// Note: chdir is not supported in atmos.yaml since it must be processed before atmos.yaml is loaded.
72113
if chdir == "" {

cmd/root_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,103 @@ func TestIsCompletionCommand(t *testing.T) {
516516
})
517517
}
518518
}
519+
520+
// TestParseChdirFromArgs tests the parseChdirFromArgs function that manually parses --chdir or -C flags.
521+
// This function is critical for commands with DisableFlagParsing=true (terraform, helmfile, packer).
522+
func TestParseChdirFromArgs(t *testing.T) {
523+
tests := []struct {
524+
name string
525+
args []string
526+
expected string
527+
}{
528+
{
529+
name: "--chdir with equals sign",
530+
args: []string{"atmos", "--chdir=/tmp/foo", "terraform", "plan"},
531+
expected: "/tmp/foo",
532+
},
533+
{
534+
name: "--chdir with space",
535+
args: []string{"atmos", "--chdir", "/tmp/bar", "terraform", "plan"},
536+
expected: "/tmp/bar",
537+
},
538+
{
539+
name: "-C with equals sign",
540+
args: []string{"atmos", "-C=/tmp/baz", "terraform", "plan"},
541+
expected: "/tmp/baz",
542+
},
543+
{
544+
name: "-C with space",
545+
args: []string{"atmos", "-C", "/tmp/qux", "terraform", "plan"},
546+
expected: "/tmp/qux",
547+
},
548+
{
549+
name: "-C concatenated (no space or equals)",
550+
args: []string{"atmos", "-C/tmp/concat", "terraform", "plan"},
551+
expected: "/tmp/concat",
552+
},
553+
{
554+
name: "-C concatenated with relative path",
555+
args: []string{"atmos", "-C../foo", "terraform", "plan"},
556+
expected: "../foo",
557+
},
558+
{
559+
name: "no chdir flag",
560+
args: []string{"atmos", "terraform", "plan"},
561+
expected: "",
562+
},
563+
{
564+
name: "--chdir at end without value",
565+
args: []string{"atmos", "terraform", "plan", "--chdir"},
566+
expected: "",
567+
},
568+
{
569+
name: "-C at end without value",
570+
args: []string{"atmos", "terraform", "plan", "-C"},
571+
expected: "",
572+
},
573+
{
574+
name: "multiple --chdir flags (first wins)",
575+
args: []string{"atmos", "--chdir=/first", "--chdir=/second", "terraform", "plan"},
576+
expected: "/first",
577+
},
578+
{
579+
name: "mixed -C and --chdir (first wins)",
580+
args: []string{"atmos", "-C/first", "--chdir=/second", "terraform", "plan"},
581+
expected: "/first",
582+
},
583+
{
584+
name: "--chdir with tilde",
585+
args: []string{"atmos", "--chdir=~/mydir", "terraform", "plan"},
586+
expected: "~/mydir",
587+
},
588+
{
589+
name: "empty args",
590+
args: []string{},
591+
expected: "",
592+
},
593+
{
594+
name: "single arg",
595+
args: []string{"atmos"},
596+
expected: "",
597+
},
598+
}
599+
600+
for _, tt := range tests {
601+
t.Run(tt.name, func(t *testing.T) {
602+
// Save and restore os.Args.
603+
originalArgs := os.Args
604+
defer func() { os.Args = originalArgs }()
605+
606+
// Set os.Args to the test args.
607+
os.Args = tt.args
608+
609+
// Call the function.
610+
result := parseChdirFromArgs()
611+
612+
// Verify.
613+
assert.Equal(t, tt.expected, result,
614+
"parseChdirFromArgs() with args %v should return %q, got %q",
615+
tt.args, tt.expected, result)
616+
})
617+
}
618+
}

0 commit comments

Comments
 (0)