feat: read project stack state live from CloudFormation - #2112
Conversation
Adds a reader that describes a project's CloudFormation stack and classifies its lifecycle into not-deployed / in-progress / failed / ready, returning the stack outputs (resource ARNs/IDs) only when settled and successful. This is the source-of-truth side of the deploy-state refactor: resource details come from CloudFormation on demand rather than a local snapshot that can go stale. Generalizes the existing bootstrap not-found helper to isStackNotFound and reuses it. No command is wired to this yet; project status consumes it in a follow-up.
|
Claude Security Review: no high-confidence findings. (run) |
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Looks good
Small, well-scoped reader with tight tests. A few observations, none blocking:
classifyStackincludesIMPORT_ROLLBACK_COMPLETEinREADY_STATUSESalongsideIMPORT_COMPLETE. That's arguably closer toROLLBACK_COMPLETE(a failed operation with no meaningful post-state) than toUPDATE_ROLLBACK_COMPLETE, and the comment above the set only justifiesUPDATE_ROLLBACK_COMPLETE. Worth double-checking against how you plan to render this inproject status, but it's easy to flip later and no caller consumes it yet.- The
readStackState"propagates non-not-found errors" test only covers the injected-reader path; the real not-found →undefinedconversion lives insidedescribeStackand isn't exercised. Not a correctness issue (it just reuses the already-testedisStackNotFound), just noting for coverage. - Telemetry isn't wired here, which is fine given the PR body says the caller is a follow-up — please make sure
project status(or whichever command consumes this) instruments the read.
Nothing here needs to change before merging.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## refactor #2112 +/- ##
============================================
- Coverage 97.38% 97.30% -0.08%
============================================
Files 440 447 +7
Lines 26626 27067 +441
============================================
+ Hits 25929 26338 +409
- Misses 697 729 +32 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Hweinstock
left a comment
There was a problem hiding this comment.
I wonder if we're coming at this problem from the wrong side. I understand the end goal is to have a status handlers that reads off the resources and whether they are deployed or not, but it feels strange to build the functionality before the interface.
I feel like we're inevitably going to be reworking the functionality to match the interface or derive the interface from the functionality (which I don't think we want).
| const describeStack: StackReader = async (region, credentials, stackName) => { | ||
| const { CloudFormationClient, DescribeStacksCommand } = | ||
| await import("@aws-sdk/client-cloudformation"); | ||
| const client = new CloudFormationClient({ credentials, region }); |
There was a problem hiding this comment.
Is there a way we can inject this client from core clients?
Maybe define cloudformation client like the others here
agentcore-cli/src/core/index.tsx
Lines 47 to 57 in 01c9317
and inject it into the project manager?
There was a problem hiding this comment.
I made the call injectable so it's testable without a real client. Left it lazy-importing the SDK for now rather than going through CoreClient. figured that's cleaner to wire up when status actually consumes it. Lmk if you'd rather do it now.
| }; | ||
| } | ||
|
|
||
| describe("classifyStack", () => { |
There was a problem hiding this comment.
I feel like it would be stronger to test this through the handler (once it exists), but since it doesn't exist yet this seems reasonable
I see your point and I agree. I thiknk the classification is really a status-interface decision so it can be worked on together with it. I can pull it back in this PR and only have the raw API (describeStackByName → DescribeStacks, returning the stack or undefined) implemented. |
Per review, drop the stack-status classification (not-deployed / in-progress / failed / ready) and the StackState shape — that's a project status interface decision and belongs with whoever builds it, not baked in ahead of the consumer. Keep just describeStack: a DescribeStacks call that returns the stack or undefined when it doesn't exist. The CloudFormation call is injectable at the function seam (lazy-loaded like environment.ts), so it's unit-tested without a real client; wiring it through CoreClient/the project manager is left to the consumer.
|
Claude Security Review: no high-confidence findings. (run) |
|
Trimmed it down to just the raw describeStack call, dropped the status classification since that's really a status decision, not this PR's. |
|
patch coverage being low is expected for now. the tests deliberately bypass it by injecting a fake describe |
| try { | ||
| // Not-found is a thrown ValidationError, not an empty result; every other | ||
| // error (auth, throttling, malformed request) is real and propagates. | ||
| return (await describe(stackName))?.[0]; |
There was a problem hiding this comment.
nit: An empty successful DescribeStacks response is different from a missing stack. CloudFormation reports not-found through the thrown ValidationError. An empty successful response is malformed. Returning undefined here makes callers report “not deployed” instead of the actual service-response problem. This should throw MalformedServiceResponseError, like the bootstrap reader does.
Could be a followup or other PR though.
There was a problem hiding this comment.
good point, fixed it in this PR itself
A missing stack is reported by a thrown ValidationError, so that stays the only not-found (undefined) signal. A successful response with no stack is malformed, not not-found; return undefined there would misreport a service problem as 'not deployed'. Throw MalformedServiceResponseError instead, matching the bootstrap reader.
|
Claude Security Review: no high-confidence findings. (run) |
Adds the read-side of the deploy-state refactor: a small helper to describe a project's CloudFormation stack, so resource details can be read live instead of from a local snapshot that goes stale.
What's here
describeStack(region, credentials, stackName)— aDescribeStackscall that returns the stack, orundefinedwhen it doesn't exist. Accepts a stack name or ARN. The AWS call is injectable (and lazy-loaded likeenvironment.ts), so it's unit-tested without a real client.isBootstrapStackNotFound→isStackNotFoundand reused it.Not here (by design)
Interpreting the stack — status classification (deployed / in-progress / failed) and which outputs to surface — is a
project statusdecision and is left to whoever builds it, rather than baked in ahead of the consumer.