Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fc0d48e
fix file-scoped locals, add tests, add PRD and blog post
aknysh Jan 6, 2026
b733e9b
Address CodeRabbit review: verify output content in tests, use Debug …
aknysh Jan 6, 2026
7f62be6
address comments
aknysh Jan 6, 2026
bab6408
address comments, add unit and integration tests, implement `atmos de…
aknysh Jan 6, 2026
e64061f
address comments, add tests
aknysh Jan 7, 2026
56adaa2
implement `atmos describe locals <component> -s <stack>`, add tests
aknysh Jan 7, 2026
6f34d31
update PRDs and docs
aknysh Jan 7, 2026
63266c4
update PRDs and docs
aknysh Jan 7, 2026
fa24dfb
address comments, fix tests
aknysh Jan 7, 2026
84fd259
address comments, fix tests
aknysh Jan 7, 2026
5653580
address comments
aknysh Jan 7, 2026
3352849
Merge branch 'main' into aknysh/fix-locals-1
aknysh Jan 8, 2026
375448c
Merge branch 'main' into aknysh/fix-locals-1
aknysh Jan 8, 2026
c58dfed
Merge branch 'main' into aknysh/fix-locals-1
aknysh Jan 10, 2026
40e3fd0
add component-level locals, add tests
aknysh Jan 10, 2026
a8311e5
update docs
aknysh Jan 10, 2026
a8780d8
update `atmos describe locals` commands and docs
aknysh Jan 10, 2026
bb7bfca
update docs and PRDs
aknysh Jan 10, 2026
a9103fa
update docs and PRDs
aknysh Jan 10, 2026
847ae38
fix tests
aknysh Jan 10, 2026
32e7841
address comments
aknysh Jan 11, 2026
dde1c8d
address comments
aknysh Jan 11, 2026
0c5af47
address comments, add tests
aknysh Jan 11, 2026
623a2bd
address comments, add tests
aknysh Jan 11, 2026
a12b267
address comments, add tests, update `atmos describe locals` command
aknysh Jan 11, 2026
5dd6a00
address comments, add tests
aknysh Jan 11, 2026
4d4db37
address comments, add tests
aknysh Jan 12, 2026
cd98be3
address comments, add tests
aknysh Jan 12, 2026
03138e2
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 12, 2026
723d6b6
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Jan 12, 2026
befe171
address comments
aknysh Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions cmd/describe_locals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
"github.com/spf13/pflag"

errUtils "github.com/cloudposse/atmos/errors"
"github.com/cloudposse/atmos/internal/exec"
cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/perf"
"github.com/cloudposse/atmos/pkg/schema"
)

const stackFlagName = "stack"

// describeLocalsCmd describes locals for stacks.
var describeLocalsCmd = &cobra.Command{
Use: "locals [component] -s <stack>",
Short: "Display locals from Atmos stack manifests",
Long: `This command displays the locals defined in Atmos stack manifests.

When called with --stack, it shows the locals defined in that stack manifest file.
When a component is also specified, it shows the merged locals that would be
available to that component (global + section-specific + component-level).`,
Example: ` # Show locals for a specific stack
atmos describe locals --stack deploy/dev

# Show locals for a specific stack (using logical stack name)
atmos describe locals -s prod-us-east-1

# Show locals for a component in a stack
atmos describe locals vpc -s prod

# Output as JSON
atmos describe locals --stack dev --format json

# Query specific values
atmos describe locals -s deploy/dev --query '.["deploy/dev"].locals.namespace'`,
Args: cobra.MaximumNArgs(1),
RunE: getRunnableDescribeLocalsCmd(getRunnableDescribeLocalsCmdProps{
checkAtmosConfig: checkAtmosConfig,
processCommandLineArgs: exec.ProcessCommandLineArgs,
initCliConfig: cfg.InitCliConfig,
validateStacks: exec.ValidateStacks,
newDescribeLocalsExecFactory: exec.NewDescribeLocalsExec,
}),
}

type getRunnableDescribeLocalsCmdProps struct {
checkAtmosConfig func(opts ...AtmosValidateOption)
processCommandLineArgs func(
componentType string,
cmd *cobra.Command,
args []string,
additionalArgsAndFlags []string,
) (schema.ConfigAndStacksInfo, error)
initCliConfig func(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks bool) (schema.AtmosConfiguration, error)
validateStacks func(atmosConfig *schema.AtmosConfiguration) error
newDescribeLocalsExecFactory func() exec.DescribeLocalsExec
}

func getRunnableDescribeLocalsCmd(
g getRunnableDescribeLocalsCmdProps,
) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
defer perf.Track(nil, "cmd.describeLocals")()

// Fail fast: check --stack before expensive init operations.
// Cobra has already parsed flags by the time RunE is called.
stackFlag, _ := cmd.Flags().GetString(stackFlagName)
if stackFlag == "" {
return errUtils.ErrStackRequired
}

// Check Atmos configuration.
g.checkAtmosConfig()

// Pass args to ensure config-selection flags (--base-path, --config, etc.) are parsed.
// The component positional arg is extracted separately below.
info, err := g.processCommandLineArgs("", cmd, args, nil)
if err != nil {
return err
}

atmosConfig, err := g.initCliConfig(info, true)
if err != nil {
return err
}

err = g.validateStacks(&atmosConfig)
if err != nil {
return err
}

describeArgs := &exec.DescribeLocalsArgs{}

// Extract component from positional argument if provided.
if len(args) > 0 {
describeArgs.Component = args[0]
}

err = setCliArgsForDescribeLocalsCli(cmd.Flags(), describeArgs)
if err != nil {
return err
}

// Create executor lazily to avoid init-time side effects.
executor := g.newDescribeLocalsExecFactory()
err = executor.Execute(&atmosConfig, describeArgs)
return err
}
}

func setCliArgsForDescribeLocalsCli(flags *pflag.FlagSet, args *exec.DescribeLocalsArgs) error {
var err error

if flags.Changed(stackFlagName) {
args.FilterByStack, err = flags.GetString(stackFlagName)
if err != nil {
return fmt.Errorf("%w: read --stack: %w", errUtils.ErrInvalidFlag, err)
}
}

if flags.Changed("format") {
args.Format, err = flags.GetString("format")
if err != nil {
return fmt.Errorf("%w: read --format: %w", errUtils.ErrInvalidFlag, err)
}
}

if flags.Changed("file") {
args.File, err = flags.GetString("file")
if err != nil {
return fmt.Errorf("%w: read --file: %w", errUtils.ErrInvalidFlag, err)
}
}

if flags.Changed("query") {
args.Query, err = flags.GetString("query")
if err != nil {
return fmt.Errorf("%w: read --query: %w", errUtils.ErrInvalidFlag, err)
}
}

// Set default format.
if args.Format == "" {
args.Format = "yaml"
}

return nil
}

func init() {
// Use Flags() instead of PersistentFlags() since this command has no subcommands.
describeLocalsCmd.Flags().StringP(stackFlagName, "s", "",
"Stack to display locals for (required)\n"+
"Supports top-level stack manifest names (including subfolder paths) and logical stack names (derived from context vars)\n"+
"Parse errors in the matched stack are reported; errors in other stacks are skipped during the search",
)
AddStackCompletion(describeLocalsCmd)

describeLocalsCmd.Flags().StringP("format", "f", "yaml", "Specify the output format (`yaml` is default)")

describeLocalsCmd.Flags().String("file", "", "Write the result to file")

describeLocalsCmd.Flags().StringP("query", "q", "", "Query the result using `yq` expression")

describeCmd.AddCommand(describeLocalsCmd)
}
Loading
Loading