Skip to content

feat: Lifecycle hooks and outputs for custom component types#2584

Open
Ben (Benbentwo) wants to merge 3 commits into
cloudposse:mainfrom
Benbentwo:benbentwo/dev-3755-custom-component-types
Open

feat: Lifecycle hooks and outputs for custom component types#2584
Ben (Benbentwo) wants to merge 3 commits into
cloudposse:mainfrom
Benbentwo:benbentwo/dev-3755-custom-component-types

Conversation

@Benbentwo

@Benbentwo Ben (Benbentwo) commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Custom component types can now fire lifecycle hooks and publish outputs to a store — closing the run → outputs → next-run loop for non-Terraform components, the same way after-terraform-apply + store already works for Terraform.

This continues the work from #2469 (which was auto-closed when its base branch, feature/dev-3755-custom-component-types, merged to main and was deleted). It is now rebased onto main as a single commit.

What Changed

  • Event taxonomy <phase>.<type>.<subcommand> — e.g. before.agent.greeting / after.agent.greeting, mirroring the built-in terraform events rather than anchoring custom types to terraform's apply verb. pkg/hooks: ComponentEvent(phase, type, subcommand) + PhaseBefore/PhaseAfter.
  • HooksFromComponent builds hooks from an already-resolved component map, avoiding a re-describe the built-in describe path can't do for custom types.
  • $ATMOS_OUTPUTS mechanism — custom command steps publish KEY=VALUE or JSON; the store hook resolves leading-dot output references against that file. Store output resolution dispatches on component type (terraform state vs outputs file).
  • cmd/internal/runhooks.go shared RunHooks with an optional pre-resolved component; the custom-command path fires before/after derived events.
  • Stack processing — custom component types deep-merge global vars/settings/env via m.Merge (matching built-in inheritance) and error on malformed config.
  • Exampleexamples/custom-components hello-world with a post-run store hook (Redis).
  • Docswebsite/docs/stacks/hooks.mdx, PRD, changelog blog post, roadmap milestone.

Why This Matters

Lets a custom component (e.g. an Anthropic Managed Agent) persist what its apply created — IDs, ARNs — to a store and read it back on the next run, declaratively, instead of imperative write-backs inside the command.

References

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Lifecycle hooks for custom component types using before.. and after...
    • Custom commands can publish key/value outputs (auto-detected JSON or KEY=VALUE) for post-run hooks.
  • Improvements

    • Store hooks resolve leading-dot output references from Terraform or custom-published outputs; missing custom keys return explicit errors.
    • Custom component config validation and deep-merge behavior improved.
    • AWS SSM retrieval now returns decrypted SecureString values.
  • Tests

    • Added comprehensive unit tests covering event normalization, outputs file parsing, hooks-from-component, and store command behaviors.
  • Documentation

    • Blog post, docs, examples, and README updated to demonstrate custom-component hooks and output publishing.

@Benbentwo Ben (Benbentwo) requested review from a team as code owners June 8, 2026 20:21
@atmos-pro

atmos-pro Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@github-actions github-actions Bot added the size/l Large size PR label Jun 8, 2026
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds lifecycle hooks and outputs support for custom component types: typed components can publish outputs to a temp file (exposed via ATMOS_OUTPUTS), fire before.<type>.<subcommand> and after.<type>.<subcommand> events, and have store hooks resolve outputs from either Terraform state or the custom outputs file.

Changes

Custom Component Lifecycle Hooks & Outputs

Layer / File(s) Summary
Hook event taxonomy and normalization
pkg/hooks/event.go, pkg/hooks/event_test.go
Adds PhaseBefore/PhaseAfter, built-in dotted terraform events, ComponentEvent(), NormalizeEvent(), and tests for normalization and matching semantics.
Hook extraction from resolved components
pkg/hooks/hooks.go, pkg/hooks/hooks_test.go
Adds HooksFromComponent() to build typed Hooks from resolved component maps, handling missing/malformed hooks: sections with tests.
Outputs file reading and parsing
pkg/hooks/outputs_file.go, pkg/hooks/outputs_file_test.go
Adds ReadOutputsFile() with JSON vs KEY=VALUE detection, JSON and key/value parsers, trimming/quoting rules, and extensive tests including read-error wrapping.
Store command output routing
pkg/hooks/store_cmd.go, pkg/hooks/store_cmd_test.go, pkg/hooks/store_cmd_custom_test.go, pkg/hooks/store_cmd_nil_handling_test.go
Refactors StoreCommand to inject terraformOutputter and customOutputter, dispatch by component type, preserve terraform null/error semantics, and return ErrCustomOutputMissing for absent custom outputs; includes new tests for dispatch, file reads, and error propagation.
Hook execution entrypoint
cmd/internal/runhooks.go
Adds RunHooks() shared entrypoint that processes CLI args, initializes CLI config, optionally populates ConfigAndStacksInfo, resolves hooks (including from a pre-resolved component), and runs them with error handling.
Custom command execution with lifecycle hooks
cmd/cmd_utils.go
Extends executeCustomCommand to create a temp outputs file for typed components, export ATMOS_OUTPUTS to steps, populate resolved component into hook data, fire before.<type>.<subcommand> once, and fire after.<type>.<subcommand> on overall success.
Schema fields and error definitions
pkg/schema/schema.go, errors/errors.go
Adds ConfigAndStacksInfo.OutputsFilePath and three sentinel errors for outputs-file read/create/missing conditions.
Custom component config deep merging
internal/exec/stack_processor_process_stacks.go
Validates components is a map[string]any and deep-merges global vars, settings, and env into each custom component using the shared merge helper.
AWS SSM parameter decryption
pkg/store/aws_ssm_param_store.go, pkg/store/aws_ssm_param_store_test.go
Adds WithDecryption: true to SSM GetParameter calls and updates tests to expect the flag.
Documentation and examples
docs/prd/hooks-for-custom-components.md, website/blog/2026-06-03-custom-component-hooks.mdx, website/docs/stacks/hooks.mdx, website/src/data/roadmap.js, examples/custom-components/*
Adds PRD, blog post, docs updates, roadmap entry, example hello/world component, greeting script, Redis-backed store example, and README/example config updates demonstrating the new lifecycle/hooks/output flow.

Sequence diagram — Custom command lifecycle (high-level):

sequenceDiagram
  participant Executor
  participant HookRunner
  participant StepRunner
  participant OutputsFile as OutputsFile

  Executor->>HookRunner: fire before.<type>.<subcommand> (with resolved component + OutputsFilePath)
  HookRunner-->>Executor: before complete
  Executor->>StepRunner: run steps with ATMOS_OUTPUTS env (writes to OutputsFile)
  StepRunner-->>Executor: steps complete
  Executor->>HookRunner: fire after.<type>.<subcommand>
  HookRunner->>OutputsFile: read OutputsFilePath (for custom outputs)
  HookRunner-->>Executor: after complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • cloudposse/atmos#2309: Prior hook/event plumbing and store-command event handling work that interacts with this PR’s event normalization and post-execution resolution.
  • cloudposse/atmos#1885: Related changes to pkg/hooks/store_cmd.go and store output resolution that this PR extends with custom-component outputs and hook plumbing.
  • cloudposse/atmos#1899: Related modifications to cmd/cmd_utils.go’s custom command execution path that touch the same step loop this PR augments.

Suggested labels

minor

Suggested reviewers

  • osterman
  • aknysh
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Lifecycle hooks and outputs for custom component types' accurately and clearly summarizes the main change—enabling lifecycle hooks and outputs publishing for custom components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
cmd/internal/runhooks.go (1)

77-81: 💤 Low value

Unreachable return after CheckErrorPrintAndExit.

Line 78 calls CheckErrorPrintAndExit, which calls os.Exit and never returns. The return nil on line 80 is unreachable. Consider either removing the return or documenting why CheckErrorPrintAndExit is used here (it appears to be the intended behavior for hook failures to terminate the process).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/internal/runhooks.go` around lines 77 - 81, The code calls
errUtils.CheckErrorPrintAndExit(err, "", "") which terminates the process,
making the subsequent "return nil" unreachable; either remove the unreachable
"return nil" or change the error handling to return the error instead of exiting
(i.e., replace the CheckErrorPrintAndExit call with a non-fatal return of err)
depending on intended behavior for hooks in RunAll; update the RunAll invocation
block accordingly and keep only the appropriate path (explicit os.Exit usage OR
regular error propagation) around RunAll and CheckErrorPrintAndExit.
pkg/hooks/hooks.go (1)

54-104: YAML version usage in HooksFromComponent matches the hooks package (yaml.v2 is already used).

pkg/hooks/hooks.go imports gopkg.in/yaml.v2, and HooksFromComponent uses the same yaml.Marshal/yaml.Unmarshal as GetHooks in that file—so there’s no mismatch specific to this function. go.mod also depends on both gopkg.in/yaml.v2 v2.4.0 and gopkg.in/yaml.v3 v3.0.1; moving hooks to v3 would be a broader optional refactor rather than a local fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/hooks/hooks.go` around lines 54 - 104, The YAML version usage is
correct—leave HooksFromComponent as-is: ensure the file imports gopkg.in/yaml.v2
and that HooksFromComponent continues to use yaml.Marshal and yaml.Unmarshal to
(un)marshal hooksSection into map[string]Hook; do not switch to yaml.v3 here,
but if you want to consolidate versions project-wide, handle that as a separate
cross-cutting change in go.mod and all packages rather than in this function
(refer to HooksFromComponent, Hooks, and the yaml import to locate the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/prd/hooks-for-custom-components.md`:
- Line 3: Update the incorrect PR number in the Status line: change the text
"**Status:** Implemented (PR `#2469`, stacked on and pending merge into `#1904`)" to
reference PR `#2584` instead of `#2469` so it reads "**Status:** Implemented (PR
`#2584`, stacked on and pending merge into `#1904`)"; edit the string in
docs/prd/hooks-for-custom-components.md where that exact status line appears to
match the PR objectives header.

In `@pkg/hooks/outputs_file.go`:
- Around line 74-78: The loop using strings.Split(string(raw), "\n") leaves a
trailing '\r' on Windows which breaks the KEY=VALUE parser; update the
processing inside the loop that iterates over lineno, line so it removes any
trailing carriage return before trimming and parsing (e.g., strip '\r' from line
or use strings.TrimRight(line, "\r") or normalize line endings), then continue
to use trimmed as before; refer to the lineno, line and trimmed variables in
outputs_file.go and ensure the parser that reads KEY=VALUE sees lines without
'\r'.
- Around line 86-89: The current quote removal uses strings.Trim(val, `"'`)
which strips all leading/trailing quote characters; change it to only drop a
single matching outer quote pair: after computing val (in the same block where
key and val are derived in pkg/hooks/outputs_file.go), check if len(val) >= 2
and if the first and last rune are matching quotes (' or "), and if so set val =
val[1:len(val)-1]; otherwise leave val unchanged — update the code around the
existing key/val assignment (the block that sets key :=
strings.TrimSpace(trimmed[:eq]) and val := strings.TrimSpace(trimmed[eq+1:])) to
use this outer-pair removal logic.

In `@website/src/data/roadmap.js`:
- Line 313: Update the roadmap entry for the item with label 'Lifecycle hooks
and outputs for custom component types' by correcting its pr field from 2469 to
2584; locate the object in website/src/data/roadmap.js (the entry containing
label, status, quarter, pr, docs, changelog, description) and set pr: 2584 so
the roadmap points to the correct PR.
- Line 335: The prs entry for the roadmap item "Lifecycle hooks and outputs for
custom component types" has an incorrect PR number (number: 2469); update that
object's number field to 2584 so it matches the actual PR. Locate the object
with title "Lifecycle hooks and outputs for custom component types" in the prs
array and change its number property from 2469 to 2584.

---

Nitpick comments:
In `@cmd/internal/runhooks.go`:
- Around line 77-81: The code calls errUtils.CheckErrorPrintAndExit(err, "", "")
which terminates the process, making the subsequent "return nil" unreachable;
either remove the unreachable "return nil" or change the error handling to
return the error instead of exiting (i.e., replace the CheckErrorPrintAndExit
call with a non-fatal return of err) depending on intended behavior for hooks in
RunAll; update the RunAll invocation block accordingly and keep only the
appropriate path (explicit os.Exit usage OR regular error propagation) around
RunAll and CheckErrorPrintAndExit.

In `@pkg/hooks/hooks.go`:
- Around line 54-104: The YAML version usage is correct—leave HooksFromComponent
as-is: ensure the file imports gopkg.in/yaml.v2 and that HooksFromComponent
continues to use yaml.Marshal and yaml.Unmarshal to (un)marshal hooksSection
into map[string]Hook; do not switch to yaml.v3 here, but if you want to
consolidate versions project-wide, handle that as a separate cross-cutting
change in go.mod and all packages rather than in this function (refer to
HooksFromComponent, Hooks, and the yaml import to locate the code).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc72929d-63b6-4614-b5a1-a26d81873775

📥 Commits

Reviewing files that changed from the base of the PR and between 2867836 and fda54b9.

📒 Files selected for processing (26)
  • cmd/cmd_utils.go
  • cmd/internal/runhooks.go
  • docs/prd/hooks-for-custom-components.md
  • errors/errors.go
  • examples/custom-components/README.md
  • examples/custom-components/atmos.yaml
  • examples/custom-components/components/hello/world/greeting.sh
  • examples/custom-components/stacks/catalog/hello/world.yaml
  • examples/custom-components/stacks/deploy/dev.yaml
  • internal/exec/stack_processor_process_stacks.go
  • pkg/hooks/event.go
  • pkg/hooks/event_test.go
  • pkg/hooks/hooks.go
  • pkg/hooks/hooks_test.go
  • pkg/hooks/outputs_file.go
  • pkg/hooks/outputs_file_test.go
  • pkg/hooks/store_cmd.go
  • pkg/hooks/store_cmd_custom_test.go
  • pkg/hooks/store_cmd_nil_handling_test.go
  • pkg/hooks/store_cmd_test.go
  • pkg/schema/schema.go
  • pkg/store/aws_ssm_param_store.go
  • pkg/store/aws_ssm_param_store_test.go
  • website/blog/2026-06-03-custom-component-hooks.mdx
  • website/docs/stacks/hooks.mdx
  • website/src/data/roadmap.js

Comment thread docs/prd/hooks-for-custom-components.md Outdated
Comment thread pkg/hooks/outputs_file.go
Comment thread pkg/hooks/outputs_file.go
Comment thread website/src/data/roadmap.js Outdated
Comment thread website/src/data/roadmap.js Outdated
Ben (Benbentwo) added a commit to Benbentwo/atmos that referenced this pull request Jun 8, 2026
- outputs_file: strip exactly one pair of surrounding quotes (single or
  double) instead of greedily trimming all quote chars, so an inner quote
  survives (e.g. "'hello'" -> 'hello'). Add a nested-quote test.
- outputs_file: add a CRLF test documenting that TrimSpace already strips
  trailing \r from Windows line endings (the flagged concern was already
  handled — no code change needed).
- docs/roadmap: correct stale PR references from cloudposse#2469 to cloudposse#2584; update the
  PRD status (custom component types cloudposse#1904 has merged to main).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@mergify

mergify Bot commented Jun 9, 2026

Copy link
Copy Markdown

💥 This pull request now has conflicts. Could you fix it Ben (@Benbentwo)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jun 9, 2026
Ben (Benbentwo) added a commit to Benbentwo/atmos that referenced this pull request Jun 9, 2026
- outputs_file: strip exactly one pair of surrounding quotes (single or
  double) instead of greedily trimming all quote chars, so an inner quote
  survives (e.g. "'hello'" -> 'hello'). Add a nested-quote test.
- outputs_file: add a CRLF test documenting that TrimSpace already strips
  trailing \r from Windows line endings (the flagged concern was already
  handled — no code change needed).
- docs/roadmap: correct stale PR references from cloudposse#2469 to cloudposse#2584; update the
  PRD status (custom component types cloudposse#1904 has merged to main).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@Benbentwo Ben (Benbentwo) force-pushed the benbentwo/dev-3755-custom-component-types branch from 8cbc1ae to e5ce6f1 Compare June 9, 2026 20:10
@mergify mergify Bot removed the conflict This PR has conflicts label Jun 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/hooks/hooks.go (1)

81-84: ⚡ Quick win

Inconsistent error return value.

Line 83 returns &Hooks{} (empty struct), but the other early-return branches (lines 73, 78) return &Hooks{config: atmosConfig, info: info, items: nil}. Preserving config and info even on error is safer—callers can log context without nil-derefs, and it's consistent with the success path.

♻️ Align with other branches
 	hooksSection, ok := rawHooks.(map[string]any)
 	if !ok {
-		return &Hooks{}, fmt.Errorf("hooks section is not a map: got %T", rawHooks)
+		return &Hooks{config: atmosConfig, info: info, items: nil}, fmt.Errorf("hooks section is not a map: got %T", rawHooks)
 	}

Apply the same fix to line 88 and line 96 for full consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/hooks/hooks.go` around lines 81 - 84, The error-return branch that
currently returns &Hooks{} should instead return &Hooks{config: atmosConfig,
info: info, items: nil} so callers retain context; update the branch that checks
rawHooks type assertion (and the similar early-return branches around the other
type-checks at the locations you noted) to return the consistent Hooks value
preserving atmosConfig and info; ensure all error returns in this function (the
ones currently returning &Hooks{}) are changed to &Hooks{config: atmosConfig,
info: info, items: nil} so Hooks, atmosConfig and info are always present for
callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/hooks/hooks.go`:
- Around line 81-84: The error-return branch that currently returns &Hooks{}
should instead return &Hooks{config: atmosConfig, info: info, items: nil} so
callers retain context; update the branch that checks rawHooks type assertion
(and the similar early-return branches around the other type-checks at the
locations you noted) to return the consistent Hooks value preserving atmosConfig
and info; ensure all error returns in this function (the ones currently
returning &Hooks{}) are changed to &Hooks{config: atmosConfig, info: info,
items: nil} so Hooks, atmosConfig and info are always present for callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e826be26-a17a-44e4-a7ab-f136ed4ad529

📥 Commits

Reviewing files that changed from the base of the PR and between 8cbc1ae and e5ce6f1.

📒 Files selected for processing (26)
  • cmd/cmd_utils.go
  • cmd/internal/runhooks.go
  • docs/prd/hooks-for-custom-components.md
  • errors/errors.go
  • examples/custom-components/README.md
  • examples/custom-components/atmos.yaml
  • examples/custom-components/components/hello/world/greeting.sh
  • examples/custom-components/stacks/catalog/hello/world.yaml
  • examples/custom-components/stacks/deploy/dev.yaml
  • internal/exec/stack_processor_process_stacks.go
  • pkg/hooks/event.go
  • pkg/hooks/event_test.go
  • pkg/hooks/hooks.go
  • pkg/hooks/hooks_test.go
  • pkg/hooks/outputs_file.go
  • pkg/hooks/outputs_file_test.go
  • pkg/hooks/store_cmd.go
  • pkg/hooks/store_cmd_custom_test.go
  • pkg/hooks/store_cmd_nil_handling_test.go
  • pkg/hooks/store_cmd_test.go
  • pkg/schema/schema.go
  • pkg/store/aws_ssm_param_store.go
  • pkg/store/aws_ssm_param_store_test.go
  • website/blog/2026-06-03-custom-component-hooks.mdx
  • website/docs/stacks/hooks.mdx
  • website/src/data/roadmap.js
✅ Files skipped from review due to trivial changes (4)
  • examples/custom-components/stacks/catalog/hello/world.yaml
  • website/docs/stacks/hooks.mdx
  • website/src/data/roadmap.js
  • pkg/hooks/store_cmd_nil_handling_test.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • errors/errors.go
  • pkg/store/aws_ssm_param_store.go
  • examples/custom-components/components/hello/world/greeting.sh
  • pkg/hooks/hooks_test.go
  • examples/custom-components/atmos.yaml
  • pkg/hooks/outputs_file.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/store/aws_ssm_param_store_test.go
  • pkg/hooks/store_cmd_custom_test.go
  • examples/custom-components/stacks/deploy/dev.yaml
  • pkg/hooks/event.go
  • pkg/schema/schema.go
  • cmd/internal/runhooks.go
  • pkg/hooks/outputs_file_test.go
  • pkg/hooks/store_cmd.go
  • cmd/cmd_utils.go

@mergify

mergify Bot commented Jun 10, 2026

Copy link
Copy Markdown

💥 This pull request now has conflicts. Could you fix it Ben (@Benbentwo)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jun 10, 2026
Ben (Benbentwo) and others added 2 commits June 10, 2026 09:52
Custom component types fire `<phase>.<type>.<subcommand>` lifecycle hooks
(e.g. before.agent.greeting / after.agent.greeting) and publish outputs via
$ATMOS_OUTPUTS so a `store` hook can persist results — closing the
run → outputs → next-run loop Terraform already has.

- pkg/hooks: ComponentEvent(phase,type,subcommand) helper + PhaseBefore/After;
  HooksFromComponent builds hooks from an already-resolved component map
  (avoids re-describing custom types the built-in describe path can't see);
  store_cmd dispatches output resolution on component type (terraform getter
  vs ATMOS_OUTPUTS file via defaultCustomOutputter); outputs_file parses
  JSON or KEY=VALUE.
- cmd: cmd/internal/runhooks.go shared RunHooks with optional
  preResolvedComponent; custom command path fires before/after derived events.
- internal/exec: custom component types deep-merge global vars/settings/env
  via m.Merge (matching built-in inheritance) and error on malformed config.
- errors: ErrReadOutputsFile, ErrCreateOutputsFile, ErrCustomOutputMissing.
- examples/custom-components: hello-world with a post-run store hook (Redis).
- docs: PRD, website hooks docs, changelog blog, roadmap milestone.
- tests: event naming, outputs-file parsing, store dispatch, SSM WithDecryption.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- outputs_file: strip exactly one pair of surrounding quotes (single or
  double) instead of greedily trimming all quote chars, so an inner quote
  survives (e.g. "'hello'" -> 'hello'). Add a nested-quote test.
- outputs_file: add a CRLF test documenting that TrimSpace already strips
  trailing \r from Windows line endings (the flagged concern was already
  handled — no code change needed).
- docs/roadmap: correct stale PR references from cloudposse#2469 to cloudposse#2584; update the
  PRD status (custom component types cloudposse#1904 has merged to main).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@Benbentwo Ben (Benbentwo) force-pushed the benbentwo/dev-3755-custom-component-types branch from e5ce6f1 to 2305e0b Compare June 10, 2026 16:52
@mergify mergify Bot removed the conflict This PR has conflicts label Jun 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/hooks/event_test.go (1)

74-106: ⚡ Quick win

Consider adding test coverage for terraform deploy/apply semantic equivalence.

The test comprehensively covers dash/dot normalization and custom component matching. However, there's no explicit case documenting that hooks configured for "apply" also fire for "deploy" events (and vice versa). This semantic equivalence is implemented in HookEvent.Normalize() and is important behavior worth documenting at the MatchesEvent level.

📝 Suggested additional test cases
 		{"empty events list matches all (back-compat)", []string{}, AfterTerraformApply, true},
+		{"hook for apply matches deploy event", []string{"after-terraform-apply"}, AfterTerraformDeploy, true},
+		{"hook for deploy matches apply event", []string{"after-terraform-deploy"}, AfterTerraformApply, true},
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/hooks/event_test.go` around lines 74 - 106, Add explicit unit cases in
TestHookMatchesEvent to assert the semantic equivalence between "apply" and
"deploy": create cases where a hook configured with "after-terraform-apply"
should match a fired event like AfterTerraformDeploy and vice versa, and also
cover component variants (e.g., ComponentEvent(...,"apply") vs
ComponentEvent(...,"deploy")). Ensure these new cases exercise Hook.MatchesEvent
and rely on HookEvent.Normalize() behavior by including both dotted and dashed
aliases (e.g., "after.terraform.apply" and "after-terraform-deploy") and the
corresponding HookEvent constants (AfterTerraformApply/AfterTerraformDeploy or
ComponentEvent variants) so the test fails if Normalize no longer maps
apply<->deploy.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/hooks/event_test.go`:
- Around line 74-106: Add explicit unit cases in TestHookMatchesEvent to assert
the semantic equivalence between "apply" and "deploy": create cases where a hook
configured with "after-terraform-apply" should match a fired event like
AfterTerraformDeploy and vice versa, and also cover component variants (e.g.,
ComponentEvent(...,"apply") vs ComponentEvent(...,"deploy")). Ensure these new
cases exercise Hook.MatchesEvent and rely on HookEvent.Normalize() behavior by
including both dotted and dashed aliases (e.g., "after.terraform.apply" and
"after-terraform-deploy") and the corresponding HookEvent constants
(AfterTerraformApply/AfterTerraformDeploy or ComponentEvent variants) so the
test fails if Normalize no longer maps apply<->deploy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d23b3ca-4c3f-4105-b8d9-c5ea3bafb479

📥 Commits

Reviewing files that changed from the base of the PR and between e5ce6f1 and 2305e0b.

📒 Files selected for processing (26)
  • cmd/cmd_utils.go
  • cmd/internal/runhooks.go
  • docs/prd/hooks-for-custom-components.md
  • errors/errors.go
  • examples/custom-components/README.md
  • examples/custom-components/atmos.yaml
  • examples/custom-components/components/hello/world/greeting.sh
  • examples/custom-components/stacks/catalog/hello/world.yaml
  • examples/custom-components/stacks/deploy/dev.yaml
  • internal/exec/stack_processor_process_stacks.go
  • pkg/hooks/event.go
  • pkg/hooks/event_test.go
  • pkg/hooks/hooks.go
  • pkg/hooks/hooks_test.go
  • pkg/hooks/outputs_file.go
  • pkg/hooks/outputs_file_test.go
  • pkg/hooks/store_cmd.go
  • pkg/hooks/store_cmd_custom_test.go
  • pkg/hooks/store_cmd_nil_handling_test.go
  • pkg/hooks/store_cmd_test.go
  • pkg/schema/schema.go
  • pkg/store/aws_ssm_param_store.go
  • pkg/store/aws_ssm_param_store_test.go
  • website/blog/2026-06-03-custom-component-hooks.mdx
  • website/docs/stacks/hooks.mdx
  • website/src/data/roadmap.js
✅ Files skipped from review due to trivial changes (3)
  • examples/custom-components/stacks/deploy/dev.yaml
  • pkg/hooks/store_cmd_test.go
  • website/docs/stacks/hooks.mdx
🚧 Files skipped from review as they are similar to previous changes (17)
  • examples/custom-components/atmos.yaml
  • errors/errors.go
  • pkg/hooks/hooks_test.go
  • pkg/hooks/store_cmd_custom_test.go
  • pkg/store/aws_ssm_param_store_test.go
  • pkg/store/aws_ssm_param_store.go
  • pkg/schema/schema.go
  • pkg/hooks/hooks.go
  • internal/exec/stack_processor_process_stacks.go
  • examples/custom-components/components/hello/world/greeting.sh
  • pkg/hooks/outputs_file.go
  • pkg/hooks/store_cmd_nil_handling_test.go
  • cmd/cmd_utils.go
  • cmd/internal/runhooks.go
  • pkg/hooks/outputs_file_test.go
  • pkg/hooks/store_cmd.go
  • pkg/hooks/event.go

@mergify

mergify Bot commented Jun 15, 2026

Copy link
Copy Markdown

💥 This pull request now has conflicts. Could you fix it Ben (@Benbentwo)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflict This PR has conflicts size/l Large size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant