feat: AP-163 send raw instance status to Atmos Pro, extend to apply @milldr (#2216)
## What- CLI sends raw
command+exit_codeto 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-statusflag) - Reads
--upload-statusflag 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-1branch
Summary by CodeRabbit
Release Notes
-
New Features
- Added
--upload-statusflag to upload raw Terraform execution data (command and exit code) to Atmos Pro for bothplanandapplyoperations. - Introduced configurable CI exit code mapping to remap Terraform exit codes while preserving original execution results in cloud uploads.
- Added
-
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
ExecuteTerraformmonolith (~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,buildTerraformCommandArgsinit branch, andstoreAutoDetectedIdentityguard - Document the
GenerateFilesForComponentdouble-invocation whenAutoGenerateFiles=true(pre-existing behavior, not a regression)
Architecture after refactor
| File | Lines | Responsibility |
|---|---|---|
terraform.go
| 189 | Orchestrator: ExecuteTerraform → prepareComponentExecution → executeCommandPipeline → cleanupTerraformFiles
|
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
executeTerraformInitPhaseandbuildInitSubcommandArgsis documented — both callprepareInitExecutionbut are guarded bySubCommand == "init"branching
Lint fixes (16 issues → 0)
revive/cyclomatic: extractshouldSkipWorkspaceSetup,runPreExecutionSteps,autoGenerateComponentFiles,provisionComponentSource,logAndWriteComponentVars,logCliVarsOverrides,handlePlanStatusUploadrevive/add-constant:subcommandApply/Deploy/Init/Workspace,dirPermissionsgocritic/hugeParam:handleVersionSubcommandtakes pointer paramsgocritic/unlambda:defaultMergedAuthConfigGetteruses direct function refgocritic/filepathJoin: split path segments in testunparam: remove unusedplanFilefrombuildApplySubcommandArgsforbidigo: nolint forTF_WORKSPACE(Terraform convention)nestif: extracthandlePlanStatusUploadrevive/argument-limit: nolint for variadic opts
why
ExecuteTerraformwas 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 importerror whenATMOS_BASE_PATHis set to a relative path on CI workers without a.gitdirectory (e.g., Spacelift) - Make
tryResolveWithGitRootandtryResolveWithConfigPathsource-aware by passing thesourceparameter through the call chain - For runtime sources (
ATMOS_BASE_PATHenv var,--base-pathflag,atmos_base_pathprovider param),tryResolveWithConfigPathnow tries CWD first before config dir - Both paths use
os.Statvalidation with fallback - Add 5 bug reproduction tests for the base path resolution fix
- Add 4 cycle detection tests for the
metadata.componentstack overflow fix verification - Extract
basePathSourceRuntimeandcwdResolutionErrFmtconstants - Extract
tryCWDRelativehelper 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)
- User sets
ATMOS_BASE_PATH=.terraform/modules/monorepo processEnvVarssetsBasePathandBasePathSource = "runtime"resolveAbsolutePathclassifies.terraform/modules/monorepoas a bare path → callstryResolveWithGitRootgetGitRootOrEmpty()returns""— no.giton CI- Falls to
tryResolveWithConfigPath(".terraform/modules/monorepo", "/workspace") - Unconditionally returns
/workspace/.terraform/modules/monorepo— WRONG (doesn't exist) - 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.