Mthenn
Mthenn

Reputation: 359

Getting generated name in AWS Cdk

When creating a WebAcl in CDK and letting CDK generate the name, I want to use that generated name as a variable in CDK, i.e. when generating the WebAcl like this (no explicit name set in the properties) ...

const webAcl = new CfnWebACL(this, "webAcl", {
  defaultAction: {
    allow: {},
  },
  scope: myScope,
  visibilityConfig: {
    cloudWatchMetricsEnabled: true,
    metricName: "webACL",
    sampledRequestsEnabled: true,
  }
});

... the webAcl will have a generated Name like webAcl-7xtQ0oTU473X after deployment (the id with an appended hash). The problem is that I do not know how to reference this name as a variable in CDK.

I found the following possibilities to get some type of ids/names but none of them resolve to webAcl-7xtQ0oTU473X after deployment (some, however, include the value I want):

webAcl.name // is undefined in CDK
webAcl.logicalId // resolves to 'webAcl' (no hash)
webAcl.attrId // resolves to the id
webAcl.attrLabelNamespace // resolves to 'awswaf:<accountNumber>:webacl:webACL-7xtQ0oTU473X:'
webAcl.attrArn // resolves to the full Arn
Names.uniqueId(webAcl) // resolves to '<stackName><nestedStackName>webACL<someOtherHash>'

Is there some other way to get the desired value as a variable?

Upvotes: 0

Views: 1419

Answers (1)

Victor Smirnov
Victor Smirnov

Reputation: 3780

L1 construct's properties

L1 constructs are exactly the resources defined by AWS CloudFormation—no more, no less. You must provide the resource's required configuration yourself.

From the Developer Guide

The library does not define any values for us but uses only the values we have provided. So, for example, if we do not set any value for the Name, then there is no value in the synthesized template.

Below is our resource definition from the synthesized template.

Resources:
  webAcl:
    Type: AWS::WAFv2::WebACL
    Properties:
      DefaultAction:
        Allow: {}
      Scope: my-scope
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: webACL
        SampledRequestsEnabled: true

The resource definition does not have the Name property. CloudFormation generates the property when it deploys the template.

There is no way to access properties we have not set. Okay, without hacks.

L1 construct's return values

In the CloudFormation template, we can use the resource's return values. Please, find the list in the CloudFormation user guide for the WebACL. Every resource has this section.

We can access the values in the CDK script as tokens.

  console.log({
    ref: webAcl.ref,
    attrArn: webAcl.attrArn,
    attrCapacity: webAcl.attrCapacity,
    attrId: webAcl.attrId,
    attrLabelNamespace: webAcl.attrLabelNamespace
  })

enter image description here

And these are the only values generated by the CloudFormation during the deployment, which we can access in our script. The CDK library maps them nicely to the CloudFormation GetAtt, or Ref function calls in the synthesized template.

For example, we get this for the webAcl.attrCapacity. I have no idea why it looks weird in the console log.

        Fn::GetAtt:
          - webAcl
          - Capacity

Maybe a workaround

We can use intrinsic functions to extract our value from the resource's values.

Fn.select(3, Fn.split(':', webAcl.attrLabelNamespace))

In the synthesized template, we get the chain of function calls.

        Fn::Select:
          - 3
          - Fn::Split:
              - ":"
              - Fn::GetAtt:
                  - webAcl
                  - LabelNamespace

This looks fragile to me, but it might work. Please keep in mind that you can not apply any JavaScript operations to this value but use it as a construct property only. Because the CDK substitutes it with function calls in the template.

Code for reference

I use the following code to test the answer.

import { App, Fn, Stack } from 'aws-cdk-lib'
import { env } from 'process'
import { CfnBucket } from 'aws-cdk-lib/aws-s3'
import { CfnWebACL } from 'aws-cdk-lib/aws-wafv2'

function createStack (scope, id, props) {
  const stack = new Stack(scope, id, props)

  const webAcl = new CfnWebACL(stack, 'webAcl', {
    defaultAction: {
      allow: {},
    },
    scope: 'my-scope',
    visibilityConfig: {
      cloudWatchMetricsEnabled: true,
      metricName: 'webACL',
      sampledRequestsEnabled: true,
    }
  })

  new CfnBucket(stack, 'bucket', {
    bucketName: Fn.select(3, Fn.split(':', webAcl.attrLabelNamespace))
  })

  console.log({
    ref: webAcl.ref,
    attrArn: webAcl.attrArn,
    attrCapacity: webAcl.attrCapacity,
    attrId: webAcl.attrId,
    attrLabelNamespace: webAcl.attrLabelNamespace
  })

  return stack
}

const app = new App()
createStack(app, 'WebAclName', {
  env: { account: env.CDK_DEFAULT_ACCOUNT, region: env.CDK_DEFAULT_REGION }
})

Upvotes: 4

Related Questions