Repository navigation
feat(toolkit-lib): unstable evaluate action to resolve a stack's template against its deployed stack - #2066
Draft
iankhou wants to merge 3 commits into
Draft
feat(toolkit-lib): unstable evaluate action to resolve a stack's template against its deployed stack#2066iankhou wants to merge 3 commits into
iankhou wants to merge 3 commits into
Conversation
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
This was referenced Oct 21, 2026
…late against its deployed stack
toolkit.evaluate(cx, { stacks }) (unstable feature 'evaluate') returns each selected stack's template
with every expression in Resources and Outputs that can be evaluated against the deployed stack
replaced by its value, and the expressions that could not be evaluated with the reason.
- reads the deployed stack's parameters, physical ids and nested stack templates
- evaluates CDK-managed nested stacks recursively, with the parameter values their parent passes
- NoEcho parameters (in the evaluated or deployed template) are reported, never replaced by a default
- skips stacks that are not deployed, or whose creation failed or never ran, and lists them in notDeployed
- warns when the deployed template has different resources than the evaluated one
- one shared limit on resource lookups for the whole run
… which is now the default
Contributor
|
Total lines changed 1720 is greater than 1000. Please consider breaking this PR down. |
This branch had an error being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third of three stacked PRs. Review the earlier ones first; this PR's diff only contains the action itself:
unavailableParameters,SharedLookups, error handling)Fn::If, NoEcho, error classification)evaluateactionThe description below covers the whole feature; the parts implemented in #2074 and #2075 are described in
more detail there.
Reason for this change
Tools that check deployed resources against their CloudFormation templates (for example, validating that an
emulator or a deployment produced the configuration the template asked for) need the template with its
references, attributes, parameters and pseudo parameters replaced by the values CloudFormation actually used.
toolkit-lib already has this capability in
EvaluateCloudFormationTemplate(used by hotswap), but it is notpart of the public API, and its constructor takes the private
SDKtype, so it can't be exported as-is.Description of changes
Adds an unstable
evaluateaction (unstableFeatures: ['evaluate']):For each selected stack (up to
concurrencystacks at a time, default 10) it:diagnosedoes (EnvironmentAccess.accessStackForLookupBestEffort),CloudFormationStack.lookup, so SSM-resolved values are used) and itsnested stack templates (
loadCurrentTemplateWithNestedStacks, as hotswap does),ResourcesandOutputswithEvaluateCloudFormationTemplate, one expression at a time, and on topof it the template's
Conditions(Fn::Equals,Fn::And,Fn::Or,Fn::Not,Condition),Fn::IfandAWS::NoValue; resources and outputs whose condition is false are dropped, as CloudFormation does,Fn::GetAttexpressions (and${Resource.Attribute}placeholders inFn::Sub) upfront, only those in resources, outputs and
Fn::Ifbranches CloudFormation creates, and evaluates each distinct expression once per stack. Resource lookups gothrough one shared limit for the whole run (10 in flight, across stacks and nested stacks), and each Cloud
Control resource is read once,
in the parent; a value the parent could not evaluate is reported in the nested stack wherever it is used.
When the parent uses a nested stack's output, it gets the value CloudFormation reports for the deployed nested
stack (found by its physical id, so this also works for nested stacks CDK doesn't manage),
The comparison is structural, since
GetTemplatedoesn't always return the template's characters exactly.Expressions that can't be evaluated are left in place and reported with the reason; when an expression fails
because one of its arguments failed, only the innermost failure is reported, and a function the evaluator doesn't
support is reported itself (the evaluator now throws
CfnUnsupportedFunctionExceptionfor these). Expressions that referenceNoEchoparameters are reported rather than evaluated,since
DescribeStacksonly returns****for their values; they never fall back to the parameter'sDefault,including where the evaluator uses them internally (Cloud Control identifiers, nested stack parameters). A
parameter is treated as NoEcho (case-insensitively) if either the cloud assembly's template or the deployed
template says so. Dynamic references (
{{resolve:ssm:...}},{{resolve:secretsmanager:...}}) are reported asunresolved, since CloudFormation resolves them at deployment (and secrets must not be read).
Only evaluation failures are reported this way: any other error (expired credentials, throttling, permissions,
unexpected exceptions) fails the action.
Fn::Equalscompares values the way CloudFormation does (scalars asstrings, so a deployed
'10'equals10). One unresolvable expressiontherefore doesn't prevent the rest of the template from being evaluated. Selected stacks that aren't deployed
(including
REVIEW_IN_PROGRESS,ROLLBACK_IN_PROGRESS,ROLLBACK_COMPLETE,CREATE_FAILEDand deleted stacks) are skipped with a warning andlisted in
notDeployed.AWS::StackNameandAWS::StackIdare passed to the evaluator alongside the stack parameters, since it doesn'tprovide them itself.
Decisions worth reviewing
Public API. The new public surface is the
evaluatemethod,'evaluate'inUnstableFeatureandToolkitAction, and theEvaluateOptions,EvaluateResult,EvaluatedStackandUnresolvedExpressiontypes. A nested stack's
hierarchicalIdis its parent's followed by its logical id, since nested stacks aren'tcloud assembly artifacts.
EvaluateCloudFormationTemplateitself stays internal; the action exposes its result instead of itsconstructor (which would require exporting
SDK). Gated as unstable so the shape can still change.Which errors are reported, and which are thrown. Evaluation failures are reported per expression, so the
rest of the template is still evaluated. Everything else is thrown. For
Fn::GetAttvalues looked up throughCloud Control, the evaluator now keeps the original error as
cause; onlyResourceNotFoundException,TypeNotFoundException,UnsupportedActionException,InvalidRequestExceptionandValidationExceptioncauses count as "can't be evaluated" (these are the not-found / unsupported cases seen against real stacks),
as do Cloud Control handler failures (
HandlerFailureException,HandlerInternalFailureException,GeneralServiceException) andAccessDeniedException: one resource that can't be read (for example withleast-privilege credentials) doesn't stop the rest. Credentials that can't read the stack fail on
DescribeStacks. Expired credentials, throttling and network errors still fail the action.Shared evaluator changes (in fix(toolkit-lib): stricter, opt-in evaluation modes for EvaluateCloudFormationTemplate #2074).
EvaluateCloudFormationTemplatenow evaluates values the wayCloudFormation does (an empty string is a value, and only a missing or
nullvalue is missing;Fn::Selectoutof range is an evaluation failure;
Fn::Subtreats${!Literal}as literal text), which also fixes those casesfor hotswap. It gets two opt-in props that only this action sets:
unavailableParameters: aRefto one fails instead of falling back to itsDefault.lookups(SharedLookups): Cloud Control reads are cached per resource and limited across evaluators, andnested stack outputs come from
DescribeStacksof the nested stack.The walker also reports an expression that evaluates to no value, and a function whose whole argument is
AWS::NoValue, instead of dropping the property or leaking the placeholder. Other changes, visible to hotswap only in errormessages: unsupported functions throw
CfnUnsupportedFunctionException(aCfnEvaluationException, samemessage), failed Cloud Control lookups keep their cause, and a Cloud Control identifier that can't be evaluated
throws its own evaluation error rather than being reported as a failed lookup (any other error while building
the identifier is still wrapped in a
CfnEvaluationException, so hotswap still treats it as non-hotswappable).Fn::GetAttof an output of a nested stack whose template has noOutputsis now an evaluation failureinstead of a
TypeError, which also fixes that case for hotswap. Hotswap's handling of empty-stringparameters (it uses the default) is unchanged here and could be fixed separately.
Scope. Conditions,
Fn::IfandAWS::NoValueare evaluated in the action's own walker, not inEvaluateCloudFormationTemplate.Template evaluated. The action evaluates the cloud assembly's template (the one that was deployed from it),
and warns on structural differences from the deployed template, rather than evaluating
GetTemplate's output.Not changed.
loadCurrentTemplateWithNestedStacks(shared with hotswap and diff) is used as is, so the rootstack is described twice and listed twice per evaluation, outside the shared lookup limit.
Known limitation:
Fn::Ifin a Cloud Control identifier. Conditions are evaluated by the action's walker,not by
EvaluateCloudFormationTemplate, so a resource whose Cloud Control identifier comes from a conditionalproperty is reported as unresolved. Supporting it would mean moving condition evaluation into the shared
evaluator.
Throttling still fails the action (after the SDK's retries); the shared lookup limit is what keeps it rare.
Known limitations (existing evaluator behaviour, left for follow-ups):
Fn::GetAZs,Fn::FindInMap,Fn::Base64andFn::Cidrare not supported;Fn::GetAttonAWS::EC2::EIPbuilds the wrong Cloud Controlidentifier (the public IP is used as the allocation ID); attributes of custom resources can't be looked up;
with a custom endpoint (
AWS_ENDPOINT_URL),AWS::URLSuffixis derived from the endpoint host.CLI. No CLI command is added.
cdk-toolkit.tslists'evaluate'in its exhaustiveRecord<UnstableFeature, true>, as for the other unstable features.Description of how you validated changes
sections) and for the action (feature gate,
Ref/Fn::GetAttformatted and looked up through CloudControl /
AWS::StackName/Fn::Join, Cloud Control failure reported as unresolved, nested stacks andunevaluable nested stack parameters, undeployed / failed / never-executed stacks skipped, template drift
warning, concurrency validation, bounded and reachable-only lookups, handler failures and wrapped causes), and for
the shared evaluator (CloudFormation value semantics,
unavailableParametersincluding theCloud Control identifier path,
nullattributes),using a new
stack-with-referencesfixture generated withgen-fixture(with creation stack tracesdisabled, so no local paths are checked in).
account: construct-hub-dev (275 resources), a CDK CLI telemetry service (145) and atmosphere-dev (143).
AWS::EC2::EIPFn::GetAtt, a custom-resource attribute,Fn::GetAZs). An independent resolver left unresolved values in 126, 66 and 75 resources on the same stacks.other resolver read the template through
GetTemplate, which replaced an em dash with?.action's values: 1,761 properties matched (each also failed when the expected value was altered), including
every property only the action resolved; the remaining mismatches are known service-side representations
(and one shared account-level setting that two of the stacks both set).
expressions on the same three apps deployed to an emulator (these apps have no nested stacks, so nested
stack evaluation is covered by the unit tests only). After the stricter
Fn::Select, construct-hub on theemulator reports four more unresolved expressions: the emulator returns a name where CloudFormation returns
an ECS task definition ARN, and the template splits that ARN. Previously those parts were silently empty.
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license