github cloudposse/atmos v1.211.0-rc.1

pre-release6 hours ago
feat: AP-163 send raw instance status to Atmos Pro, extend to apply @milldr (#2216) ## What
  • CLI sends raw command + exit_code to Atmos Pro instead of interpreting exit codes into status strings
  • Extends status upload from plan-only to both plan and apply (both require explicit --upload-status flag)
  • Reads --upload-status flag via Cobra/Viper (fixes silent no-op when flag was consumed by Cobra before reaching the upload code path)
  • Treats plan exit code 2 (changes detected) as success after upload completes, so CI workflows don't fail

Why

Instances on the Atmos Pro dashboard show "Unknown" status after completed workflow runs. The CLI was only uploading status for plan (not apply), and was interpreting exit codes client-side. Moving interpretation server-side means status logic can be updated without a CLI release, and all exit codes (including errors) are now reported.

Ref

  • AP-163
  • PRD: docs/prd/instance-status-raw-upload.md
  • Full PRD (Atmos Pro): cloudposse-corp/apps → apps/atmos-pro/prd/instance-status-from-workflow-hooks.md
  • Atmos Pro counterpart: cloudposse-corp/apps qa-1 branch

Summary by CodeRabbit

Release Notes

  • New Features

    • Added --upload-status flag to upload raw Terraform execution data (command and exit code) to Atmos Pro for both plan and apply operations.
    • Introduced configurable CI exit code mapping to remap Terraform exit codes while preserving original execution results in cloud uploads.
  • Bug Fixes

    • Improved plan output detection to correctly identify cases where only output values change.
  • Documentation

    • Added feature documentation and blog post explaining the new upload capability.

🚀 Enhancements

refactor(terraform): reduce ExecuteTerraform complexity 160→9, improve test coverage @[copilot-swe-agent[bot]](https://github.com/apps/copilot-swe-agent) (#2226) ## what
  • Extract 30+ focused helper functions from the ExecuteTerraform monolith (~900 lines, cyclomatic complexity 160), reducing complexity to ~9 across 4 source files all under 600 lines
  • Add 100+ unit tests across 6 test files covering argument builders, auth setup, workspace management, cleanup, exit-code resolution, and the execution pipeline
  • Fix all 16 golangci-lint issues: cyclomatic complexity, magic constants, unused params, forbidden API calls, huge params, nested blocks, argument limits
  • Address all 10 CodeRabbit audit pass 7 items: remove duplicate/tautological tests, add missing coverage for tenv != nil, buildTerraformCommandArgs init branch, and storeAutoDetectedIdentity guard
  • Document the GenerateFilesForComponent double-invocation when AutoGenerateFiles=true (pre-existing behavior, not a regression)

Architecture after refactor

File Lines Responsibility
terraform.go 189 Orchestrator: ExecuteTerraformprepareComponentExecutionexecuteCommandPipelinecleanupTerraformFiles
terraform_execute_helpers.go 546 Auth, env vars, init, validation, config generation helpers
terraform_execute_helpers_args.go 156 Per-subcommand argument builders (plan/apply/init/workspace/destroy)
terraform_execute_helpers_exec.go 350 Execution pipeline, workspace setup, TTY guard, exit-code resolution, cleanup

Key design decisions

  • Injectable test vars (defaultMergedAuthConfigGetter, defaultComponentConfigFetcher, defaultAuthManagerCreator) enable isolated unit testing of auth paths without real infrastructure
  • Named subcommand constants (subcommandApply, subcommandDeploy, subcommandInit, subcommandWorkspace) replace 16+ magic string literals
  • Mutual exclusion contract between executeTerraformInitPhase and buildInitSubcommandArgs is documented — both call prepareInitExecution but are guarded by SubCommand == "init" branching

Lint fixes (16 issues → 0)

  • revive/cyclomatic: extract shouldSkipWorkspaceSetup, runPreExecutionSteps, autoGenerateComponentFiles, provisionComponentSource, logAndWriteComponentVars, logCliVarsOverrides, handlePlanStatusUpload
  • revive/add-constant: subcommandApply/Deploy/Init/Workspace, dirPermissions
  • gocritic/hugeParam: handleVersionSubcommand takes pointer params
  • gocritic/unlambda: defaultMergedAuthConfigGetter uses direct function ref
  • gocritic/filepathJoin: split path segments in test
  • unparam: remove unused planFile from buildApplySubcommandArgs
  • forbidigo: nolint for TF_WORKSPACE (Terraform convention)
  • nestif: extract handlePlanStatusUpload
  • revive/argument-limit: nolint for variadic opts

why

  • ExecuteTerraform was the highest-complexity function in the codebase (cyclomatic 160) — untestable as a unit, any change risked regressions in auth, workspace, plan-file, or cleanup logic
  • The function handled 10+ distinct responsibilities in a single method: path resolution, auto-generation, JIT provisioning, toolchain deps, auth hooks, env var assembly, init pre-step, argument construction, workspace setup, TTY guard, command execution, status upload, and cleanup
  • Breaking it into focused helpers means each responsibility is independently testable, and adding new subcommands or flags requires changes in one place instead of wading through 900 lines
  • The 16 lint issues were blocking pre-commit hooks on any file in the same package

references

  • Fix doc: docs/fixes/2026-03-20-executeterraform-refactor.md
  • Blog post: website/blog/2026-03-18-refactoring-executeterraform.mdx
  • Roadmap: website/src/data/roadmap.js (Code Quality initiative milestone)

Summary by CodeRabbit

Release Notes

  • Refactor

    • Improved internal code organization and maintainability by decomposing complex Terraform execution logic into smaller, focused components. Complexity metrics reduced significantly.
  • Tests

    • Added comprehensive unit test suite (100+ tests) to improve reliability and catch edge cases.
  • Documentation

    • Added documentation detailing refactoring approach and audit findings.
    • Published blog post about internal improvements and lessons learned.
  • Chores

    • Updated roadmap to reflect completed internal optimization work.
refactor(cli_utils): DRY processArgsAndFlags with table-driven flag parsing, 100% unit test coverage @[copilot-swe-agent[bot]](https://github.com/apps/copilot-swe-agent) (#2225) - [x] **Bug fix**: Boolean flags (`--dry-run`, `--skip-init`, `--affected`, `--all`, `--process-templates`, `--process-functions`, `-h`/`--help`) were unconditionally stripping `args[i+1]` from Terraform/Helmfile pass-through args — silently dropping user-intended flags like `--refresh=false` or `--parallelism=10`. Fix: added `valueTakingCommonFlags` set; only value-consuming flags strip `args[i+1]`. - [x] **Bug fix**: `strings.Split(arg, "=")` rejected flag values containing `=` (e.g. `--query=.tags[?env==prod]`, `--append-user-agent=Key=Val`). Fix: `strings.SplitN(arg, "=", 2)` throughout. - [x] **Bug fix**: `strings.HasPrefix(arg+"=", flag)` was logically inverted — `--terraform-command-extra` matched `--terraform-command`. Fix: `strings.HasPrefix(arg, flag+"=")` at all call sites. - [x] **Bug fix**: `--from-plan` and `--identity` unconditionally consumed `args[i+1]` even when it was another flag. Fix: both only strip `args[i+1]` when it does not start with `-`. - [x] **Bug fix**: `--settings-list-merge-strategy` was parsed into `ArgsAndFlagsInfo` but absent from `commonFlags`, leaking it into Terraform pass-through args. Fix: added to both `commonFlags` and `stringFlagDefs`. - [x] **Refactor**: replaced 25+ copy-paste `if/else` chains (~200 lines) with a table-driven design: `stringFlagDefs` (26 entries), `parseFlagValue` helper, `parseIdentityFlag` helper, `parseFromPlanFlag` helper, and `valueTakingCommonFlags` set. Cyclomatic complexity: ~67 → ~15. - [x] **Tests**: added `internal/exec/cli_utils_helpers_test.go` — 100% coverage on `processArgsAndFlags`, `parseFlagValue`, `parseIdentityFlag`, `parseFromPlanFlag`, `parseQuotedCompoundSubcommand`. Regression tests confirm boolean flags no longer drop adjacent pass-through args. - [x] **Docs**: blog post `website/blog/2026-03-18-process-args-flags-refactor.mdx`, roadmap milestone `website/src/data/roadmap.js`, code comment clarifying `--process-templates`/`--process-functions` are Cobra-only flags. - [x] Closes #2204

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

fix: base path resolution fallback when git root is unavailable @aknysh (#2236) ## what
  • Fix failed to find import error when ATMOS_BASE_PATH is set to a relative path on CI workers without a .git directory (e.g., Spacelift)
  • Make tryResolveWithGitRoot and tryResolveWithConfigPath source-aware by passing the source parameter through the call chain
  • For runtime sources (ATMOS_BASE_PATH env var, --base-path flag, atmos_base_path provider param), tryResolveWithConfigPath now tries CWD first before config dir
  • Both paths use os.Stat validation with fallback
  • Add 5 bug reproduction tests for the base path resolution fix
  • Add 4 cycle detection tests for the metadata.component stack overflow fix verification
  • Extract basePathSourceRuntime and cwdResolutionErrFmt constants
  • Extract tryCWDRelative helper to reduce cognitive complexity

why

The v1.210.1 fix (PR #2215) added os.Stat + CWD fallback to tryResolveWithGitRoot, but this only works when git root IS available. On CI workers without .git (e.g., Spacelift), getGitRootOrEmpty() returns "" and the code falls through to tryResolveWithConfigPath — which lacked the same fallback. It unconditionally joined with cliConfigPath (the atmos.yaml directory), producing the wrong path.

The broken code path (before this fix)

  1. User sets ATMOS_BASE_PATH=.terraform/modules/monorepo
  2. processEnvVars sets BasePath and BasePathSource = "runtime"
  3. resolveAbsolutePath classifies .terraform/modules/monorepo as a bare path → calls tryResolveWithGitRoot
  4. getGitRootOrEmpty() returns "" — no .git on CI
  5. Falls to tryResolveWithConfigPath(".terraform/modules/monorepo", "/workspace")
  6. Unconditionally returns /workspace/.terraform/modules/monorepoWRONG (doesn't exist)
  7. Correct path: /workspace/components/terraform/iam-delegated-roles/.terraform/modules/monorepo

Why absolute paths work

When ATMOS_BASE_PATH is set to an absolute path, resolveAbsolutePath returns it as-is at the first check (filepath.IsAbs), bypassing all resolution logic.

The asymmetry fixed

Function Before After
tryResolveWithGitRoot Has os.Stat + CWD fallback (v1.210.1) Unchanged — now passes source to fallback
tryResolveWithConfigPath No os.Stat, no CWD fallback Source-aware: runtime → CWD first, config → config dir first, both with os.Stat

Resolution order after fix

Source Git Root Available Git Root Unavailable
Runtime (env var, CLI flag, provider param) git root → CWD (existing) CWD → config dir (new)
Config (base_path in atmos.yaml) git root → CWD (existing) config dir → CWD (new)

Stack overflow verification

The same user also reported fatal error: stack overflow when abstract components have metadata.component set (fixed in v1.210.0, PR #2214). Additional tests were written to verify the cycle detection works for various patterns including multiple abstract/real pairs, cross-component cycles, and defer delete re-entry. All pass — the cycle detection is working correctly for all reproducible patterns.

references

  • Follow-up to #2215 (base path resolution fix, v1.210.1)
  • Follow-up to #2214 (stack overflow fix for metadata.component, v1.210.0)
  • Related issue: #2183
  • Fix doc: docs/fixes/2026-03-19-failed-to-find-import-no-git-root-fallback.md
  • Previous fix doc: docs/fixes/2026-03-17-failed-to-find-import-base-path-resolution.md
  • Stack overflow fix doc: docs/fixes/2026-03-16-metadata-component-abstract-stack-overflow.md

Summary by CodeRabbit

  • Documentation

    • New guide describing CI-only "failed to find import" behavior with relative ATMOS_BASE_PATH and how resolution differs vs absolute paths.
  • Bug Fixes

    • Source-aware base-path resolution with proper existence checks and adjusted fallback ordering to prefer CWD for runtime cases, preventing incorrect joined paths.
  • Tests

    • New and updated regression tests covering base-path resolution scenarios and multiple cycle-detection cases to prevent stack-overflow and ensure correct fallback behavior.

Don't miss a new atmos release

NewReleases is sending notifications on new releases.