diff --git a/.golangci.yml b/.golangci.yml index e19c81770e..0565e820c4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -326,6 +326,11 @@ linters: - revive path: internal/exec/terraform_clean\.go$ text: "file-length-limit" + # schema.go is a core configuration struct file that's expected to be large. + - linters: + - revive + path: pkg/schema/schema\.go$ + text: "file-length-limit" paths: - experiments/.* - third_party$ diff --git a/cmd/cmd_utils.go b/cmd/cmd_utils.go index ecdf516a00..d8e9141ec2 100644 --- a/cmd/cmd_utils.go +++ b/cmd/cmd_utils.go @@ -16,7 +16,6 @@ import ( "github.com/go-git/go-git/v5" "github.com/samber/lo" "github.com/spf13/cobra" - "github.com/spf13/pflag" errUtils "github.com/cloudposse/atmos/errors" e "github.com/cloudposse/atmos/internal/exec" @@ -65,49 +64,6 @@ func WithStackValidation(check bool) AtmosValidateOption { } } -// getGlobalFlagNames returns a set of all global persistent flag names and shorthands. -// It also includes reserved names like "identity". -// It queries RootCmd at runtime so the set stays current without maintaining a static list. -func getGlobalFlagNames() map[string]bool { - return getReservedFlagNamesFor(RootCmd) -} - -// getReservedFlagNamesFor returns a set of all reserved flag names and shorthands. -// For a given parent command, this includes: -// 1. The parent command's own persistent flags. -// 2. Flags inherited from ancestor commands (via InheritedFlags). -// 3. The hardcoded "identity" flag that gets added to every custom command. -// -// This function is used to validate that child commands don't define flags that -// would conflict with their parent's flags at any level of nesting. -func getReservedFlagNamesFor(parent *cobra.Command) map[string]bool { - reserved := make(map[string]bool) - - // Query persistent flags from the parent command. - parent.PersistentFlags().VisitAll(func(f *pflag.Flag) { - reserved[f.Name] = true - if f.Shorthand != "" { - reserved[f.Shorthand] = true - } - }) - - // Query inherited flags from ancestor commands. - // InheritedFlags() returns flags that are inherited from parent commands - // but not defined on this command itself. - parent.InheritedFlags().VisitAll(func(f *pflag.Flag) { - reserved[f.Name] = true - if f.Shorthand != "" { - reserved[f.Shorthand] = true - } - }) - - // Also include the hardcoded "identity" flag that gets added to every custom command. - // This prevents user-defined flags from conflicting with it. - reserved["identity"] = true - - return reserved -} - // processCustomCommands registers custom commands defined in the Atmos configuration onto the given parent Cobra command. // // It reads the provided command definitions, reuses any existing top-level commands when appropriate, and adds new Cobra @@ -156,12 +112,9 @@ func processCustomCommands( customCommand.PersistentFlags().String("identity", "", "Identity to use for authentication (overrides identity in command config)") AddIdentityCompletion(customCommand) - // Get reserved flag names by querying the parent command's persistent and inherited flags. - // This ensures we detect conflicts with both global flags and parent custom command flags. - reservedFlags := getReservedFlagNamesFor(parentCommand) - - // Validate flags don't conflict with global/reserved flags or parent command flags, - // and detect duplicate flag names/shorthands within the same command config. + // Validate flags for duplicates and type conflicts. + // Custom commands can declare flags that already exist (globally or on parent), + // but only if the types match (inheritance). Type mismatches are errors. seen := make(map[string]bool) for _, flag := range commandConfig.Flags { // Detect duplicates within the same command config early. @@ -175,22 +128,45 @@ func processCustomCommands( } seen[flag.Name] = true - if reservedFlags[flag.Name] { - return errUtils.Build(errUtils.ErrReservedFlagName). - WithExplanation(fmt.Sprintf("Custom command '%s' defines flag '--%s' which conflicts with a reserved or parent command flag", commandConfig.Name, flag.Name)). - WithHint("Rename the flag in your atmos.yaml to avoid conflicts with reserved flag names"). - WithContext("command", commandConfig.Name). - WithContext("flag", flag.Name). - Err() + // Check if this flag already exists on parent or globally. + // If it exists, verify types match (inheritance allowed). + // If types don't match, error (can't redefine with different type). + // Only check PersistentFlags() and InheritedFlags() - NOT Flags(). + // Local flags (Flags()) on the parent are NOT inherited by subcommands, + // so they shouldn't block subcommands from defining the same flag. + existingFlag := parentCommand.PersistentFlags().Lookup(flag.Name) + if existingFlag == nil { + existingFlag = parentCommand.InheritedFlags().Lookup(flag.Name) } - if flag.Shorthand != "" && reservedFlags[flag.Shorthand] { - return errUtils.Build(errUtils.ErrReservedFlagName). - WithExplanation(fmt.Sprintf("Custom command '%s' defines flag shorthand '-%s' which conflicts with a reserved or parent command flag shorthand", commandConfig.Name, flag.Shorthand)). - WithHint("Change the shorthand in your atmos.yaml to avoid conflicts with reserved flag shorthands"). - WithContext("command", commandConfig.Name). - WithContext("shorthand", flag.Shorthand). - Err() + + if existingFlag != nil { + // Flag exists - check type compatibility. + customFlagType := flag.Type + if customFlagType == "" || customFlagType == "string" { + customFlagType = "string" + } + + existingFlagType := existingFlag.Value.Type() + // Normalize type names for comparison. + // pflag types: "string", "bool", "int", etc. + if existingFlagType != customFlagType { + return errUtils.Build(errUtils.ErrReservedFlagName). + WithExplanation(fmt.Sprintf("Custom command '%s' in atmos.yaml declares flag '--%s' with type '%s', but it already exists with type '%s'", + commandConfig.Name, flag.Name, customFlagType, existingFlagType)). + WithHint("Check the 'commands' section in atmos.yaml"). + WithHint("Either use the existing flag type, or rename your flag to avoid conflicts"). + WithContext("command", commandConfig.Name). + WithContext("flag", flag.Name). + WithContext("declared_type", customFlagType). + WithContext("existing_type", existingFlagType). + WithContext("config_path", fmt.Sprintf("commands.%s.flags", commandConfig.Name)). + Err() + } + // Types match - this flag will be inherited, skip further validation. + continue } + + // Flag doesn't exist yet - validate shorthand for new flags only. if flag.Shorthand != "" { if seen[flag.Shorthand] { return errUtils.Build(errUtils.ErrDuplicateFlagRegistration). @@ -201,11 +177,50 @@ func processCustomCommands( Err() } seen[flag.Shorthand] = true + + // Check if shorthand conflicts with existing persistent/inherited flags. + // Only check PersistentFlags() and InheritedFlags() - NOT Flags(). + // Local flags (Flags()) on the parent are NOT inherited by subcommands. + existingByShorthand := parentCommand.PersistentFlags().ShorthandLookup(flag.Shorthand) + if existingByShorthand == nil { + existingByShorthand = parentCommand.InheritedFlags().ShorthandLookup(flag.Shorthand) + } + if existingByShorthand != nil { + return errUtils.Build(errUtils.ErrReservedFlagName). + WithExplanation(fmt.Sprintf("Custom command '%s' in atmos.yaml defines flag shorthand '-%s' which conflicts with existing flag '--%s'", + commandConfig.Name, flag.Shorthand, existingByShorthand.Name)). + WithHint("Check the 'commands' section in atmos.yaml"). + WithHint("Change the shorthand to avoid conflicts"). + WithContext("command", commandConfig.Name). + WithContext("shorthand", flag.Shorthand). + WithContext("existing_flag", existingByShorthand.Name). + WithContext("config_path", fmt.Sprintf("commands.%s.flags", commandConfig.Name)). + Err() + } } } // Process and add flags to the command. + // Skip flags that are inherited from parent command chain. for _, flag := range commandConfig.Flags { + // Skip flags that already exist as persistent/inherited (not local). + // Local flags (Flags()) on the parent are NOT inherited by subcommands, + // so we should register the flag even if a local flag with the same name exists. + existingFlag := parentCommand.PersistentFlags().Lookup(flag.Name) + if existingFlag == nil { + existingFlag = parentCommand.InheritedFlags().Lookup(flag.Name) + } + if existingFlag != nil { + // Flag exists and type was validated above - skip registration (inherit). + continue + } + + // Get flag description, preferring Description over Usage for backward compatibility. + flagUsage := flag.Description + if flagUsage == "" { + flagUsage = flag.Usage + } + if flag.Type == "bool" { defaultVal := false if flag.Default != nil { @@ -214,9 +229,9 @@ func processCustomCommands( } } if flag.Shorthand != "" { - customCommand.PersistentFlags().BoolP(flag.Name, flag.Shorthand, defaultVal, flag.Usage) + customCommand.PersistentFlags().BoolP(flag.Name, flag.Shorthand, defaultVal, flagUsage) } else { - customCommand.PersistentFlags().Bool(flag.Name, defaultVal, flag.Usage) + customCommand.PersistentFlags().Bool(flag.Name, defaultVal, flagUsage) } } else { defaultVal := "" @@ -226,9 +241,9 @@ func processCustomCommands( } } if flag.Shorthand != "" { - customCommand.PersistentFlags().StringP(flag.Name, flag.Shorthand, defaultVal, flag.Usage) + customCommand.PersistentFlags().StringP(flag.Name, flag.Shorthand, defaultVal, flagUsage) } else { - customCommand.PersistentFlags().String(flag.Name, defaultVal, flag.Usage) + customCommand.PersistentFlags().String(flag.Name, defaultVal, flagUsage) } } diff --git a/cmd/custom_command_flag_conflict_test.go b/cmd/custom_command_flag_conflict_test.go index df14bf99ad..d0844fd165 100644 --- a/cmd/custom_command_flag_conflict_test.go +++ b/cmd/custom_command_flag_conflict_test.go @@ -29,15 +29,16 @@ func TestCustomCommand_FlagNameConflictWithGlobalFlag(t *testing.T) { atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) require.NoError(t, err) - // Create a custom command with a flag that conflicts with the global --mask flag. + // Create a custom command with a flag that has the same name as global --mask + // but DIFFERENT type (string instead of bool). This should error. testCommand := schema.Command{ Name: "test-flag-conflict", - Description: "Test command with conflicting flag name", + Description: "Test command with type-conflicting flag name", Flags: []schema.CommandFlag{ { Name: "mask", - Type: "bool", - Usage: "This conflicts with global --mask flag", + Type: "string", // Global mask is bool - type mismatch! + Usage: "This conflicts with global --mask flag (different type)", }, }, Steps: stepsFromStrings("echo test"), @@ -46,17 +47,18 @@ func TestCustomCommand_FlagNameConflictWithGlobalFlag(t *testing.T) { // Add the test command to the config. atmosConfig.Commands = []schema.Command{testCommand} - // Process custom commands - should return error, not panic. + // Process custom commands - should return error for type mismatch. err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) // Verify the error is returned correctly. - require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for conflicting flag name") + require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for type mismatch") // Get the explanation from the error details. details := cockerrors.GetAllDetails(err) detailsStr := strings.Join(details, " ") assert.Contains(t, detailsStr, "mask", "Error details should mention the conflicting flag name") assert.Contains(t, detailsStr, "test-flag-conflict", "Error details should mention the command name") + assert.Contains(t, detailsStr, "type", "Error details should mention type mismatch") } // TestCustomCommand_FlagShorthandConflictWithGlobalFlag tests that custom commands with flag @@ -105,8 +107,8 @@ func TestCustomCommand_FlagShorthandConflictWithGlobalFlag(t *testing.T) { assert.Contains(t, detailsStr, "test-shorthand-conflict", "Error details should mention the command name") } -// TestCustomCommand_IdentityFlagConflict tests that custom commands cannot define -// an --identity flag since it's reserved for runtime identity override. +// TestCustomCommand_IdentityFlagConflict tests that custom commands can declare +// they need --identity flag (with matching type), but cannot redefine it with different type. func TestCustomCommand_IdentityFlagConflict(t *testing.T) { // Set up test fixture. testDir := "../tests/fixtures/scenarios/complete" @@ -120,15 +122,16 @@ func TestCustomCommand_IdentityFlagConflict(t *testing.T) { atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) require.NoError(t, err) - // Create a custom command that tries to define its own --identity flag. + // Create a custom command that tries to redefine identity flag with DIFFERENT type. + // The identity flag added to custom commands is always string type. testCommand := schema.Command{ Name: "test-identity-conflict", - Description: "Test command trying to redefine identity flag", + Description: "Test command trying to redefine identity flag with wrong type", Flags: []schema.CommandFlag{ { Name: "identity", - Type: "string", - Usage: "This conflicts with reserved identity flag", + Type: "bool", // Wrong type! Identity is string. + Usage: "This conflicts with reserved identity flag (type mismatch)", }, }, Steps: stepsFromStrings("echo test"), @@ -137,16 +140,17 @@ func TestCustomCommand_IdentityFlagConflict(t *testing.T) { // Add the test command to the config. atmosConfig.Commands = []schema.Command{testCommand} - // Process custom commands - should return error, not panic. + // Process custom commands - should return error for type mismatch. err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) // Verify the error is returned correctly. - require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for conflicting identity flag") + require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for type mismatch on identity flag") // Get the explanation from the error details. details := cockerrors.GetAllDetails(err) detailsStr := strings.Join(details, " ") assert.Contains(t, detailsStr, "identity", "Error details should mention the identity flag") + assert.Contains(t, detailsStr, "type", "Error details should mention type mismatch") } // TestCustomCommand_ValidFlagsNoConflict tests that custom commands with valid, @@ -212,31 +216,14 @@ func TestCustomCommand_ValidFlagsNoConflict(t *testing.T) { } // TestGetGlobalFlagNames tests that getGlobalFlagNames returns the expected reserved flags. -func TestGetGlobalFlagNames(t *testing.T) { - // Create a test kit to ensure clean RootCmd state with global flags registered. - _ = NewTestKit(t) - - // Get the reserved flag names. - reserved := getGlobalFlagNames() - - // Log all reserved flags for debugging. - t.Logf("Reserved flags: %v", reserved) - - // Verify that known global flags are in the reserved set. - assert.True(t, reserved["chdir"], "chdir should be reserved") - assert.True(t, reserved["C"], "C (chdir shorthand) should be reserved") - assert.True(t, reserved["mask"], "mask should be reserved") - assert.True(t, reserved["identity"], "identity should be reserved") - assert.True(t, reserved["no-color"], "no-color should be reserved") - - // Verify that random names are not reserved. - assert.False(t, reserved["my-custom-flag"], "my-custom-flag should NOT be reserved") - assert.False(t, reserved["xyz"], "xyz should NOT be reserved") -} +// TestGetGlobalFlagNames was removed. +// The implementation no longer uses a pre-computed set of reserved flags. +// Instead, flags are validated dynamically by checking if they already exist +// on parent commands, allowing inheritance when types match. // TestCustomCommand_NestedFlagConflictWithParent tests that nested custom commands -// cannot define flags that conflict with their parent custom command's persistent flags. -// This addresses the CodeRabbit review comment about validating flags at each recursion level. +// cannot redefine flags with different types than their parent's flags. +// Child can declare same flag with same type (inheritance), but different type is an error. func TestCustomCommand_NestedFlagConflictWithParent(t *testing.T) { // Set up test fixture. testDir := "../tests/fixtures/scenarios/complete" @@ -262,16 +249,16 @@ func TestCustomCommand_NestedFlagConflictWithParent(t *testing.T) { Usage: "A flag defined on the parent command", }, }, - // Define a nested child command that tries to redefine the same flag. + // Define a nested child command that tries to redefine the same flag with DIFFERENT type. Commands: []schema.Command{ { Name: "child", - Description: "Child command trying to redefine parent's flag", + Description: "Child command trying to redefine parent's flag with different type", Flags: []schema.CommandFlag{ { - Name: "parent-flag", // Conflicts with parent's --parent-flag. - Type: "bool", - Usage: "This conflicts with parent's flag", + Name: "parent-flag", // Same name as parent's flag. + Type: "bool", // But different type (parent is string)! + Usage: "This conflicts with parent's flag (type mismatch)", }, }, Steps: stepsFromStrings("echo child"), @@ -283,17 +270,18 @@ func TestCustomCommand_NestedFlagConflictWithParent(t *testing.T) { // Add the test command to the config. atmosConfig.Commands = []schema.Command{parentCommand} - // Process custom commands - should return error for the nested conflict. + // Process custom commands - should return error for type mismatch. err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) // Verify the error is returned correctly. - require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for nested flag conflict with parent") + require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for type mismatch") // Get the explanation from the error details. details := cockerrors.GetAllDetails(err) detailsStr := strings.Join(details, " ") assert.Contains(t, detailsStr, "parent-flag", "Error details should mention the conflicting flag name") assert.Contains(t, detailsStr, "child", "Error details should mention the child command name") + assert.Contains(t, detailsStr, "type", "Error details should mention type mismatch") } // TestCustomCommand_NestedShorthandConflictWithParent tests that nested custom commands @@ -444,47 +432,14 @@ func TestCustomCommand_NestedValidFlags(t *testing.T) { require.NotNil(t, inheritedFlag, "parent-flag should be inherited by child") } -// TestGetReservedFlagNamesFor tests that getReservedFlagNamesFor correctly returns -// flags from both the command itself and its ancestors. -func TestGetReservedFlagNamesFor(t *testing.T) { - // Create a test kit to ensure clean RootCmd state. - _ = NewTestKit(t) - - // Create a parent command with some persistent flags. - parentCmd := &cobra.Command{ - Use: "parent", - Short: "Parent command", - } - parentCmd.PersistentFlags().String("parent-option", "", "Parent's option") - parentCmd.PersistentFlags().StringP("another-option", "a", "", "Another option") - - // Add parent to RootCmd so it inherits global flags. - RootCmd.AddCommand(parentCmd) - defer RootCmd.RemoveCommand(parentCmd) - - // Get reserved flags for the parent command. - reserved := getReservedFlagNamesFor(parentCmd) - - // Should include parent's own persistent flags. - assert.True(t, reserved["parent-option"], "parent-option should be reserved") - assert.True(t, reserved["another-option"], "another-option should be reserved") - assert.True(t, reserved["a"], "shorthand 'a' should be reserved") - - // Should include inherited global flags from RootCmd. - assert.True(t, reserved["chdir"], "chdir (inherited) should be reserved") - assert.True(t, reserved["C"], "C (inherited shorthand) should be reserved") - assert.True(t, reserved["mask"], "mask (inherited) should be reserved") - - // Should include the hardcoded identity flag. - assert.True(t, reserved["identity"], "identity should be reserved") - - // Should NOT include random names. - assert.False(t, reserved["random-flag"], "random-flag should NOT be reserved") -} +// TestGetReservedFlagNamesFor was removed. +// The implementation no longer uses a pre-computed set of reserved flags. +// Instead, flags are validated dynamically by checking if they already exist +// on parent commands, allowing inheritance when types match. // TestCustomCommand_ExistingCommandReuseWithNestedConflict tests that when a custom command -// reuses an existing built-in command (like terraform), nested subcommands still can't -// define flags that conflict with the built-in command's flags. +// reuses an existing built-in command (like terraform), nested subcommands can declare they +// need the same flags (inheritance), but cannot redefine them with different types. func TestCustomCommand_ExistingCommandReuseWithNestedConflict(t *testing.T) { // Set up test fixture. testDir := "../tests/fixtures/scenarios/complete" @@ -507,7 +462,7 @@ func TestCustomCommand_ExistingCommandReuseWithNestedConflict(t *testing.T) { require.NoError(t, err) // Create a custom command that reuses "terraform" (built-in command name) - // with a nested subcommand that tries to define --stack (which terraform has). + // with a nested subcommand that tries to redefine --stack with DIFFERENT type. terraformCommand := schema.Command{ Name: "terraform", // This will reuse the existing terraform command. Description: "Custom terraform subcommands", @@ -517,10 +472,10 @@ func TestCustomCommand_ExistingCommandReuseWithNestedConflict(t *testing.T) { Description: "Custom provision subcommand", Flags: []schema.CommandFlag{ { - Name: "stack", // Conflicts with terraform's --stack flag. - Shorthand: "s", // Also conflicts with terraform's -s shorthand. - Type: "string", - Usage: "This conflicts with terraform's stack flag", + Name: "stack", // Same name as terraform's --stack flag. + Shorthand: "s", // Same shorthand. + Type: "bool", // But different type! Terraform's stack is string. + Usage: "This conflicts with terraform's stack flag (type mismatch)", }, }, Steps: stepsFromStrings("echo provision"), @@ -953,7 +908,7 @@ func TestCustomCommand_MultipleCommandsWithMixedValidity(t *testing.T) { atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) require.NoError(t, err) - // Create multiple commands - first valid, second has conflict. + // Create multiple commands - first valid, second has type conflict. commands := []schema.Command{ { Name: "valid-cmd-1", @@ -965,9 +920,9 @@ func TestCustomCommand_MultipleCommandsWithMixedValidity(t *testing.T) { }, { Name: "invalid-cmd", - Description: "Command with conflict", + Description: "Command with type mismatch", Flags: []schema.CommandFlag{ - {Name: "mask", Type: "bool", Usage: "Conflicts with global mask"}, // Conflict! + {Name: "mask", Type: "string", Usage: "Type mismatch - global mask is bool"}, // Type conflict! }, Steps: stepsFromStrings("echo invalid"), }, @@ -1095,16 +1050,16 @@ func TestCustomCommand_VerboseFlagConflict(t *testing.T) { atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) require.NoError(t, err) - // Create a custom command with verbose flag (conflicts with global -v). + // Create a custom command with verbose flag but DIFFERENT type (type mismatch). testCommand := schema.Command{ Name: "test-verbose-conflict", - Description: "Test command with verbose flag conflict", + Description: "Test command with verbose flag type mismatch", Flags: []schema.CommandFlag{ { Name: "verbose", Shorthand: "v", - Type: "bool", - Usage: "This conflicts with global verbose flag", + Type: "string", // Global verbose is bool - type mismatch! + Usage: "This conflicts with global verbose flag (different type)", }, }, Steps: stepsFromStrings("echo test"), @@ -1113,11 +1068,11 @@ func TestCustomCommand_VerboseFlagConflict(t *testing.T) { // Add the test command to the config. atmosConfig.Commands = []schema.Command{testCommand} - // Process custom commands - should return error. + // Process custom commands - should return error for type mismatch. err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) // Verify the error is returned correctly. - require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for verbose flag conflict") + require.ErrorIs(t, err, errUtils.ErrReservedFlagName, "Should return ErrReservedFlagName for type mismatch") } // customCommandTestHelper provides shared setup for custom command tests. @@ -1245,3 +1200,309 @@ func TestCustomCommand_ShorthandMatchesFlagName(t *testing.T) { require.ErrorIs(t, err, errUtils.ErrDuplicateFlagRegistration, "Should return ErrDuplicateFlagRegistration when shorthand matches another flag's name") } + +// TestCustomCommand_VersionFlagAllowed tests that custom commands can define their own +// --version flag since --version is now a local flag on RootCmd (not persistent). +// This is important for tools like `atmos toolchain install --version 1.0.0`. +func TestCustomCommand_VersionFlagAllowed(t *testing.T) { + // Set up test fixture. + testDir := "../tests/fixtures/scenarios/complete" + t.Setenv("ATMOS_CLI_CONFIG_PATH", testDir) + t.Setenv("ATMOS_BASE_PATH", testDir) + + // Create a test kit to ensure clean RootCmd state. + _ = NewTestKit(t) + + // Load atmos configuration. + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + require.NoError(t, err) + + // Create a custom command with --version flag (string type). + // This should work because --version is not a persistent global flag anymore. + // Note: We don't use -v shorthand because that conflicts with global --verbose flag. + testCommand := schema.Command{ + Name: "install", + Description: "Install a tool with specific version", + Flags: []schema.CommandFlag{ + { + Name: "version", + Shorthand: "V", // Use uppercase V to avoid conflict with --verbose (-v) + Type: "string", + Usage: "Version to install", + Description: "Specify the version of the tool to install", + }, + }, + Steps: stepsFromStrings("echo Installing version"), + } + + // Add the test command to the config. + atmosConfig.Commands = []schema.Command{testCommand} + + // Process custom commands - should succeed since --version is not inherited. + err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) + require.NoError(t, err, "Custom command should be able to define --version flag since it's not a persistent global flag") + + // Find and verify the custom command. + var customCmd *cobra.Command + for _, cmd := range RootCmd.Commands() { + if cmd.Name() == "install" { + customCmd = cmd + break + } + } + require.NotNil(t, customCmd, "Custom 'install' command should be registered") + + // Verify --version flag is registered on the custom command. + versionFlag := customCmd.PersistentFlags().Lookup("version") + require.NotNil(t, versionFlag, "--version flag should be registered on custom command") + assert.Equal(t, "V", versionFlag.Shorthand, "Shorthand should be 'V'") + assert.Equal(t, "string", versionFlag.Value.Type(), "Flag type should be string") +} + +// TestCustomCommand_FlagInheritanceWithMatchingType tests that custom commands can declare +// flags that already exist on parent commands when the types match. +// The flag should be inherited (not re-registered) and accessible to the command. +func TestCustomCommand_FlagInheritanceWithMatchingType(t *testing.T) { + // Set up test fixture. + testDir := "../tests/fixtures/scenarios/complete" + t.Setenv("ATMOS_CLI_CONFIG_PATH", testDir) + t.Setenv("ATMOS_BASE_PATH", testDir) + + // Create a test kit to ensure clean RootCmd state. + _ = NewTestKit(t) + + // Load atmos configuration. + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + require.NoError(t, err) + + // Create a custom command that declares --mask flag with SAME type as global (bool). + // This should succeed - the flag is inherited, not re-registered. + testCommand := schema.Command{ + Name: "test-inherit-mask", + Description: "Test command inheriting global --mask flag", + Flags: []schema.CommandFlag{ + { + Name: "mask", + Type: "bool", // Same type as global --mask (bool). + Usage: "This should inherit the global --mask flag", + }, + }, + Steps: stepsFromStrings("echo test"), + } + + // Add the test command to the config. + atmosConfig.Commands = []schema.Command{testCommand} + + // Process custom commands - should succeed since types match. + err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) + require.NoError(t, err, "Custom command should be able to declare flag with matching type (inheritance)") + + // Find and verify the custom command. + var customCmd *cobra.Command + for _, cmd := range RootCmd.Commands() { + if cmd.Name() == "test-inherit-mask" { + customCmd = cmd + break + } + } + require.NotNil(t, customCmd, "Custom command should be registered") + + // Verify --mask flag is accessible via inherited flags (not local). + inheritedMask := customCmd.InheritedFlags().Lookup("mask") + require.NotNil(t, inheritedMask, "--mask flag should be inherited from global") + assert.Equal(t, "bool", inheritedMask.Value.Type(), "Inherited flag type should be bool") + + // Verify the flag was NOT re-registered as a local persistent flag. + localMask := customCmd.PersistentFlags().Lookup("mask") + assert.Nil(t, localMask, "--mask should NOT be re-registered as local persistent flag") +} + +// TestCustomCommand_FlagInheritanceWithMatchingTypeString tests that custom commands can +// inherit string-type flags that match parent commands. +func TestCustomCommand_FlagInheritanceWithMatchingTypeString(t *testing.T) { + // Set up test fixture. + testDir := "../tests/fixtures/scenarios/complete" + t.Setenv("ATMOS_CLI_CONFIG_PATH", testDir) + t.Setenv("ATMOS_BASE_PATH", testDir) + + // Create a test kit to ensure clean RootCmd state. + _ = NewTestKit(t) + + // Load atmos configuration. + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + require.NoError(t, err) + + // Create a custom command that declares --chdir flag with SAME type as global (string). + testCommand := schema.Command{ + Name: "test-inherit-chdir", + Description: "Test command inheriting global --chdir flag", + Flags: []schema.CommandFlag{ + { + Name: "chdir", + Type: "string", // Same type as global --chdir (string). + Usage: "This should inherit the global --chdir flag", + }, + }, + Steps: stepsFromStrings("echo test"), + } + + // Add the test command to the config. + atmosConfig.Commands = []schema.Command{testCommand} + + // Process custom commands - should succeed since types match. + err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) + require.NoError(t, err, "Custom command should be able to declare string flag with matching type") + + // Find and verify the custom command. + var customCmd *cobra.Command + for _, cmd := range RootCmd.Commands() { + if cmd.Name() == "test-inherit-chdir" { + customCmd = cmd + break + } + } + require.NotNil(t, customCmd, "Custom command should be registered") + + // Verify --chdir flag is accessible via inherited flags. + inheritedChdir := customCmd.InheritedFlags().Lookup("chdir") + require.NotNil(t, inheritedChdir, "--chdir flag should be inherited from global") + assert.Equal(t, "string", inheritedChdir.Value.Type(), "Inherited flag type should be string") +} + +// TestCustomCommand_FlagInheritanceVerboseBool tests that custom commands can declare +// they need --verbose flag with matching bool type. +func TestCustomCommand_FlagInheritanceVerboseBool(t *testing.T) { + // Set up test fixture. + testDir := "../tests/fixtures/scenarios/complete" + t.Setenv("ATMOS_CLI_CONFIG_PATH", testDir) + t.Setenv("ATMOS_BASE_PATH", testDir) + + // Create a test kit to ensure clean RootCmd state. + _ = NewTestKit(t) + + // Load atmos configuration. + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + require.NoError(t, err) + + // Create a custom command that declares --verbose flag with SAME type as global (bool). + testCommand := schema.Command{ + Name: "test-inherit-verbose", + Description: "Test command inheriting global --verbose flag", + Flags: []schema.CommandFlag{ + { + Name: "verbose", + Shorthand: "v", // Same shorthand as global. + Type: "bool", + Usage: "This should inherit the global --verbose flag", + }, + }, + Steps: stepsFromStrings("echo test"), + } + + // Add the test command to the config. + atmosConfig.Commands = []schema.Command{testCommand} + + // Process custom commands - should succeed since types match. + err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) + require.NoError(t, err, "Custom command should be able to declare --verbose with matching type") + + // Find and verify the custom command. + var customCmd *cobra.Command + for _, cmd := range RootCmd.Commands() { + if cmd.Name() == "test-inherit-verbose" { + customCmd = cmd + break + } + } + require.NotNil(t, customCmd, "Custom command should be registered") + + // Verify --verbose flag is accessible via inherited flags. + inheritedVerbose := customCmd.InheritedFlags().Lookup("verbose") + require.NotNil(t, inheritedVerbose, "--verbose flag should be inherited from global") + assert.Equal(t, "bool", inheritedVerbose.Value.Type(), "Inherited flag type should be bool") +} + +// TestCustomCommand_NestedFlagInheritanceWithMatchingType tests that nested custom commands +// can inherit flags from their parent custom command when types match. +func TestCustomCommand_NestedFlagInheritanceWithMatchingType(t *testing.T) { + // Set up test fixture. + testDir := "../tests/fixtures/scenarios/complete" + t.Setenv("ATMOS_CLI_CONFIG_PATH", testDir) + t.Setenv("ATMOS_BASE_PATH", testDir) + + // Create a test kit to ensure clean RootCmd state. + _ = NewTestKit(t) + + // Load atmos configuration. + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + require.NoError(t, err) + + // Create a parent command with a custom flag. + parentCommand := schema.Command{ + Name: "parent-with-flag", + Description: "Parent command with custom flag", + Flags: []schema.CommandFlag{ + { + Name: "parent-option", + Shorthand: "p", + Type: "string", + Usage: "A flag defined on the parent command", + }, + }, + // Define a nested child command that declares the SAME flag with SAME type. + Commands: []schema.Command{ + { + Name: "child-inherits", + Description: "Child command that inherits parent's flag", + Flags: []schema.CommandFlag{ + { + Name: "parent-option", // Same name as parent's flag. + Shorthand: "p", // Same shorthand. + Type: "string", // Same type - should inherit! + Usage: "This should inherit from parent", + }, + }, + Steps: stepsFromStrings("echo child"), + }, + }, + Steps: stepsFromStrings("echo parent"), + } + + // Add the test command to the config. + atmosConfig.Commands = []schema.Command{parentCommand} + + // Process custom commands - should succeed since types match. + err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) + require.NoError(t, err, "Nested command should be able to inherit parent's flag with matching type") + + // Find parent command. + var parentCmd *cobra.Command + for _, cmd := range RootCmd.Commands() { + if cmd.Name() == "parent-with-flag" { + parentCmd = cmd + break + } + } + require.NotNil(t, parentCmd, "Parent command should be registered") + + // Find child command. + var childCmd *cobra.Command + for _, cmd := range parentCmd.Commands() { + if cmd.Name() == "child-inherits" { + childCmd = cmd + break + } + } + require.NotNil(t, childCmd, "Child command should be registered") + + // Verify parent has the flag as persistent. + parentFlag := parentCmd.PersistentFlags().Lookup("parent-option") + require.NotNil(t, parentFlag, "Parent should have --parent-option as persistent flag") + + // Verify child inherits the flag (not re-registered locally). + inheritedFlag := childCmd.InheritedFlags().Lookup("parent-option") + require.NotNil(t, inheritedFlag, "Child should inherit --parent-option from parent") + + localFlag := childCmd.PersistentFlags().Lookup("parent-option") + assert.Nil(t, localFlag, "Child should NOT have --parent-option as local persistent flag") +} diff --git a/cmd/list/components.go b/cmd/list/components.go index 6174706e8a..7e3ae2b75c 100644 --- a/cmd/list/components.go +++ b/cmd/list/components.go @@ -195,8 +195,10 @@ func initAndExtractComponents(cmd *cobra.Command, args []string, opts *Component return nil, nil, fmt.Errorf("%w: %w", errUtils.ErrExecuteDescribeStacks, err) } - // Extract components into structured data. - components, err := extract.Components(stacksMap) + // Extract unique components (deduplicated across all stacks). + // This is the original "list components" behavior - showing unique component definitions. + // Pass the stack pattern to filter which stacks to consider when deduplicating. + components, err := extract.UniqueComponents(stacksMap, opts.Stack) if err != nil { return nil, nil, err } @@ -234,20 +236,15 @@ func renderComponents(atmosConfig *schema.AtmosConfiguration, opts *ComponentsOp } // buildComponentFilters creates filters based on command options. +// Note: --stack filter is not applicable to unique components (use "list instances" for per-stack filtering). func buildComponentFilters(opts *ComponentsOptions) []filter.Filter { defer perf.Track(nil, "list.components.buildComponentFilters")() var filters []filter.Filter - // Stack filter (glob pattern). - if opts.Stack != "" { - globFilter, err := filter.NewGlobFilter("stack", opts.Stack) - if err != nil { - _ = ui.Warning(fmt.Sprintf("Invalid glob pattern '%s': %v, filter will be ignored", opts.Stack, err)) - } else { - filters = append(filters, globFilter) - } - } + // Note: Stack filter is intentionally not applied here. + // "list components" shows unique component definitions, not per-stack instances. + // Use "atmos list instances --stack=xxx" for per-stack filtering. // Type filter (authoritative when provided, targets component_type field). if opts.Type != "" && opts.Type != "all" { @@ -270,7 +267,7 @@ func buildComponentFilters(opts *ComponentsOptions) []filter.Filter { return filters } -// getComponentColumns returns column configuration. +// getComponentColumns returns column configuration for unique components listing. func getComponentColumns(atmosConfig *schema.AtmosConfiguration, columnsFlag []string) []column.Config { defer perf.Track(nil, "list.components.getComponentColumns")() @@ -279,10 +276,10 @@ func getComponentColumns(atmosConfig *schema.AtmosConfiguration, columnsFlag []s return parseColumnsFlag(columnsFlag) } - // Check atmos.yaml for components.list.columns configuration. - if len(atmosConfig.Components.List.Columns) > 0 { + // Check new config path: list.components.columns. + if len(atmosConfig.List.Components.Columns) > 0 { var configs []column.Config - for _, col := range atmosConfig.Components.List.Columns { + for _, col := range atmosConfig.List.Components.Columns { configs = append(configs, column.Config{ Name: col.Name, Value: col.Value, @@ -292,14 +289,14 @@ func getComponentColumns(atmosConfig *schema.AtmosConfiguration, columnsFlag []s return configs } - // Default columns: show all standard component fields. + // Default columns: show unique component information. + // Note: Stack is not included because this lists unique components, not instances. + // Note: We intentionally do NOT fall back to components.list.columns here because + // that configuration is designed for instances (per-stack data), not unique components. return []column.Config{ {Name: "Component", Value: "{{ .component }}"}, - {Name: "Stack", Value: "{{ .stack }}"}, {Name: "Type", Value: "{{ .type }}"}, - {Name: "Component Type", Value: "{{ .component_type }}"}, - {Name: "Enabled", Value: "{{ .enabled }}"}, - {Name: "Locked", Value: "{{ .locked }}"}, + {Name: "Stacks", Value: "{{ .stack_count }}"}, } } @@ -308,7 +305,7 @@ func buildComponentSorters(sortSpec string) ([]*listSort.Sorter, error) { defer perf.Track(nil, "list.components.buildComponentSorters")() if sortSpec == "" { - // Default sort: by component ascending. + // Default sort: by component name ascending for deterministic output. return []*listSort.Sorter{ listSort.NewSorter("Component", listSort.Ascending), }, nil diff --git a/cmd/list/components_test.go b/cmd/list/components_test.go index 67f111ef31..b50a016d29 100644 --- a/cmd/list/components_test.go +++ b/cmd/list/components_test.go @@ -264,7 +264,7 @@ func TestBuildComponentFilters(t *testing.T) { opts: &ComponentsOptions{ Stack: "prod-*", }, - expectedCount: 2, // Stack filter + abstract filter + expectedCount: 1, // abstract filter only (stack filter not applicable to unique components) description: "Stack glob filter + abstract filter", }, { @@ -315,7 +315,7 @@ func TestBuildComponentFilters(t *testing.T) { Enabled: &enabledTrue, Locked: &lockedTrue, }, - expectedCount: 4, // Stack + Type + Enabled + Locked (no abstract filter when Type is set) + expectedCount: 3, // Type + Enabled + Locked (stack filter not applicable to unique components) description: "All filters combined", }, { @@ -353,7 +353,7 @@ func TestGetComponentColumns(t *testing.T) { }, }, columnsFlag: []string{}, - expectLen: 6, // All standard fields: Component, Stack, Type, Component Type, Enabled, Locked + expectLen: 3, // Unique component fields: Component, Type, Stacks expectName: "Component", }, { @@ -370,8 +370,8 @@ func TestGetComponentColumns(t *testing.T) { { name: "Columns from config", atmosConfig: &schema.AtmosConfiguration{ - Components: schema.Components{ - List: schema.ListConfig{ + List: schema.TopLevelListConfig{ + Components: schema.ListConfig{ Columns: []schema.ListColumnConfig{ {Name: "Component", Value: "{{ .component }}"}, {Name: "Stack", Value: "{{ .stack }}"}, @@ -410,7 +410,7 @@ func TestBuildComponentSorters(t *testing.T) { { name: "Empty sort (default)", sortSpec: "", - expectLen: 1, // Default sort by component ascending + expectLen: 1, // Default sort by component ascending only (unique components) }, { name: "Single sort field ascending", @@ -873,8 +873,8 @@ func TestRenderComponents(t *testing.T) { { name: "Components with custom columns from config", atmosConfig: &schema.AtmosConfiguration{ - Components: schema.Components{ - List: schema.ListConfig{ + List: schema.TopLevelListConfig{ + Components: schema.ListConfig{ Columns: []schema.ListColumnConfig{ {Name: "Name", Value: "{{ .component }}"}, {Name: "Environment", Value: "{{ .stack }}"}, diff --git a/cmd/list/instances.go b/cmd/list/instances.go index e7ba18fb2b..d0d508ad86 100644 --- a/cmd/list/instances.go +++ b/cmd/list/instances.go @@ -72,7 +72,7 @@ var instancesCmd = &cobra.Command{ } // columnsCompletionForInstances provides dynamic tab completion for --columns flag. -// Returns column names from atmos.yaml components.list.columns configuration. +// Returns column names from atmos.yaml list.instances.columns or components.list.columns configuration. func columnsCompletionForInstances(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { // Load atmos configuration. configAndStacksInfo, err := e.ProcessCommandLineArgs("list", cmd, args, nil) @@ -85,7 +85,16 @@ func columnsCompletionForInstances(cmd *cobra.Command, args []string, toComplete return nil, cobra.ShellCompDirectiveNoFileComp } - // Extract column names from atmos.yaml configuration. + // Check new config path: list.instances.columns. + if len(atmosConfig.List.Instances.Columns) > 0 { + var columnNames []string + for _, col := range atmosConfig.List.Instances.Columns { + columnNames = append(columnNames, col.Name) + } + return columnNames, cobra.ShellCompDirectiveNoFileComp + } + + // Backward compatibility: check old config path components.list.columns. if len(atmosConfig.Components.List.Columns) > 0 { var columnNames []string for _, col := range atmosConfig.Components.List.Columns { diff --git a/cmd/root.go b/cmd/root.go index a4a6af6605..2414684fc9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1497,6 +1497,12 @@ func init() { log.Error("Failed to bind global flags to viper", "error", err) } + // Register --version as a LOCAL flag (not inherited by subcommands). + // This allows custom commands to define their own --version flag (e.g., for tool versions). + // The flag is only meaningful on the root command and is handled specially in main.go + // before Execute() is called. + RootCmd.Flags().Bool("version", false, "Display the Atmos CLI version") + // Register built-in commands from the registry. // This must happen AFTER persistent flags are registered so commands inherit them. // Commands register themselves via init() functions when their packages @@ -1509,7 +1515,8 @@ func init() { cobra.AddTemplateFunc("wrappedFlagUsages", templates.WrappedFlagUsages) // Special handling for version flag: clear DefValue for cleaner --help output. - if versionFlag := RootCmd.PersistentFlags().Lookup("version"); versionFlag != nil { + // Note: --version is a LOCAL flag (not persistent), so use Flags() not PersistentFlags(). + if versionFlag := RootCmd.Flags().Lookup("version"); versionFlag != nil { versionFlag.DefValue = "" } // Configure viper for automatic environment variable binding. diff --git a/cmd/root_test.go b/cmd/root_test.go index 3bd45e0c3e..19f5aa0eee 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -348,7 +348,8 @@ func TestVersionFlagParsing(t *testing.T) { _ = NewTestKit(t) // Reset flag states before each test - need to reset both value and Changed state. - versionFlag := RootCmd.PersistentFlags().Lookup("version") + // Note: --version is a LOCAL flag (not persistent), so use Flags() not PersistentFlags(). + versionFlag := RootCmd.Flags().Lookup("version") if versionFlag != nil { versionFlag.Value.Set("false") versionFlag.Changed = false @@ -383,7 +384,8 @@ func TestVersionFlagExecutionPath(t *testing.T) { { name: "version subcommand works normally without deep exit", setup: func() { - versionFlag := RootCmd.PersistentFlags().Lookup("version") + // Note: --version is a LOCAL flag (not persistent), so use Flags() not PersistentFlags(). + versionFlag := RootCmd.Flags().Lookup("version") if versionFlag != nil { versionFlag.Value.Set("false") versionFlag.Changed = false diff --git a/examples/quick-start-advanced/atmos.yaml b/examples/quick-start-advanced/atmos.yaml index 4ca43f859c..07eb411778 100644 --- a/examples/quick-start-advanced/atmos.yaml +++ b/examples/quick-start-advanced/atmos.yaml @@ -54,30 +54,6 @@ components: # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_CLUSTER_NAME_PATTERN' ENV var cluster_name_pattern: "{namespace}-{tenant}-{environment}-{stage}-eks-cluster" - # List command configuration for components - list: - # Custom columns for 'atmos list instances' command - # Each column supports Go template syntax with access to instance data - columns: - - name: " " - value: "{{ .status }}" - - name: Stack - value: "{{ .stack }}" - - name: Component - value: "{{ .component }}" - - name: Type - value: "{{ .type }}" - - name: Tenant - value: "{{ .vars.tenant }}" - - name: Environment - value: "{{ .vars.environment }}" - - name: Stage - value: "{{ .vars.stage }}" - - name: Region - value: "{{ .vars.region }}" - - name: Component Folder - value: "{{ .component_folder }}" - stacks: # Can also be set using 'ATMOS_STACKS_BASE_PATH' ENV var, or '--config-dir' and '--stacks-dir' command-line arguments # Supports both absolute and relative paths @@ -255,3 +231,42 @@ settings: # If the source and destination lists have the same length, all items in the destination lists are # deep-merged with all items in the source list. list_merge_strategy: replace + +# List command configurations +# Configure output columns and format for different list commands +list: + # Configuration for "atmos list components" command + # Shows unique component definitions (deduplicated across stacks) + components: + format: table + columns: + - name: Component + value: "{{ .component }}" + - name: Type + value: "{{ .type }}" + - name: Stacks + value: "{{ .stack_count }}" + + # Configuration for "atmos list instances" command + # Shows component instances (one entry per component+stack pair) + instances: + format: table + columns: + - name: " " + value: "{{ .status }}" + - name: Stack + value: "{{ .stack }}" + - name: Component + value: "{{ .component }}" + - name: Type + value: "{{ .type }}" + - name: Tenant + value: "{{ .vars.tenant }}" + - name: Environment + value: "{{ .vars.environment }}" + - name: Stage + value: "{{ .vars.stage }}" + - name: Region + value: "{{ .vars.region }}" + - name: Component Folder + value: "{{ .component_folder }}" diff --git a/pkg/flags/global_builder.go b/pkg/flags/global_builder.go index e896bb6663..b51e4ee65e 100644 --- a/pkg/flags/global_builder.go +++ b/pkg/flags/global_builder.go @@ -177,7 +177,11 @@ func (b *GlobalOptionsBuilder) registerSystemFlags(defaults *global.Flags) { // Verbose flag for error formatting. b.WithVerbose() - b.options = append(b.options, WithBoolFlag("version", "", defaults.Version, "Display the Atmos CLI version")) + // Note: --version flag is NOT registered here as a persistent flag. + // It's registered as a LOCAL flag on RootCmd only (in cmd/root.go) because: + // 1. It only makes sense at the root level (atmos --version) + // 2. Custom commands should be able to define their own --version flag + // 3. Native commands may want to pass --version to underlying tools // Version management flag - specify which version of Atmos to use. // Note: ATMOS_VERSION and ATMOS_VERSION_USE env vars are also checked in reexec.go. diff --git a/pkg/flags/global_builder_test.go b/pkg/flags/global_builder_test.go index 191255f7d6..bf9a64998d 100644 --- a/pkg/flags/global_builder_test.go +++ b/pkg/flags/global_builder_test.go @@ -55,7 +55,9 @@ func TestGlobalOptionsBuilder(t *testing.T) { // System flags. assert.NotNil(t, cmd.PersistentFlags().Lookup("redirect-stderr"), "redirect-stderr flag should be registered") - assert.NotNil(t, cmd.PersistentFlags().Lookup("version"), "version flag should be registered") + assert.NotNil(t, cmd.PersistentFlags().Lookup("use-version"), "use-version flag should be registered") + // Note: --version is NOT a global persistent flag - it's a local flag on RootCmd only. + // See global_builder.go registerSystemFlags() for explanation. }) t.Run("uses defaults from global.NewFlags", func(t *testing.T) { diff --git a/pkg/list/extract/components.go b/pkg/list/extract/components.go index a3b584648e..7f86455215 100644 --- a/pkg/list/extract/components.go +++ b/pkg/list/extract/components.go @@ -2,6 +2,8 @@ package extract import ( "fmt" + "path" + "path/filepath" errUtils "github.com/cloudposse/atmos/errors" perf "github.com/cloudposse/atmos/pkg/perf" @@ -11,6 +13,11 @@ const ( // Component metadata field names. metadataEnabled = "enabled" metadataLocked = "locked" + + // Field names for component data. + fieldComponent = "component" + fieldComponentFolder = "component_folder" + fieldMetadata = "metadata" ) // Components transforms stacksMap into structured component data. @@ -70,9 +77,9 @@ func buildBaseComponent(componentName, stackName, componentType string) map[stri defer perf.Track(nil, "list.extract.buildBaseComponent")() return map[string]any{ - "component": componentName, - "stack": stackName, - "type": componentType, + fieldComponent: componentName, + "stack": stackName, + "type": componentType, } } @@ -85,14 +92,34 @@ func enrichComponentWithMetadata(comp map[string]any, componentData any) { return } - metadata, hasMetadata := compMap["metadata"].(map[string]any) + metadata, hasMetadata := compMap[fieldMetadata].(map[string]any) if hasMetadata { - comp["metadata"] = metadata + comp[fieldMetadata] = metadata extractMetadataFields(comp, metadata) } else { setDefaultMetadataFields(comp) } + // Extract vars to top level for easy template access ({{ .vars.tenant }}). + if vars, ok := compMap["vars"].(map[string]any); ok { + comp["vars"] = vars + } + + // Extract settings to top level. + if settings, ok := compMap["settings"].(map[string]any); ok { + comp["settings"] = settings + } + + // Extract component_folder for column templates. + if folder, ok := compMap[fieldComponentFolder].(string); ok { + comp[fieldComponentFolder] = folder + } + + // Extract terraform_component if different from component name. + if tfComp, ok := compMap["terraform_component"].(string); ok { + comp["terraform_component"] = tfComp + } + comp["data"] = compMap } @@ -100,9 +127,27 @@ func enrichComponentWithMetadata(comp map[string]any, componentData any) { func extractMetadataFields(comp map[string]any, metadata map[string]any) { defer perf.Track(nil, "list.extract.extractMetadataFields")() - comp[metadataEnabled] = getBoolWithDefault(metadata, metadataEnabled, true) - comp[metadataLocked] = getBoolWithDefault(metadata, metadataLocked, false) + enabled := getBoolWithDefault(metadata, metadataEnabled, true) + locked := getBoolWithDefault(metadata, metadataLocked, false) + + comp[metadataEnabled] = enabled + comp[metadataLocked] = locked comp["component_type"] = getStringWithDefault(metadata, "type", "real") + + // Compute status indicators for display. + // status: Colored dot (●) for table display. + // status_text: Semantic text ("enabled", "disabled", "locked") for JSON/CSV/YAML. + comp["status"] = getStatusIndicator(enabled, locked) + comp["status_text"] = getStatusText(enabled, locked) + + // Extract component_folder from metadata.component (the terraform component path). + // This is the relative path to the component within the components directory. + // If metadata.component is not set, fall back to the component name. + if folder, ok := metadata[fieldComponent].(string); ok && folder != "" { + comp[fieldComponentFolder] = folder + } else if componentName, ok := comp[fieldComponent].(string); ok { + comp[fieldComponentFolder] = componentName + } } // setDefaultMetadataFields sets default values for metadata fields. @@ -112,6 +157,15 @@ func setDefaultMetadataFields(comp map[string]any) { comp[metadataEnabled] = true comp[metadataLocked] = false comp["component_type"] = "real" + + // Default status indicators for enabled, not locked state. + comp["status"] = getStatusIndicator(true, false) + comp["status_text"] = getStatusText(true, false) + + // Default component_folder to component name when no metadata.component is set. + if componentName, ok := comp[fieldComponent].(string); ok { + comp[fieldComponentFolder] = componentName + } } // getBoolWithDefault safely extracts a bool value or returns the default. @@ -134,6 +188,111 @@ func getStringWithDefault(m map[string]any, key string, defaultValue string) str return defaultValue } +// UniqueComponents extracts deduplicated components from all stacks. +// Returns unique component names with aggregated metadata (stack count, types). +// This is the original behavior of "list components" - showing unique component definitions. +// The stackPattern parameter is an optional glob pattern to filter which stacks to consider. +func UniqueComponents(stacksMap map[string]any, stackPattern string) ([]map[string]any, error) { + defer perf.Track(nil, "list.extract.UniqueComponents")() + + if stacksMap == nil { + return nil, errUtils.ErrStackNotFound + } + + // Use a map to deduplicate by component name + type combination. + // Key: "componentName:componentType" (e.g., "vpc:terraform"). + seen := make(map[string]map[string]any) + + for stackName, stackData := range stacksMap { + // Apply stack filter if provided. + if stackPattern != "" { + // Stack names are slash-separated; normalize for cross-platform matching. + // Use path.Match (not filepath.Match) to ensure consistent behavior on Windows. + name := filepath.ToSlash(stackName) + pattern := filepath.ToSlash(stackPattern) + matched, err := path.Match(pattern, name) + if err != nil || !matched { + continue + } + } + + stackMap, ok := stackData.(map[string]any) + if !ok { + continue + } + + componentsMap, ok := stackMap["components"].(map[string]any) + if !ok { + continue + } + + // Process each component type. + extractUniqueComponentType("terraform", componentsMap, seen) + extractUniqueComponentType("helmfile", componentsMap, seen) + extractUniqueComponentType("packer", componentsMap, seen) + } + + // Convert map to slice. + var components []map[string]any + for _, comp := range seen { + components = append(components, comp) + } + + return components, nil +} + +// extractUniqueComponentType extracts unique components of a specific type. +func extractUniqueComponentType(componentType string, componentsMap map[string]any, seen map[string]map[string]any) { + typeComponents, ok := componentsMap[componentType].(map[string]any) + if !ok { + return + } + + for componentName, componentData := range typeComponents { + key := componentName + ":" + componentType + + // If we haven't seen this component, add it. + if _, exists := seen[key]; !exists { + comp := map[string]any{ + fieldComponent: componentName, + "type": componentType, + "stack_count": 0, + } + + // Extract metadata from first occurrence. + enrichUniqueComponentMetadata(comp, componentData) + seen[key] = comp + } + + // Increment stack count. + if count, ok := seen[key]["stack_count"].(int); ok { + seen[key]["stack_count"] = count + 1 + } + } +} + +// enrichUniqueComponentMetadata adds metadata fields to a unique component. +func enrichUniqueComponentMetadata(comp map[string]any, componentData any) { + compMap, ok := componentData.(map[string]any) + if !ok { + setDefaultMetadataFields(comp) + return + } + + metadata, hasMetadata := compMap[fieldMetadata].(map[string]any) + if hasMetadata { + comp[fieldMetadata] = metadata + extractMetadataFields(comp, metadata) + } else { + setDefaultMetadataFields(comp) + } + + // Extract component_folder for column templates. + if folder, ok := compMap[fieldComponentFolder].(string); ok { + comp[fieldComponentFolder] = folder + } +} + // ComponentsForStack extracts components for a specific stack only. func ComponentsForStack(stackName string, stacksMap map[string]any) ([]map[string]any, error) { defer perf.Track(nil, "list.extract.ComponentsForStack")() diff --git a/pkg/list/extract/components_test.go b/pkg/list/extract/components_test.go index f64e7d6b3d..ccaf443979 100644 --- a/pkg/list/extract/components_test.go +++ b/pkg/list/extract/components_test.go @@ -243,3 +243,554 @@ func TestExtractComponentType_MissingType(t *testing.T) { components := extractComponentType("test-stack", "terraform", componentsMap) assert.Nil(t, components) } + +// Tests for UniqueComponents function. +func TestUniqueComponents(t *testing.T) { + stacksMap := map[string]any{ + "plat-ue2-dev": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{ + "metadata": map[string]any{ + "enabled": true, + "locked": false, + "type": "real", + "component": "vpc-base", + }, + }, + "eks": map[string]any{ + "metadata": map[string]any{ + "enabled": true, + }, + }, + }, + "helmfile": map[string]any{ + "ingress": map[string]any{}, + }, + }, + }, + "plat-ue2-prod": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{ + "metadata": map[string]any{ + "enabled": true, + }, + }, + "rds": map[string]any{}, + }, + }, + }, + } + + components, err := UniqueComponents(stacksMap, "") + require.NoError(t, err) + // Should have 4 unique components: vpc, eks, ingress, rds. + assert.Len(t, components, 4) + + // Verify vpc component has stack_count of 2. + for _, comp := range components { + if comp["component"] == "vpc" && comp["type"] == "terraform" { + assert.Equal(t, 2, comp["stack_count"], "vpc should appear in 2 stacks") + } + } +} + +func TestUniqueComponents_Nil(t *testing.T) { + _, err := UniqueComponents(nil, "") + assert.ErrorIs(t, err, errUtils.ErrStackNotFound) +} + +func TestUniqueComponents_WithStackFilter(t *testing.T) { + stacksMap := map[string]any{ + "plat-ue2-dev": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + }, + }, + }, + "plat-ue2-prod": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + "rds": map[string]any{}, + }, + }, + }, + "other-stack": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "s3": map[string]any{}, + }, + }, + }, + } + + // Filter to only plat-* stacks. + components, err := UniqueComponents(stacksMap, "plat-*") + require.NoError(t, err) + // Should only include vpc and rds from plat-* stacks. + assert.Len(t, components, 2) + + componentNames := make(map[string]bool) + for _, comp := range components { + componentNames[comp["component"].(string)] = true + } + assert.True(t, componentNames["vpc"]) + assert.True(t, componentNames["rds"]) + assert.False(t, componentNames["s3"]) +} + +func TestUniqueComponents_InvalidStackPattern(t *testing.T) { + stacksMap := map[string]any{ + "test-stack": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + }, + }, + }, + } + + // Invalid pattern with unmatched bracket. + components, err := UniqueComponents(stacksMap, "[invalid") + require.NoError(t, err) + // Should skip the stack due to pattern error. + assert.Empty(t, components) +} + +func TestUniqueComponents_EmptyStacks(t *testing.T) { + stacksMap := map[string]any{} + components, err := UniqueComponents(stacksMap, "") + require.NoError(t, err) + assert.Empty(t, components) +} + +func TestUniqueComponents_InvalidStackData(t *testing.T) { + stacksMap := map[string]any{ + "test": "invalid-not-a-map", + } + components, err := UniqueComponents(stacksMap, "") + require.NoError(t, err) + assert.Empty(t, components) +} + +func TestUniqueComponents_NoComponents(t *testing.T) { + stacksMap := map[string]any{ + "test": map[string]any{ + "vars": map[string]any{}, + }, + } + components, err := UniqueComponents(stacksMap, "") + require.NoError(t, err) + assert.Empty(t, components) +} + +func TestUniqueComponents_AllTypes(t *testing.T) { + stacksMap := map[string]any{ + "test-stack": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + }, + "helmfile": map[string]any{ + "ingress": map[string]any{}, + }, + "packer": map[string]any{ + "ami-builder": map[string]any{}, + }, + }, + }, + } + + components, err := UniqueComponents(stacksMap, "") + require.NoError(t, err) + assert.Len(t, components, 3) + + // Verify all types are present. + types := make(map[string]bool) + for _, comp := range components { + types[comp["type"].(string)] = true + } + assert.True(t, types["terraform"]) + assert.True(t, types["helmfile"]) + assert.True(t, types["packer"]) +} + +// Tests for enrichComponentWithMetadata edge cases. +func TestEnrichComponentWithMetadata_WithVars(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + "stack": "test-stack", + "type": "terraform", + } + + componentData := map[string]any{ + "vars": map[string]any{ + "region": "us-east-1", + "environment": "prod", + }, + } + + enrichComponentWithMetadata(comp, componentData) + + vars, ok := comp["vars"].(map[string]any) + require.True(t, ok) + assert.Equal(t, "us-east-1", vars["region"]) + assert.Equal(t, "prod", vars["environment"]) +} + +func TestEnrichComponentWithMetadata_WithSettings(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + } + + componentData := map[string]any{ + "settings": map[string]any{ + "spacelift": map[string]any{ + "workspace_enabled": true, + }, + }, + } + + enrichComponentWithMetadata(comp, componentData) + + settings, ok := comp["settings"].(map[string]any) + require.True(t, ok) + assert.NotNil(t, settings["spacelift"]) +} + +func TestEnrichComponentWithMetadata_WithComponentFolder(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + } + + componentData := map[string]any{ + "component_folder": "infra/vpc", + } + + enrichComponentWithMetadata(comp, componentData) + + assert.Equal(t, "infra/vpc", comp["component_folder"]) +} + +func TestEnrichComponentWithMetadata_WithTerraformComponent(t *testing.T) { + comp := map[string]any{ + "component": "vpc-dev", + } + + componentData := map[string]any{ + "terraform_component": "vpc", + } + + enrichComponentWithMetadata(comp, componentData) + + assert.Equal(t, "vpc", comp["terraform_component"]) +} + +func TestEnrichComponentWithMetadata_InvalidData(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + } + + // Pass non-map data. + enrichComponentWithMetadata(comp, "invalid") + + // Component should remain unchanged (just default fields set). + assert.Equal(t, "vpc", comp["component"]) +} + +func TestEnrichComponentWithMetadata_WithMetadataComponent(t *testing.T) { + comp := map[string]any{ + "component": "vpc-derived", + } + + componentData := map[string]any{ + "metadata": map[string]any{ + "component": "vpc", + "enabled": true, + "locked": false, + "type": "abstract", + }, + } + + enrichComponentWithMetadata(comp, componentData) + + // component_folder should be set from metadata.component. + assert.Equal(t, "vpc", comp["component_folder"]) + assert.Equal(t, "abstract", comp["component_type"]) +} + +func TestEnrichComponentWithMetadata_ComponentFolderFallback(t *testing.T) { + comp := map[string]any{ + "component": "my-vpc", + } + + componentData := map[string]any{ + "metadata": map[string]any{ + "enabled": true, + }, + } + + enrichComponentWithMetadata(comp, componentData) + + // component_folder should fall back to component name. + assert.Equal(t, "my-vpc", comp["component_folder"]) +} + +// Tests for extractMetadataFields function. +func TestExtractMetadataFields(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + } + + metadata := map[string]any{ + "enabled": false, + "locked": true, + "type": "abstract", + "component": "base-vpc", + } + + extractMetadataFields(comp, metadata) + + assert.Equal(t, false, comp["enabled"]) + assert.Equal(t, true, comp["locked"]) + assert.Equal(t, "abstract", comp["component_type"]) + assert.Equal(t, "base-vpc", comp["component_folder"]) + assert.NotEmpty(t, comp["status"]) + assert.Equal(t, "locked", comp["status_text"]) +} + +// Tests for setDefaultMetadataFields function. +func TestSetDefaultMetadataFields(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + } + + setDefaultMetadataFields(comp) + + assert.Equal(t, true, comp["enabled"]) + assert.Equal(t, false, comp["locked"]) + assert.Equal(t, "real", comp["component_type"]) + assert.NotEmpty(t, comp["status"]) + assert.Equal(t, "enabled", comp["status_text"]) + assert.Equal(t, "vpc", comp["component_folder"]) +} + +// Tests for helper functions. +func TestGetBoolWithDefault(t *testing.T) { + m := map[string]any{ + "enabled": true, + "locked": false, + } + + assert.True(t, getBoolWithDefault(m, "enabled", false)) + assert.False(t, getBoolWithDefault(m, "locked", true)) + assert.True(t, getBoolWithDefault(m, "missing", true)) + assert.False(t, getBoolWithDefault(m, "missing", false)) +} + +func TestGetStringWithDefault(t *testing.T) { + m := map[string]any{ + "type": "abstract", + "region": "us-east-1", + } + + assert.Equal(t, "abstract", getStringWithDefault(m, "type", "real")) + assert.Equal(t, "us-east-1", getStringWithDefault(m, "region", "")) + assert.Equal(t, "default", getStringWithDefault(m, "missing", "default")) +} + +// Tests for enrichUniqueComponentMetadata function. +func TestEnrichUniqueComponentMetadata_WithMetadata(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + "type": "terraform", + "stack_count": 0, + } + + componentData := map[string]any{ + "metadata": map[string]any{ + "enabled": true, + "locked": false, + "type": "real", + "component": "base-vpc", + }, + "component_folder": "infra/vpc", + } + + enrichUniqueComponentMetadata(comp, componentData) + + assert.Equal(t, true, comp["enabled"]) + assert.Equal(t, false, comp["locked"]) + assert.Equal(t, "real", comp["component_type"]) + assert.Equal(t, "infra/vpc", comp["component_folder"]) +} + +func TestEnrichUniqueComponentMetadata_WithoutMetadata(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + "type": "terraform", + "stack_count": 0, + } + + componentData := map[string]any{ + "vars": map[string]any{ + "region": "us-east-1", + }, + } + + enrichUniqueComponentMetadata(comp, componentData) + + // Should use defaults. + assert.Equal(t, true, comp["enabled"]) + assert.Equal(t, false, comp["locked"]) + assert.Equal(t, "real", comp["component_type"]) +} + +func TestEnrichUniqueComponentMetadata_InvalidData(t *testing.T) { + comp := map[string]any{ + "component": "vpc", + "type": "terraform", + "stack_count": 0, + } + + enrichUniqueComponentMetadata(comp, "invalid") + + // Should use defaults. + assert.Equal(t, true, comp["enabled"]) + assert.Equal(t, false, comp["locked"]) + assert.Equal(t, "real", comp["component_type"]) +} + +// Tests for buildBaseComponent function. +func TestBuildBaseComponent(t *testing.T) { + comp := buildBaseComponent("vpc", "test-stack", "terraform") + + assert.Equal(t, "vpc", comp["component"]) + assert.Equal(t, "test-stack", comp["stack"]) + assert.Equal(t, "terraform", comp["type"]) +} + +// Test extractUniqueComponentType function. +func TestExtractUniqueComponentType(t *testing.T) { + seen := make(map[string]map[string]any) + componentsMap := map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{ + "metadata": map[string]any{ + "enabled": true, + }, + }, + "eks": map[string]any{}, + }, + } + + extractUniqueComponentType("terraform", componentsMap, seen) + + assert.Len(t, seen, 2) + assert.Contains(t, seen, "vpc:terraform") + assert.Contains(t, seen, "eks:terraform") + assert.Equal(t, 1, seen["vpc:terraform"]["stack_count"]) + assert.Equal(t, 1, seen["eks:terraform"]["stack_count"]) +} + +func TestExtractUniqueComponentType_MultipleStacks(t *testing.T) { + seen := make(map[string]map[string]any) + + // First stack. + componentsMap1 := map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + }, + } + extractUniqueComponentType("terraform", componentsMap1, seen) + + // Second stack (same component). + componentsMap2 := map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + }, + } + extractUniqueComponentType("terraform", componentsMap2, seen) + + assert.Len(t, seen, 1) + assert.Equal(t, 2, seen["vpc:terraform"]["stack_count"]) +} + +func TestExtractUniqueComponentType_InvalidType(t *testing.T) { + seen := make(map[string]map[string]any) + componentsMap := map[string]any{ + "terraform": "not-a-map", + } + + extractUniqueComponentType("terraform", componentsMap, seen) + + assert.Empty(t, seen) +} + +func TestExtractUniqueComponentType_MissingType(t *testing.T) { + seen := make(map[string]map[string]any) + componentsMap := map[string]any{ + "helmfile": map[string]any{}, + } + + extractUniqueComponentType("terraform", componentsMap, seen) + + assert.Empty(t, seen) +} + +// Test Components with all component types. +func TestComponents_AllTypes(t *testing.T) { + stacksMap := map[string]any{ + "test-stack": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + }, + "helmfile": map[string]any{ + "ingress": map[string]any{}, + }, + "packer": map[string]any{ + "ami": map[string]any{}, + }, + }, + }, + } + + components, err := Components(stacksMap) + require.NoError(t, err) + assert.Len(t, components, 3) + + types := make(map[string]bool) + for _, comp := range components { + types[comp["type"].(string)] = true + } + assert.True(t, types["terraform"]) + assert.True(t, types["helmfile"]) + assert.True(t, types["packer"]) +} + +// Test ComponentsForStack with all component types. +func TestComponentsForStack_AllTypes(t *testing.T) { + stacksMap := map[string]any{ + "test-stack": map[string]any{ + "components": map[string]any{ + "terraform": map[string]any{ + "vpc": map[string]any{}, + }, + "helmfile": map[string]any{ + "ingress": map[string]any{}, + }, + "packer": map[string]any{ + "ami": map[string]any{}, + }, + }, + }, + } + + components, err := ComponentsForStack("test-stack", stacksMap) + require.NoError(t, err) + assert.Len(t, components, 3) +} diff --git a/pkg/list/extract/metadata.go b/pkg/list/extract/metadata.go index ca34125ff7..b9f5df9e73 100644 --- a/pkg/list/extract/metadata.go +++ b/pkg/list/extract/metadata.go @@ -1,20 +1,41 @@ package extract import ( + "sync" + "github.com/charmbracelet/lipgloss" "github.com/cloudposse/atmos/pkg/perf" "github.com/cloudposse/atmos/pkg/schema" + "github.com/cloudposse/atmos/pkg/terminal" "github.com/cloudposse/atmos/pkg/ui/theme" ) +// isTTYCached caches the TTY check to avoid repeated allocations when processing many instances. +var isTTYCached = sync.OnceValue(func() bool { + term := terminal.New() + return term.IsTTY(terminal.Stdout) +}) + // getStatusIndicator returns a colored dot indicator based on enabled/locked state. // - Gray (●) if enabled: false (disabled). // - Red (●) if locked: true. // - Green (●) if enabled: true and not locked. +// When output is piped (non-TTY), returns the semantic status text instead. func getStatusIndicator(enabled, locked bool) string { + return getStatusIndicatorWithTTY(enabled, locked, isTTYCached()) +} + +// getStatusIndicatorWithTTY is the internal implementation that accepts the TTY state. +// This allows testing both TTY and non-TTY code paths. +func getStatusIndicatorWithTTY(enabled, locked, isTTY bool) string { const statusDot = "●" + // If not a TTY, return semantic status text for machine-readable output. + if !isTTY { + return getStatusText(enabled, locked) + } + switch { case locked: // Red for locked - use theme error color. diff --git a/pkg/list/extract/metadata_test.go b/pkg/list/extract/metadata_test.go index 6c85546028..c3d773a76e 100644 --- a/pkg/list/extract/metadata_test.go +++ b/pkg/list/extract/metadata_test.go @@ -296,33 +296,34 @@ func TestMetadata_IncludesVarsSettingsEnv(t *testing.T) { assert.Equal(t, "us-east-2", env["AWS_REGION"]) } -func TestGetStatusIndicator(t *testing.T) { +func TestGetStatusIndicatorTTY(t *testing.T) { + // Test TTY mode: should return colored dot. tests := []struct { name string enabled bool locked bool - contains string // Check if output contains the dot character + contains string // The colored dot character }{ { - name: "enabled and not locked shows green", + name: "enabled and not locked shows green dot", enabled: true, locked: false, contains: "●", }, { - name: "locked shows red", + name: "locked shows red dot", enabled: true, locked: true, contains: "●", }, { - name: "disabled shows gray", + name: "disabled shows gray dot", enabled: false, locked: false, contains: "●", }, { - name: "disabled and locked shows red (locked takes precedence)", + name: "disabled and locked shows red dot (locked takes precedence)", enabled: false, locked: true, contains: "●", @@ -331,15 +332,57 @@ func TestGetStatusIndicator(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := getStatusIndicator(tt.enabled, tt.locked) - // Always contains the status dot. - assert.Contains(t, result, tt.contains) - // Result is non-empty (may or may not have ANSI codes depending on TTY). + // Test TTY mode explicitly. + result := getStatusIndicatorWithTTY(tt.enabled, tt.locked, true) + assert.Contains(t, result, tt.contains, "TTY mode should contain the status dot") assert.NotEmpty(t, result) }) } } +func TestGetStatusIndicatorNonTTY(t *testing.T) { + // Test non-TTY mode: should return semantic text. + tests := []struct { + name string + enabled bool + locked bool + expected string // The semantic text + }{ + { + name: "enabled and not locked returns enabled text", + enabled: true, + locked: false, + expected: "enabled", + }, + { + name: "locked returns locked text", + enabled: true, + locked: true, + expected: "locked", + }, + { + name: "disabled returns disabled text", + enabled: false, + locked: false, + expected: "disabled", + }, + { + name: "disabled and locked returns locked text (locked takes precedence)", + enabled: false, + locked: true, + expected: "locked", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test non-TTY mode explicitly. + result := getStatusIndicatorWithTTY(tt.enabled, tt.locked, false) + assert.Equal(t, tt.expected, result, "non-TTY mode should return semantic text") + }) + } +} + func TestGetStatusText(t *testing.T) { tests := []struct { name string diff --git a/pkg/list/list_instances.go b/pkg/list/list_instances.go index a6a2af1401..41c8ae0140 100644 --- a/pkg/list/list_instances.go +++ b/pkg/list/list_instances.go @@ -227,6 +227,7 @@ func sortInstances(instances []schema.Instance) []schema.Instance { // getInstanceColumns returns column configuration from CLI flag, atmos.yaml, or defaults. // Returns error if CLI flag parsing fails. +// Precedence: CLI flag > list.instances.columns > components.list.columns (deprecated) > defaults. func getInstanceColumns(atmosConfig *schema.AtmosConfiguration, columnsFlag []string) ([]column.Config, error) { // If --columns flag is provided, parse it and return. if len(columnsFlag) > 0 { @@ -237,7 +238,21 @@ func getInstanceColumns(atmosConfig *schema.AtmosConfiguration, columnsFlag []st return columns, nil } - // Check if custom columns are configured in atmos.yaml. + // Check new config path: list.instances.columns. + if len(atmosConfig.List.Instances.Columns) > 0 { + columns := make([]column.Config, len(atmosConfig.List.Instances.Columns)) + for i, col := range atmosConfig.List.Instances.Columns { + columns[i] = column.Config{ + Name: col.Name, + Value: col.Value, + Width: col.Width, + } + } + return columns, nil + } + + // Backward compatibility: check old config path components.list.columns. + // This is deprecated but supported for existing configurations. if len(atmosConfig.Components.List.Columns) > 0 { columns := make([]column.Config, len(atmosConfig.Components.List.Columns)) for i, col := range atmosConfig.Components.List.Columns { diff --git a/pkg/list/list_instances_coverage_test.go b/pkg/list/list_instances_coverage_test.go index ac3c22c516..18fab7bc16 100644 --- a/pkg/list/list_instances_coverage_test.go +++ b/pkg/list/list_instances_coverage_test.go @@ -303,6 +303,66 @@ func TestGetInstanceColumns(t *testing.T) { columnsFlag: []string{"InvalidSpec"}, expectErr: true, }, + { + name: "new list.instances.columns config takes precedence over deprecated components.list.columns", + atmosConfig: &schema.AtmosConfiguration{ + List: schema.TopLevelListConfig{ + Instances: schema.ListConfig{ + Columns: []schema.ListColumnConfig{ + {Name: "NewConfigStack", Value: "{{ .stack }}", Width: 20}, + {Name: "NewConfigComponent", Value: "{{ .component }}", Width: 30}, + }, + }, + }, + Components: schema.Components{ + List: schema.ListConfig{ + Columns: []schema.ListColumnConfig{ + {Name: "OldConfigColumn", Value: "{{ .old }}"}, + }, + }, + }, + }, + columnsFlag: nil, + expected: []column.Config{ + {Name: "NewConfigStack", Value: "{{ .stack }}", Width: 20}, + {Name: "NewConfigComponent", Value: "{{ .component }}", Width: 30}, + }, + expectErr: false, + }, + { + name: "new list.instances.columns config used when no deprecated config", + atmosConfig: &schema.AtmosConfiguration{ + List: schema.TopLevelListConfig{ + Instances: schema.ListConfig{ + Columns: []schema.ListColumnConfig{ + {Name: "InstanceColumn", Value: "{{ .instance }}"}, + }, + }, + }, + }, + columnsFlag: nil, + expected: []column.Config{ + {Name: "InstanceColumn", Value: "{{ .instance }}"}, + }, + expectErr: false, + }, + { + name: "CLI flag takes precedence over new list.instances.columns config", + atmosConfig: &schema.AtmosConfiguration{ + List: schema.TopLevelListConfig{ + Instances: schema.ListConfig{ + Columns: []schema.ListColumnConfig{ + {Name: "ConfigColumn", Value: "{{ .config }}"}, + }, + }, + }, + }, + columnsFlag: []string{"FlagColumn={{ .flag }}"}, + expected: []column.Config{ + {Name: "FlagColumn", Value: "{{ .flag }}"}, + }, + expectErr: false, + }, } for _, tc := range tests { diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 30393d9bc1..4a82261b9d 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -97,6 +97,8 @@ type AtmosConfiguration struct { Devcontainer map[string]any `yaml:"devcontainer,omitempty" json:"devcontainer,omitempty" mapstructure:"devcontainer"` Profiles ProfilesConfig `yaml:"profiles,omitempty" json:"profiles,omitempty" mapstructure:"profiles"` Metadata ConfigMetadata `yaml:"metadata,omitempty" json:"metadata,omitempty" mapstructure:"metadata"` + // List holds command-specific list configurations (list.components, list.instances, list.stacks). + List TopLevelListConfig `yaml:"list,omitempty" json:"list,omitempty" mapstructure:"list"` } func (m *AtmosConfiguration) GetSchemaRegistry(key string) SchemaRegistry { @@ -1073,3 +1075,18 @@ type ListColumnConfig struct { Value string `yaml:"value" json:"value" mapstructure:"value"` Width int `yaml:"width,omitempty" json:"width,omitempty" mapstructure:"width"` } + +// TopLevelListConfig holds command-specific list configurations. +// This is a top-level configuration that separates settings for different list commands. +type TopLevelListConfig struct { + // Components configures the "atmos list components" command output. + // Shows unique component definitions (deduplicated across stacks). + Components ListConfig `yaml:"components,omitempty" json:"components,omitempty" mapstructure:"components"` + + // Instances configures the "atmos list instances" command output. + // Shows component instances (one entry per component+stack pair). + Instances ListConfig `yaml:"instances,omitempty" json:"instances,omitempty" mapstructure:"instances"` + + // Stacks configures the "atmos list stacks" command output. + Stacks ListConfig `yaml:"stacks,omitempty" json:"stacks,omitempty" mapstructure:"stacks"` +} diff --git a/pkg/utils/yaml_utils.go b/pkg/utils/yaml_utils.go index 7bf096aaf7..ba8f902896 100644 --- a/pkg/utils/yaml_utils.go +++ b/pkg/utils/yaml_utils.go @@ -270,7 +270,7 @@ func parseAndCacheYAML(atmosConfig *schema.AtmosConfiguration, input string, fil // Extract positions if provenance tracking is enabled. var positions PositionMap - if atmosConfig.TrackProvenance { + if atmosConfig != nil && atmosConfig.TrackProvenance { positions = ExtractYAMLPositions(&parsedNode, true) } @@ -296,14 +296,20 @@ func handleCacheMiss(atmosConfig *schema.AtmosConfiguration, file string, input // Double-check: another goroutine may have cached it while we waited for the lock. node, positions, found := getCachedParsedYAML(file, input) if found { - // Another goroutine cached it while we waited - cache hit! - parsedYAMLCacheStats.Lock() - parsedYAMLCacheStats.hits++ - parsedYAMLCacheStats.Unlock() - return node, positions, nil + // Check if we need positions but the cached entry lacks them. + // This can happen if the file was first parsed without provenance tracking. + needsPositions := atmosConfig != nil && atmosConfig.TrackProvenance && len(positions) == 0 + if !needsPositions { + // Another goroutine cached it while we waited - valid cache hit! + parsedYAMLCacheStats.Lock() + parsedYAMLCacheStats.hits++ + parsedYAMLCacheStats.Unlock() + return node, positions, nil + } + // Fall through to re-parse with position tracking. } - // Still not in cache - we're the first goroutine to parse this file. + // Still not in cache (or needs re-parsing for positions). // Track cache miss. parsedYAMLCacheStats.Lock() parsedYAMLCacheStats.misses++ @@ -790,11 +796,22 @@ func UnmarshalYAMLFromFileWithPositions[T any](atmosConfig *schema.AtmosConfigur // Try to get cached parsed YAML first (fast path with read lock). node, positions, found := getCachedParsedYAML(file, input) if found { - // Cache hit on first check. - parsedYAMLCacheStats.Lock() - parsedYAMLCacheStats.hits++ - parsedYAMLCacheStats.Unlock() - } else { + // Cache hit - but check if we need positions and don't have them. + // This can happen if the file was first parsed without provenance tracking, + // then later requested with provenance enabled. + if atmosConfig.TrackProvenance && len(positions) == 0 { + // Need to re-parse with position tracking. + // Force a cache miss to re-parse and update the cache with positions. + found = false + } else { + // Valid cache hit. + parsedYAMLCacheStats.Lock() + parsedYAMLCacheStats.hits++ + parsedYAMLCacheStats.Unlock() + } + } + + if !found { // Cache miss - use per-key locking to prevent multiple goroutines // from parsing the same file simultaneously. var err error diff --git a/pkg/utils/yaml_utils_test.go b/pkg/utils/yaml_utils_test.go index 5e1a3ef22c..eefd6c4a13 100644 --- a/pkg/utils/yaml_utils_test.go +++ b/pkg/utils/yaml_utils_test.go @@ -1461,3 +1461,169 @@ func TestLongString_MarshalYAML(t *testing.T) { // The result should be a yaml.Node with FoldedStyle. require.NotNil(t, node) } + +// TestUnmarshalYAMLFromFileWithPositions_ProvenanceReparse tests that when a file +// is first cached without provenance tracking, then requested with provenance enabled, +// the file is re-parsed to extract position information. +func TestUnmarshalYAMLFromFileWithPositions_ProvenanceReparse(t *testing.T) { + yamlContent := `name: test +value: 42 +nested: + key: value` + fileName := "test-provenance-reparse.yaml" + + // Capture old cache state and restore after test. + parsedYAMLCacheMu.Lock() + oldCache := parsedYAMLCache + parsedYAMLCacheMu.Unlock() + t.Cleanup(func() { + parsedYAMLCacheMu.Lock() + parsedYAMLCache = oldCache + parsedYAMLCacheMu.Unlock() + }) + + // Clear cache to ensure clean state. + parsedYAMLCacheMu.Lock() + parsedYAMLCache = make(map[string]*parsedYAMLCacheEntry) + parsedYAMLCacheMu.Unlock() + + // First parse WITHOUT provenance tracking. + configNoProvenance := &schema.AtmosConfiguration{ + TrackProvenance: false, + } + + result1, positions1, err := UnmarshalYAMLFromFileWithPositions[map[string]any](configNoProvenance, yamlContent, fileName) + require.NoError(t, err) + require.NotNil(t, result1) + assert.Equal(t, "test", result1["name"]) + + // Positions should be empty when provenance is disabled. + assert.Empty(t, positions1, "Positions should be empty when provenance is disabled") + + // Second parse WITH provenance tracking - should re-parse to get positions. + configWithProvenance := &schema.AtmosConfiguration{ + TrackProvenance: true, + } + + result2, positions2, err := UnmarshalYAMLFromFileWithPositions[map[string]any](configWithProvenance, yamlContent, fileName) + require.NoError(t, err) + require.NotNil(t, result2) + assert.Equal(t, "test", result2["name"]) + + // Positions should now be populated. + assert.NotEmpty(t, positions2, "Positions should be populated when provenance is enabled and file is re-parsed") + + // Verify positions contain expected keys. + assert.Contains(t, positions2, "name", "Positions should contain 'name' key") + assert.Contains(t, positions2, "value", "Positions should contain 'value' key") + assert.Contains(t, positions2, "nested", "Positions should contain 'nested' key") +} + +// TestUnmarshalYAMLFromFileWithPositions_NilConfigReturnsError tests that nil atmosConfig +// returns an error (by design - config is required). +func TestUnmarshalYAMLFromFileWithPositions_NilConfigReturnsError(t *testing.T) { + yamlContent := `name: test` + fileName := "test-nil-config-error.yaml" + + // Call with nil config - should return error (not panic). + _, _, err := UnmarshalYAMLFromFileWithPositions[map[string]any](nil, yamlContent, fileName) + require.Error(t, err, "Nil config should return an error") + assert.Contains(t, err.Error(), "nil", "Error should mention nil config") +} + +// TestHandleCacheMiss_NilConfigSafe tests that handleCacheMiss handles nil atmosConfig safely. +// This tests the nil checks we added to prevent panics. +func TestHandleCacheMiss_NilConfigSafe(t *testing.T) { + // Capture old cache state and restore after test. + parsedYAMLCacheMu.Lock() + oldCache := parsedYAMLCache + parsedYAMLCacheMu.Unlock() + t.Cleanup(func() { + parsedYAMLCacheMu.Lock() + parsedYAMLCache = oldCache + parsedYAMLCacheMu.Unlock() + }) + + // Clear cache to ensure clean state. + parsedYAMLCacheMu.Lock() + parsedYAMLCache = make(map[string]*parsedYAMLCacheEntry) + parsedYAMLCacheMu.Unlock() + + yamlContent := `name: test` + + // Call handleCacheMiss with nil config - should NOT panic. + // This tests the nil check we added in parseAndCacheYAML. + node, positions, err := handleCacheMiss(nil, "test-file.yaml", yamlContent) + require.NoError(t, err) + require.NotNil(t, node) + + // Positions should be empty since config is nil (TrackProvenance defaults to false). + assert.Empty(t, positions, "Positions should be empty when config is nil") +} + +// TestUnmarshalYAMLFromFileWithPositions_CacheHitWithProvenance tests that +// a cache hit with provenance already populated doesn't re-parse. +func TestUnmarshalYAMLFromFileWithPositions_CacheHitWithProvenance(t *testing.T) { + yamlContent := `name: cached +value: 123` + fileName := "test-cache-hit-provenance.yaml" + + // Capture old cache state and restore after test. + // Note: Only capture values, not the struct containing the mutex. + parsedYAMLCacheMu.Lock() + oldCache := parsedYAMLCache + parsedYAMLCacheMu.Unlock() + parsedYAMLCacheStats.Lock() + oldHits := parsedYAMLCacheStats.hits + oldMisses := parsedYAMLCacheStats.misses + parsedYAMLCacheStats.Unlock() + t.Cleanup(func() { + parsedYAMLCacheMu.Lock() + parsedYAMLCache = oldCache + parsedYAMLCacheMu.Unlock() + parsedYAMLCacheStats.Lock() + parsedYAMLCacheStats.hits = oldHits + parsedYAMLCacheStats.misses = oldMisses + parsedYAMLCacheStats.Unlock() + }) + + // Clear cache to ensure clean state. + parsedYAMLCacheMu.Lock() + parsedYAMLCache = make(map[string]*parsedYAMLCacheEntry) + parsedYAMLCacheMu.Unlock() + + // Reset stats. + parsedYAMLCacheStats.Lock() + parsedYAMLCacheStats.hits = 0 + parsedYAMLCacheStats.misses = 0 + parsedYAMLCacheStats.Unlock() + + // First parse WITH provenance tracking. + configWithProvenance := &schema.AtmosConfiguration{ + TrackProvenance: true, + } + + result1, positions1, err := UnmarshalYAMLFromFileWithPositions[map[string]any](configWithProvenance, yamlContent, fileName) + require.NoError(t, err) + require.NotNil(t, result1) + assert.NotEmpty(t, positions1, "First call should have positions") + + // Get cache stats before second call. + parsedYAMLCacheStats.RLock() + hitsBefore := parsedYAMLCacheStats.hits + parsedYAMLCacheStats.RUnlock() + + // Second parse WITH provenance - should be a cache HIT (not re-parsed). + result2, positions2, err := UnmarshalYAMLFromFileWithPositions[map[string]any](configWithProvenance, yamlContent, fileName) + require.NoError(t, err) + require.NotNil(t, result2) + assert.NotEmpty(t, positions2, "Second call should also have positions") + + // Get cache stats after second call. + parsedYAMLCacheStats.RLock() + hitsAfter := parsedYAMLCacheStats.hits + parsedYAMLCacheStats.RUnlock() + + // Verify cache hit (hits should increase). + assert.Greater(t, hitsAfter, hitsBefore, "Cache hits should increase on second call") +} diff --git a/tests/fixtures/components/terraform/mock/main.tf b/tests/fixtures/components/terraform/mock/main.tf index bdf2009b54..26147de5d4 100644 --- a/tests/fixtures/components/terraform/mock/main.tf +++ b/tests/fixtures/components/terraform/mock/main.tf @@ -3,6 +3,31 @@ variable "stage" { default = "nonprod" } +variable "enabled" { + type = bool + default = true +} + +variable "name" { + type = string + default = "" +} + +variable "environment" { + type = string + default = "" +} + +variable "region" { + type = string + default = "" +} + +variable "message" { + type = string + default = "" +} + variable "foo" { type = string default = "foo" diff --git a/tests/fixtures/scenarios/custom-version-flag/atmos.yaml b/tests/fixtures/scenarios/custom-version-flag/atmos.yaml new file mode 100644 index 0000000000..8216d94d2b --- /dev/null +++ b/tests/fixtures/scenarios/custom-version-flag/atmos.yaml @@ -0,0 +1,37 @@ +base_path: "./" + +# Minimal config to test custom commands with --version flag +components: + terraform: + base_path: "components/terraform" + +stacks: + base_path: "stacks" + name_pattern: "{tenant}-{environment}-{stage}" + included_paths: + - "**/*" + +commands: + # Test that custom commands can define their own --version flag. + # This was previously blocked because --version was a persistent global flag. + # Note: Don't use -v shorthand because it conflicts with global --verbose flag. + - name: install + description: "Install a tool with specific version" + flags: + - name: version + shorthand: V + type: string + description: "Version to install" + required: true + steps: + - "echo Installing version {{ .Flags.version }}" + + # Test with bool type --version flag (same type as atmos's --version) + - name: check-version + description: "Check if version is installed" + flags: + - name: version + type: bool + description: "Show version info" + steps: + - "echo Version check: {{ .Flags.version }}" diff --git a/tests/fixtures/scenarios/flag-inheritance/atmos.yaml b/tests/fixtures/scenarios/flag-inheritance/atmos.yaml new file mode 100644 index 0000000000..5fa3e52e7f --- /dev/null +++ b/tests/fixtures/scenarios/flag-inheritance/atmos.yaml @@ -0,0 +1,49 @@ +# Test fixture for flag inheritance scenarios. +# Tests that custom commands can inherit flags from global/parent commands. + +base_path: "." + +components: + terraform: + base_path: "../../components/terraform" + +stacks: + base_path: "stacks" + included_paths: + - "**/*" + name_pattern: "{stage}" + +# Custom commands that test flag inheritance +commands: + # Test 1: Custom command inheriting global --verbose flag (bool) + - name: echo + description: Echo commands with verbose support + commands: + - name: info + description: Echo information with optional verbose output + flags: + - name: verbose + shorthand: v + type: bool + description: Enable verbose output + steps: + - 'echo "Info: This is a basic message"' + - '{{ if .Flags.verbose }}echo "Verbose: Additional details enabled"{{ end }}' + + # Test 2: Custom command with --stack flag (same as terraform's parent flag) + - name: deploy + description: Deploy commands with stack support + commands: + - name: component + description: Deploy a component to a stack + arguments: + - name: component + description: Name of the component + flags: + - name: stack + shorthand: s + description: Name of the stack + required: true + steps: + - 'echo "Deploying component: {{ .Arguments.component }}"' + - 'echo "To stack: {{ .Flags.stack }}"' diff --git a/tests/fixtures/scenarios/flag-inheritance/stacks/dev.yaml b/tests/fixtures/scenarios/flag-inheritance/stacks/dev.yaml new file mode 100644 index 0000000000..5db0cd1ae3 --- /dev/null +++ b/tests/fixtures/scenarios/flag-inheritance/stacks/dev.yaml @@ -0,0 +1,9 @@ +# Minimal stack for flag inheritance tests +vars: + stage: dev + +components: + terraform: + mock: + vars: + message: "hello" diff --git a/tests/fixtures/scenarios/provenance/atmos.yaml b/tests/fixtures/scenarios/provenance/atmos.yaml new file mode 100644 index 0000000000..ebfa87de93 --- /dev/null +++ b/tests/fixtures/scenarios/provenance/atmos.yaml @@ -0,0 +1,14 @@ +# Minimal atmos configuration for provenance testing. +base_path: "." + +components: + terraform: + base_path: "../../components/terraform" + +stacks: + base_path: "stacks" + included_paths: + - "orgs/**/*" + excluded_paths: + - "**/_defaults.yaml" + name_template: "{{ .vars.tenant }}-{{ .vars.stage }}-{{ .vars.region }}" diff --git a/tests/fixtures/scenarios/provenance/stacks/catalog/mock/defaults.yaml b/tests/fixtures/scenarios/provenance/stacks/catalog/mock/defaults.yaml new file mode 100644 index 0000000000..022f5e970c --- /dev/null +++ b/tests/fixtures/scenarios/provenance/stacks/catalog/mock/defaults.yaml @@ -0,0 +1,11 @@ +# Base defaults for mock component (depth 3 in import chain). +components: + terraform: + mock: + metadata: + component: mock + vars: + enabled: true + name: mock + tags: + managed_by: atmos diff --git a/tests/fixtures/scenarios/provenance/stacks/mixins/stage/dev.yaml b/tests/fixtures/scenarios/provenance/stacks/mixins/stage/dev.yaml new file mode 100644 index 0000000000..412df3a801 --- /dev/null +++ b/tests/fixtures/scenarios/provenance/stacks/mixins/stage/dev.yaml @@ -0,0 +1,15 @@ +# Stage mixin for dev environment (depth 2 in import chain). +import: + - catalog/mock/defaults + +vars: + stage: dev + environment: development + +components: + terraform: + mock: + vars: + environment: development + tags: + stage: dev diff --git a/tests/fixtures/scenarios/provenance/stacks/orgs/acme/_defaults.yaml b/tests/fixtures/scenarios/provenance/stacks/orgs/acme/_defaults.yaml new file mode 100644 index 0000000000..86498ed7ee --- /dev/null +++ b/tests/fixtures/scenarios/provenance/stacks/orgs/acme/_defaults.yaml @@ -0,0 +1,14 @@ +# Organization defaults for acme (depth 2 in import chain). +import: + - mixins/stage/dev + +vars: + tenant: acme + namespace: acme + +components: + terraform: + mock: + vars: + tags: + tenant: acme diff --git a/tests/fixtures/scenarios/provenance/stacks/orgs/acme/dev/us-east-1.yaml b/tests/fixtures/scenarios/provenance/stacks/orgs/acme/dev/us-east-1.yaml new file mode 100644 index 0000000000..d5c35dd807 --- /dev/null +++ b/tests/fixtures/scenarios/provenance/stacks/orgs/acme/dev/us-east-1.yaml @@ -0,0 +1,14 @@ +# Stack file for acme-dev-use1 (depth 1 - the parent stack being described). +import: + - orgs/acme/_defaults + +vars: + region: us-east-1 + +components: + terraform: + mock: + vars: + region: us-east-1 + tags: + region: us-east-1 diff --git a/tests/snapshots/TestCLICommands_atmos_about_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_about_--help.stdout.golden index 358b9f672e..06bdeebbd9 100644 --- a/tests/snapshots/TestCLICommands_atmos_about_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_about_--help.stdout.golden @@ -86,6 +86,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_atlantis_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_atlantis_--help.stdout.golden index c82a1adb1a..8dbae42b3f 100644 --- a/tests/snapshots/TestCLICommands_atmos_atlantis_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_atlantis_--help.stdout.golden @@ -85,8 +85,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - Use atmos atlantis [command] --help for more information about a command. diff --git a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_--help.stdout.golden index 2c8dc77396..91656c774a 100644 --- a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_--help.stdout.golden @@ -85,8 +85,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - Use atmos atlantis generate [command] --help for more information about a diff --git a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_help.stdout.golden index 2c8dc77396..91656c774a 100644 --- a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_help.stdout.golden @@ -85,8 +85,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - Use atmos atlantis generate [command] --help for more information about a diff --git a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_--help.stdout.golden index ef97c295d0..503edc327f 100644 --- a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_--help.stdout.golden @@ -181,6 +181,4 @@ GLOBAL FLAGS --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_help.stdout.golden index ef97c295d0..503edc327f 100644 --- a/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_atlantis_generate_repo-config_help.stdout.golden @@ -181,6 +181,4 @@ GLOBAL FLAGS --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_atlantis_help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_atlantis_help.stdout.golden index c82a1adb1a..8dbae42b3f 100644 --- a/tests/snapshots/TestCLICommands_atmos_atlantis_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_atlantis_help.stdout.golden @@ -85,8 +85,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - Use atmos atlantis [command] --help for more information about a command. diff --git a/tests/snapshots/TestCLICommands_atmos_auth_env_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_auth_env_--help.stdout.golden index 1b87be9549..940f0569db 100644 --- a/tests/snapshots/TestCLICommands_atmos_auth_env_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_auth_env_--help.stdout.golden @@ -81,6 +81,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_auth_exec_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_auth_exec_--help.stdout.golden index 5d9acf859b..e59689980d 100644 --- a/tests/snapshots/TestCLICommands_atmos_auth_exec_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_auth_exec_--help.stdout.golden @@ -84,6 +84,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_auth_login_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_auth_login_--help.stdout.golden index 4946db9a50..1267d709c8 100644 --- a/tests/snapshots/TestCLICommands_atmos_auth_login_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_auth_login_--help.stdout.golden @@ -78,6 +78,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_auth_user_configure_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_auth_user_configure_--help.stdout.golden index 675f2bf236..09f8dee376 100644 --- a/tests/snapshots/TestCLICommands_atmos_auth_user_configure_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_auth_user_configure_--help.stdout.golden @@ -76,6 +76,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_auth_validate_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_auth_validate_--help.stdout.golden index 9ebd2e6777..5bf338d682 100644 --- a/tests/snapshots/TestCLICommands_atmos_auth_validate_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_auth_validate_--help.stdout.golden @@ -77,6 +77,4 @@ GLOBAL FLAGS --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_auth_whoami_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_auth_whoami_--help.stdout.golden index 815efe156b..c7c9bb0973 100644 --- a/tests/snapshots/TestCLICommands_atmos_auth_whoami_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_auth_whoami_--help.stdout.golden @@ -78,6 +78,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden b/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden index f3e81e646d..6a8e02d91f 100644 --- a/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden @@ -233,5 +233,19 @@ "use_lock_file": false }, "profiles": {}, - "metadata": {} + "metadata": {}, + "list": { + "components": { + "format": "", + "columns": null + }, + "instances": { + "format": "", + "columns": null + }, + "stacks": { + "format": "", + "columns": null + } + } } diff --git a/tests/snapshots/TestCLICommands_atmos_helmfile_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_helmfile_--help.stdout.golden index a6cefba919..e8a661f035 100644 --- a/tests/snapshots/TestCLICommands_atmos_helmfile_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_helmfile_--help.stdout.golden @@ -98,8 +98,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - Use atmos helmfile [command] --help for more information about a command. diff --git a/tests/snapshots/TestCLICommands_atmos_helmfile_apply_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_helmfile_apply_--help.stdout.golden index 2622fdfdca..fc210f6422 100644 --- a/tests/snapshots/TestCLICommands_atmos_helmfile_apply_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_helmfile_apply_--help.stdout.golden @@ -89,6 +89,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_helmfile_apply_help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_helmfile_apply_help.stdout.golden index 2622fdfdca..fc210f6422 100644 --- a/tests/snapshots/TestCLICommands_atmos_helmfile_apply_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_helmfile_apply_help.stdout.golden @@ -89,6 +89,4 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - diff --git a/tests/snapshots/TestCLICommands_atmos_helmfile_help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_helmfile_help.stdout.golden index a6cefba919..e8a661f035 100644 --- a/tests/snapshots/TestCLICommands_atmos_helmfile_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_helmfile_help.stdout.golden @@ -98,8 +98,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - Use atmos helmfile [command] --help for more information about a command. diff --git a/tests/snapshots/TestCLICommands_atmos_terraform_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_terraform_--help.stdout.golden index 45ef911c2c..e0b373ff0d 100644 --- a/tests/snapshots/TestCLICommands_atmos_terraform_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_terraform_--help.stdout.golden @@ -170,8 +170,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - COMPATIBILITY FLAGS diff --git a/tests/snapshots/TestCLICommands_atmos_terraform_--help_alias_subcommand_check.stdout.golden b/tests/snapshots/TestCLICommands_atmos_terraform_--help_alias_subcommand_check.stdout.golden index 45ef911c2c..e0b373ff0d 100644 --- a/tests/snapshots/TestCLICommands_atmos_terraform_--help_alias_subcommand_check.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_terraform_--help_alias_subcommand_check.stdout.golden @@ -170,8 +170,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - COMPATIBILITY FLAGS diff --git a/tests/snapshots/TestCLICommands_atmos_terraform_apply_--help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_terraform_apply_--help.stdout.golden index ec7dc6835d..5c23423e2f 100644 --- a/tests/snapshots/TestCLICommands_atmos_terraform_apply_--help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_terraform_apply_--help.stdout.golden @@ -147,8 +147,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - COMPATIBILITY FLAGS diff --git a/tests/snapshots/TestCLICommands_atmos_terraform_apply_help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_terraform_apply_help.stdout.golden index ec7dc6835d..5c23423e2f 100644 --- a/tests/snapshots/TestCLICommands_atmos_terraform_apply_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_terraform_apply_help.stdout.golden @@ -147,8 +147,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - COMPATIBILITY FLAGS diff --git a/tests/snapshots/TestCLICommands_atmos_terraform_help.stdout.golden b/tests/snapshots/TestCLICommands_atmos_terraform_help.stdout.golden index 6c66a984de..0275dc65f7 100644 --- a/tests/snapshots/TestCLICommands_atmos_terraform_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_terraform_help.stdout.golden @@ -168,8 +168,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - COMPATIBILITY FLAGS diff --git a/tests/snapshots/TestCLICommands_config_alias_tp_--help_shows_terraform_plan_help.stdout.golden b/tests/snapshots/TestCLICommands_config_alias_tp_--help_shows_terraform_plan_help.stdout.golden index b5e65799c1..06ec5932d8 100644 --- a/tests/snapshots/TestCLICommands_config_alias_tp_--help_shows_terraform_plan_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_config_alias_tp_--help_shows_terraform_plan_help.stdout.golden @@ -149,8 +149,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - COMPATIBILITY FLAGS diff --git a/tests/snapshots/TestCLICommands_config_alias_tr_--help_shows_terraform_help.stdout.golden b/tests/snapshots/TestCLICommands_config_alias_tr_--help_shows_terraform_help.stdout.golden index 45ef911c2c..e0b373ff0d 100644 --- a/tests/snapshots/TestCLICommands_config_alias_tr_--help_shows_terraform_help.stdout.golden +++ b/tests/snapshots/TestCLICommands_config_alias_tr_--help_shows_terraform_help.stdout.golden @@ -170,8 +170,6 @@ GLOBAL FLAGS -v, --verbose Enable verbose error output with full context, stack traces, and detailed information - --version Display the Atmos CLI version - COMPATIBILITY FLAGS diff --git a/tests/snapshots/TestCLICommands_deploy_component_help_shows_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_deploy_component_help_shows_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_deploy_component_help_shows_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_deploy_component_help_shows_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_deploy_component_help_shows_stack_flag.stdout.golden new file mode 100644 index 0000000000..35baa00e68 --- /dev/null +++ b/tests/snapshots/TestCLICommands_deploy_component_help_shows_stack_flag.stdout.golden @@ -0,0 +1,84 @@ + +👽 test darwin/arm64 + +Deploy a component to a stack + +USAGE + + + $ atmos deploy component [flags] + + +FLAGS + + -- Use double dashes to separate Atmos-specific options from native arguments and flags for the + command. + + -h, --help help for component + + --identity string Identity to use for authentication (overrides identity in command config) + + -s, --stack string Name of the stack + + +GLOBAL FLAGS + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in this + directory) + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) (default + bar) + + --interactive Enable interactive prompts for missing required flags, optional value flags using the + sentinel pattern, and missing positional arguments (requires TTY, disabled in CI) + (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard file + descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed information + + diff --git a/tests/snapshots/TestCLICommands_deploy_component_runs_with_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_deploy_component_runs_with_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_deploy_component_runs_with_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_deploy_component_runs_with_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_deploy_component_runs_with_stack_flag.stdout.golden new file mode 100644 index 0000000000..e0e7413529 --- /dev/null +++ b/tests/snapshots/TestCLICommands_deploy_component_runs_with_stack_flag.stdout.golden @@ -0,0 +1,2 @@ +Deploying component: echo +To stack: dev diff --git a/tests/snapshots/TestCLICommands_describe_component_help_shows_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_describe_component_help_shows_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_help_shows_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_describe_component_help_shows_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_describe_component_help_shows_stack_flag.stdout.golden new file mode 100644 index 0000000000..9ad3f9d6a6 --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_help_shows_stack_flag.stdout.golden @@ -0,0 +1,132 @@ + +👽 test darwin/arm64 + +Display the configuration details for a specific Atmos component within a +designated Atmos stack, including its dependencies, settings, and overrides. + +USAGE + + + $ atmos describe component [flags] + + +EXAMPLES + + + - The output format + + + $ atmos describe component -s --format=yaml|json + + + - Write the result to the file + + + $ atmos describe component -s --file component.yaml + + + - Enable/disable Go template processing in Atmos stack manifests when executing the command + + + $ atmos describe component -s --process-templates=false + + + - Enable/disable YAML functions processing in Atmos stack manifests when executing the command + + + $ atmos describe component -s --process-functions=false + + + - Skip executing a YAML function in the Atmos stack manifests when executing the command + + + $ atmos describe component -s --skip=terraform.output + + +FLAGS + + --file string Write the result to the file + + -f, --format string The output format (default yaml) + + -h, --help help for component + + --process-functions Enable/disable YAML functions processing in Atmos stack manifests when executing the + command (default true) + + --process-templates Enable/disable Go template processing in Atmos stack manifests when executing the command + (default true) + + --provenance Enable provenance tracking to show where configuration values originated + + --skip strings Skip executing a YAML function in the Atmos stack manifests when executing the command + + -s, --stack string The stack flag specifies the environment or configuration set for deployment in Atmos + CLI. + + +GLOBAL FLAGS + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in this + directory) + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) (default + bar) + + -i, --identity string Specify the identity to authenticate with before describing + + --interactive Enable interactive prompts for missing required flags, optional value flags using the + sentinel pattern, and missing positional arguments (requires TTY, disabled in CI) + (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard file + descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + -q, --query string Query the results of an atmos describe command using yq expressions + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed information + + diff --git a/tests/snapshots/TestCLICommands_describe_component_provenance_advanced.stderr.golden b/tests/snapshots/TestCLICommands_describe_component_provenance_advanced.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_provenance_advanced.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_describe_component_provenance_advanced.stdout.golden b/tests/snapshots/TestCLICommands_describe_component_provenance_advanced.stdout.golden new file mode 100644 index 0000000000..d35c5c245c --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_provenance_advanced.stdout.golden @@ -0,0 +1,46 @@ +# Provenance Legend: +# ● [1] Defined in parent stack +# ○ [N] Inherited/imported (N=2+ levels deep) +# ∴ Computed/templated + +# Stack: orgs/acme/plat/dev/us-east-2 + +import: # ○ [3] orgs/acme/plat/_defaults.yaml:2 + - catalog/vpc-flow-logs-bucket/defaults # ○ [3] orgs/acme/plat/_defaults.yaml:2 + - catalog/vpc/defaults # ○ [3] orgs/acme/plat/_defaults.yaml:3 + - catalog/vpc/dev # ● [1] orgs/acme/plat/dev/us-east-2.yaml:5 + - catalog/vpc/ue2 # ○ [2] mixins/region/us-east-2.yaml:3 + - mixins/region/us-east-2 # ● [1] orgs/acme/plat/dev/us-east-2.yaml:3 + - mixins/stage/dev # ○ [2] orgs/acme/plat/dev/_defaults.yaml:3 + - mixins/tenant/plat # ○ [3] orgs/acme/plat/_defaults.yaml:3 + - orgs/acme/_defaults # ○ [3] orgs/acme/plat/_defaults.yaml:2 + - orgs/acme/plat/_defaults # ○ [2] orgs/acme/plat/dev/_defaults.yaml:2 + - orgs/acme/plat/dev/_defaults # ● [1] orgs/acme/plat/dev/us-east-2.yaml:2 +settings: # ○ [4] catalog/vpc/defaults.yaml:10 + templates: # ○ [4] orgs/acme/_defaults.yaml:7 + settings: # ○ [4] orgs/acme/_defaults.yaml:9 + gomplate: # ○ [4] orgs/acme/_defaults.yaml:12 + datasources: {} # ○ [4] orgs/acme/_defaults.yaml:14 + timeout: 5 # ○ [4] orgs/acme/_defaults.yaml:12 + sprig: {} # ○ [4] orgs/acme/_defaults.yaml:9 +vars: # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:9 + enabled: true # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:9 + environment: ue2 # ○ [2] mixins/region/us-east-2.yaml:9 + force_destroy: true # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:12 + lifecycle_rule_enabled: false # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:13 + name: vpc-flow-logs # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:10 + namespace: acme # ○ [4] orgs/acme/_defaults.yaml:2 + region: us-east-2 # ○ [2] mixins/region/us-east-2.yaml:8 + stage: dev # ○ [3] mixins/stage/dev.yaml:2 + tags: # ○ [4] orgs/acme/_defaults.yaml:20 + atmos_component: vpc-flow-logs-bucket # ○ [4] orgs/acme/_defaults.yaml:20 + atmos_component_description: >- # ○ [4] orgs/acme/_defaults.yaml:29 + Vpc-Flow-Logs-Bucket component "vpc-flow-logs" provisioned in the stack "plat-ue2-dev" + atmos_manifest: orgs/acme/plat/dev/us-east-2 # ○ [4] orgs/acme/_defaults.yaml:22 + atmos_stack: plat-ue2-dev # ○ [4] orgs/acme/_defaults.yaml:21 + provisioned_by_user: user # ○ [4] orgs/acme/_defaults.yaml:27 + terraform_component: vpc-flow-logs-bucket # ○ [4] orgs/acme/_defaults.yaml:24 + terraform_workspace: plat-ue2-dev # ○ [4] orgs/acme/_defaults.yaml:23 + tenant: plat # ○ [4] mixins/tenant/plat.yaml:2 + traffic_type: ALL # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:11 + diff --git a/tests/snapshots/TestCLICommands_describe_component_provenance_basic.stderr.golden b/tests/snapshots/TestCLICommands_describe_component_provenance_basic.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_provenance_basic.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_describe_component_provenance_basic.stdout.golden b/tests/snapshots/TestCLICommands_describe_component_provenance_basic.stdout.golden new file mode 100644 index 0000000000..fac734f399 --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_provenance_basic.stdout.golden @@ -0,0 +1,25 @@ +# Provenance Legend: +# ● [1] Defined in parent stack +# ○ [N] Inherited/imported (N=2+ levels deep) +# ∴ Computed/templated + +# Stack: orgs/acme/dev/us-east-1 + +import: # ○ [3] mixins/stage/dev.yaml:3 + - catalog/mock/defaults # ○ [3] mixins/stage/dev.yaml:3 + - mixins/stage/dev # ○ [2] orgs/acme/_defaults.yaml:3 + - orgs/acme/_defaults # ● [1] orgs/acme/dev/us-east-1.yaml:3 +vars: # ○ [4] catalog/mock/defaults.yaml:8 + enabled: true # ○ [4] catalog/mock/defaults.yaml:8 + environment: development # ○ [3] mixins/stage/dev.yaml:13 + name: mock # ○ [4] catalog/mock/defaults.yaml:9 + namespace: acme # ○ [2] orgs/acme/_defaults.yaml:7 + region: us-east-1 # ● [1] orgs/acme/dev/us-east-1.yaml:12 + stage: dev # ○ [3] mixins/stage/dev.yaml:6 + tags: # ○ [4] catalog/mock/defaults.yaml:11 + managed_by: atmos # ○ [4] catalog/mock/defaults.yaml:11 + region: us-east-1 # ● [1] orgs/acme/dev/us-east-1.yaml:14 + stage: dev # ○ [3] mixins/stage/dev.yaml:15 + tenant: acme # ○ [2] orgs/acme/_defaults.yaml:14 + tenant: acme # ○ [2] orgs/acme/_defaults.yaml:6 + diff --git a/tests/snapshots/TestCLICommands_describe_component_with_provenance_and_stack.stderr.golden b/tests/snapshots/TestCLICommands_describe_component_with_provenance_and_stack.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_with_provenance_and_stack.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_describe_component_with_provenance_and_stack.stdout.golden b/tests/snapshots/TestCLICommands_describe_component_with_provenance_and_stack.stdout.golden new file mode 100644 index 0000000000..d35c5c245c --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_with_provenance_and_stack.stdout.golden @@ -0,0 +1,46 @@ +# Provenance Legend: +# ● [1] Defined in parent stack +# ○ [N] Inherited/imported (N=2+ levels deep) +# ∴ Computed/templated + +# Stack: orgs/acme/plat/dev/us-east-2 + +import: # ○ [3] orgs/acme/plat/_defaults.yaml:2 + - catalog/vpc-flow-logs-bucket/defaults # ○ [3] orgs/acme/plat/_defaults.yaml:2 + - catalog/vpc/defaults # ○ [3] orgs/acme/plat/_defaults.yaml:3 + - catalog/vpc/dev # ● [1] orgs/acme/plat/dev/us-east-2.yaml:5 + - catalog/vpc/ue2 # ○ [2] mixins/region/us-east-2.yaml:3 + - mixins/region/us-east-2 # ● [1] orgs/acme/plat/dev/us-east-2.yaml:3 + - mixins/stage/dev # ○ [2] orgs/acme/plat/dev/_defaults.yaml:3 + - mixins/tenant/plat # ○ [3] orgs/acme/plat/_defaults.yaml:3 + - orgs/acme/_defaults # ○ [3] orgs/acme/plat/_defaults.yaml:2 + - orgs/acme/plat/_defaults # ○ [2] orgs/acme/plat/dev/_defaults.yaml:2 + - orgs/acme/plat/dev/_defaults # ● [1] orgs/acme/plat/dev/us-east-2.yaml:2 +settings: # ○ [4] catalog/vpc/defaults.yaml:10 + templates: # ○ [4] orgs/acme/_defaults.yaml:7 + settings: # ○ [4] orgs/acme/_defaults.yaml:9 + gomplate: # ○ [4] orgs/acme/_defaults.yaml:12 + datasources: {} # ○ [4] orgs/acme/_defaults.yaml:14 + timeout: 5 # ○ [4] orgs/acme/_defaults.yaml:12 + sprig: {} # ○ [4] orgs/acme/_defaults.yaml:9 +vars: # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:9 + enabled: true # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:9 + environment: ue2 # ○ [2] mixins/region/us-east-2.yaml:9 + force_destroy: true # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:12 + lifecycle_rule_enabled: false # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:13 + name: vpc-flow-logs # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:10 + namespace: acme # ○ [4] orgs/acme/_defaults.yaml:2 + region: us-east-2 # ○ [2] mixins/region/us-east-2.yaml:8 + stage: dev # ○ [3] mixins/stage/dev.yaml:2 + tags: # ○ [4] orgs/acme/_defaults.yaml:20 + atmos_component: vpc-flow-logs-bucket # ○ [4] orgs/acme/_defaults.yaml:20 + atmos_component_description: >- # ○ [4] orgs/acme/_defaults.yaml:29 + Vpc-Flow-Logs-Bucket component "vpc-flow-logs" provisioned in the stack "plat-ue2-dev" + atmos_manifest: orgs/acme/plat/dev/us-east-2 # ○ [4] orgs/acme/_defaults.yaml:22 + atmos_stack: plat-ue2-dev # ○ [4] orgs/acme/_defaults.yaml:21 + provisioned_by_user: user # ○ [4] orgs/acme/_defaults.yaml:27 + terraform_component: vpc-flow-logs-bucket # ○ [4] orgs/acme/_defaults.yaml:24 + terraform_workspace: plat-ue2-dev # ○ [4] orgs/acme/_defaults.yaml:23 + tenant: plat # ○ [4] mixins/tenant/plat.yaml:2 + traffic_type: ALL # ○ [3] catalog/vpc-flow-logs-bucket/defaults.yaml:11 + diff --git a/tests/snapshots/TestCLICommands_describe_component_with_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_describe_component_with_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_with_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_describe_component_with_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_describe_component_with_stack_flag.stdout.golden new file mode 100644 index 0000000000..63d1a529e6 --- /dev/null +++ b/tests/snapshots/TestCLICommands_describe_component_with_stack_flag.stdout.golden @@ -0,0 +1,1261 @@ +atmos_cli_config: + base_path: . + components: + terraform: + base_path: components/terraform + apply_auto_approve: false + append_user_agent: Atmos/test (Cloud Posse; +https://atmos.tools) + deploy_run_init: true + init_run_reconfigure: true + auto_generate_backend_file: true + auto_generate_files: false + command: terraform + shell: + prompt: "" + init: + pass_vars: false + plan: + skip_planfile: false + plugin_cache: true + helmfile: + base_path: components/helmfile + use_eks: true + kubeconfig_path: /dev/shm + helm_aws_profile_pattern: '{namespace}-{tenant}-gbl-{stage}-helm' + cluster_name_pattern: '{namespace}-{tenant}-{environment}-{stage}-eks-cluster' + command: "" + packer: + base_path: "" + command: "" + stacks: + base_path: stacks + included_paths: + - orgs/**/* + excluded_paths: + - '**/_defaults.yaml' + name_pattern: '{tenant}-{environment}-{stage}' + name_template: "" + workflows: + base_path: stacks/workflows + list: + format: "" + columns: [] +atmos_component: vpc +atmos_manifest: orgs/acme/plat/dev/us-east-2 +atmos_stack: plat-ue2-dev +atmos_stack_file: orgs/acme/plat/dev/us-east-2 +auth: {} +backend: {} +backend_type: "" +cli_args: + - describe + - component +command: terraform +component: vpc +component_info: + component_path: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc + component_type: terraform + terraform_config: + path: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc + variables: + additional_tag_map: + name: additional_tag_map + type: map(string) + description: | + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + default: {} + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 181 + assign_generated_ipv6_cidr_block: + name: assign_generated_ipv6_cidr_block + type: bool + description: When `true`, assign AWS generated IPv6 CIDR block to the VPC. Conflicts with `ipv6_ipam_pool_id`. + default: false + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 92 + attributes: + name: attributes + type: list(string) + description: | + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + default: [] + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 146 + availability_zone_ids: + name: availability_zone_ids + type: list(string) + description: | + List of Availability Zones IDs where subnets will be created. Overrides `availability_zones`. + Can be the full name, e.g. `use1-az1`, or just the part after the AZ ID region code, e.g. `-az1`, + to allow reusable values across regions. Consider contention for resources and spot pricing in each AZ when selecting. + Useful in some regions when using only some AZs and you want to use the same ones across multiple accounts. + default: [] + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 20 + availability_zones: + name: availability_zones + type: list(string) + description: | + List of Availability Zones (AZs) where subnets will be created. Ignored when `availability_zone_ids` is set. + Can be the full name, e.g. `us-east-1a`, or just the part after the region, e.g. `a` to allow reusable values across regions. + The order of zones in the list ***must be stable*** or else Terraform will continually make changes. + If no AZs are specified, then `max_subnet_count` AZs will be selected in alphabetical order. + If `max_subnet_count > 0` and `length(var.availability_zones) > max_subnet_count`, the list + will be truncated. We recommend setting `availability_zones` and `max_subnet_count` explicitly as constant + (not computed) values for predictability, consistency, and stability. + default: [] + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 6 + context: + name: context + type: any + description: | + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + default: + additional_tag_map: {} + attributes: [] + delimiter: null + descriptor_formats: {} + enabled: true + environment: null + id_length_limit: null + label_key_case: null + label_order: [] + label_value_case: null + labels_as_tags: + - unset + name: null + namespace: null + regex_replace_chars: null + stage: null + tags: {} + tenant: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 50 + delimiter: + name: delimiter + type: string + description: | + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 137 + descriptor_formats: + name: descriptor_formats + type: any + description: | + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + default: {} + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 260 + enabled: + name: enabled + type: bool + description: Set to false to prevent the module from creating any resources + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 97 + environment: + name: environment + type: string + description: ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 115 + gateway_vpc_endpoints: + name: gateway_vpc_endpoints + type: set(string) + description: A list of Gateway VPC Endpoints to provision into the VPC. Only valid values are "dynamodb" and "s3". + default: [] + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 199 + id_length_limit: + name: id_length_limit + type: number + description: | + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 211 + interface_vpc_endpoints: + name: interface_vpc_endpoints + type: set(string) + description: A list of Interface VPC Endpoints to provision into the VPC. + default: [] + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 205 + ipv4_additional_cidr_block_associations: + name: ipv4_additional_cidr_block_associations + type: |- + map(object({ + ipv4_cidr_block = string + ipv4_ipam_pool_id = string + ipv4_netmask_length = number + })) + description: | + IPv4 CIDR blocks to assign to the VPC. + `ipv4_cidr_block` can be set explicitly, or set to `null` with the CIDR block derived from `ipv4_ipam_pool_id` using `ipv4_netmask_length`. + Map keys must be known at `plan` time, and are only used to track changes. + default: {} + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 53 + ipv4_cidr_block_association_timeouts: + name: ipv4_cidr_block_association_timeouts + type: |- + object({ + create = string + delete = string + }) + description: Timeouts (in `go` duration format) for creating and destroying IPv4 CIDR block associations + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 67 + ipv4_cidrs: + name: ipv4_cidrs + type: |- + list(object({ + private = list(string) + public = list(string) + })) + description: | + Lists of CIDRs to assign to subnets. Order of CIDRs in the lists must not change over time. + Lists may contain more CIDRs than needed. + default: [] + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 76 + ipv4_primary_cidr_block: + name: ipv4_primary_cidr_block + type: string + description: | + The primary IPv4 CIDR block for the VPC. + Either `ipv4_primary_cidr_block` or `ipv4_primary_cidr_block_association` must be set, but not both. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 31 + ipv4_primary_cidr_block_association: + name: ipv4_primary_cidr_block_association + type: |- + object({ + ipv4_ipam_pool_id = string + ipv4_netmask_length = number + }) + description: | + Configuration of the VPC's primary IPv4 CIDR block via IPAM. Conflicts with `ipv4_primary_cidr_block`. + One of `ipv4_primary_cidr_block` or `ipv4_primary_cidr_block_association` must be set. + Additional CIDR blocks can be set via `ipv4_additional_cidr_block_associations`. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 40 + label_key_case: + name: label_key_case + type: string + description: | + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 226 + label_order: + name: label_order + type: list(string) + description: | + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 191 + label_value_case: + name: label_value_case + type: string + description: | + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 242 + labels_as_tags: + name: labels_as_tags + type: set(string) + description: | + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + default: + - default + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 157 + map_public_ip_on_launch: + name: map_public_ip_on_launch + type: bool + description: Instances launched into a public subnet should be assigned a public IP address + default: true + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 136 + max_subnet_count: + name: max_subnet_count + type: number + description: Sets the maximum amount of subnets to deploy. 0 will deploy a subnet for every provided availability zone (in `region_availability_zones` variable) within the region + default: 0 + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 147 + name: + name: name + type: string + description: | + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 127 + namespace: + name: namespace + type: string + description: ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 103 + nat_eip_aws_shield_protection_enabled: + name: nat_eip_aws_shield_protection_enabled + type: bool + description: Enable or disable AWS Shield Advanced protection for NAT EIPs. If set to 'true', a subscription to AWS Shield Advanced must exist in this account. + default: false + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 193 + nat_gateway_enabled: + name: nat_gateway_enabled + type: bool + description: Flag to enable/disable NAT gateways + default: true + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 107 + nat_instance_ami_id: + name: nat_instance_ami_id + type: list(string) + description: | + A list optionally containing the ID of the AMI to use for the NAT instance. + If the list is empty (the default), the latest official AWS NAT instance AMI + will be used. NOTE: The Official NAT instance AMI is being phased out and + does not support NAT64. Use of a NAT gateway is recommended instead. + default: [] + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 125 + nat_instance_enabled: + name: nat_instance_enabled + type: bool + description: Flag to enable/disable NAT instances + default: false + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 113 + nat_instance_type: + name: nat_instance_type + type: string + description: NAT Instance type + default: t3.micro + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 119 + public_subnets_enabled: + name: public_subnets_enabled + type: bool + description: | + If false, do not create public subnets. + Since NAT gateways and instances must be created in public subnets, these will also not be created when `false`. + default: true + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 98 + regex_replace_chars: + name: regex_replace_chars + type: string + description: | + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 201 + region: + name: region + type: string + description: AWS Region + default: null + required: true + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 1 + stage: + name: stage + type: string + description: ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 121 + subnet_type_tag_key: + name: subnet_type_tag_key + type: string + description: Key for subnet type tag to provide information about the type of subnets, e.g. `cpco/subnet/type=private` or `cpcp/subnet/type=public` + default: null + required: true + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 142 + tags: + name: tags + type: map(string) + description: | + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + default: {} + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 172 + tenant: + name: tenant + type: string + description: ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 109 + vpc_flow_logs_bucket_environment_name: + name: vpc_flow_logs_bucket_environment_name + type: string + description: The name of the environment where the VPC Flow Logs bucket is provisioned + default: "" + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 171 + vpc_flow_logs_bucket_stage_name: + name: vpc_flow_logs_bucket_stage_name + type: string + description: The stage (account) name where the VPC Flow Logs bucket is provisioned + default: "" + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 177 + vpc_flow_logs_bucket_tenant_name: + name: vpc_flow_logs_bucket_tenant_name + type: string + description: | + The name of the tenant where the VPC Flow Logs bucket is provisioned. + + If the `tenant` label is not used, leave this as `null`. + default: null + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 183 + vpc_flow_logs_enabled: + name: vpc_flow_logs_enabled + type: bool + description: Enable or disable the VPC Flow Logs + default: true + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 153 + vpc_flow_logs_log_destination_type: + name: vpc_flow_logs_log_destination_type + type: string + description: 'The type of the logging destination. Valid values: `cloud-watch-logs`, `s3`' + default: s3 + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 165 + vpc_flow_logs_traffic_type: + name: vpc_flow_logs_traffic_type + type: string + description: 'The type of traffic to capture. Valid values: `ACCEPT`, `REJECT`, `ALL`' + default: ALL + required: false + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/variables.tf + line: 159 + outputs: + availability_zones: + name: availability_zones + description: List of Availability Zones where subnets were created + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 117 + type: "" + az_private_subnets_map: + name: az_private_subnets_map + description: Map of AZ names to list of private subnet IDs in the AZs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 122 + type: "" + az_public_subnets_map: + name: az_public_subnets_map + description: Map of AZ names to list of public subnet IDs in the AZs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 127 + type: "" + interface_vpc_endpoints: + name: interface_vpc_endpoints + description: List of Interface VPC Endpoints in this VPC. + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 112 + type: "" + max_subnet_count: + name: max_subnet_count + description: Maximum allowed number of subnets before all subnet CIDRs need to be recomputed + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 102 + type: "" + nat_eip_protections: + name: nat_eip_protections + description: List of AWS Shield Advanced Protections for NAT Elastic IPs. + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 107 + type: "" + nat_gateway_ids: + name: nat_gateway_ids + description: NAT Gateway IDs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 87 + type: "" + nat_gateway_public_ips: + name: nat_gateway_public_ips + description: NAT Gateway public IPs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 97 + type: "" + nat_instance_ids: + name: nat_instance_ids + description: NAT Instance IDs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 92 + type: "" + private_route_table_ids: + name: private_route_table_ids + description: Private subnet route table IDs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 65 + type: "" + private_subnet_cidrs: + name: private_subnet_cidrs + description: Private subnet CIDRs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 16 + type: "" + private_subnet_ids: + name: private_subnet_ids + description: Private subnet IDs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 11 + type: "" + public_route_table_ids: + name: public_route_table_ids + description: Public subnet route table IDs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 70 + type: "" + public_subnet_cidrs: + name: public_subnet_cidrs + description: Public subnet CIDRs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 6 + type: "" + public_subnet_ids: + name: public_subnet_ids + description: Public subnet IDs + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 1 + type: "" + route_tables: + name: route_tables + description: Route tables info map + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 75 + type: "" + subnets: + name: subnets + description: Subnets info map + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 21 + type: "" + vpc: + name: vpc + description: VPC info map + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 55 + type: "" + vpc_cidr: + name: vpc_cidr + description: VPC CIDR + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 50 + type: "" + vpc_default_network_acl_id: + name: vpc_default_network_acl_id + description: The ID of the network ACL created by default on VPC creation + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 35 + type: "" + vpc_default_security_group_id: + name: vpc_default_security_group_id + description: The ID of the security group created by default on VPC creation + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 40 + type: "" + vpc_id: + name: vpc_id + description: VPC ID + sensitive: false + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/outputs.tf + line: 45 + type: "" + requiredcore: + - '>= 1.0.0' + requiredproviders: + aws: + source: hashicorp/aws + versionconstraints: + - '>= 4.9.0' + configurationaliases: [] + providerconfigs: + aws: + name: aws + alias: "" + managedresources: + aws_flow_log.default: + mode: 77 + type: aws_flow_log + name: default + provider: + name: aws + alias: "" + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/vpc-flow-logs.tf + line: 3 + aws_shield_protection.nat_eip_shield_protection: + mode: 77 + type: aws_shield_protection + name: nat_eip_shield_protection + provider: + name: aws + alias: "" + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 174 + dataresources: + data.aws_caller_identity.current: + mode: 68 + type: aws_caller_identity + name: current + provider: + name: aws + alias: "" + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 164 + data.aws_eip.eip: + mode: 68 + type: aws_eip + name: eip + provider: + name: aws + alias: "" + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 168 + modulecalls: + endpoint_security_groups: + name: endpoint_security_groups + source: cloudposse/security-group/aws + version: 2.2.0 + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 98 + subnets: + name: subnets + source: cloudposse/dynamic-subnets/aws + version: 2.3.0 + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 140 + this: + name: this + source: cloudposse/label/null + version: 0.25.0 + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/context.tf + line: 23 + utils: + name: utils + source: cloudposse/utils/aws + version: 1.4.0 + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 68 + vpc: + name: vpc + source: cloudposse/vpc/aws + version: 2.1.1 + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 73 + vpc_endpoints: + name: vpc_endpoints + source: cloudposse/vpc/aws/modules/vpc-endpoints + version: 2.1.0 + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/main.tf + line: 127 + vpc_flow_logs_bucket: + name: vpc_flow_logs_bucket + source: cloudposse/stack-config/yaml/modules/remote-state + version: 1.5.0 + pos: + filename: /absolute/path/to/repo/examples/quick-start-advanced/components/terraform/vpc/remote-state.tf + line: 1 + diagnostics: [] +component_type: terraform +deps: + - catalog/vpc/defaults + - catalog/vpc/ue2 + - mixins/region/us-east-2 + - mixins/stage/dev + - mixins/tenant/plat + - orgs/acme/_defaults +deps_all: + - catalog/vpc/defaults + - catalog/vpc/dev + - catalog/vpc/ue2 + - mixins/region/us-east-2 + - mixins/stage/dev + - mixins/tenant/plat + - orgs/acme/_defaults + - orgs/acme/plat/dev/us-east-2 +env: {} +generate: {} +hooks: {} +imports: + - catalog/vpc-flow-logs-bucket/defaults + - catalog/vpc/defaults + - catalog/vpc/dev + - catalog/vpc/ue2 + - mixins/region/us-east-2 + - mixins/stage/dev + - mixins/tenant/plat + - orgs/acme/_defaults + - orgs/acme/plat/_defaults + - orgs/acme/plat/dev/_defaults +inheritance: [] +metadata: + component: vpc + description: Virtual Private Cloud with subnets and NAT gateway +overrides: {} +providers: {} +provision: {} +remote_state_backend: {} +remote_state_backend_type: "" +settings: + depends_on: + 1: + component: vpc-flow-logs-bucket + templates: + settings: + gomplate: + datasources: {} + timeout: 5 + sprig: {} + validation: + check-vpc-component-config-with-opa-policy: + description: Check 'vpc' component configuration using OPA policy + disabled: false + module_paths: + - catalog/constants + schema_path: vpc/validate-vpc-component.rego + schema_type: opa + timeout: 10 + validate-vpc-component-with-jsonschema: + description: Validate 'vpc' component variables using JSON Schema + schema_path: vpc/validate-vpc-component.json + schema_type: jsonschema +source: {} +sources: + backend: {} + env: {} + settings: + depends_on: + final_value: + 1: + component: vpc-flow-logs-bucket + name: depends_on + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.settings + variable_value: + 1: + component: vpc-flow-logs-bucket + templates: + final_value: + settings: + gomplate: + datasources: {} + timeout: 5 + sprig: {} + name: templates + stack_dependencies: + - dependency_type: import + stack_file: orgs/acme/_defaults + stack_file_section: settings + variable_value: + settings: + gomplate: + datasources: {} + timeout: 5 + sprig: {} + validation: + final_value: + check-vpc-component-config-with-opa-policy: + description: Check 'vpc' component configuration using OPA policy + disabled: false + module_paths: + - catalog/constants + schema_path: vpc/validate-vpc-component.rego + schema_type: opa + timeout: 10 + validate-vpc-component-with-jsonschema: + description: Validate 'vpc' component variables using JSON Schema + schema_path: vpc/validate-vpc-component.json + schema_type: jsonschema + name: validation + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.settings + variable_value: + check-vpc-component-config-with-opa-policy: + description: Check 'vpc' component configuration using OPA policy + disabled: false + module_paths: + - catalog/constants + schema_path: vpc/validate-vpc-component.rego + schema_type: opa + timeout: 10 + validate-vpc-component-with-jsonschema: + description: Validate 'vpc' component variables using JSON Schema + schema_path: vpc/validate-vpc-component.json + schema_type: jsonschema + vars: + assign_generated_ipv6_cidr_block: + final_value: false + name: assign_generated_ipv6_cidr_block + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: false + availability_zones: + final_value: + - us-east-2a + - us-east-2b + - us-east-2c + name: availability_zones + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/ue2 + stack_file_section: components.terraform.vars + variable_value: + - us-east-2a + - us-east-2b + - us-east-2c + enabled: + final_value: true + name: enabled + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: true + environment: + final_value: ue2 + name: environment + stack_dependencies: + - dependency_type: import + stack_file: mixins/region/us-east-2 + stack_file_section: vars + variable_value: ue2 + ipv4_primary_cidr_block: + final_value: 10.7.0.0/18 + name: ipv4_primary_cidr_block + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: 10.9.0.0/18 + - dependency_type: import + stack_file: catalog/vpc/dev + stack_file_section: components.terraform.vars + variable_value: 10.7.0.0/18 + map_public_ip_on_launch: + final_value: true + name: map_public_ip_on_launch + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: true + max_subnet_count: + final_value: 3 + name: max_subnet_count + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: 3 + name: + final_value: common + name: name + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: common + namespace: + final_value: acme + name: namespace + stack_dependencies: + - dependency_type: import + stack_file: orgs/acme/_defaults + stack_file_section: vars + variable_value: acme + nat_eip_aws_shield_protection_enabled: + final_value: false + name: nat_eip_aws_shield_protection_enabled + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: false + nat_gateway_enabled: + final_value: true + name: nat_gateway_enabled + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: true + nat_instance_enabled: + final_value: false + name: nat_instance_enabled + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: false + region: + final_value: us-east-2 + name: region + stack_dependencies: + - dependency_type: import + stack_file: mixins/region/us-east-2 + stack_file_section: vars + variable_value: us-east-2 + stage: + final_value: dev + name: stage + stack_dependencies: + - dependency_type: import + stack_file: mixins/stage/dev + stack_file_section: vars + variable_value: dev + subnet_type_tag_key: + final_value: acme/subnet/type + name: subnet_type_tag_key + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: acme/subnet/type + tags: + final_value: + atmos_component: vpc + atmos_component_description: Vpc component "common" provisioned in the stack "plat-ue2-dev" + atmos_manifest: orgs/acme/plat/dev/us-east-2 + atmos_stack: plat-ue2-dev + provisioned_by_user: user + terraform_component: vpc + terraform_workspace: plat-ue2-dev + name: tags + stack_dependencies: + - dependency_type: import + stack_file: orgs/acme/_defaults + stack_file_section: terraform.vars + variable_value: + atmos_component: vpc + atmos_component_description: Vpc component "common" provisioned in the stack "plat-ue2-dev" + atmos_manifest: orgs/acme/plat/dev/us-east-2 + atmos_stack: plat-ue2-dev + provisioned_by_user: user + terraform_component: vpc + terraform_workspace: plat-ue2-dev + tenant: + final_value: plat + name: tenant + stack_dependencies: + - dependency_type: import + stack_file: mixins/tenant/plat + stack_file_section: vars + variable_value: plat + vpc_flow_logs_enabled: + final_value: false + name: vpc_flow_logs_enabled + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: true + - dependency_type: import + stack_file: catalog/vpc/dev + stack_file_section: components.terraform.vars + variable_value: false + vpc_flow_logs_log_destination_type: + final_value: s3 + name: vpc_flow_logs_log_destination_type + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: s3 + vpc_flow_logs_traffic_type: + final_value: ALL + name: vpc_flow_logs_traffic_type + stack_dependencies: + - dependency_type: import + stack_file: catalog/vpc/defaults + stack_file_section: components.terraform.vars + variable_value: ALL +stack: plat-ue2-dev +tf_cli_vars: {} +vars: + assign_generated_ipv6_cidr_block: false + availability_zones: + - us-east-2a + - us-east-2b + - us-east-2c + enabled: true + environment: ue2 + ipv4_primary_cidr_block: 10.7.0.0/18 + map_public_ip_on_launch: true + max_subnet_count: 3 + name: common + namespace: acme + nat_eip_aws_shield_protection_enabled: false + nat_gateway_enabled: true + nat_instance_enabled: false + region: us-east-2 + stage: dev + subnet_type_tag_key: acme/subnet/type + tags: + atmos_component: vpc + atmos_component_description: Vpc component "common" provisioned in the stack "plat-ue2-dev" + atmos_manifest: orgs/acme/plat/dev/us-east-2 + atmos_stack: plat-ue2-dev + provisioned_by_user: user + terraform_component: vpc + terraform_workspace: plat-ue2-dev + tenant: plat + vpc_flow_logs_enabled: false + vpc_flow_logs_log_destination_type: s3 + vpc_flow_logs_traffic_type: ALL +workspace: plat-ue2-dev + diff --git a/tests/snapshots/TestCLICommands_echo_info_help_shows_verbose_flag.stderr.golden b/tests/snapshots/TestCLICommands_echo_info_help_shows_verbose_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_echo_info_help_shows_verbose_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_echo_info_help_shows_verbose_flag.stdout.golden b/tests/snapshots/TestCLICommands_echo_info_help_shows_verbose_flag.stdout.golden new file mode 100644 index 0000000000..e851c7479b --- /dev/null +++ b/tests/snapshots/TestCLICommands_echo_info_help_shows_verbose_flag.stdout.golden @@ -0,0 +1,82 @@ + +👽 test darwin/arm64 + +Echo information with optional verbose output + +USAGE + + + $ atmos echo info [flags] + + +FLAGS + + -- Use double dashes to separate Atmos-specific options from native arguments and flags for the + command. + + -h, --help help for info + + --identity string Identity to use for authentication (overrides identity in command config) + + +GLOBAL FLAGS + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in this + directory) + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) (default + bar) + + --interactive Enable interactive prompts for missing required flags, optional value flags using the + sentinel pattern, and missing positional arguments (requires TTY, disabled in CI) + (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard file + descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed information + + diff --git a/tests/snapshots/TestCLICommands_echo_info_runs_with_verbose_flag.stderr.golden b/tests/snapshots/TestCLICommands_echo_info_runs_with_verbose_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_echo_info_runs_with_verbose_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_echo_info_runs_with_verbose_flag.stdout.golden b/tests/snapshots/TestCLICommands_echo_info_runs_with_verbose_flag.stdout.golden new file mode 100644 index 0000000000..cf29586a4a --- /dev/null +++ b/tests/snapshots/TestCLICommands_echo_info_runs_with_verbose_flag.stdout.golden @@ -0,0 +1,2 @@ +Info: This is a basic message +Verbose: Additional details enabled diff --git a/tests/snapshots/TestCLICommands_echo_info_runs_without_verbose.stderr.golden b/tests/snapshots/TestCLICommands_echo_info_runs_without_verbose.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_echo_info_runs_without_verbose.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_echo_info_runs_without_verbose.stdout.golden b/tests/snapshots/TestCLICommands_echo_info_runs_without_verbose.stdout.golden new file mode 100644 index 0000000000..0be0ea9e7f --- /dev/null +++ b/tests/snapshots/TestCLICommands_echo_info_runs_without_verbose.stdout.golden @@ -0,0 +1 @@ +Info: This is a basic message diff --git a/tests/snapshots/TestCLICommands_help_flag_works.stderr.golden b/tests/snapshots/TestCLICommands_help_flag_works.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_help_flag_works.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_help_flag_works.stdout.golden b/tests/snapshots/TestCLICommands_help_flag_works.stdout.golden new file mode 100644 index 0000000000..4e9e85686d --- /dev/null +++ b/tests/snapshots/TestCLICommands_help_flag_works.stdout.golden @@ -0,0 +1,117 @@ + +👽 test darwin/arm64 + +Atmos is a framework for orchestrating and operating infrastructure workflows +across multiple cloud and DevOps toolchains. + +USAGE + + + $ atmos [flags] + $ atmos [sub-command] [flags] + + +SUBCOMMAND ALIASES + + hf Alias of atmos helmfile command + pk Alias of atmos packer command + tf Alias of atmos terraform command + +AVAILABLE COMMANDS + + about Learn about Atmos + atlantis [command] Generate and manage Atlantis configurations + auth [command] Authenticate with cloud providers and identity services. + aws [command] Run AWS-specific commands for interacting with cloud resources + completion [command] Generate autocompletion scripts for Bash, Zsh, Fish, and PowerShell + describe [command] Show details about Atmos configurations and components + devcontainer [command] Manage development containers + docs [command] Open Atmos documentation or display component-specific docs + env Output environment variables configured in atmos.yaml + helmfile [command] Manage Helmfile-based Kubernetes deployments + help Help about any command + list [command] List available stacks and components + packer [command] Manage packer-based machine images for multiple platforms + pro [command] Access premium features integrated with atmos-pro.com + profile [command] Manage configuration profiles + show [command] Execute 'show' commands + support Show Atmos support options + terraform [command] Execute Terraform commands using Atmos stack configurations + tf [command] Execute 'terraform' commands + theme [command] Manage terminal themes for Atmos CLI + toolchain [command] Manage tool versions and installations + validate [command] Validate configurations against OPA policies and JSON schemas + vendor [command] Manage external dependencies for components or stacks + version [command] Display the version of Atmos you are running and check for updates + workflow Run predefined tasks using workflows + +FLAGS + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in this + directory) + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) (default + bar) + + -h, --help help for atmos + + --identity string Identity to use for authentication. Use --identity to select interactively, -- + identity=NAME to specify + + --interactive Enable interactive prompts for missing required flags, optional value flags using the + sentinel pattern, and missing positional arguments (requires TTY, disabled in CI) + (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard file + descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed information + + --version Display the Atmos CLI version + + + +Use atmos [command] --help for more information about a command. diff --git a/tests/snapshots/TestCLICommands_list_components_works.stderr.golden b/tests/snapshots/TestCLICommands_list_components_works.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_list_components_works.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_list_components_works.stdout.golden b/tests/snapshots/TestCLICommands_list_components_works.stdout.golden new file mode 100644 index 0000000000..1e278865fb --- /dev/null +++ b/tests/snapshots/TestCLICommands_list_components_works.stdout.golden @@ -0,0 +1,2 @@ +vpc terraform 6 +vpc-flow-logs-bucket terraform 6 diff --git a/tests/snapshots/TestCLICommands_list_stacks_works.stderr.golden b/tests/snapshots/TestCLICommands_list_stacks_works.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_list_stacks_works.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_list_stacks_works.stdout.golden b/tests/snapshots/TestCLICommands_list_stacks_works.stdout.golden new file mode 100644 index 0000000000..f2ff213896 --- /dev/null +++ b/tests/snapshots/TestCLICommands_list_stacks_works.stdout.golden @@ -0,0 +1,6 @@ +plat-ue2-dev +plat-ue2-prod +plat-ue2-staging +plat-uw2-dev +plat-uw2-prod +plat-uw2-staging diff --git a/tests/snapshots/TestCLICommands_show_component_help_shows_inherited_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_show_component_help_shows_inherited_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_show_component_help_shows_inherited_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_show_component_help_shows_inherited_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_show_component_help_shows_inherited_stack_flag.stdout.golden new file mode 100644 index 0000000000..ea13797369 --- /dev/null +++ b/tests/snapshots/TestCLICommands_show_component_help_shows_inherited_stack_flag.stdout.golden @@ -0,0 +1,84 @@ + +👽 test darwin/arm64 + +Execute 'show component' command + +USAGE + + + $ atmos show component [flags] + + +FLAGS + + -- Use double dashes to separate Atmos-specific options from native arguments and flags for the + command. + + -h, --help help for component + + --identity string Identity to use for authentication (overrides identity in command config) + + -s, --stack string Name of the stack + + +GLOBAL FLAGS + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in this + directory) + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) (default + bar) + + --interactive Enable interactive prompts for missing required flags, optional value flags using the + sentinel pattern, and missing positional arguments (requires TTY, disabled in CI) + (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard file + descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed information + + diff --git a/tests/snapshots/TestCLICommands_show_component_reads_stack_flag_value.stderr.golden b/tests/snapshots/TestCLICommands_show_component_reads_stack_flag_value.stderr.golden new file mode 100644 index 0000000000..9eb3a9cd53 --- /dev/null +++ b/tests/snapshots/TestCLICommands_show_component_reads_stack_flag_value.stderr.golden @@ -0,0 +1,5 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry +# Error + +**Error:** template: step-4:1:44: executing "step-4" at +<.ComponentConfig.backend.bucket>: map has no entry for key "bucket" diff --git a/tests/snapshots/TestCLICommands_show_component_reads_stack_flag_value.stdout.golden b/tests/snapshots/TestCLICommands_show_component_reads_stack_flag_value.stdout.golden new file mode 100644 index 0000000000..0d2c85d62b --- /dev/null +++ b/tests/snapshots/TestCLICommands_show_component_reads_stack_flag_value.stdout.golden @@ -0,0 +1,4 @@ +Atmos component from argument: vpc +ATMOS_COMPONENT: vpc +Atmos stack: plat-ue2-dev +Terraform component: vpc diff --git a/tests/snapshots/TestCLICommands_terraform_help_shows_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_terraform_help_shows_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_terraform_help_shows_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_terraform_help_shows_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_terraform_help_shows_stack_flag.stdout.golden new file mode 100644 index 0000000000..d87d4974e7 --- /dev/null +++ b/tests/snapshots/TestCLICommands_terraform_help_shows_stack_flag.stdout.golden @@ -0,0 +1,184 @@ + +👽 test darwin/arm64 + +This command allows you to execute Terraform commands, such as plan, apply, and +destroy, using Atmos stack configurations for consistent infrastructure +management. + +USAGE + + + $ atmos terraform [flags] + $ atmos terraform [sub-command] [flags] + + +ALIASES + + terraform, tf + +EXAMPLES + + + – Execute a terraform subcommand + + + $ atmos terraform [subcommand] -s + + +AVAILABLE COMMANDS + + apply Apply changes to infrastructure + backend [command] Manage Terraform state backends + clean Clean up Terraform state and artifacts + console Try Terraform expressions at an interactive command prompt + deploy Deploy the specified infrastructure using Terraform + destroy Destroy previously-created infrastructure + fmt Reformat your configuration in the standard style + force-unlock Release a stuck lock on the current workspace + generate [command] Generate Terraform configuration files for Atmos components and stacks + get Install or upgrade remote Terraform modules + graph Generate a Graphviz graph of the steps in an operation + help Show help for terraform command + import Import existing infrastructure into Terraform state + init Prepare your working directory for other commands + login Obtain and save credentials for a remote host + logout Remove locally-stored credentials for a remote host + metadata Metadata related commands + modules Show all declared modules in a working directory + output Show output values from your root module + plan Show changes required by the current configuration + plan-diff Compare two Terraform plans and show the differences + providers Show the providers required for this configuration + provision This command provisions terraform components + refresh Update the state to match remote systems + shell Configure an environment for an Atmos component and start a new shell + show Show the current state or a saved plan + source [command] Manage Terraform component sources (JIT vendoring) + state Advanced state management + taint Mark a resource instance as not fully functional + test Execute integration tests for Terraform modules + untaint Remove the 'tainted' state from a resource instance + validate Check whether the configuration is valid + version Show the current Terraform version + workdir [command] Manage component working directories + workspace Manage Terraform workspaces + +FLAGS + + --append-user-agent string Customize User-Agent string in Terraform provider requests (sets + TF_APPEND_USER_AGENT) + + --clone-target-ref Clone the target reference with which to compare the current branch + + --components strings Filter by specific components + + --dry-run Perform dry run without making actual changes + + -h, --help help for terraform + + -i, --identity string Specify the identity to authenticate to before running Terraform commands. Use + without value to interactively select. + + --include-dependents For each affected component, detect the dependent components and process them in the + dependency order + + --init-pass-vars Pass the generated varfile to terraform init using --var-file flag (OpenTofu feature) + + --process-functions Enable/disable YAML functions processing in Atmos stack manifests (default true) + + --process-templates Enable/disable Go template processing in Atmos stack manifests (default true) + + -q, --query string Execute atmos terraform command on components filtered by a YQ expression + + --ref string Git reference with which to compare the current branch + + --repo-path string Filesystem path to the already cloned target repository with which to compare the + current branch + + --sha string Git commit SHA with which to compare the current branch + + --skip strings Skip executing specific YAML functions in the Atmos stack manifests + + --skip-init Skip terraform init before running command + + --ssh-key string Path to PEM-encoded private key to clone private repos using SSH + + --ssh-key-password string Encryption password for the PEM-encoded private key if the key contains a password- + encrypted PEM block + + -s, --stack string Stack name + + --upload-status Upload plan status to Atmos Pro + + +GLOBAL FLAGS + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in this + directory) + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) (default + bar) + + --interactive Enable interactive prompts for missing required flags, optional value flags using the + sentinel pattern, and missing positional arguments (requires TTY, disabled in CI) + (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard file + descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed information + + +COMPATIBILITY FLAGS + + These flags are passed through to the underlying terraform/tofu command. + + -chdir Switch to a different working directory before executing the given subcommand + -help Show terraform help output + -version Show terraform version + + +Use atmos terraform [command] --help for more information about a command. diff --git a/tests/snapshots/TestCLICommands_terraform_provision_help_shows_inherited_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_terraform_provision_help_shows_inherited_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_terraform_provision_help_shows_inherited_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_terraform_provision_help_shows_inherited_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_terraform_provision_help_shows_inherited_stack_flag.stdout.golden new file mode 100644 index 0000000000..83a92f0809 --- /dev/null +++ b/tests/snapshots/TestCLICommands_terraform_provision_help_shows_inherited_stack_flag.stdout.golden @@ -0,0 +1,124 @@ + +👽 test darwin/arm64 + +This command provisions terraform components + +USAGE + + + $ atmos terraform provision [flags] + + +FLAGS + + -- Use double dashes to separate Atmos-specific options from native arguments and flags for the + command. + + -h, --help help for provision + + --identity string Identity to use for authentication (overrides identity in command config) + + +GLOBAL FLAGS + + --append-user-agent string Customize User-Agent string in Terraform provider requests (sets + TF_APPEND_USER_AGENT) + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in + this directory) + + --clone-target-ref Clone the target reference with which to compare the current branch + + --components strings Filter by specific components + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --dry-run Perform dry run without making actual changes + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 + latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) + (default bar) + + --include-dependents For each affected component, detect the dependent components and process them in the + dependency order + + --init-pass-vars Pass the generated varfile to terraform init using --var-file flag (OpenTofu feature) + + --interactive Enable interactive prompts for missing required flags, optional value flags using + the sentinel pattern, and missing positional arguments (requires TTY, disabled in + CI) (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard + file descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --process-functions Enable/disable YAML functions processing in Atmos stack manifests (default true) + + --process-templates Enable/disable Go template processing in Atmos stack manifests (default true) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + -q, --query string Execute atmos terraform command on components filtered by a YQ expression + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --ref string Git reference with which to compare the current branch + + --repo-path string Filesystem path to the already cloned target repository with which to compare the + current branch + + --sha string Git commit SHA with which to compare the current branch + + --skip strings Skip executing specific YAML functions in the Atmos stack manifests + + --skip-init Skip terraform init before running command + + --ssh-key string Path to PEM-encoded private key to clone private repos using SSH + + --ssh-key-password string Encryption password for the PEM-encoded private key if the key contains a password- + encrypted PEM block + + -s, --stack string Stack name + + --upload-status Upload plan status to Atmos Pro + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed + information + + diff --git a/tests/snapshots/TestCLICommands_tf_plan_help_shows_inherited_stack_flag.stderr.golden b/tests/snapshots/TestCLICommands_tf_plan_help_shows_inherited_stack_flag.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_tf_plan_help_shows_inherited_stack_flag.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_tf_plan_help_shows_inherited_stack_flag.stdout.golden b/tests/snapshots/TestCLICommands_tf_plan_help_shows_inherited_stack_flag.stdout.golden new file mode 100644 index 0000000000..6655009b7c --- /dev/null +++ b/tests/snapshots/TestCLICommands_tf_plan_help_shows_inherited_stack_flag.stdout.golden @@ -0,0 +1,174 @@ + +👽 test darwin/arm64 + +Generate an execution plan, which shows what actions Terraform will take to +reach the desired state of the configuration. + +This command shows what Terraform will do when you run 'apply'. It helps you +verify changes before making them to your infrastructure. + +For complete Terraform/OpenTofu documentation, see: +https://developer.hashicorp.com/terraform/cli/commands/plan +https://opentofu.org/docs/cli/commands/plan + +USAGE + + + $ atmos terraform plan [flags] + + +EXAMPLES + + + – Execute a terraform plan + + + $ atmos terraform plan -s + + +FLAGS + + --affected Plan the affected components in dependency order + + --all Plan all components in all stacks + + --auto-generate-backend-file string Override auto_generate_backend_file setting from atmos.yaml (true/false) + + -h, --help help for plan + + --init-run-reconfigure string Override init_run_reconfigure setting from atmos.yaml (true/false) + + --skip-planfile Skip writing the plan to a file by not passing the -out flag to Terraform + when executing the command. Set it to true when using Terraform Cloud since + the -out flag is not supported. Terraform Cloud automatically stores plans + in its backend + + --upload-status If set atmos will upload the plan result to the pro API + + +GLOBAL FLAGS + + --append-user-agent string Customize User-Agent string in Terraform provider requests (sets + TF_APPEND_USER_AGENT) + + --base-path string Base path for Atmos project + + -C, --chdir string Change working directory before executing the command (run as if Atmos started in + this directory) + + --clone-target-ref Clone the target reference with which to compare the current branch + + --components strings Filter by specific components + + --config strings Paths to configuration files (comma-separated or repeated flag) + + --config-path strings Paths to search for Atmos configuration (comma-separated or repeated flag) + + --dry-run Perform dry run without making actual changes + + --force-color Force color output even when not a TTY (useful for screenshots) + + --force-tty Force TTY mode with sane defaults when terminal detection fails (useful for + screenshots) + + --heatmap Show performance heatmap visualization after command execution (includes P95 + latency) + + --heatmap-mode string Heatmap visualization mode: bar, sparkline, table (press 1-3 to switch in TUI) + (default bar) + + -i, --identity string Specify the identity to authenticate to before running Terraform commands. Use + without value to interactively select. + + --include-dependents For each affected component, detect the dependent components and process them in the + dependency order + + --init-pass-vars Pass the generated varfile to terraform init using --var-file flag (OpenTofu feature) + + --interactive Enable interactive prompts for missing required flags, optional value flags using + the sentinel pattern, and missing positional arguments (requires TTY, disabled in + CI) (default true) + + --logs-file string The file to write Atmos logs to. Logs can be written to any file or any standard + file descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null' (default + /dev/stderr) + + --logs-level string Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log + level is set to Off, Atmos will not log any messages (default Warning) + + --mask Enable automatic masking of sensitive data in output (use --mask=false to disable) + (default true) + + --no-color Disable color output + + --pager string Enable pager for output (--pager or --pager=true to enable, --pager=false to disable, -- + pager=less to use specific pager) + + --process-functions Enable/disable YAML functions processing in Atmos stack manifests (default true) + + --process-templates Enable/disable Go template processing in Atmos stack manifests (default true) + + --profile strings Activate configuration profiles (comma-separated or repeated flag) + + --profile-file string Write profiling data to file instead of starting server + + --profile-type string Type of profile to collect when using --profile-file. Options: cpu, heap, allocs, + goroutine, block, mutex, threadcreate, trace (default cpu) + + --profiler-enabled Enable pprof profiling server + + --profiler-host string Host for pprof profiling server (default localhost) + + --profiler-port int Port for pprof profiling server (default 6060) + + -q, --query string Execute atmos terraform command on components filtered by a YQ expression + + --redirect-stderr string File descriptor to redirect stderr to. Errors can be redirected to any file or any + standard file descriptor (including '/dev/null') + + --ref string Git reference with which to compare the current branch + + --repo-path string Filesystem path to the already cloned target repository with which to compare the + current branch + + --sha string Git commit SHA with which to compare the current branch + + --skip strings Skip executing specific YAML functions in the Atmos stack manifests + + --skip-init Skip terraform init before running command + + --ssh-key string Path to PEM-encoded private key to clone private repos using SSH + + --ssh-key-password string Encryption password for the PEM-encoded private key if the key contains a password- + encrypted PEM block + + -s, --stack string Stack name + + --use-version string Use a specific version of Atmos (e.g., --use-version=1.160.0) + + -v, --verbose Enable verbose error output with full context, stack traces, and detailed + information + + +COMPATIBILITY FLAGS + + These flags are passed through to the underlying terraform/tofu command. + + -compact-warnings Show warnings in a more compact form + -destroy Create a plan to destroy all remote objects + -detailed-exitcode Return detailed exit codes (0=success, 1=error, 2=changes) + -generate-config-out Write HCL for resources to import + -input Ask for input for variables if not directly set (default: true) + -json Output plan in a machine-readable JSON format + -lock Lock the state file when locking is supported (default: true) + -lock-timeout Duration to retry a state lock (default: 0s) + -no-color Disable color output in the command output + -out Write the plan to the given path + -parallelism Limit the number of concurrent operations (default: 10) + -refresh Update state prior to checking for differences (default: true) + -refresh-only Create a plan to update state only (no resource changes) + -replace Force replacement of a particular resource instance + -target Target specific resources for planning/applying + -var Set a value for one of the input variables + -var-file Load variable values from the given file + diff --git a/tests/snapshots/TestCLICommands_version_command_works.stderr.golden b/tests/snapshots/TestCLICommands_version_command_works.stderr.golden new file mode 100644 index 0000000000..926cbd783e --- /dev/null +++ b/tests/snapshots/TestCLICommands_version_command_works.stderr.golden @@ -0,0 +1 @@ +**Notice:** Telemetry Enabled - Atmos now collects anonymous telemetry regarding usage. This information is used to shape the Atmos roadmap and prioritize features. You can learn more, including how to opt out if you'd prefer not to participate in this anonymous program, by visiting: https://atmos.tools/cli/telemetry diff --git a/tests/snapshots/TestCLICommands_version_command_works.stdout.golden b/tests/snapshots/TestCLICommands_version_command_works.stdout.golden new file mode 100644 index 0000000000..82c26c3598 --- /dev/null +++ b/tests/snapshots/TestCLICommands_version_command_works.stdout.golden @@ -0,0 +1,3 @@ + +👽 Atmos test on darwin/arm64 + diff --git a/tests/test-cases/custom-command-version-flag.yaml b/tests/test-cases/custom-command-version-flag.yaml new file mode 100644 index 0000000000..7936aaa1ab --- /dev/null +++ b/tests/test-cases/custom-command-version-flag.yaml @@ -0,0 +1,60 @@ +tests: + # Test that custom commands can define their own --version flag (string type). + # This verifies the fix for the issue where --version was a persistent global flag + # and prevented custom commands from defining their own --version flag. + - name: custom command with string version flag + enabled: true + description: "Custom commands can define their own --version flag with string type" + workdir: "fixtures/scenarios/custom-version-flag" + command: "atmos" + args: + - "install" + - "--version" + - "1.2.3" + expect: + stdout: + - "Installing version 1\\.2\\.3" + exit_code: 0 + + # Test using shorthand -V for version flag (not -v, which conflicts with --verbose). + - name: custom command with version flag shorthand + enabled: true + description: "Custom commands can use shorthand for their --version flag" + workdir: "fixtures/scenarios/custom-version-flag" + command: "atmos" + args: + - "install" + - "-V" + - "2.0.0" + expect: + stdout: + - "Installing version 2\\.0\\.0" + exit_code: 0 + + # Test help output shows the custom --version flag. + - name: custom command help shows version flag + enabled: true + description: "Custom command help output shows the --version flag" + workdir: "fixtures/scenarios/custom-version-flag" + command: "atmos" + args: + - "install" + - "--help" + expect: + stdout: + - "--version" + - "Version to install" + exit_code: 0 + + # Test that atmos --version still works at root level. + - name: atmos version flag still works + enabled: true + description: "atmos --version at root level still works" + workdir: "fixtures/scenarios/custom-version-flag" + command: "atmos" + args: + - "--version" + expect: + stdout: + - "(?i)atmos" + exit_code: 0 diff --git a/tests/test-cases/flag-inheritance.yaml b/tests/test-cases/flag-inheritance.yaml new file mode 100644 index 0000000000..fa1b4daa06 --- /dev/null +++ b/tests/test-cases/flag-inheritance.yaml @@ -0,0 +1,355 @@ +# yaml-language-server: $schema=schema.json + +# Tests for flag inheritance in custom commands. +# These tests verify that custom commands can declare flags that already exist +# on parent commands, and that the flags are properly inherited. +# +# Key behaviors tested: +# - Custom commands can declare --stack (same type as parent) and inherit it +# - Custom commands show inherited flags in their help output +# - Custom commands can actually read inherited flag values +# - Type mismatches are rejected with clear error messages + +tests: + # ============================================ + # GROUP 1: Custom Command Help Output + # Verify that inherited flags appear in help + # ============================================ + + - name: tf plan help shows inherited stack flag + enabled: true + snapshot: true + description: "Custom 'tf plan' command should show --stack flag inherited from terraform" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "tf" + - "plan" + - "--help" + expect: + exit_code: 0 + stdout: + - "stack" + - "-s" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + - name: terraform provision help shows inherited stack flag + enabled: true + snapshot: true + description: "Custom 'terraform provision' command should show --stack flag" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "terraform" + - "provision" + - "--help" + expect: + exit_code: 0 + stdout: + - "stack" + - "-s" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + - name: show component help shows inherited stack flag + enabled: true + snapshot: true + description: "Custom 'show component' command should show --stack flag" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "show" + - "component" + - "--help" + expect: + exit_code: 0 + stdout: + - "stack" + - "-s" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + # ============================================ + # GROUP 2: Built-in Command Help Output + # Verify that terraform still shows its flags + # ============================================ + + - name: terraform help shows stack flag + enabled: true + snapshot: true + description: "Built-in terraform command should show --stack flag" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "terraform" + - "--help" + expect: + exit_code: 0 + stdout: + - "stack" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + - name: describe component help shows stack flag + enabled: true + snapshot: true + description: "Built-in describe component command should show --stack flag" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "describe" + - "component" + - "--help" + expect: + exit_code: 0 + stdout: + - "stack" + - "-s" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + # ============================================ + # GROUP 3: Custom Command Execution + # Verify that custom commands can read flag values + # ============================================ + + - name: show component reads stack flag value + enabled: true + snapshot: true + description: "Custom 'show component' command should read --stack flag value correctly" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "show" + - "component" + - "vpc" + - "-s" + - "plat-ue2-dev" + expect: + exit_code: 1 # Expected to fail on backend.bucket, but flag reading works + stdout: + - "Atmos component from argument: vpc" + - "Atmos stack: plat-ue2-dev" + - "Terraform component: vpc" + + # ============================================ + # GROUP 4: Global Flags Still Work + # Verify that global flags are accessible + # ============================================ + + - name: version command works + enabled: true + snapshot: true + description: "Version command should work (global flags not broken)" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "version" + expect: + exit_code: 0 + stdout: + - "Atmos" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 Atmos .* on (darwin|linux|windows)/(arm64|amd64)" + + - name: help flag works + enabled: true + snapshot: true + description: "Global --help flag should work" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "--help" + expect: + exit_code: 0 + stdout: + - "USAGE" + - "atmos" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + # ============================================ + # GROUP 5: Describe Component with Flags + # Verify built-in commands with flags work + # ============================================ + + - name: describe component with stack flag + enabled: true + snapshot: true + description: "Describe component should work with --stack flag" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "describe" + - "component" + - "vpc" + - "-s" + - "plat-ue2-dev" + - "--format" + - "yaml" + expect: + exit_code: 0 + stdout: + - "component: vpc" + - "vars:" + - "tenant: plat" + + - name: describe component with provenance and stack + enabled: true + snapshot: true + description: "Describe component with --provenance should work with --stack" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "describe" + - "component" + - "vpc-flow-logs-bucket" + - "-s" + - "plat-ue2-dev" + - "--provenance" + expect: + exit_code: 0 + stdout: + - "# Provenance Legend:" + - "vars:" + - "enabled: true" + diff: + # Ignore lines with variable whitespace alignment in provenance comments + - "provisioned_by_user:.*#" + + # ============================================ + # GROUP 6: Custom Command with --verbose Flag + # This is the scenario that led to the flag inheritance fix. + # Custom commands should be able to declare they need --verbose + # (a global flag) and inherit it without error. + # Uses dedicated test fixture: fixtures/scenarios/flag-inheritance + # ============================================ + + - name: echo info help shows verbose flag + enabled: true + snapshot: true + description: "Custom 'echo info' command should show --verbose flag inherited from global" + workdir: "fixtures/scenarios/flag-inheritance" + command: "atmos" + args: + - "echo" + - "info" + - "--help" + expect: + exit_code: 0 + stdout: + - "verbose" + - "-v" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + - name: echo info runs without verbose + enabled: true + snapshot: true + description: "Custom 'echo info' command should run without --verbose flag" + workdir: "fixtures/scenarios/flag-inheritance" + command: "atmos" + args: + - "echo" + - "info" + expect: + exit_code: 0 + stdout: + - "Info: This is a basic message" + + - name: echo info runs with verbose flag + enabled: true + snapshot: true + description: "Custom 'echo info' command should run with --verbose flag" + workdir: "fixtures/scenarios/flag-inheritance" + command: "atmos" + args: + - "echo" + - "info" + - "--verbose" + expect: + exit_code: 0 + stdout: + - "Info: This is a basic message" + - "Verbose: Additional details enabled" + + - name: deploy component help shows stack flag + enabled: true + snapshot: true + description: "Custom 'deploy component' should show --stack flag" + workdir: "fixtures/scenarios/flag-inheritance" + command: "atmos" + args: + - "deploy" + - "component" + - "--help" + expect: + exit_code: 0 + stdout: + - "stack" + - "-s" + diff: + # Ignore platform-specific line (darwin/arm64 vs linux/amd64) + - "👽 test (darwin|linux|windows)/(arm64|amd64)" + + - name: deploy component runs with stack flag + enabled: true + snapshot: true + description: "Custom 'deploy component' should read --stack flag value" + workdir: "fixtures/scenarios/flag-inheritance" + command: "atmos" + args: + - "deploy" + - "component" + - "echo" + - "-s" + - "dev" + expect: + exit_code: 0 + stdout: + - "Deploying component: echo" + - "To stack: dev" + + # ============================================ + # GROUP 7: List Commands + # Verify list commands work in this example + # ============================================ + + - name: list stacks works + enabled: true + snapshot: true + description: "List stacks should work in quick-start-advanced" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "list" + - "stacks" + expect: + exit_code: 0 + stdout: + - "plat-ue2-dev" + - "plat-ue2-prod" + + - name: list components works + enabled: true + snapshot: true + description: "List components should work in quick-start-advanced" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "list" + - "components" + expect: + exit_code: 0 + stdout: + - "vpc" + - "vpc-flow-logs-bucket" diff --git a/tests/test-cases/provenance-snapshots.yaml b/tests/test-cases/provenance-snapshots.yaml new file mode 100644 index 0000000000..2fbc7ad7de --- /dev/null +++ b/tests/test-cases/provenance-snapshots.yaml @@ -0,0 +1,55 @@ +# yaml-language-server: $schema=schema.json + +# Tests for provenance output. +# These tests capture the expected provenance output format from the initial release. +# Regression: Current output is missing the vars section and has incorrect line numbers. + +tests: + - name: describe component provenance basic + enabled: true + snapshot: true + description: "Test provenance output with YAML format (default)" + workdir: "fixtures/scenarios/provenance" + command: "atmos" + args: + - "describe" + - "component" + - "mock" + - "-s" + - "acme-dev-us-east-1" + - "--provenance" + expect: + exit_code: 0 + stdout: + - "# Provenance Legend:" + - "● \\[1\\]" # Defined in parent stack (escaped for regex) + - "○ \\[2\\]" # Inherited from depth 2 (escaped for regex) + - "○ \\[3\\]" # Inherited from depth 3 (escaped for regex) + - "vars:" # Should have vars section with provenance + - "enabled: true" + - "region: us-east-1" + + - name: describe component provenance advanced + enabled: true + snapshot: true + description: "Test provenance output with complex multi-level imports (quick-start-advanced)" + workdir: "../examples/quick-start-advanced" + command: "atmos" + args: + - "describe" + - "component" + - "vpc-flow-logs-bucket" + - "-s" + - "plat-ue2-dev" + - "--provenance" + expect: + exit_code: 0 + stdout: + - "# Provenance Legend:" + - "● \\[1\\]" # Defined in parent stack + - "○ \\[2\\]" # Inherited from depth 2 + - "vars:" # Should have vars section + - "enabled: true" + diff: + # Ignore lines with variable whitespace alignment in provenance comments + - "provisioned_by_user:.*#" diff --git a/tests/test-cases/version.yaml b/tests/test-cases/version.yaml index 83e91837a9..e62bb8840e 100644 --- a/tests/test-cases/version.yaml +++ b/tests/test-cases/version.yaml @@ -16,5 +16,5 @@ tests: expect: exit_code: 0 ignore: - # Ignore platform-specific line (darwin/arm64 vs linux/amd64) - - "👽 Atmos .* on (darwin|linux)/(arm64|amd64)" + # Ignore platform-specific line (darwin/arm64 vs linux/amd64 vs windows/amd64) + - "👽 Atmos .* on (darwin|linux|windows)/(arm64|amd64)" diff --git a/website/blog/2026-01-11-list-components-fix.mdx b/website/blog/2026-01-11-list-components-fix.mdx new file mode 100644 index 0000000000..e8790bd66e --- /dev/null +++ b/website/blog/2026-01-11-list-components-fix.mdx @@ -0,0 +1,146 @@ +--- +slug: list-components-behavior-clarification +title: "New List Configuration and Stack Filtering" +authors: [atmos] +tags: [enhancement, bugfix] +--- + +The `atmos list components` command now correctly shows unique component definitions, supports stack filtering, and uses a new dedicated configuration namespace. + + + +## What Changed + +The `atmos list components` command behavior has been restored to its original intent: + +- **`list components`** - Shows unique component definitions (deduplicated across all stacks) +- **`list instances`** - Shows all component instances (one entry per component+stack pair) + +Previously, both commands were returning the same data (component instances), which was confusing. + +## New: Filter Components by Stack + +You can now filter which stacks to consider when listing unique components using the `--stack` flag with glob patterns: + +```bash +# List components that exist in any dev stack +atmos list components --stack "*-dev" + +# List components in a specific environment +atmos list components --stack "plat-ue2-*" + +# List components across all production stacks +atmos list components --stack "*-prod" +``` + +This is useful when you want to see what components are defined for a particular environment or stage without seeing the full list across all stacks. + +## New Configuration Namespace + +We've introduced a cleaner configuration structure for list commands with separate settings for `list components` and `list instances`: + +```yaml +# atmos.yaml +list: + # Configuration for "atmos list components" + # Shows unique component definitions (deduplicated) + components: + format: table + columns: + - name: Component + value: "{{ .component }}" + - name: Type + value: "{{ .type }}" + - name: Stacks + value: "{{ .stack_count }}" + + # Configuration for "atmos list instances" + # Shows component instances (one per component+stack pair) + instances: + format: table + columns: + - name: Stack + value: "{{ .stack }}" + - name: Component + value: "{{ .component }}" + - name: Type + value: "{{ .type }}" + - name: Tenant + value: "{{ .vars.tenant }}" + - name: Environment + value: "{{ .vars.environment }}" +``` + +### Available Fields + +**For `list components` (unique component fields):** +- `{{ .component }}` - Component name +- `{{ .type }}` - Component type (terraform, helmfile, packer) +- `{{ .stack_count }}` - Number of stacks using this component +- `{{ .component_folder }}` - Path to component folder + +**For `list instances` (per-instance fields):** +- All unique component fields above, plus: +- `{{ .stack }}` - Stack name +- `{{ .vars.* }}` - Any variable from the component (tenant, environment, stage, region, etc.) +- `{{ .status }}` - Component status indicator + +## Backward Compatibility + +Existing configurations using `components.list.columns` continue to work for `list instances`. The precedence is: + +**For `list instances`:** +1. `--columns` CLI flag +2. `list.instances.columns` (new) +3. `components.list.columns` (deprecated, backward compat) +4. Default columns + +**For `list components`:** +1. `--columns` CLI flag +2. `list.components.columns` (new) +3. Default columns (Component, Type, Stacks) + +Note: The old `components.list.columns` does **not** fall back for `list components` because those columns were designed for per-instance data (with stack-specific fields). + +## Example Output + +**`atmos list components`** now shows unique components: +``` +Component Type Stacks +vpc terraform 6 +vpc-flow-logs-bucket terraform 6 +``` + +**`atmos list components --stack "*-dev"`** filters to dev stacks: +``` +Component Type Stacks +vpc terraform 2 +vpc-flow-logs-bucket terraform 2 +``` + +**`atmos list instances`** continues to show all instances: +``` +Stack Component Type ... +plat-ue2-dev vpc terraform ... +plat-ue2-dev vpc-flow-logs-bucket terraform ... +plat-ue2-prod vpc terraform ... +... +``` + +## Migration + +Update your `atmos.yaml` from: +```yaml +components: + list: + columns: [...] +``` + +To: +```yaml +list: + instances: + columns: [...] +``` + +And optionally add a `list.components` section to customize the unique components output.