Skip to content
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

Merged
merged 20 commits into from
May 23, 2023

Conversation

tmokmss
Copy link
Contributor

@tmokmss tmokmss commented Apr 4, 2023

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:

function findAllReferences(root: IConstruct) {
const result = new Array<{ source: CfnElement, value: CfnReference }>();

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.

export class RememberingTokenResolver extends DefaultTokenResolver {
private readonly tokensSeen = new Set<IResolvable>();
public resolveToken(t: IResolvable, context: IResolveContext, postProcessor: IPostProcessor) {
this.tokensSeen.add(t);
return super.resolveToken(t, context, postProcessor);
}

However, the resolver is not used specifically when we resolve tokens in raw overrides. Actually the current interface of postProcess function of PostResolveToken class makes It difficult to use an external resolver.

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,
});

That is why, in this PR, we move the resolution process outside of the postProcess, allowing to resolve tokens in raw overrides with the RememberingTokenResolver 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

@github-actions github-actions bot added admired-contributor [Pilot] contributed between 13-24 PRs to the CDK p2 labels Apr 4, 2023
@aws-cdk-automation aws-cdk-automation requested a review from a team April 4, 2023 07:41
Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a 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.

@tmokmss
Copy link
Contributor Author

tmokmss commented Apr 4, 2023

Exemption Request

Existing integration tests already cover the changes of this PR. Also, aws-cdk-lib/core does not have any existing integ test.

@aws-cdk-automation aws-cdk-automation added the pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback. label Apr 4, 2023
@github-actions github-actions bot added bug This issue is a bug. p1 and removed p2 labels Apr 4, 2023
Copy link
Contributor

@TheRealAmazonKendra TheRealAmazonKendra left a 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)) {
Copy link
Contributor

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.

Copy link
Contributor Author

@tmokmss tmokmss Apr 6, 2023

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?

Copy link
Contributor

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

@TheRealAmazonKendra TheRealAmazonKendra removed the pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback. label Apr 5, 2023
@tmokmss
Copy link
Contributor Author

tmokmss commented Apr 6, 2023

Clarification Request

Why I should not use instanceof?

@aws-cdk-automation aws-cdk-automation added the pr/reviewer-clarification-requested The contributor has requested clarification on feedback, a failing build, or a failing PR Linter run label Apr 6, 2023
@tmokmss
Copy link
Contributor Author

tmokmss commented Apr 6, 2023

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.

ERROR      test/integ.cfn-resource 4.098s
      "cdk-integ" can only operate on apps with a single stack.

  If your app has multiple stacks, specify which stack to select by adding this to your test source:

      /// !cdk-integ STACK ...

  Available stacks: Stack1 Stack2 (wildcards are also supported)

@aws-cdk-automation aws-cdk-automation added the pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback. label Apr 6, 2023
@aws-cdk-automation aws-cdk-automation dismissed their stale review April 15, 2023 08:18

✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.

@tmokmss
Copy link
Contributor Author

tmokmss commented Apr 15, 2023

I just changed a random integ test to keep the PR open. I'll revert it after a review.

@aws-cdk-automation
Copy link
Collaborator

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.

@gitpod-io
Copy link

gitpod-io bot commented Apr 27, 2023

@mergify mergify bot dismissed TheRealAmazonKendra’s stale review April 27, 2023 01:31

Pull request has been modified.

Copy link
Contributor

@corymhall corymhall left a 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)) {
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

@tmokmss tmokmss May 3, 2023

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.

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?

Copy link
Contributor

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) => {

Copy link
Contributor

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.

Copy link
Contributor Author

@tmokmss tmokmss May 6, 2023

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?

Copy link
Contributor

@corymhall corymhall left a 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.

@corymhall corymhall added the pr-linter/exempt-integ-test The PR linter will not require integ test changes label May 1, 2023
@tmokmss tmokmss marked this pull request as draft May 2, 2023 00:21
@mergify mergify bot dismissed corymhall’s stale review May 3, 2023 00:14

Pull request has been modified.

@tmokmss tmokmss marked this pull request as ready for review May 7, 2023 10:06
@aws-cdk-automation aws-cdk-automation added the pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. label May 7, 2023
@aws-cdk-automation
Copy link
Collaborator

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.

@aws-cdk-automation aws-cdk-automation removed the pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. label May 23, 2023
@tmokmss tmokmss requested a review from corymhall May 23, 2023 03:05
@tmokmss
Copy link
Contributor Author

tmokmss commented May 23, 2023

Hi @corymhall could you review this again? Idk why it's in CHANGES REQUESTED state but it's ready for review.

@mergify
Copy link
Contributor

mergify bot commented May 23, 2023

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-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: cdae366
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify mergify bot merged commit 1135356 into aws:main May 23, 2023
@mergify
Copy link
Contributor

mergify bot commented May 23, 2023

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).

@tmokmss tmokmss deleted the fix_cross_stack_override branch May 24, 2023 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
admired-contributor [Pilot] contributed between 13-24 PRs to the CDK bug This issue is a bug. p1 pr/reviewer-clarification-requested The contributor has requested clarification on feedback, a failing build, or a failing PR Linter run pr-linter/exempt-integ-test The PR linter will not require integ test changes pr-linter/exemption-requested The contributor has requested an exemption to the PR Linter feedback.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PropertyOverrides: Tokens not properly resolved in cross stack scenarios when using property overrides
5 participants