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(custom-resources): unable to use a resource attributes as dictionary keys in AwsCustomResource #13074

Merged
merged 90 commits into from
Feb 24, 2021

Conversation

eladb
Copy link
Contributor

@eladb eladb commented Feb 16, 2021

AWS calls sometimes require to specify resource names or ARNs in dictionary keys (e.g. dynamodb:BatchWriteItem). Since resource attributes resolve only during deployment, we
expect to be able to pass them in dictionary keys. This does not work out of the box because JSON only allows strings in dictionary keys and attributes in CFN are modeled as JSON objects ({ "Fn::GetAtt": ["X", "Y" ] }, etc).

To address this, we simply encode Create, Update and Delete properties passed to the custom resource as token-aware JSON strings using stack.toJsonString(), and decode them in the custom resource.

Since the entire object is encoded, the special-casing for boolean encoding is no longer needed.

Additionally, we added support for toJSON() in the token resolution logic (stack.resolve()). If resolve encounters an object that has a toJSON() method, it will call it and continue resolution with the output value. This is needed here before AwsCustomResource uses this standard JS behavior to encode physical name references in requests.

We also made CfnJson.value public to allow directly accessing the referenced value in case it is needed (this could have been used as an alternative to the built-in support for token-aware stringification if we didn’t support it in AwsCustomResource) - see referenced issue.

Resolves #13063


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

…ary keys in AwsCustomResource

AWS calls sometimes require to specify resource names or ARNs in dictionary keys (e.g. `dynamodb:BatchWriteItem`). Since resource attributes resolve only during deployment, we
expect to be able to pass them in dictionary keys. This does not work out of the box because JSON only allows strings in dictionary keys and attributes in CFN are modeled as JSON objects (`{ "Fn::GetAtt": ["X", "Y" ] }`, etc).

To address this, we simply encode `Create`, `Update` and `Delete` properties passed to the custom resource as token-aware JSON strings using `stack.toJsonString()`, and decode them in the custom resource.

Since the entire object is encoded, the special-casing for boolean encoding is no longer needed.

Additionally, we added support for `toJSON()` in the token resolution logic (`stack.resolve()`). If resolve encounters an object that has a `toJSON()` method, it will call it and continue resolution with the output value. This is needed here before `AwsCustomResource` uses this standard JS behavior to encode physical name references in requests.

We also made `CfnJson.value` public to allow directly accessing the referenced value in case it is needed (this could have been used as an alternative to the built-in support for token-aware stringification if we didn’t support it in AwsCustomResource) - see referenced issue.

Resolves #13063
@gitpod-io
Copy link

gitpod-io bot commented Feb 16, 2021

@eladb eladb requested a review from rix0rrr February 16, 2021 07:39
@github-actions github-actions bot added the @aws-cdk/custom-resources Related to AWS CDK Custom Resources label Feb 16, 2021
@eladb
Copy link
Contributor Author

eladb commented Feb 16, 2021

@jogold can you please take a look?

@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Feb 16, 2021
dependabot-preview bot and others added 16 commits February 22, 2021 15:11
By default, when creating a Proxy,
we were not creating a Security Group for it,
and because of that, the Proxy could not connect to the Cluster/Instance.

See docs at: https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…LED state (#12837)

fixes #12836

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
….0 (#13072)

Bumps [eslint-import-resolver-typescript](https://github.com/alexgorbatchev/eslint-import-resolver-typescript) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](import-js/eslint-import-resolver-typescript@v2.3.0...v2.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.843.0 to 2.844.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md)
- [Commits](aws/aws-sdk-js@v2.843.0...v2.844.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…y from its ARN (#13066)

To align it with how other resources work, for example
[S3 Buckets](https://github.com/aws/aws-cdk/blob/c6256992902fc4237ceb9f965e970e2c2ef00777/packages/%40aws-cdk/aws-s3/lib/bucket.ts#L1285-L1286). 

Fixes #13025

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Use `jest` for RDS tests, as I plan to add data-driven tests.


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
See #13019 for the original fix, which was committed to the v2-main branch.
This backports the change to the master/v1 branch.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Most L2 resources employ the "PhysicalName" protocol, which checks usage of
resource names across environment borders, and can automatically turn
auto-named resources into physically-named resources if the situation calls for
it.

Unfortunately, this wrapped token is a generic IResolvable, not a Reference,
and so did not work with the `exportValue()` automatic reference detection.

Make the token returned by `getResourceNameAttribute()` etc. a `Reference`
that mimics the underlying `Reference` to make this work out.

Fixes #13002, fixes #12918.


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…v2-main (#13056)

This was originally commented out - d7e028a - since this was not yet
compatible with the v2-main branch.

Since then, the v2-main branch has been updated - 7554246 - with the
necessary fixes and the conditional has been enabled there[1].

No effective change to master branch because of this. It reduces divergence
of the two active branches.

[1]: 7554246#diff-d325e066c7cdf5d2a8423e65b0190045defe711b4dc25adc0a94a11eef163ed9


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…3084)

Update how feature flags that are destined to be dropped in v2 should be
developed.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
fixes #12415

To generate the correct policy, the DatabaseProxy ARN is parsed
and the resulting components are used along with a new parameter
to grantConnect.

The unit test was updated and passes. Caveat lector, I was not
able to get a full docker build or a full local build to work on
my box.

I'm not sure if this should be considered a breaking change. While
it technically alters the functionality of a published function,
the current behavior provides no utility.


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Fixes #5850

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.844.0 to 2.845.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md)
- [Commits](aws/aws-sdk-js@v2.844.0...v2.845.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 24.1.3 to 24.1.4.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/master/CHANGELOG.md)
- [Commits](jest-community/eslint-plugin-jest@v24.1.3...v24.1.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject89A8053A-LhjRyN9kxr8o
  • Commit ID: 3ac2523
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

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

@mergify
Copy link
Contributor

mergify bot commented Feb 24, 2021

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot merged commit 3cb3104 into master Feb 24, 2021
@mergify mergify bot deleted the benisrae/stringify-aws-custom-resource branch February 24, 2021 07:14
@hughevans
Copy link

hughevans commented Mar 9, 2021

I guess this is what broke my use of getResponseField() on AwsCustomResource in v1.92.0.

So now I need to somehow use CfnJson to retrieve those values? It's not immediately clear to me how to do that.

Broken custom resource
1:17:15 pm | UPDATE_FAILED        | AWS::IAM::Policy                    | …/WildcardCer...Role/DefaultPolicy
CustomResource attribute error: Vendor response doesn't contain HostedZone.Id key in object arn:aws:cloudformation:…
// Allows CDK to use a reusable delegation set. Sourced from:
// https://pablissimo.com/1100/creating-a-route-53-public-hosted-zone-with-a-reusable-delegation-set-id-in-cdk

import {
  AwsCustomResource,
  AwsCustomResourcePolicy,
} from "@aws-cdk/custom-resources";
import { Construct, Fn, Names } from "@aws-cdk/core";
import {
  IPublicHostedZone,
  PublicHostedZone,
  PublicHostedZoneProps,
} from "@aws-cdk/aws-route53";
import { PhysicalResourceId } from "@aws-cdk/custom-resources";

export interface PublicHostedZoneWithReusableDelegationSetProps
  extends PublicHostedZoneProps {
  delegationSetId: string;
}

export class PublicHostedZoneWithReusableDelegationSet extends Construct {
  private publicHostedZone: AwsCustomResource;
  private hostedZoneName: string;

  constructor(
    scope: Construct,
    id: string,
    props: PublicHostedZoneWithReusableDelegationSetProps
  ) {
    super(scope, id);

    this.hostedZoneName = props.zoneName;

    const normaliseId = (id: string) => id.split("/").slice(-1)[0];
    const normalisedDelegationSetId = normaliseId(props.delegationSetId);

    this.publicHostedZone = new AwsCustomResource(
      this,
      "CreatePublicHostedZone",
      {
        onCreate: {
          service: "Route53",
          action: "createHostedZone",
          parameters: {
            CallerReference: Names.uniqueId(this),
            Name: this.hostedZoneName,
            DelegationSetId: normalisedDelegationSetId,
            HostedZoneConfig: {
              Comment: props.comment,
              PrivateZone: false,
            },
          },
          physicalResourceId: PhysicalResourceId.fromResponse("HostedZone.Id"),
        },
        policy: AwsCustomResourcePolicy.fromSdkCalls({
          resources: AwsCustomResourcePolicy.ANY_RESOURCE,
        }),
      }
    );

    new AwsCustomResource(this, "DeletePublicHostedZone", {
      onDelete: {
        service: "Route53",
        action: "deleteHostedZone",
        parameters: {
          Id: this.publicHostedZone.getResponseField("HostedZone.Id"),
        },
      },
      policy: AwsCustomResourcePolicy.fromSdkCalls({
        resources: AwsCustomResourcePolicy.ANY_RESOURCE,
      }),
    });
  }

  asPublicHostedZone(): IPublicHostedZone {
    return PublicHostedZone.fromHostedZoneAttributes(
      this,
      "CreatedPublicHostedZone",
      {
        hostedZoneId: Fn.select(
          2,
          Fn.split("/", this.publicHostedZone.getResponseField("HostedZone.Id"))
        ),
        zoneName: this.hostedZoneName,
      }
    );
  }
}

Update Please ignore, this issue happens on v1.91.0 whenever I change any of the attributes on my custom resources so it's likely something else.

@charles-salmon
Copy link

The changes that were made here are actually breaking, from the perspective of @aws-cdk/assert. The release notes don't currently make this clear (although I acknowledge that this is not a breaking change to any particular feature).

Specifically, this makes usage of the hasResourceLike function fairly awkward, if testing against specific properties under the Create, Update or Delete properties, given that these have now been encoded. Further, I believe this now enforces that tests must specify all properties in the same order as those that are output.

I see that a number of tests have been refactored as part of this PR to include the entire encoded contents of the Create, Update or Delete properties. I believe this potentially adds some fragility to the tests.

@rix0rrr Can you potentially comment as to whether or not implementing IResolvable, as opposed to encoding these properties, would alleviate these pains?

@seeebiii
Copy link

Specifically, this makes usage of the hasResourceLike function fairly awkward, if testing against specific properties under the Create, Update or Delete properties, given that these have now been encoded. Further, I believe this now enforces that tests must specify all properties in the same order as those that are output.

In response to @charles-salmon , I had to go through this problem with tests defined in my constructs, e.g. @seeebiii/ses-verify-identities. Here's how I solved it in case anyone else faces this "breaking change":

Simple Checks

If your custom resource parameters are rather simple and don't reference other resources, you can use a combination of encodedJson() and objectLike() (from @aws-cdk/assert) to verify the properties:

// testing a custom resource using AwsCustomResource that is executing SES:deleteIdentity

expectCDK(stack).to(
    haveResourceLike('Custom::AWS', {
      Create: encodedJson(objectLike({
        service: 'SES',
        action: 'deleteIdentity',
        parameters: {
          Identity: emailAddress,
        },
      })),
    }),
  );

Advanced Checks

In cases where your parameters reference other resources, CDK adds a Fn::Join to the Create/Update/Delete parameter and builds the JSON string by breaking it up into separate parts, so they can reference other resources. Here testing is a bit harder and I solved it using deepObjectLike() and stringLike() and anything() (you can get all from @aws-cdk/assert):

// testing a custom resource using AwsCustomResource that is executing SES:setIdentityNotificationTopic

const domain = ...

expectCDK(stack).to(haveResourceLike('Custom::AWS', {
      Create: deepObjectLike({
        'Fn::Join': [
          '', [
            stringLike(`{\"service\":\"SES\",\"action\":\"setIdentityNotificationTopic\",\"parameters\":{\"Identity\":\"${domain}\",\"NotificationType\":\"Bounce\"*`),
            anything(),
            anything(),
          ],
        ],
      }),
    }));

This isn't pretty but it's enough in my case. You can use stringLike() by having a string with a glob pattern, e.g. stringLike(foo*). I'm using anything() for the rest of the JSON string because I don't care about the remaining contents in this case.

I hope this helps anyone in case you run into the same problem when upgrading your CDK construct to 1.92.0.

@0xdevalias
Copy link
Contributor

0xdevalias commented May 22, 2023

This PR looks as though it's likely the source of the following issue(s):

Doing a little digging, I suspect this may have been introduced with this change introduced in 1.92.0 (2021-03-06):

Originally posted by @0xdevalias in #25668 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/custom-resources Related to AWS CDK Custom Resources contribution/core This is a PR that came from AWS.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

(custom-resources): unable to use a tokens in dictionary keys in AwsCustomResource