Conversation
Add support for loading .env files into the environment with the following features:
- New structured env configuration (env.vars + env.files)
- Backward-compatible flat form (env: {KEY: value})
- Glob pattern support for .env files (e.g., .env.*)
- Optional parent directory walking with security bounds
- Early .env loading to support ATMOS_* vars influencing Atmos behavior
- Working directory .env loading for component-specific environments
- Case preservation for environment variable names
- UI feedback when files are loaded
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Dependency Review✅ No vulnerabilities or license issues found.Scanned FilesNone |
📝 WalkthroughWalkthroughAdds structured env configuration and .env file support: new pkg/env loader, early .env injection during config load, EnvConfig schema (vars + files), and propagates case-sensitive env merging and per-directory .env merging into shell/terraform execution paths. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant ConfigLoader as Config Loader
participant EnvLoader as Env File Loader
participant ShellExec as Shell Executor
participant OS as OS Process
User->>ConfigLoader: Load atmos.yaml
ConfigLoader->>EnvLoader: loadEnvFilesEarly(env.files)
EnvLoader->>OS: Inject global .env vars into process env
ConfigLoader->>ConfigLoader: parseEnvConfig -> AtmosConfiguration.Env (EnvConfig)
User->>ShellExec: Run command (component/workflow)
ShellExec->>EnvLoader: LoadFromDirectory(component dir, patterns, parents)
EnvLoader->>ShellExec: Return merged component .env vars + file list
ShellExec->>ShellExec: Merge OS env + global env (GetCaseSensitiveEnvVars) + component env
ShellExec->>OS: Execute command with final environment
OS-->>User: Command output
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (7)
pkg/env/file.go (2)
104-108: Consider removing redundant perf tracking.
MapToSlicedelegates toConvertMapToSlice, which already has its ownperf.Track. This creates double tracking overhead for the same operation.🔎 Proposed fix
func MapToSlice(m map[string]string) []string { - defer perf.Track(nil, "env.MapToSlice")() - return ConvertMapToSlice(m) }
120-126: Silent skip of invalid glob patterns may hide configuration issues.When a pattern has invalid syntax (e.g.,
[invalid), it's silently skipped. Users won't know their pattern is malformed. Consider logging a warning.🔎 Proposed enhancement
for _, pattern := range patterns { fullPattern := filepath.Join(dir, pattern) matches, err := filepath.Glob(fullPattern) if err != nil { - // Invalid pattern - skip it but don't fail. + // Invalid pattern syntax - log warning but don't fail. + log.Debug("Invalid glob pattern", "pattern", pattern, "error", err) continue }This would require importing the log package.
pkg/schema/schema_test.go (1)
145-186: GetCaseSensitiveMap tests align with new EnvConfig semanticsThe new subtests exercise EnvConfig.Vars both with and without CaseMaps and cover the nil Vars case; this matches the updated GetCaseSensitiveMap / GetCaseSensitiveEnvVars behavior and preserves previous expectations for the
"env"path.If you want to harden coverage further, consider adding a small subtest that calls
GetCaseSensitiveMap("env.vars")explicitly to lock in the structured-path behavior, but the current suite is already functionally solid.cmd/cmd_utils.go (1)
551-612: Custom‑command env is wired correctly, but we now double‑merge global envUsing
atmosConfig.GetCaseSensitiveEnvVars()when seedingenvand switchingExecuteShellto&atmosConfigkeeps custom commands aligned with the new env handling (including case preservation and .env behavior).Right now
envalready includesos.Environ()+ global env, andExecuteShellrepeats that merge internally before applyingenvVars, so the same keys get processed twice. It’s harmless but a bit redundant. Longer‑term, it may be cleaner to treatenvhere as “overrides only” (commandConfig/env + auth) and letExecuteShellown the system/global/.env merge.pkg/schema/schema.go (1)
53-72: EnvConfig / EnvFilesConfig and case‑sensitive env access look coherentThe new
EnvFilesConfig+EnvConfigstructs, the switch toAtmosConfiguration.Env EnvConfig, and the updatedGetCaseSensitiveMap/GetCaseSensitiveEnvVarsall line up cleanly:
- Dual form is supported (flat
envvs structuredenv.vars/env.files) without fighting mapstructure.- Case preservation for env vars is centralized via
GetCaseSensitiveEnvVars()and the extendedcaseSensitivePaths("env","env.vars"), which matches how workflows, shell exec, and auth shells are now consuming env.- Returning the raw
Varsmap whenCaseMapsis nil preserves previous behavior.Only minor thought: if you ever care about distinguishing “unset” vs “empty map” when
CaseMapsis present, you might add an earlyif source == nil { return nil }before callingApplyCase, but as it stands this is fully workable.Also applies to: 113-114, 243-270, 1322-1323
pkg/config/load.go (1)
21-29: Early .env support and dual‑form env parsing are wired correctlyNice job threading the new env behavior through config loading:
parseEnvConfigcleanly supports both legacy flatenvand structuredenv.vars/env.fileswithout reintroducing the old mapstructure collision withCommand.Env.loadEnvFilesEarlyruns aftersetEnv(v)but before profile resolution, usesenv.files.enabled/env.files.paths, and injects only missing vars intoos.Environ, so ATMOS_* in.envcan influence profile selection and other config without clobbering explicit env.- UI feedback via
ui.Success("Loaded …")is consistent with the later working‑dir.envloading path.- Extending
caseSensitivePathswith"env"and"env.vars"completes the link soGetCaseSensitiveEnvVars()can rely on casemap for both flat and structured forms.One behavioral nuance to keep in mind (likely acceptable): early
.envloading will honorenv.files.*from the base config, not from profiles, since profiles are applied later. If you ever need profile‑specific.envbehavior at bootstrap time, that would require a follow‑up design, but this implementation is internally consistent.Also applies to: 218-237, 280-284, 1237-1317, 1321-1325
internal/exec/shell_utils.go (1)
18-26: Consider merging command env with MergeEnvSlices to avoid duplicate keysThe new layering in
ExecuteShellCommand—system env → global env → working‑dir.env→ command env—is the right shape, and usingGetCaseSensitiveEnvVars()keeps it aligned with the dual‑form env config.Right now the final step is:
cmdEnv = append(cmdEnv, env...)Since
envis already a full environment slice (fromprepareStepEnvironment), this can introduce duplicateKEY=valueentries and leaves which value “wins” up to the OS/env implementation. To make precedence explicit and avoid duplicates, you could merge the slices instead:cmdEnv = envpkg.MergeEnvSlices(cmdEnv, env)That keeps “command/env manager output” as the clear highest‑priority layer while still honoring global +
.envvalues.Also applies to: 51-76
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (13)
cmd/auth_exec.gocmd/cmd_utils.gogo.modinternal/exec/shell_utils.gointernal/exec/shell_utils_test.gointernal/exec/stack_processor_process_stacks.gointernal/exec/workflow_adapters.gointernal/exec/workflow_utils.gopkg/config/load.gopkg/env/file.gopkg/env/file_test.gopkg/schema/schema.gopkg/schema/schema_test.go
🧰 Additional context used
📓 Path-based instructions (5)
cmd/**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
cmd/**/*.go: Use Cobra's recommended command structure with a root command and subcommands, implementing each command in a separate file undercmd/directory
Provide comprehensive help text for all commands and flags, include examples in command help, and follow Go's documentation conventions in Cobra command definitions
Provide meaningful feedback to users and include progress indicators for long-running operations in CLI commands
Files:
cmd/auth_exec.gocmd/cmd_utils.go
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context usingfmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: usegofmtandgoimportsto format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging
**/*.go: All comments must end with periods (enforced bygodotlinter) in Go code
Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (cfg,log,u,errUtils)
All errors MUST be wrapped using static errors defined inerrors/errors.go- useerrors.Joinfor combining errors,fmt.Errorfwith%wfor context, anderrors.Is()for error checking
Never manually create mocks - usego.uber.org/mock/mockgenwith//go:generatedirectives in Go code
Keep files small and focused - under 600 lines with one cmd/impl per file, co-locate tests, never use//revive:disable:file-length-limit
Use colors frompkg/ui/theme/colors.gofor all UI theming in Go code
Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, usefilepath.Join()instead of h...
Files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/env/file_test.gopkg/schema/schema.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gopkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
**/{pkg,internal,cmd}/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Add
defer perf.Track(atmosConfig, "pkg.FuncName")()plus blank line to all public functions, usingnilif no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions
Files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/env/file_test.gopkg/schema/schema.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gopkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
**/*_test.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*_test.go: Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages
Use table-driven tests for testing multiple scenarios in Go
Include integration tests for command flows and test CLI end-to-end when possible with test fixturesPrefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with
go.uber.org/mock/mockgen, use table-driven tests, target >80% coverage
Files:
pkg/schema/schema_test.gopkg/env/file_test.gointernal/exec/shell_utils_test.go
{go.mod,go.sum}
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies
Files:
go.mod
🧠 Learnings (64)
📓 Common learnings
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.
Learnt from: aknysh
Repo: cloudposse/atmos PR: 0
File: :0-0
Timestamp: 2025-01-19T22:30:27.600Z
Learning: The Atmos YAML function `!env` is used to retrieve environment variables and assign them to sections in stack manifests. It supports both simple types (string, number, boolean) and complex types (JSON-encoded lists, maps, objects).
Learnt from: Listener430
Repo: cloudposse/atmos PR: 934
File: tests/fixtures/scenarios/docs-generate/README.md.gotmpl:99-118
Timestamp: 2025-01-25T03:51:57.689Z
Learning: For the cloudposse/atmos repository, changes to template contents should be handled in dedicated PRs and are typically considered out of scope for PRs focused on other objectives.
📚 Learning: 2025-12-13T06:10:25.156Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/env/file_test.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gogo.modpkg/env/file.gointernal/exec/shell_utils.gointernal/exec/workflow_utils.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/schema/schema.gocmd/cmd_utils.gopkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
📚 Learning: 2025-11-08T19:56:18.660Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/schema/schema.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gopkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*.go : Use `viper.BindEnv("ATMOS_VAR", "ATMOS_VAR", "FALLBACK")` for environment variables - ATMOS_ prefix required in Go code
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/schema/schema.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gogo.modpkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/schema/schema.gocmd/cmd_utils.gogo.modpkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2025-11-11T03:47:59.576Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.
Applied to files:
cmd/auth_exec.gopkg/schema/schema_test.gopkg/env/file_test.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gopkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
📚 Learning: 2025-11-11T03:47:45.878Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/env/file_test.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gopkg/env/file.gointernal/exec/shell_utils.go
📚 Learning: 2025-09-29T15:47:10.908Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1540
File: internal/exec/terraform_cli_args_utils.go:64-73
Timestamp: 2025-09-29T15:47:10.908Z
Learning: In the Atmos codebase, viper.BindEnv is required for CLI commands in the cmd/ package, but internal utilities can use os.Getenv directly when parsing environment variables for business logic purposes. The requirement to use viper is specific to the CLI interface layer, not all environment variable access throughout the codebase.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2024-12-07T16:16:13.038Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 825
File: internal/exec/helmfile_generate_varfile.go:28-31
Timestamp: 2024-12-07T16:16:13.038Z
Learning: In `internal/exec/helmfile_generate_varfile.go`, the `--help` command (`./atmos helmfile generate varfile --help`) works correctly without requiring stack configurations, and the only change needed was to make `ProcessCommandLineArgs` exportable by capitalizing its name.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gointernal/exec/shell_utils.gointernal/exec/workflow_utils.go
📚 Learning: 2025-09-07T18:07:00.549Z
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1452
File: cmd/auth_login.go:43-44
Timestamp: 2025-09-07T18:07:00.549Z
Learning: In the atmos project, the identity flag is defined as a persistent flag on the auth root command (cmd/auth.go), making it available to all auth subcommands without needing to be redefined in each individual subcommand.
Applied to files:
cmd/auth_exec.go
📚 Learning: 2024-12-11T18:40:12.808Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 844
File: cmd/helmfile.go:37-37
Timestamp: 2024-12-11T18:40:12.808Z
Learning: In the atmos project, `cliConfig` is initialized within the `cmd` package in `root.go` and can be used in other command files.
Applied to files:
cmd/auth_exec.gopkg/schema/schema.gocmd/cmd_utils.gointernal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2025-12-13T04:37:25.223Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/root.go:0-0
Timestamp: 2025-12-13T04:37:25.223Z
Learning: In Atmos cmd/root.go Execute(), after cfg.InitCliConfig, we must call both toolchainCmd.SetAtmosConfig(&atmosConfig) and toolchain.SetAtmosConfig(&atmosConfig) so the CLI wrapper and the toolchain package receive configuration; missing either can cause nil-pointer panics in toolchain path resolution.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
📚 Learning: 2025-08-15T14:43:41.030Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1352
File: pkg/store/artifactory_store_test.go:108-113
Timestamp: 2025-08-15T14:43:41.030Z
Learning: In test files for the atmos project, it's acceptable to ignore errors from os.Setenv/Unsetenv operations during test environment setup and teardown, as these are controlled test scenarios.
Applied to files:
cmd/auth_exec.gopkg/schema/schema_test.gopkg/env/file_test.gointernal/exec/shell_utils_test.gopkg/env/file.gointernal/exec/shell_utils.go
📚 Learning: 2025-12-13T04:37:40.435Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/toolchain/get.go:23-40
Timestamp: 2025-12-13T04:37:40.435Z
Learning: In Go CLI command files using Cobra, constrain the subcommand to accept at most one positional argument (MaximumNArgs(1)) so it supports both listing all items (zero args) and fetching a specific item (one arg). Define and parse flags with a standard parser (e.g., flags.NewStandardParser()) and avoid binding flags to Viper (no viper.BindEnv/BindPFlag). This promotes explicit argument handling and predictable flag behavior across command files.
Applied to files:
cmd/auth_exec.gocmd/cmd_utils.go
📚 Learning: 2025-12-21T04:10:29.030Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1891
File: internal/exec/describe_affected.go:468-468
Timestamp: 2025-12-21T04:10:29.030Z
Learning: In Go, package-level declarations (constants, variables, types, and functions) are visible to all files in the same package without imports. During reviews in cloudposse/atmos (and similar Go codebases), before suggesting to declare a new identifier, first check if it already exists in another file of the same package. If it exists, you can avoid adding a new declaration; if not, proceed with a proper package-level declaration.
Applied to files:
cmd/auth_exec.gointernal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/env/file_test.gopkg/schema/schema.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gopkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
📚 Learning: 2024-11-13T21:37:07.852Z
Learnt from: Cerebrovinny
Repo: cloudposse/atmos PR: 764
File: internal/exec/describe_stacks.go:289-295
Timestamp: 2024-11-13T21:37:07.852Z
Learning: In the `internal/exec/describe_stacks.go` file of the `atmos` project written in Go, avoid extracting the stack name handling logic into a helper function within the `ExecuteDescribeStacks` method, even if the logic appears duplicated.
Applied to files:
internal/exec/stack_processor_process_stacks.go
📚 Learning: 2025-12-13T03:21:35.786Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1813
File: cmd/terraform/shell.go:28-73
Timestamp: 2025-12-13T03:21:35.786Z
Learning: In Atmos, when calling cfg.InitCliConfig, you must first populate the schema.ConfigAndStacksInfo struct with global flag values using flags.ParseGlobalFlags(cmd, v) rather than passing an empty struct. The LoadConfig function (pkg/config/load.go) reads config selection fields (AtmosConfigFilesFromArg, AtmosConfigDirsFromArg, BasePath, ProfilesFromArg) directly from the ConfigAndStacksInfo struct, NOT from Viper. Passing an empty struct causes config selection flags (--base-path, --config, --config-path, --profile) to be silently ignored. Correct pattern: parse flags → populate struct → call InitCliConfig. See cmd/terraform/plan_diff.go for reference implementation.
Applied to files:
internal/exec/stack_processor_process_stacks.gopkg/schema/schema_test.gopkg/schema/schema.gocmd/cmd_utils.gointernal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2025-10-13T18:13:54.020Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1622
File: pkg/perf/perf.go:140-184
Timestamp: 2025-10-13T18:13:54.020Z
Learning: In pkg/perf/perf.go, the `trackWithSimpleStack` function intentionally skips ownership checks at call stack depth > 1 to avoid expensive `getGoroutineID()` calls on every nested function. This is a performance optimization for the common single-goroutine execution case (most Atmos commands), accepting the rare edge case of potential metric corruption if multi-goroutine execution occurs at depth > 1. The ~19× performance improvement justifies this trade-off.
Applied to files:
internal/exec/stack_processor_process_stacks.go
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.
Applied to files:
internal/exec/stack_processor_process_stacks.gogo.modinternal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2025-08-16T23:32:40.412Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1405
File: internal/exec/describe_dependents_test.go:455-456
Timestamp: 2025-08-16T23:32:40.412Z
Learning: In the cloudposse/atmos Go codebase, `InitCliConfig` returns a `schema.AtmosConfiguration` value (not a pointer), while `ExecuteDescribeDependents` expects a `*schema.AtmosConfiguration` pointer parameter. Therefore, when passing the result of `InitCliConfig` to `ExecuteDescribeDependents`, use `&atmosConfig` to pass the address of the value.
Applied to files:
pkg/schema/schema_test.gopkg/schema/schema.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gocmd/cmd_utils.gopkg/config/load.gointernal/exec/workflow_utils.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.
Applied to files:
pkg/schema/schema_test.go
📚 Learning: 2025-11-10T23:23:39.771Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.
Applied to files:
pkg/schema/schema_test.go
📚 Learning: 2024-10-20T13:12:46.499Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 736
File: pkg/config/const.go:6-6
Timestamp: 2024-10-20T13:12:46.499Z
Learning: In `cmd/cmd_utils.go`, it's acceptable to have hardcoded references to `atmos.yaml` in logs, and it's not necessary to update them to use the `CliConfigFileName` constant.
Applied to files:
pkg/schema/schema_test.gopkg/schema/schema.gointernal/exec/workflow_adapters.gointernal/exec/shell_utils.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Use table-driven tests for testing multiple scenarios in Go
Applied to files:
pkg/env/file_test.gointernal/exec/shell_utils_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages
Applied to files:
pkg/env/file_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Include integration tests for command flows and test CLI end-to-end when possible with test fixtures
Applied to files:
pkg/env/file_test.gointernal/exec/shell_utils_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: The atmos codebase has a custom extension to *testing.T that provides a Chdir method, allowing test functions to call t.Chdir() to change working directories during tests. This is used consistently across test files in the codebase.
Applied to files:
pkg/env/file_test.gointernal/exec/shell_utils_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Applied to files:
pkg/env/file_test.gopkg/schema/schema.gopkg/env/file.gopkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*_test.go : Prefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with `go.uber.org/mock/mockgen`, use table-driven tests, target >80% coverage
Applied to files:
pkg/env/file_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests. This is implemented through custom testing framework extensions and is used consistently throughout the test suite.
Applied to files:
pkg/env/file_test.gointernal/exec/shell_utils_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests.
Applied to files:
pkg/env/file_test.gointernal/exec/shell_utils_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method that can be called on *testing.T objects. This functionality is implemented through custom testing framework extensions and is used consistently throughout the test suite for changing working directories during tests.
Applied to files:
pkg/env/file_test.gointernal/exec/shell_utils_test.go
📚 Learning: 2024-12-25T20:28:47.526Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 887
File: internal/exec/stack_processor_utils.go:380-380
Timestamp: 2024-12-25T20:28:47.526Z
Learning: Windows path handling often requires `filepath.Join` to ensure correct separators and comparisons. Insufficient tests can break cross-platform compatibility, so migrating from `path.Join` to `filepath.Join` needs thorough testing on Windows before merging.
Applied to files:
pkg/env/file_test.go
📚 Learning: 2024-10-27T04:28:40.966Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:155-175
Timestamp: 2024-10-27T04:28:40.966Z
Learning: In the `CollectDirectoryObjects` function in `internal/exec/terraform_clean.go`, recursive search through all subdirectories is not needed.
Applied to files:
pkg/env/file_test.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/{pkg,internal,cmd}/**/*.go : Add `defer perf.Track(atmosConfig, "pkg.FuncName")()` plus blank line to all public functions, using `nil` if no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions
Applied to files:
pkg/schema/schema.gopkg/env/file.gointernal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.
Applied to files:
pkg/schema/schema.gopkg/config/load.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.
Applied to files:
pkg/schema/schema.gocmd/cmd_utils.gointernal/exec/workflow_utils.go
📚 Learning: 2025-01-25T15:21:40.413Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: examples/demo-atmos-cli-imports/atmos.yaml:8-8
Timestamp: 2025-01-25T15:21:40.413Z
Learning: In Atmos, when a directory is specified for configuration loading (e.g., in the `import` section of atmos.yaml), all files within that directory should be treated as Atmos configurations. Do not suggest restricting file extensions in directory-based glob patterns.
Applied to files:
pkg/schema/schema.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Use Viper for managing configuration, environment variables, and flags in CLI commands
Applied to files:
pkg/schema/schema.gogo.modpkg/config/load.go
📚 Learning: 2024-11-16T17:30:52.893Z
Learnt from: pkbhowmick
Repo: cloudposse/atmos PR: 786
File: internal/exec/shell_utils.go:159-162
Timestamp: 2024-11-16T17:30:52.893Z
Learning: For the `atmos terraform shell` command in `internal/exec/shell_utils.go`, input validation for the custom shell prompt is not required, as users will use this as a CLI tool and any issues will impact themselves.
Applied to files:
internal/exec/workflow_adapters.gointernal/exec/shell_utils_test.gointernal/exec/shell_utils.go
📚 Learning: 2025-08-16T23:33:07.477Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1405
File: internal/exec/describe_dependents_test.go:651-652
Timestamp: 2025-08-16T23:33:07.477Z
Learning: In the cloudposse/atmos Go codebase, ExecuteDescribeDependents expects a pointer to AtmosConfiguration (*schema.AtmosConfiguration), so when calling it with a value returned by cfg.InitCliConfig (which returns schema.AtmosConfiguration), the address-of operator (&) is necessary: ExecuteDescribeDependents(&atmosConfig, ...).
Applied to files:
internal/exec/workflow_adapters.gocmd/cmd_utils.gointernal/exec/workflow_utils.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to cmd/**/*_test.go : Always use `cmd.NewTestKit(t)` for cmd tests to auto-clean RootCmd state
Applied to files:
internal/exec/shell_utils_test.go
📚 Learning: 2025-12-10T18:32:51.237Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1808
File: cmd/terraform/backend/backend_delete_test.go:9-23
Timestamp: 2025-12-10T18:32:51.237Z
Learning: In cmd subpackages (e.g., cmd/terraform/backend/), tests cannot use cmd.NewTestKit(t) due to Go's test visibility rules (NewTestKit is in a parent package test file). These tests only need TestKit if they execute commands through RootCmd or modify RootCmd state. Structural tests that only verify command structure/flags without touching RootCmd don't require TestKit cleanup.
Applied to files:
internal/exec/shell_utils_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to {go.mod,go.sum} : Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies
Applied to files:
go.modpkg/env/file.go
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: Go version 1.23.0 was deliberately introduced by the maintainer (aknysh) in January 2025. While this might be a pre-release or development version of Go, it has been approved for use in this project.
Applied to files:
go.mod
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: The project uses Go version 1.23.0 which has been confirmed by the maintainer to be working in production for months. Do not flag this as an invalid Go version.
Applied to files:
go.mod
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.
Applied to files:
go.mod
📚 Learning: 2025-04-26T15:54:10.506Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 1195
File: internal/exec/terraform_clean.go:99-99
Timestamp: 2025-04-26T15:54:10.506Z
Learning: The error variable `ErrRelPath` is defined in `internal/exec/terraform_clean_util.go` and is used across files in the `exec` package, including in `terraform_clean.go`. This is part of an approach to standardize error handling in the codebase.
Applied to files:
go.mod
📚 Learning: 2025-03-12T21:38:42.699Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1139
File: pkg/config/go-homedir/homedir.go:183-196
Timestamp: 2025-03-12T21:38:42.699Z
Learning: The code in pkg/config/go-homedir is a direct fork of the mitchellh/go-homedir package and was intentionally imported as-is without modifications to maintain consistency with the original source. Security concerns or other improvements may be addressed in future PRs.
Applied to files:
go.mod
📚 Learning: 2025-04-10T20:48:22.687Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1147
File: pkg/config/load.go:0-0
Timestamp: 2025-04-10T20:48:22.687Z
Learning: In the `bindEnv` function in `pkg/config/load.go`, panic is used deliberately instead of returning errors because errors from `BindEnv` would only occur due to developer mistakes. Using panic helps with early detection of these developer errors during initialization.
Applied to files:
pkg/env/file.gopkg/config/load.go
📚 Learning: 2025-10-10T23:51:36.597Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1599
File: internal/exec/terraform.go:394-402
Timestamp: 2025-10-10T23:51:36.597Z
Learning: In Atmos (internal/exec/terraform.go), when adding OpenTofu-specific flags like `--var-file` for `init`, do not gate them based on command name (e.g., checking if `info.Command == "tofu"` or `info.Command == "opentofu"`) because command names don't reliably indicate the actual binary being executed (symlinks, aliases). Instead, document the OpenTofu requirement in code comments and documentation, trusting users who enable the feature (e.g., `PassVars`) to ensure their terraform command points to an OpenTofu binary.
Applied to files:
internal/exec/shell_utils.go
📚 Learning: 2025-09-13T18:06:07.674Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1466
File: toolchain/list.go:39-42
Timestamp: 2025-09-13T18:06:07.674Z
Learning: In the cloudposse/atmos repository, for UI messages in the toolchain package, use utils.PrintfMessageToTUI instead of log.Error or fmt.Fprintln(os.Stderr, ...). Import pkg/utils with alias "u" to follow the established pattern.
Applied to files:
internal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*.go : Use colors from `pkg/ui/theme/colors.go` for all UI theming in Go code
Applied to files:
internal/exec/shell_utils.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*.go : Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (`cfg`, `log`, `u`, `errUtils`)
Applied to files:
internal/exec/shell_utils.gopkg/config/load.go
📚 Learning: 2025-11-09T19:06:58.470Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1752
File: pkg/profile/list/formatter_table.go:27-29
Timestamp: 2025-11-09T19:06:58.470Z
Learning: In the cloudposse/atmos repository, performance tracking with `defer perf.Track()` is enforced on all functions via linting, including high-frequency utility functions, formatters, and renderers. This is a repository-wide policy to maintain consistency and avoid making case-by-case judgment calls about which functions should have profiling.
Applied to files:
internal/exec/shell_utils.go
📚 Learning: 2024-10-28T01:51:30.811Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:329-332
Timestamp: 2024-10-28T01:51:30.811Z
Learning: In the Atmos Go code, when deleting directories or handling file paths (e.g., in `terraform_clean.go`), always resolve the absolute path using `filepath.Abs` and use the logger `u.LogWarning` for logging messages instead of using `fmt.Printf`.
Applied to files:
internal/exec/shell_utils.go
📚 Learning: 2025-02-06T13:38:07.216Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 984
File: internal/exec/copy_glob.go:0-0
Timestamp: 2025-02-06T13:38:07.216Z
Learning: The `u.LogTrace` function in the `cloudposse/atmos` repository accepts `atmosConfig` as its first parameter, followed by the message string.
Applied to files:
internal/exec/shell_utils.go
📚 Learning: 2025-09-27T20:50:20.564Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1533
File: pkg/config/load.go:585-637
Timestamp: 2025-09-27T20:50:20.564Z
Learning: In the cloudposse/atmos repository, command merging prioritizes precedence over display ordering. Help commands are displayed lexicographically regardless of internal array order, so the mergeCommandArrays function focuses on ensuring the correct precedence chain (top-level file wins) rather than maintaining specific display order.
Applied to files:
internal/exec/shell_utils.go
📚 Learning: 2025-05-30T03:21:37.197Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1274
File: go.mod:63-63
Timestamp: 2025-05-30T03:21:37.197Z
Learning: The redis dependency (github.com/redis/go-redis/v9) in the atmos project is only used in tests, not in production code.
Applied to files:
pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: New config templates support Go templating with `FuncMap()` from `internal/exec/template_funcs.go`
Applied to files:
pkg/config/load.go
📚 Learning: 2025-09-23T04:43:31.857Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1430
File: pkg/config/config.go:98-107
Timestamp: 2025-09-23T04:43:31.857Z
Learning: In the Atmos codebase, NO_PAGER environment variable handling is intentionally kept as direct os.Getenv() access in pkg/config/config.go rather than using Viper binding, because adding no_pager to the config file would be confusing for users. This is an acknowledged exception to the normal Viper binding pattern for environment variables.
Applied to files:
pkg/config/load.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
Repo: cloudposse/atmos PR: 1229
File: internal/exec/workflow_test.go:0-0
Timestamp: 2025-06-02T14:12:02.710Z
Learning: In the atmos codebase, workflow error handling was refactored to use `PrintErrorMarkdown` followed by returning specific error variables (like `ErrWorkflowNoSteps`, `ErrInvalidFromStep`, `ErrInvalidWorkflowStepType`, `ErrWorkflowStepFailed`) instead of `PrintErrorMarkdownAndExit`. This pattern allows proper error testing without the function terminating the process with `os.Exit`, enabling unit tests to assert on error conditions while maintaining excellent user-facing error formatting.
Applied to files:
internal/exec/workflow_utils.go
🧬 Code graph analysis (9)
cmd/auth_exec.go (1)
pkg/env/global.go (1)
MergeGlobalEnv(16-35)
pkg/schema/schema_test.go (1)
pkg/schema/schema.go (2)
AtmosConfiguration(75-120)EnvConfig(67-72)
pkg/env/file_test.go (1)
pkg/env/file.go (4)
LoadEnvFiles(19-27)LoadFromDirectory(33-69)MergeEnvMaps(72-82)MergeEnvSlices(85-100)
pkg/schema/schema.go (1)
pkg/config/casemap/casemap.go (1)
CaseMaps(20-22)
internal/exec/workflow_adapters.go (2)
pkg/perf/perf.go (1)
Track(121-138)internal/exec/shell_utils.go (1)
ExecuteShell(133-191)
cmd/cmd_utils.go (2)
pkg/env/global.go (1)
MergeGlobalEnv(16-35)internal/exec/shell_utils.go (1)
ExecuteShell(133-191)
pkg/env/file.go (2)
pkg/perf/perf.go (1)
Track(121-138)pkg/env/global.go (1)
ConvertMapToSlice(39-51)
internal/exec/shell_utils.go (2)
pkg/env/global.go (3)
MergeGlobalEnv(16-35)MergeSystemEnvWithGlobal(87-91)MergeSystemEnvSimpleWithGlobal(105-109)pkg/env/file.go (3)
LoadFromDirectory(33-69)MergeEnvSlices(85-100)MapToSlice(104-108)
internal/exec/workflow_utils.go (1)
internal/exec/shell_utils.go (1)
ExecuteShell(133-191)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Summary
🔇 Additional comments (21)
pkg/env/file.go (6)
19-27: LGTM.Clean delegation with proper empty pattern handling.
33-69: LGTM — parent directory walking logic is correct.The precedence model (repo root lowest, working dir highest) aligns with the expected behavior. Error handling propagates correctly.
72-82: LGTM.Simple and correct. Nil maps are handled gracefully.
85-100: LGTM.Properly handles values containing
=by splitting only at the first occurrence. Entries without=or with empty keys are silently skipped, which is reasonable.
164-213: LGTM — security boundary enforced correctly.The
isWithinOrEqualcheck at Line 187 prevents walking beyond repo root. Path normalization withfilepath.Absandfilepath.Cleanensures cross-platform compatibility.
216-231: LGTM — path containment check is robust.Adding the separator before the prefix check (Line 229) correctly prevents false positives like
/a/b/c-othermatching/a/b/c.pkg/env/file_test.go (4)
353-377: LGTM — comprehensive table-driven tests for path containment.Good coverage of edge cases including trailing slashes and similar prefixes. The hardcoded Unix paths work because
isWithinOrEqualusesfilepath.Cleanwhich normalizes separators.
12-125: LGTM — thorough test coverage for LoadEnvFiles.Tests cover key scenarios: single file, glob patterns, missing files, case preservation, empty patterns, values with equals, quoted values, and directory filtering.
127-230: LGTM — LoadFromDirectory tests validate parent traversal and security boundaries.The test at Line 172-200 correctly verifies that
.envfiles outside repo root are not loaded.
279-281: No issue here. ThesplitStringAtFirstOccurrencehelper is defined inpkg/env/env.goat line 55 and is accessible throughout the package.cmd/auth_exec.go (1)
111-111: LGTM — updated to use case-sensitive env vars.Consistent with the broader refactor to
GetCaseSensitiveEnvVars()across the codebase.internal/exec/shell_utils_test.go (2)
600-609: LGTM — nil atmosConfig is appropriate for basic shell tests.These tests verify shell execution mechanics, not Atmos-specific env var handling.
611-669: LGTM — remaining ExecuteShell test updates are consistent.All test cases correctly pass
nilas the first argument with appropriate comments.internal/exec/workflow_adapters.go (1)
34-40: LGTM — nil atmosConfig is intentional for workflow shell commands.Per the learning, workflow non-identity steps use
baseWorkflowEnv. The comment clearly documents the rationale: shell commands don't needatmosConfigfor basic execution.go.mod (1)
71-71: LGTM — godotenv promoted to direct dependency.This correctly reflects the new direct import in
pkg/env/file.go. v1.5.1 is the latest stable version.internal/exec/stack_processor_process_stacks.go (1)
211-215: LGTM — atmos.yaml env correctly positioned as lowest priority in merge chain.
GetCaseSensitiveEnvVars()provides case-preserved env vars fromatmos.yaml. The merge order ensures component-levelenvoverrides globalenv, which overridesatmos.yamlenv.internal/exec/workflow_utils.go (1)
324-334: Workflow env handling now correctly uses case‑preserving env accessWiring
prepareStepEnvironmenttoatmosConfig.GetCaseSensitiveEnvVars()and passing&atmosConfigintoExecuteShellkeeps workflow steps in line with the new EnvConfig / .env behavior and perf tracking. The change is consistent with how other call sites are being updated.internal/exec/shell_utils.go (3)
133-191: ExecuteShell updates correctly integrate global env and per‑dir .env supportPassing
*schema.AtmosConfigurationintoExecuteShelland merging:
os.Environ()- global env via
atmosConfig.GetCaseSensitiveEnvVars()- per‑directory
.envviaLoadFromDirectorybefore applying
envVarsgives shells the same enriched environment as workflows and other exec paths, while still allowing the caller‑supplied entries to override. TheatmosConfig == nilguard keeps tests and non‑Atmos usage safe.
210-305: Terraform shell now respects global env via GetCaseSensitiveEnvVarsSwitching
execTerraformShellCommandto buildmergedEnvwith:mergedEnv := envpkg.MergeSystemEnvWithGlobal(componentEnvList, atmosConfig.GetCaseSensitiveEnvVars())ensures both legacy flat
envand structuredenv.varsfeed into the Terraform shell’s environment with proper case restoration, while still letting the per‑component entries incomponentEnvListtake precedence. This keeps the Terraform shell consistent with the rest of the env pipeline.Also applies to: 297-304
361-419: Auth shell correctly layers global env under auth‑specific varsIn
ExecAuthShellCommand, usingMergeSystemEnvSimpleWithGlobal(authEnvList, atmosConfig.GetCaseSensitiveEnvVars())makes the auth shell’s environment:
- Start from the host env.
- Add global env from Atmos config (case‑preserved).
- Finally overlay the auth/env list (including ATMOS_IDENTITY and provider‑specific credentials).
That matches the documented precedence and keeps auth shells aligned with the new env model.
Also applies to: 402-403
pkg/schema/schema_test.go (1)
3-10: Import path resolves correctly —go.yaml.in/yaml/v3is declared as a direct dependency in go.mod, so no resolution issues here.The inconsistency you noticed is real (codebase mixes
go.yaml.in/yaml/v3andgopkg.in/yaml.v3), but both import paths are intentionally maintained as separate dependencies. If consistency across the codebase matters for maintenance, that's a separate refactoring conversation.Likely an incorrect or invalid review comment.
|
Warning Release Documentation RequiredThis PR is labeled
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
pkg/config/load.go (2)
1243-1275: Consider more robust structured form detection.The detection at lines 1259-1260 checks only for key existence, not value type. If a flat env map contains a key literally named
varsorfiles, it would be misinterpreted as structured form.♻️ More robust detection
- // Detect structured form by presence of "vars" or "files" keys. - _, hasVars := envMap["vars"] - _, hasFiles := envMap["files"] + // Detect structured form by presence of "vars" or "files" keys with map values. + varsVal, hasVars := envMap["vars"] + filesVal, hasFiles := envMap["files"] + + // Only treat as structured if the values are maps/objects, not simple strings. + if hasVars { + if _, ok := varsVal.(map[string]any); !ok { + hasVars = false + } + } + if hasFiles { + if _, ok := filesVal.(map[string]any); !ok { + hasFiles = false + } + }
1318-1320: Consider moving UI reporting to the caller.The
ui.Success()calls create side effects in the config loading function, which is called from many places. This could be noisy in test environments or when config is reloaded.Since the function already returns
loadedFiles, consider letting the caller handle UI reporting:// In LoadConfig (line 284): loadedFiles := loadEnvFilesEarly(v, v.GetString("base_path")) for _, file := range loadedFiles { ui.Success(fmt.Sprintf("Loaded %s", filepath.Base(file))) }Then remove the UI calls from inside
loadEnvFilesEarly. This separates concerns and makes the config loading function more testable.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
cmd/auth_exec.gocmd/cmd_utils.gogo.modinternal/exec/stack_processor_process_stacks.gopkg/config/load.go
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/exec/stack_processor_process_stacks.go
- cmd/cmd_utils.go
🧰 Additional context used
📓 Path-based instructions (4)
cmd/**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
cmd/**/*.go: Use Cobra's recommended command structure with a root command and subcommands, implementing each command in a separate file undercmd/directory
Provide comprehensive help text for all commands and flags, include examples in command help, and follow Go's documentation conventions in Cobra command definitions
Provide meaningful feedback to users and include progress indicators for long-running operations in CLI commands
Files:
cmd/auth_exec.go
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context usingfmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: usegofmtandgoimportsto format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging
**/*.go: All comments must end with periods (enforced bygodotlinter) in Go code
Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (cfg,log,u,errUtils)
All errors MUST be wrapped using static errors defined inerrors/errors.go- useerrors.Joinfor combining errors,fmt.Errorfwith%wfor context, anderrors.Is()for error checking
Never manually create mocks - usego.uber.org/mock/mockgenwith//go:generatedirectives in Go code
Keep files small and focused - under 600 lines with one cmd/impl per file, co-locate tests, never use//revive:disable:file-length-limit
Use colors frompkg/ui/theme/colors.gofor all UI theming in Go code
Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, usefilepath.Join()instead of h...
Files:
cmd/auth_exec.gopkg/config/load.go
**/{pkg,internal,cmd}/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Add
defer perf.Track(atmosConfig, "pkg.FuncName")()plus blank line to all public functions, usingnilif no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions
Files:
cmd/auth_exec.gopkg/config/load.go
{go.mod,go.sum}
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies
Files:
go.mod
🧠 Learnings (37)
📓 Common learnings
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.
Learnt from: aknysh
Repo: cloudposse/atmos PR: 0
File: :0-0
Timestamp: 2025-01-19T22:30:27.600Z
Learning: The Atmos YAML function `!env` is used to retrieve environment variables and assign them to sections in stack manifests. It supports both simple types (string, number, boolean) and complex types (JSON-encoded lists, maps, objects).
Learnt from: Listener430
Repo: cloudposse/atmos PR: 934
File: tests/fixtures/scenarios/docs-generate/README.md.gotmpl:99-118
Timestamp: 2025-01-25T03:51:57.689Z
Learning: For the cloudposse/atmos repository, changes to template contents should be handled in dedicated PRs and are typically considered out of scope for PRs focused on other objectives.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.
Learnt from: RoseSecurity
Repo: cloudposse/atmos PR: 725
File: internal/exec/vendor_component_utils.go:55-62
Timestamp: 2024-10-15T17:36:08.040Z
Learning: In the 'atmos' project, when adding support for multiple file extensions, the preferred approach is to directly check for the existence of the files rather than modifying constants or adding helper functions.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: examples/demo-atmos-cli-imports/atmos.yaml:8-8
Timestamp: 2025-01-25T15:21:40.413Z
Learning: In Atmos, when a directory is specified for configuration loading (e.g., in the `import` section of atmos.yaml), all files within that directory should be treated as Atmos configurations. Do not suggest restricting file extensions in directory-based glob patterns.
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1352
File: pkg/store/artifactory_store_test.go:108-113
Timestamp: 2025-08-15T14:43:41.030Z
Learning: In test files for the atmos project, it's acceptable to ignore errors from os.Setenv/Unsetenv operations during test environment setup and teardown, as these are controlled test scenarios.
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1475
File: pkg/auth/providers/github/oidc.go:96-100
Timestamp: 2025-09-10T17:34:52.568Z
Learning: The ATMOS_ environment variable binding guideline applies to Atmos configuration variables, not external service-required environment variables like GitHub Actions OIDC variables (GITHUB_ACTIONS, ACTIONS_ID_TOKEN_*) which must use their standard names.
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Use `viper.BindEnv("ATMOS_VAR", "ATMOS_VAR", "FALLBACK")` for environment variables - ATMOS_ prefix required in Go code
📚 Learning: 2025-12-13T06:10:25.156Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.
Applied to files:
cmd/auth_exec.gogo.mod
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2025-11-08T19:56:18.660Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Use `viper.BindEnv("ATMOS_VAR", "ATMOS_VAR", "FALLBACK")` for environment variables - ATMOS_ prefix required in Go code
Applied to files:
cmd/auth_exec.gogo.modpkg/config/load.go
📚 Learning: 2025-11-11T03:47:59.576Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.
Applied to files:
cmd/auth_exec.gogo.modpkg/config/load.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2025-11-11T03:47:45.878Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.
Applied to files:
cmd/auth_exec.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.
Applied to files:
cmd/auth_exec.go
📚 Learning: 2024-12-07T16:16:13.038Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 825
File: internal/exec/helmfile_generate_varfile.go:28-31
Timestamp: 2024-12-07T16:16:13.038Z
Learning: In `internal/exec/helmfile_generate_varfile.go`, the `--help` command (`./atmos helmfile generate varfile --help`) works correctly without requiring stack configurations, and the only change needed was to make `ProcessCommandLineArgs` exportable by capitalizing its name.
Applied to files:
cmd/auth_exec.go
📚 Learning: 2025-09-07T18:07:00.549Z
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1452
File: cmd/auth_login.go:43-44
Timestamp: 2025-09-07T18:07:00.549Z
Learning: In the atmos project, the identity flag is defined as a persistent flag on the auth root command (cmd/auth.go), making it available to all auth subcommands without needing to be redefined in each individual subcommand.
Applied to files:
cmd/auth_exec.go
📚 Learning: 2024-12-11T18:40:12.808Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 844
File: cmd/helmfile.go:37-37
Timestamp: 2024-12-11T18:40:12.808Z
Learning: In the atmos project, `cliConfig` is initialized within the `cmd` package in `root.go` and can be used in other command files.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2025-12-13T04:37:25.223Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/root.go:0-0
Timestamp: 2025-12-13T04:37:25.223Z
Learning: In Atmos cmd/root.go Execute(), after cfg.InitCliConfig, we must call both toolchainCmd.SetAtmosConfig(&atmosConfig) and toolchain.SetAtmosConfig(&atmosConfig) so the CLI wrapper and the toolchain package receive configuration; missing either can cause nil-pointer panics in toolchain path resolution.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2025-08-15T14:43:41.030Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1352
File: pkg/store/artifactory_store_test.go:108-113
Timestamp: 2025-08-15T14:43:41.030Z
Learning: In test files for the atmos project, it's acceptable to ignore errors from os.Setenv/Unsetenv operations during test environment setup and teardown, as these are controlled test scenarios.
Applied to files:
cmd/auth_exec.go
📚 Learning: 2025-12-13T04:37:40.435Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/toolchain/get.go:23-40
Timestamp: 2025-12-13T04:37:40.435Z
Learning: In Go CLI command files using Cobra, constrain the subcommand to accept at most one positional argument (MaximumNArgs(1)) so it supports both listing all items (zero args) and fetching a specific item (one arg). Define and parse flags with a standard parser (e.g., flags.NewStandardParser()) and avoid binding flags to Viper (no viper.BindEnv/BindPFlag). This promotes explicit argument handling and predictable flag behavior across command files.
Applied to files:
cmd/auth_exec.go
📚 Learning: 2025-12-21T04:10:29.030Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1891
File: internal/exec/describe_affected.go:468-468
Timestamp: 2025-12-21T04:10:29.030Z
Learning: In Go, package-level declarations (constants, variables, types, and functions) are visible to all files in the same package without imports. During reviews in cloudposse/atmos (and similar Go codebases), before suggesting to declare a new identifier, first check if it already exists in another file of the same package. If it exists, you can avoid adding a new declaration; if not, proceed with a proper package-level declaration.
Applied to files:
cmd/auth_exec.gopkg/config/load.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to {go.mod,go.sum} : Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies
Applied to files:
go.mod
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: Go version 1.23.0 was deliberately introduced by the maintainer (aknysh) in January 2025. While this might be a pre-release or development version of Go, it has been approved for use in this project.
Applied to files:
go.mod
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: The project uses Go version 1.23.0 which has been confirmed by the maintainer to be working in production for months. Do not flag this as an invalid Go version.
Applied to files:
go.mod
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.
Applied to files:
go.mod
📚 Learning: 2025-04-26T15:54:10.506Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 1195
File: internal/exec/terraform_clean.go:99-99
Timestamp: 2025-04-26T15:54:10.506Z
Learning: The error variable `ErrRelPath` is defined in `internal/exec/terraform_clean_util.go` and is used across files in the `exec` package, including in `terraform_clean.go`. This is part of an approach to standardize error handling in the codebase.
Applied to files:
go.mod
📚 Learning: 2025-03-12T21:38:42.699Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1139
File: pkg/config/go-homedir/homedir.go:183-196
Timestamp: 2025-03-12T21:38:42.699Z
Learning: The code in pkg/config/go-homedir is a direct fork of the mitchellh/go-homedir package and was intentionally imported as-is without modifications to maintain consistency with the original source. Security concerns or other improvements may be addressed in future PRs.
Applied to files:
go.mod
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.
Applied to files:
go.modpkg/config/load.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Use Viper for managing configuration, environment variables, and flags in CLI commands
Applied to files:
go.modpkg/config/load.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Applied to files:
pkg/config/load.go
📚 Learning: 2025-04-10T20:48:22.687Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1147
File: pkg/config/load.go:0-0
Timestamp: 2025-04-10T20:48:22.687Z
Learning: In the `bindEnv` function in `pkg/config/load.go`, panic is used deliberately instead of returning errors because errors from `BindEnv` would only occur due to developer mistakes. Using panic helps with early detection of these developer errors during initialization.
Applied to files:
pkg/config/load.go
📚 Learning: 2025-12-13T03:21:35.786Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1813
File: cmd/terraform/shell.go:28-73
Timestamp: 2025-12-13T03:21:35.786Z
Learning: In Atmos, when calling cfg.InitCliConfig, you must first populate the schema.ConfigAndStacksInfo struct with global flag values using flags.ParseGlobalFlags(cmd, v) rather than passing an empty struct. The LoadConfig function (pkg/config/load.go) reads config selection fields (AtmosConfigFilesFromArg, AtmosConfigDirsFromArg, BasePath, ProfilesFromArg) directly from the ConfigAndStacksInfo struct, NOT from Viper. Passing an empty struct causes config selection flags (--base-path, --config, --config-path, --profile) to be silently ignored. Correct pattern: parse flags → populate struct → call InitCliConfig. See cmd/terraform/plan_diff.go for reference implementation.
Applied to files:
pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/{pkg,internal,cmd}/**/*.go : Add `defer perf.Track(atmosConfig, "pkg.FuncName")()` plus blank line to all public functions, using `nil` if no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions
Applied to files:
pkg/config/load.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.
Applied to files:
pkg/config/load.go
📚 Learning: 2025-09-13T18:06:07.674Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1466
File: toolchain/list.go:39-42
Timestamp: 2025-09-13T18:06:07.674Z
Learning: In the cloudposse/atmos repository, for UI messages in the toolchain package, use utils.PrintfMessageToTUI instead of log.Error or fmt.Fprintln(os.Stderr, ...). Import pkg/utils with alias "u" to follow the established pattern.
Applied to files:
pkg/config/load.go
📚 Learning: 2025-05-30T03:21:37.197Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1274
File: go.mod:63-63
Timestamp: 2025-05-30T03:21:37.197Z
Learning: The redis dependency (github.com/redis/go-redis/v9) in the atmos project is only used in tests, not in production code.
Applied to files:
pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (`cfg`, `log`, `u`, `errUtils`)
Applied to files:
pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: New config templates support Go templating with `FuncMap()` from `internal/exec/template_funcs.go`
Applied to files:
pkg/config/load.go
📚 Learning: 2025-08-16T23:32:40.412Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1405
File: internal/exec/describe_dependents_test.go:455-456
Timestamp: 2025-08-16T23:32:40.412Z
Learning: In the cloudposse/atmos Go codebase, `InitCliConfig` returns a `schema.AtmosConfiguration` value (not a pointer), while `ExecuteDescribeDependents` expects a `*schema.AtmosConfiguration` pointer parameter. Therefore, when passing the result of `InitCliConfig` to `ExecuteDescribeDependents`, use `&atmosConfig` to pass the address of the value.
Applied to files:
pkg/config/load.go
📚 Learning: 2025-09-23T04:43:31.857Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1430
File: pkg/config/config.go:98-107
Timestamp: 2025-09-23T04:43:31.857Z
Learning: In the Atmos codebase, NO_PAGER environment variable handling is intentionally kept as direct os.Getenv() access in pkg/config/config.go rather than using Viper binding, because adding no_pager to the config file would be confusing for users. This is an acknowledged exception to the normal Viper binding pattern for environment variables.
Applied to files:
pkg/config/load.go
📚 Learning: 2025-09-29T15:47:10.908Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1540
File: internal/exec/terraform_cli_args_utils.go:64-73
Timestamp: 2025-09-29T15:47:10.908Z
Learning: In the Atmos codebase, viper.BindEnv is required for CLI commands in the cmd/ package, but internal utilities can use os.Getenv directly when parsing environment variables for business logic purposes. The requirement to use viper is specific to the CLI interface layer, not all environment variable access throughout the codebase.
Applied to files:
pkg/config/load.go
🧬 Code graph analysis (2)
cmd/auth_exec.go (1)
pkg/env/global.go (1)
MergeGlobalEnv(16-35)
pkg/config/load.go (3)
pkg/schema/schema.go (1)
AtmosConfiguration(75-121)pkg/env/file.go (1)
LoadEnvFiles(19-27)pkg/ui/formatter.go (1)
Success(204-211)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Summary
🔇 Additional comments (5)
go.mod (1)
74-74: Solid dependency addition. Movingjoho/godotenvto direct dependency is the right call since it's now a first-class feature. Version v1.5.1 is the latest stable release, so you're good to go.cmd/auth_exec.go (1)
113-113: LGTM! Proper use of case-sensitive env access.The switch to
GetCaseSensitiveEnvVars()correctly aligns with the new case-sensitive environment handling introduced in this PR.pkg/config/load.go (3)
22-22: LGTM! Imports properly organized.The new imports for
envanduipackages are correctly placed and follow the three-group organization pattern.Also applies to: 26-26
281-284: Correct placement for early .env loading.The placement before profile detection is essential and well-documented. This enables ATMOS_PROFILE and other ATMOS_* variables from .env files to influence Atmos behavior during initialization.
1328-1331: Good catch on dual-form case preservation.Adding both
env(flat form) andenv.vars(structured form) ensures environment variable names maintain correct casing regardless of which configuration format is used.
…support-for-env-files
|
Warning Release Documentation RequiredThis PR is labeled
|
|
Warning Release Documentation RequiredThis PR is labeled
|
…support-for-env-files
|
Warning Release Documentation RequiredThis PR is labeled
|
- Add examples/dotenv/ demonstrating .env file support - Custom commands show-env and check-var to demonstrate functionality - Example .env, .env.local, and .env.dev files - Fix terraform.go to use GetCaseSensitiveEnvVars() after merge Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Warning Release Documentation RequiredThis PR is labeled
|
|
Warning This PR exceeds the recommended limit of 1,000 lines.Large PRs are difficult to review and may be rejected due to their size. Please verify that this PR does not address multiple issues. |
- Add blog post announcing .env file support feature - Add milestone to DX initiative in roadmap - Link to PR #1930 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add structured form configuration with env.vars and env.files - Document all env.files options (enabled, paths, parents) - Add sections for glob patterns, parent walking, early loading - Embed the dotenv example using EmbedExample component Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add dotenv to TAGS_MAP under DX category - Add dotenv to DOCS_MAP linking to env configuration docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
💥 This pull request now has conflicts. Could you fix it @osterman? 🙏 |
…support-for-env-files
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@pkg/config/load.go`:
- Around line 1286-1307: The early .env loader (loadEnvFilesEarly) currently
calls env.LoadEnvFiles(basePath, paths) which ignores the env.files.parents
flag; change it to call env.LoadFromDirectory(basePath, paths,
v.GetBool("env.files.parents"), v.GetString("repo_root")) so parent traversal is
honored during early loading and profile detection, ensuring it uses the same
repoRoot and parents flag as the main loader; preserve returned envVars,
loadedFiles and existing error handling (log.Debug and behavior) after the
switch.
- Around line 1251-1278: The code currently treats presence of "vars" or "files"
keys (via viper-lowercased envMap["vars"/"files"]) as indicating structured form
even when those keys are flat string values; update the detection logic to
verify the actual types before assuming structured form: check envMap["vars"] is
a map[string]any (or can be decoded by GetStringMapString) and envMap["files"]
is a map[string]any or a slice/struct expected by atmosConfig.Env.Files, only
then set hasVars/hasFiles true and use
GetStringMapString("env.vars")/GetBool/GetStringSlice; otherwise fall back to
the flat form and populate atmosConfig.Env.Vars from
v.GetStringMapString("env"). Ensure you reference and update the existing
symbols envRaw, envMap, envMap["vars"], envMap["files"], hasVars/hasFiles,
GetStringMapString("env.vars"), and atmosConfig.Env.Files handling so legacy
string values are not lost.
🧹 Nitpick comments (3)
pkg/schema/schema.go (1)
264-273: Fallback logic may be redundant.Both
GetCaseSensitiveMap("env.vars")andGetCaseSensitiveMap("env")returna.Env.Varsper lines 252-253. The fallback on line 272 would only differ if the casemap has different keys registered for each path. If that's the intent, it works. Otherwise, the fallback is effectively a no-op whenvarsis empty.Consider adding a brief comment explaining when the fallback would return different results, or simplify if both paths always return identical data.
website/blog/2025-01-15-dotenv-file-support.mdx (1)
37-56: Consider a brief note about.envsecrets and gitignore.
A short reminder to avoid committing.env/.env.localwould help readers.✏️ Optional doc tweak
Environment variables are now available to all Atmos commands including Terraform, Helmfile, workflows, and custom commands. + +Note: `.env` files often contain secrets. Add `.env.local` to `.gitignore` and avoid committing sensitive values.website/docs/cli/configuration/env.mdx (1)
126-209: Optional: add a brief note about missing files being skipped.
This matches the “load only when they exist” behavior and prevents confusion.✏️ Optional doc tweak
+ +Missing files are skipped silently; only existing matches are loaded. ```Enable loading in `atmos.yaml`: <File title="atmos.yaml"> ```yaml env: files: enabled: true paths: - .env
| // Check if env section uses structured or flat form. | ||
| envRaw := v.Get("env") | ||
| if envRaw == nil { | ||
| return | ||
| } | ||
|
|
||
| envMap, ok := envRaw.(map[string]any) | ||
| if !ok { | ||
| return | ||
| } | ||
|
|
||
| // Detect structured form by presence of "vars" or "files" keys. | ||
| _, hasVars := envMap["vars"] | ||
| _, hasFiles := envMap["files"] | ||
|
|
||
| if hasVars || hasFiles { | ||
| // Structured form: env.vars + env.files. | ||
| if hasVars { | ||
| atmosConfig.Env.Vars = v.GetStringMapString("env.vars") | ||
| } | ||
| // Files config is parsed via mapstructure into atmosConfig.Env.Files. | ||
| atmosConfig.Env.Files.Enabled = v.GetBool("env.files.enabled") | ||
| atmosConfig.Env.Files.Paths = v.GetStringSlice("env.files.paths") | ||
| atmosConfig.Env.Files.Parents = v.GetBool("env.files.parents") | ||
| } else { | ||
| // Flat form: all keys are vars (backward compatibility). | ||
| atmosConfig.Env.Vars = v.GetStringMapString("env") | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the parseEnvConfig function and surrounding context in load.go
cd cloudposse/atmos && head -1300 pkg/config/load.go | tail -100Repository: cloudposse/atmos
Length of output: 126
🏁 Script executed:
# Check how Viper is configured in load.go - look for case sensitivity settings
rg -n "viper\.|SetEnvKeyReplacer|SetEnvPrefix" pkg/config/load.go -A 2 -B 2Repository: cloudposse/atmos
Length of output: 10707
🏁 Script executed:
# Search for tests of parseEnvConfig
fd -t f "load.*test.go" pkg/config/ && rg -n "parseEnvConfig\|Test.*Env" pkg/config/ | head -30Repository: cloudposse/atmos
Length of output: 401
🏁 Script executed:
# Check Viper's default behavior - search for any documentation or config about case handling
rg -n "caseSensitive|CaseSensitive" pkg/config/load.go -B 3 -A 3Repository: cloudposse/atmos
Length of output: 2177
🏁 Script executed:
# Look for legacy env config examples or tests
rg -n "env:\s*\n\s+VARS|env:\s*\n\s+FILES" . -i --type yamlRepository: cloudposse/atmos
Length of output: 251
🏁 Script executed:
# Look at the parseEnvConfig function in detail
sed -n '1250,1280p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 988
🏁 Script executed:
# Check the preserveCaseSensitiveMaps and casemap extraction to understand case preservation
sed -n '1329,1375p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 1923
🏁 Script executed:
# Search for tests that exercise parseEnvConfig with legacy flat env configs
rg -n "parseEnvConfig\|env:" pkg/config/load_test.go -A 5 -B 5 | head -80Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Check the env package for parent-walking logic mentioned in the scratchpad
fd -t f "env.go" pkg/ internal/ && rg -n "Parent\|parents" pkg/env/ internal/env/ 2>/dev/null | head -30Repository: cloudposse/atmos
Length of output: 228
🏁 Script executed:
# Check Viper's behavior with case sensitivity and Get() method
web_search
Viper library case sensitivity behavior for map keys Get methodRepository: cloudposse/atmos
Length of output: 154
🏁 Script executed:
# Check env.go for parent-walking logic
sed -n '1,100p' pkg/env/env.goRepository: cloudposse/atmos
Length of output: 3224
🏁 Script executed:
# Look for tests that cover env config parsing with legacy formats
rg -n "env:" pkg/config/load_test.go -A 10 | head -60Repository: cloudposse/atmos
Length of output: 1829
🏁 Script executed:
# Check if there are YAML test fixtures with env configs
fd -t f ".*\.yaml" pkg/config | xargs rg "^env:" 2>/dev/null | head -20Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Understand how v.Get() works with Viper - look for usage patterns
rg -n "v\.Get\(" pkg/config/load.go -B 2 -A 5 | head -50Repository: cloudposse/atmos
Length of output: 634
🏁 Script executed:
# Check how case preservation affects parseEnvConfig - does it restore case before parsing?
rg -n "parseEnvConfig\|CaseMaps\|Case.*Map" pkg/config/load.go | head -30Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Look at the order of operations: when is parseEnvConfig called vs when are case maps applied?
sed -n '200,400p' pkg/config/load.go | grep -n "parseEnvConfig\|preserveCaseSensitiveMaps\|Unmarshal"Repository: cloudposse/atmos
Length of output: 275
🏁 Script executed:
# Check the schema.AtmosConfiguration to understand Env field structure
rg -n "type.*Env\s|Env.*struct" pkg/schema/ -A 15 | head -50Repository: cloudposse/atmos
Length of output: 3762
🏁 Script executed:
# Search for any legacy env configs in tests or examples with VARS or FILES as flat keys
rg -n "VARS:|FILES:" . --type yaml -i -B 2 -A 2Repository: cloudposse/atmos
Length of output: 50373
🏁 Script executed:
# Check if there are any integration tests showing backward compatibility for env
rg -n "backward|legacy|compat" pkg/config/load_test.go -i -B 2 -A 5Repository: cloudposse/atmos
Length of output: 771
🏁 Script executed:
# Let's understand the exact behavior - check what v.Get returns for structured vs flat env
sed -n '1250,1280p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 988
🏁 Script executed:
# Check loadEnvFilesEarly to see if it ignores parents
sed -n '1285,1330p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 1389
🏁 Script executed:
# Look for how env vars with case are preserved - check the CaseMap usage
rg -n "CaseMap\|CaseMaps" pkg/config/load.go -A 3 -B 3 | head -60Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Check if there are any existing tests that show env parsing with edge cases
rg -n "env.*vars\|env.*files" pkg/config/load_test.go -B 5 -A 5 | head -80Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Check the order of operations - when is parseEnvConfig called vs preserveCaseSensitiveMaps
sed -n '120,160p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 1366
🏁 Script executed:
# Search for any env config in test fixtures with VARS or FILES as actual flat keys
find tests/ -name "*.yaml" -type f -exec grep -l "^env:" {} \; | head -5 | xargs grep -A 10 "^env:"Repository: cloudposse/atmos
Length of output: 3733
🏁 Script executed:
# Check the schema to understand what Env field structure expects
rg -n "type Env struct|type EnvFilesConfig" pkg/schema/ -A 10Repository: cloudposse/atmos
Length of output: 904
🏁 Script executed:
# Check the actual order of operations for parseEnvConfig and preserveCaseSensitiveMaps
sed -n '100,160p' pkg/config/load.go | grep -n "parseEnvConfig\|preserveCaseSensitiveMaps\|Unmarshal"Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Look for any actual usage or documentation of env config structure with VARS/FILES
rg -n "env:" tests/ --type yaml -A 15 | grep -i "vars:\|files:" | head -30Repository: cloudposse/atmos
Length of output: 2024
🏁 Script executed:
# Check if there are integration tests that verify env config parsing behavior
rg -n "TestParseEnvConfig\|env.*config" pkg/config/load_test.go -iRepository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Look at the schema to understand legacy vs new form
sed -n '54,75p' pkg/schema/schema.goRepository: cloudposse/atmos
Length of output: 1169
🏁 Script executed:
# Check if case maps are actually applied back to Viper or just tracked
rg -n "CaseMap\|RestoreCase\|applyCase" pkg/config/load.go pkg/casemap/ -iRepository: cloudposse/atmos
Length of output: 112
🏁 Script executed:
# Check loadEnvFilesEarly - does it use parents option?
sed -n '1285,1328p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 1244
🏁 Script executed:
# Check env.LoadEnvFiles to see if it respects the parents flag
rg -n "LoadEnvFiles\|func.*parents" pkg/env/env.go -A 5 -B 2 | head -40Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Check if case maps are used/restored before parseEnvConfig
sed -n '100,150p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 1710
🏁 Script executed:
# Check if env.LoadEnvFiles signature includes parents parameter
rg -n "func LoadEnvFiles" pkg/env/env.go -A 10Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Let's verify the actual issue - create a test to see what happens with flat env with VARS key
rg -n "TestParseEnv\|parseEnv" pkg/config/load_test.goRepository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Check exact order: is parseEnvConfig called before or after preserveCaseSensitiveMaps?
sed -n '100,160p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 2140
🏁 Script executed:
# Look at what GetStringMapString does when value is not a map
rg -n "GetStringMapString" pkg/config/load.go -B 3 -A 3Repository: cloudposse/atmos
Length of output: 919
🏁 Script executed:
# Get the full InitCliConfig function to see order of operations
sed -n '100,160p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 2140
🏁 Script executed:
# Search for preserveCaseSensitiveMaps call timing
rg -n "preserveCaseSensitiveMaps" pkg/config/load.go -B 5 -A 5Repository: cloudposse/atmos
Length of output: 1346
🏁 Script executed:
# Verify: does GetStringMapString return empty map for non-existent paths?
python3 << 'EOF'
# Test Viper behavior conceptually
# When you call GetStringMapString("env.vars") on a config without env.vars but with env.vars as a flat key,
# does it return the flat key or empty?
# Viper documentation says GetStringMapString converts the value at that path to map[string]string
# If the path doesn't exist, it returns an empty map
print("Viper GetStringMapString behavior:")
print("- If path 'env.vars' doesn't exist as a nested map, returns empty map {}")
print("- If path 'env' exists with flat key 'vars' (string value), that's not a nested 'env.vars' path")
print("- So GetStringMapString('env.vars') returns empty {}")
print("\nThis means the review comment's concern is valid:")
print("1. Config has: env: { VARS: value }")
print("2. Viper lowercases to: env: { vars: value }")
print("3. Code checks hasVars := envMap['vars'] → true (key exists)")
print("4. Code calls GetStringMapString('env.vars') → empty (no nested map at that path)")
print("5. Result: atmosConfig.Env.Vars = {} (LOST)")
EOFRepository: cloudposse/atmos
Length of output: 635
🏁 Script executed:
# Check the exact sequence in InitCliConfig
sed -n '95,150p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 1814
🏁 Script executed:
# Verify one more detail: does the case preservation actually help if called before parseEnvConfig?
# Check if CaseMaps are used to restore values
rg -n "CaseMap\|ApplyCase\|Restore" pkg/config/ --type goRepository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Final verification: test scenario with legacy flat env VARS key
python3 << 'EOF'
# Scenario: atmos.yaml contains:
# env:
# GITHUB_TOKEN: "secret"
# AWS_KEY: "key123"
# Viper loads and lowercases:
# env:
# github_token: "secret"
# aws_key: "key123"
# Current code does:
# envMap = v.Get("env") → {github_token: "secret", aws_key: "key123"}
# _, hasVars = envMap["vars"] → false (no "vars" key)
# _, hasFiles = envMap["files"] → false (no "files" key)
# if hasVars || hasFiles → false
# else → v.GetStringMapString("env") ✓ WORKS
# But with legacy config containing VARS/FILES keys:
# env:
# VARS:
# GITHUB_TOKEN: "secret"
# FILES:
# enabled: true
# Viper lowercases:
# env:
# vars: ← THIS IS A STRING VALUE, not a map!
# github_token: "secret"
# files: ← THIS IS A BOOL or struct, not a map!
# enabled: true
# Current code does:
# envMap = v.Get("env") → {vars: {...}, files: {...}}
# _, hasVars = envMap["vars"] → true (KEY EXISTS)
# _, hasFiles = envMap["files"] → true (KEY EXISTS)
# if hasVars || hasFiles → true ✓ assumes structured
# v.GetStringMapString("env.vars") → empty {} ✗ LOST DATA
print("CONFIRMED: The review comment correctly identifies a real backward compatibility bug.")
print("\nLegacy config with VARS/FILES as flat keys will lose data:")
print("- hasVars/hasFiles check succeeds (keys exist)")
print("- GetStringMapString fails (values aren't nested maps)")
print("- Result: silent data loss")
EOFRepository: cloudposse/atmos
Length of output: 334
Check value types before treating env config as structured form.
Legacy env configs with VARS or FILES as flat keys will be misclassified as structured. Because Viper lowercases keys to vars and files, the presence check succeeds even when those keys contain string values, not nested maps. Then GetStringMapString("env.vars") returns empty, silently losing the data.
✅ Proposed fix
- _, hasVars := envMap["vars"]
- _, hasFiles := envMap["files"]
-
- if hasVars || hasFiles {
+ varsRaw, hasVars := envMap["vars"]
+ filesRaw, hasFiles := envMap["files"]
+ _, varsIsMap := varsRaw.(map[string]any)
+ _, filesIsMap := filesRaw.(map[string]any)
+
+ // If VARS/FILES are legacy flat keys (non-map), keep flat parsing.
+ if (hasVars && !varsIsMap) || (hasFiles && !filesIsMap) {
+ atmosConfig.Env.Vars = v.GetStringMapString("env")
+ return
+ }
+
+ if varsIsMap || filesIsMap {
// Structured form: env.vars + env.files.
if hasVars {
atmosConfig.Env.Vars = v.GetStringMapString("env.vars")
}🤖 Prompt for AI Agents
In `@pkg/config/load.go` around lines 1251 - 1278, The code currently treats
presence of "vars" or "files" keys (via viper-lowercased envMap["vars"/"files"])
as indicating structured form even when those keys are flat string values;
update the detection logic to verify the actual types before assuming structured
form: check envMap["vars"] is a map[string]any (or can be decoded by
GetStringMapString) and envMap["files"] is a map[string]any or a slice/struct
expected by atmosConfig.Env.Files, only then set hasVars/hasFiles true and use
GetStringMapString("env.vars")/GetBool/GetStringSlice; otherwise fall back to
the flat form and populate atmosConfig.Env.Vars from
v.GetStringMapString("env"). Ensure you reference and update the existing
symbols envRaw, envMap, envMap["vars"], envMap["files"], hasVars/hasFiles,
GetStringMapString("env.vars"), and atmosConfig.Env.Files handling so legacy
string values are not lost.
| // Check if env.files is enabled (quick bootstrap read). | ||
| if !v.GetBool("env.files.enabled") { | ||
| return nil | ||
| } | ||
|
|
||
| paths := v.GetStringSlice("env.files.paths") | ||
| if len(paths) == 0 { | ||
| paths = []string{".env"} // Default pattern. | ||
| } | ||
|
|
||
| // Determine base path for loading. | ||
| if basePath == "" { | ||
| basePath = v.GetString("base_path") | ||
| } | ||
| if basePath == "" { | ||
| basePath = "." | ||
| } | ||
|
|
||
| // Load .env files from base path. | ||
| envVars, loadedFiles, err := env.LoadEnvFiles(basePath, paths) | ||
| if err != nil { | ||
| log.Debug("Failed to load .env files", "error", err) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's understand the context around lines 1286-1307 in pkg/config/load.go
cd /tmp && head -1320 /dev/stdin | tail -100 <<'EOF'
File will be read next
EOF
# Actually, let me read the file directly
head -1320 pkg/config/load.go | tail -100Repository: cloudposse/atmos
Length of output: 247
🏁 Script executed:
# Find the function definition around line 1286
sed -n '1250,1320p' pkg/config/load.go | cat -nRepository: cloudposse/atmos
Length of output: 2763
🏁 Script executed:
# Look for env.files.parents usage in the codebase
rg "env\.files\.parents" -A 3 -B 3Repository: cloudposse/atmos
Length of output: 1157
🏁 Script executed:
# Find the env.LoadEnvFiles function signature
rg "func.*LoadEnvFiles" -A 5Repository: cloudposse/atmos
Length of output: 633
🏁 Script executed:
# Search for the main loader that handles .env files
rg "LoadEnvFiles" -B 5 -A 10Repository: cloudposse/atmos
Length of output: 8528
🏁 Script executed:
# Check the entire function that contains lines 1286-1307
sed -n '1200,1350p' pkg/config/load.goRepository: cloudposse/atmos
Length of output: 4722
🏁 Script executed:
# Find LoadFromDirectory implementation
rg "LoadFromDirectory" -A 20Repository: cloudposse/atmos
Length of output: 10461
🏁 Script executed:
# Search for where the parents flag is actually used in env loading
rg "\.Parents" pkg/env/ -B 3 -A 3Repository: cloudposse/atmos
Length of output: 42
🏁 Script executed:
# Check if there's a broader function that respects parents
rg "parents" pkg/env/file.go -B 2 -A 5Repository: cloudposse/atmos
Length of output: 834
🏁 Script executed:
# Look for where the main loader (after profile detection) loads env files
rg "loadEnvFiles" -i -B 3 -A 3Repository: cloudposse/atmos
Length of output: 7519
Early .env loader doesn't respect env.files.parents flag.
Currently, loadEnvFilesEarly uses env.LoadEnvFiles(basePath, paths), which only loads from a single directory and has no parents support. The main loader later uses env.LoadFromDirectory(dir, patterns, parents, repoRoot) which correctly walks up to the repository root when env.files.parents: true. Since early loading happens before profile detection, variables like ATMOS_PROFILE in parent directories won't be seen, causing the behavior to diverge from the main loader's handling of the same flag.
Update the early loader to use LoadFromDirectory with the parents flag (and repoRoot from config) so parent-walked .env files are honored during profile detection.
🤖 Prompt for AI Agents
In `@pkg/config/load.go` around lines 1286 - 1307, The early .env loader
(loadEnvFilesEarly) currently calls env.LoadEnvFiles(basePath, paths) which
ignores the env.files.parents flag; change it to call
env.LoadFromDirectory(basePath, paths, v.GetBool("env.files.parents"),
v.GetString("repo_root")) so parent traversal is honored during early loading
and profile detection, ensuring it uses the same repoRoot and parents flag as
the main loader; preserve returned envVars, loadedFiles and existing error
handling (log.Debug and behavior) after the switch.
|
💥 This pull request now has conflicts. Could you fix it @osterman? 🙏 |
Summary
This PR implements comprehensive
.envfile support for Atmos, allowing users to load environment variables from.envfiles throughout the configuration and execution lifecycle.Key Features
env.vars+env.files(structured) and legacyenv: {KEY: value}(flat) formats.envfiles using patterns like.env.*,.env.local, etc.ATMOS_*environment variables (e.g.,ATMOS_PROFILE,ATMOS_LOGS_LEVEL)ui.Success()for visibilityConfiguration Example
New structured form (recommended):
```yaml
env:
vars:
AWS_PROFILE: dev
files:
enabled: true
paths:
- .env
- .env.local
- .env.*
parents: false
```
Legacy flat form (still supported):
```yaml
env:
AWS_PROFILE: dev
```
Implementation Details
pkg/env/withfile.goand comprehensive test coverageEnvFilesConfigandEnvConfigstructs topkg/schema/schema.go.envloading inpkg/config/load.goExecuteShellCommand()andExecuteShell()ininternal/exec/shell_utils.goto support working directory.envfilesenv:form continues to work without changesTest Coverage
pkg/env/file_test.gocovering glob patterns, case preservation, parent walking, and security boundariespkg/schema/schema_test.goto support newEnvConfigstructureFiles Changed
pkg/schema/schema.go- NewEnvFilesConfigandEnvConfigstructs,GetCaseSensitiveEnvVars()helperpkg/env/file.go- Core env file loading implementation with glob and parent walkingpkg/env/file_test.go- Comprehensive unit testspkg/config/load.go- Dual-form parsing, early.envloading, casemap integrationinternal/exec/shell_utils.go- Working directory.envfile loadingRelated Issues
Closes DEV-2990
🤖 Generated with Claude Code
Co-Authored-By: Claude Haiku 4.5 noreply@anthropic.com
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.