-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(core): allow override with cross-stack references #24920
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pull request linter has failed. See the aws-cdk-automation comment below for failure reasons. If you believe this pull request should receive an exemption, please comment and provide a justification.
A comment requesting an exemption should contain the text Exemption Request
. Additionally, if clarification is needed add Clarification Request
to a comment.
Exemption Request Existing integration tests already cover the changes of this PR. Also, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make sure that your PR body describes the problem the PR is solving, and the design approach and alternatives considered. Explain why the PR solves the problem. A link to an issue is helpful, but does not replace an explanation of your thought process (See Contributing Guide, Pull Requests).
From the context you've provided I don't totally understand the problem being solved or how your solution fixes it. Please update the body of your PR to provide this context.
We also definitely need an integration test on this. While we don't have any in core, you can pick any module to put this into practice with. We should also run this through the testing pipeline so I'm going to add that label here as well.
@@ -644,66 +609,11 @@ function deepMerge(target: any, ...sources: any[]) { | |||
|
|||
for (const key of Object.keys(source)) { | |||
const value = source[key]; | |||
if (typeof(value) === 'object' && value != null && !Array.isArray(value)) { | |||
if (typeof(value) === 'object' && value != null && !Array.isArray(value) && !(value instanceof Intrinsic)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instanceof
is not safe in all contexts. See this PR for how we do this in other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why instanceof should not be used here. The PR has no description and it is not even merged. Could you explain a bit more in detail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially instanceOf
cannot be guaranteed to work reliably since it is
possible to have multiple copies of a library installed and instanceOf will view
each copy as a different object.
This PR has some additional context
The method we are using instead is to create an isIntrinsic
method
Clarification Request Why I should not use |
Exemption Request Currently integ-runner does not seem to support multiple-stacks deployment, which makes it difficult to test this scenario. Also, this change only affects synth phase, not deployment phase, so I believe we don't need any integration test here.
|
✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.
I just changed a random integ test to keep the PR open. I'll revert it after a review. |
This PR has been in the CHANGES REQUESTED state for 3 weeks, and looks abandoned. To keep this PR from being closed, please continue work on it. If not, it will automatically be closed in a week. |
Pull request has been modified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of questions
@@ -644,66 +609,11 @@ function deepMerge(target: any, ...sources: any[]) { | |||
|
|||
for (const key of Object.keys(source)) { | |||
const value = source[key]; | |||
if (typeof(value) === 'object' && value != null && !Array.isArray(value)) { | |||
if (typeof(value) === 'object' && value != null && !Array.isArray(value) && !(value instanceof Intrinsic)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Essentially instanceOf
cannot be guaranteed to work reliably since it is
possible to have multiple copies of a library installed and instanceOf will view
each copy as a different object.
This PR has some additional context
The method we are using instead is to create an isIntrinsic
method
@@ -158,6 +158,8 @@ export class DefaultTokenResolver implements ITokenResolver { | |||
// The token might have returned more values that need resolving, recurse | |||
resolved = context.resolve(resolved); | |||
resolved = postProcessor.postProcess(resolved, context); | |||
// resolve again since postProcess might have added more tokens (e.g. overriding) | |||
resolved = context.resolve(resolved); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we are passing the context
through to the postProcess
, but it
isn't making its way all the way through to the processor
. Is there a way we
could pass it all the way through and use that to replace the
Tokenization.resolve
function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'll check that way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@corymhall
I investigated it, and please correct me if my understanding is wrong 🙏
If we take that approach, we can use context.resolve
here instead of Tokenization.resolve
, and it can also settle the issue.
aws-cdk/packages/aws-cdk-lib/core/lib/cfn-resource.ts
Lines 441 to 447 in 04323c4
const resolvedRawOverrides = Tokenization.resolve(this.rawOverrides, { | |
scope: this, | |
resolver: CLOUDFORMATION_TOKEN_RESOLVER, | |
// we need to preserve the empty elements here, | |
// as that's how removing overrides are represented as | |
removeEmpty: false, | |
}); |
The disadvantage is that, to pass the context, we have to change the signature of _toCloudFormation
function something like _toCloudFormation(context?)
(context must be optional because currently not all the callers have context available.) It requires changes in many files and will complicate the code. Given that I don't think it is a preferred approach.
Isn't it simpler and more reasonable to resolve tokens outside of the postProcess function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of adding it to the PostResolveToken
processor
method as an optional property
constructor(value: any, private readonly processor: (x: any) => any) { |
So then it would be something like
[this.logicalId]: new PostResolveToken({
...
}, (resourceDef, context) => {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like Cory's solution. PostResolveToken
is private and so is free to have its API changed. This will (hopefully?) cleanly achieve the desired solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@corymhall @rix0rrr
Thanks, I think I have to study about CDK tokens more 😄 Will try the approach anyway.
One question is, why not resolve tokens after the deepMerge? (i.e. my original approach 905d5ed)
It can remove some hacks currently implemented like removeEmpty: false
or MERGE_EXCLUDE_KEYS
, and simplify the existing logic.
Is it performance-wise reason (to reduce recursion), or is there any edge case not to work properly that I am not aware of?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meant to request changes.
This PR has been in the CHANGES REQUESTED state for 3 weeks, and looks abandoned. To keep this PR from being closed, please continue work on it. If not, it will automatically be closed in a week. |
Hi @corymhall could you review this again? Idk why it's in CHANGES REQUESTED state but it's ready for review. |
Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
closes #18882
The problem is described in the original issue, and below is what I found as the root cause and how it can be fixed.
Previously when we used a cross-stack reference in override, it was not resolved as an
Fn::ImportValue
.To make it an
Fn::ImportValue
, we need to get every token in an app and find references (tokens that references resources outside of its stack) from them. The related code is here:aws-cdk/packages/@aws-cdk/core/lib/private/refs.ts
Lines 139 to 140 in 810d736
To get all the tokens in an app, we use
RememberingTokenResolver
, which remembers every token it has found during resolution. So basically this resolver must be used on every resolution to find all the tokens.aws-cdk/packages/@aws-cdk/core/lib/private/resolve.ts
Lines 270 to 276 in 810d736
However, the resolver is not used specifically when we resolve tokens in raw overrides. Actually the current interface of
postProcess
function ofPostResolveToken
class makes It difficult to use an external resolver.aws-cdk/packages/@aws-cdk/core/lib/cfn-resource.ts
Lines 374 to 380 in 810d736
That is why, in this PR, we move the resolution process outside of the
postProcess
, allowing to resolve tokens in raw overrides with theRememberingTokenResolver
resolver.This change also simplifies the current implementation of deepMerge as a side product.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license