Skip to content

Commit b72cbea

Browse files
committed
Merge remote-tracking branch 'origin/main' into osterman/auth-env-vars-research
2 parents 7823205 + 04b68c6 commit b72cbea

File tree

7 files changed

+675
-102
lines changed

7 files changed

+675
-102
lines changed

cmd/terraform/utils.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ func terraformRunWithOptions(parentCmd, actualCmd *cobra.Command, args []string,
224224
return err
225225
}
226226

227+
// Apply parsed options to info BEFORE prompting, so hasMultiComponentFlags() works correctly.
228+
// This fixes issue #1945: --all flag must be set before resolveAndPromptForArgs checks it.
229+
applyOptionsToInfo(&info, opts)
230+
227231
// Resolve paths and prompt for missing component/stack interactively.
228232
if err := resolveAndPromptForArgs(&info, actualCmd); err != nil {
229233
return err
@@ -235,8 +239,6 @@ func terraformRunWithOptions(parentCmd, actualCmd *cobra.Command, args []string,
235239
return nil
236240
}
237241

238-
applyOptionsToInfo(&info, opts)
239-
240242
// Handle --identity flag for interactive selection when used without a value.
241243
if info.Identity == cfg.IdentityFlagSelectValue {
242244
handleInteractiveIdentitySelection(&info)

cmd/terraform/utils_test.go

Lines changed: 138 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,144 @@ func TestCheckTerraformFlags_AllEdgeCases(t *testing.T) {
316316
}
317317
}
318318

319+
// TestHasMultiComponentFlags tests the hasMultiComponentFlags function.
320+
func TestHasMultiComponentFlags(t *testing.T) {
321+
tests := []struct {
322+
name string
323+
info *schema.ConfigAndStacksInfo
324+
expected bool
325+
}{
326+
{
327+
name: "no flags set",
328+
info: &schema.ConfigAndStacksInfo{},
329+
expected: false,
330+
},
331+
{
332+
name: "all flag set",
333+
info: &schema.ConfigAndStacksInfo{
334+
All: true,
335+
},
336+
expected: true,
337+
},
338+
{
339+
name: "affected flag set",
340+
info: &schema.ConfigAndStacksInfo{
341+
Affected: true,
342+
},
343+
expected: true,
344+
},
345+
{
346+
name: "query flag set",
347+
info: &schema.ConfigAndStacksInfo{
348+
Query: ".components.test",
349+
},
350+
expected: true,
351+
},
352+
{
353+
name: "components flag set",
354+
info: &schema.ConfigAndStacksInfo{
355+
Components: []string{"comp1", "comp2"},
356+
},
357+
expected: true,
358+
},
359+
{
360+
name: "empty components slice",
361+
info: &schema.ConfigAndStacksInfo{
362+
Components: []string{},
363+
},
364+
expected: false,
365+
},
366+
{
367+
name: "multiple multi-component flags set",
368+
info: &schema.ConfigAndStacksInfo{
369+
All: true,
370+
Query: ".components.test",
371+
},
372+
expected: true,
373+
},
374+
}
375+
376+
for _, tt := range tests {
377+
t.Run(tt.name, func(t *testing.T) {
378+
result := hasMultiComponentFlags(tt.info)
379+
assert.Equal(t, tt.expected, result)
380+
})
381+
}
382+
}
383+
384+
// TestHandleInteractiveComponentStackSelection_SkipsPromptForMultiComponentFlags tests that
385+
// interactive prompting is skipped when multi-component flags are set.
386+
// Regression test for: https://github.com/cloudposse/atmos/issues/1945
387+
// Before the fix, `--all` flag was not applied to info before this check, causing
388+
// the prompt to appear even when `--all` was specified.
389+
func TestHandleInteractiveComponentStackSelection_SkipsPromptForMultiComponentFlags(t *testing.T) {
390+
tests := []struct {
391+
name string
392+
info *schema.ConfigAndStacksInfo
393+
}{
394+
{
395+
name: "skips prompt when --all flag is set",
396+
info: &schema.ConfigAndStacksInfo{
397+
All: true,
398+
},
399+
},
400+
{
401+
name: "skips prompt when --affected flag is set",
402+
info: &schema.ConfigAndStacksInfo{
403+
Affected: true,
404+
},
405+
},
406+
{
407+
name: "skips prompt when --query flag is set",
408+
info: &schema.ConfigAndStacksInfo{
409+
Query: ".components.test",
410+
},
411+
},
412+
{
413+
name: "skips prompt when --components flag is set",
414+
info: &schema.ConfigAndStacksInfo{
415+
Components: []string{"comp1", "comp2"},
416+
},
417+
},
418+
}
419+
420+
for _, tt := range tests {
421+
t.Run(tt.name, func(t *testing.T) {
422+
cmd := &cobra.Command{Use: "plan"}
423+
424+
// Capture the initial state - ComponentFromArg should remain empty
425+
// because the function should skip prompting and return early.
426+
initialComponent := tt.info.ComponentFromArg
427+
initialStack := tt.info.Stack
428+
429+
err := handleInteractiveComponentStackSelection(tt.info, cmd)
430+
431+
// Should return nil (no error) and NOT modify the info struct.
432+
assert.NoError(t, err)
433+
assert.Equal(t, initialComponent, tt.info.ComponentFromArg,
434+
"ComponentFromArg should not be modified when multi-component flags are set")
435+
assert.Equal(t, initialStack, tt.info.Stack,
436+
"Stack should not be modified when multi-component flags are set")
437+
})
438+
}
439+
}
440+
441+
// TestHandleInteractiveComponentStackSelection_SkipsWhenBothProvided tests that
442+
// interactive prompting is skipped when both component and stack are already provided.
443+
func TestHandleInteractiveComponentStackSelection_SkipsWhenBothProvided(t *testing.T) {
444+
cmd := &cobra.Command{Use: "plan"}
445+
info := &schema.ConfigAndStacksInfo{
446+
ComponentFromArg: "my-component",
447+
Stack: "my-stack",
448+
}
449+
450+
err := handleInteractiveComponentStackSelection(info, cmd)
451+
452+
assert.NoError(t, err)
453+
assert.Equal(t, "my-component", info.ComponentFromArg)
454+
assert.Equal(t, "my-stack", info.Stack)
455+
}
456+
319457
// TestStackFlagCompletion_NoArgs tests the stackFlagCompletion function without args.
320458
func TestStackFlagCompletion_NoArgs(t *testing.T) {
321459
t.Chdir("../../examples/demo-stacks")
@@ -394,53 +532,6 @@ func TestIsMultiComponentExecution(t *testing.T) {
394532
}
395533
}
396534

397-
// TestHasMultiComponentFlags tests the hasMultiComponentFlags function.
398-
func TestHasMultiComponentFlags(t *testing.T) {
399-
tests := []struct {
400-
name string
401-
info *schema.ConfigAndStacksInfo
402-
expected bool
403-
}{
404-
{
405-
name: "all flag",
406-
info: &schema.ConfigAndStacksInfo{All: true},
407-
expected: true,
408-
},
409-
{
410-
name: "affected flag",
411-
info: &schema.ConfigAndStacksInfo{Affected: true},
412-
expected: true,
413-
},
414-
{
415-
name: "components set",
416-
info: &schema.ConfigAndStacksInfo{Components: []string{"comp1"}},
417-
expected: true,
418-
},
419-
{
420-
name: "query set",
421-
info: &schema.ConfigAndStacksInfo{Query: ".test"},
422-
expected: true,
423-
},
424-
{
425-
name: "no flags",
426-
info: &schema.ConfigAndStacksInfo{},
427-
expected: false,
428-
},
429-
{
430-
name: "empty components slice",
431-
info: &schema.ConfigAndStacksInfo{Components: []string{}},
432-
expected: false,
433-
},
434-
}
435-
436-
for _, tt := range tests {
437-
t.Run(tt.name, func(t *testing.T) {
438-
result := hasMultiComponentFlags(tt.info)
439-
assert.Equal(t, tt.expected, result)
440-
})
441-
}
442-
}
443-
444535
// TestHasNonAffectedMultiFlags tests the hasNonAffectedMultiFlags function.
445536
func TestHasNonAffectedMultiFlags(t *testing.T) {
446537
tests := []struct {

internal/exec/terraform_affected.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
log "github.com/cloudposse/atmos/pkg/logger"
66
"github.com/cloudposse/atmos/pkg/perf"
77
"github.com/cloudposse/atmos/pkg/schema"
8+
"github.com/cloudposse/atmos/pkg/ui"
89
u "github.com/cloudposse/atmos/pkg/utils"
910
)
1011

@@ -104,6 +105,7 @@ func executeAffectedComponents(affectedList []schema.Affected, info *schema.Conf
104105

105106
// Early return for empty list - nothing to process.
106107
if len(affectedList) == 0 {
108+
ui.Success("No components affected")
107109
return nil
108110
}
109111

internal/exec/terraform_query.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
log "github.com/cloudposse/atmos/pkg/logger"
66
"github.com/cloudposse/atmos/pkg/perf"
77
"github.com/cloudposse/atmos/pkg/schema"
8+
"github.com/cloudposse/atmos/pkg/ui"
89
)
910

1011
// ExecuteTerraformQuery executes `atmos terraform <command> --query <yq-expression --stack <stack>`.
@@ -16,12 +17,8 @@ func ExecuteTerraformQuery(info *schema.ConfigAndStacksInfo) error {
1617
return err
1718
}
1819

19-
var logFunc func(msg any, keyvals ...any)
20-
if info.DryRun {
21-
logFunc = log.Info
22-
} else {
23-
logFunc = log.Debug
24-
}
20+
// Always use debug level for internal logging.
21+
logFunc := log.Debug
2522

2623
stacks, err := ExecuteDescribeStacks(
2724
&atmosConfig,
@@ -40,12 +37,24 @@ func ExecuteTerraformQuery(info *schema.ConfigAndStacksInfo) error {
4037
return err
4138
}
4239

40+
// Track how many components were processed.
41+
processedCount := 0
42+
4343
err = walkTerraformComponents(stacks, func(stackName, componentName string, componentSection map[string]any) error {
44-
return processTerraformComponent(&atmosConfig, info, stackName, componentName, componentSection, logFunc)
44+
processed, err := processTerraformComponent(&atmosConfig, info, stackName, componentName, componentSection, logFunc)
45+
if processed {
46+
processedCount++
47+
}
48+
return err
4549
})
4650
if err != nil {
4751
return err
4852
}
4953

54+
// Show success message if no components matched the criteria.
55+
if processedCount == 0 {
56+
ui.Success("No components matched")
57+
}
58+
5059
return nil
5160
}

0 commit comments

Comments
 (0)