简体   繁体   中英

How to obtain pseudo-parameters user data with AWS CDK?

I would like to convert this working CloudFormation Code into a proper Python AWS CDK version. The EC2 shall be launched within a VPC. The user data are used to install apps. After completion, I need a call back to Cloudformation.

UserData:
        Fn::Base64: !Sub |
          <script>
          cfn-signal.exe --exit-code 0 --stack ${AWS::StackId} --resource EC2Instance --region ${AWS::Region}
          </script>

I tried to use the direct way with aws_cdk.core.Fn.base64 which is not working with regard to pseudo-parameters declaration within the EC2 the user data.

This is my current state:

EC2InstanceUserData = aws_ec2.UserData.for_windows()
EC2InstanceUserData.add_commands(
   "cfn-signal.exe --exit-code 0 ",
   "--stack ",
   VpcStack.stack_id(XXX, e.g. self?), # not working
   " --resource ",
   VpcStack.get_logical_id(XXX, e.g. self?), # not working
   " --region ",
   VpcStack.region(XXX, e.g. self?) # not working
)

Method 1:

Pros:

  • Can take in any amount of variables, such as those defined in the context, not just those in the core.Aws object such as Region or Account ID.

Cons:

  • You will need to prefix references to all regular variables in the user_data.sh script with $!{ rather than just ${ .

Steps

Use a mappings dict and parse it into the Fn.sub function. Personally I like these declared at the top of my user_data.sh script rather than substituted throughout so use a double underscore as a prefix and as a suffix. Note that you still need to consider the mappings as variables rather than strings.

ie

$cat user_data.sh
ACCOUNT_ID="${__ACCOUNT_ID__}"
REGION="${__REGION__}"

## Updates
yum update -y

## Fix time
ln -sf /usr/share/zoneinfo/Australia/Melbourne /etc/localtime

## ECR Repo
ECR_REPO="${!ACCOUNT_ID}.dkr.ecr.${!REGION}.amazonaws.com/"
...

In my stack declaration I then place the following dict:

mappings = {"__ACCOUNT_ID__": self.account,
            "__REGION__": self.region}

And read the user_data.sh into the sub function, with the mappings dict as the second parameter

with open("user_data/user_data.sh", 'r') as user_data_h:
    # Use a substitution
    user_data_sub = core.Fn.sub(user_data_h.read(), mappings)

Then use the custom attribute from the UserData module.

# Import substitution object into user_data set
user_data = ec2.UserData.custom(user_data_sub)

Method 2

Pros:

  • No need to change bash syntax

Cons:

  • Token variables are hard to read, and restricted to attributes of the core.Aws object. Such as AccountID and Region.

Steps

You can run print statements inside the cdk workflow to assist you in determining what the variables such as core.Aws.ACCOUNT_ID and core.Aws.REGION are evaluated to and use these inside the user_data script. (I'm writing my deployment in python and have based it from the ec2 on an existing VPC from the aws official examples repo .

ie:

host = ec2.Instance(...)
print(core.aws.ACCOUNT_ID)
print(core.Aws.REGION)

I then run cdk synth which yields:

${Token[AWS::AccountId.0]}
${Token[AWS::Region.4]}
Resources:...

From here I can use these in my user_data script: ie

#!/bin/bash
ACCOUNT_ID="${Token[AWS::AccountId.0]}"
REGION="${Token[AWS::Region.4]}"

## Updates
yum update -y

## Fix time
ln -sf /usr/share/zoneinfo/Australia/Melbourne /etc/localtime

Note now when re-running cdk synth these are recognised as special by the yaml constructor (The yaml double spacing is a known cdk bug):

...
UserData:
        Fn::Base64:
          Fn::Join:
            - ""
            - - >-
                #!/bin/bash


                # AWS vars:

                ACCOUNT_ID="
              - Ref: AWS::AccountId
              - >-
                "

                REGION="
              - Ref: AWS::Region
              - >-
                "

                ## Updates
                yum update -y

                ## Fix time
                ln -sf /usr/share/zoneinfo/Australia/Melbourne /etc/localtime

                ## ECR Repo
                EC2_REPO="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/"
...

I found the following to work with python

from aws_cdk import (
  aws_ec2,
  core
)

host = aws_ec2.Instance(...)

host.add_user_data('', join([
  'yum install -y aws-cfn-bootstrap\n',
  f'/opt/aws/bin/cfn-init -v -s {core.Aws.STACK_NAME} -r {host.node.default_child.logical_id}\n'
])

meta_data = {
  'config': {
    'packages': {...},
    'files': {...},
     ...
    }
}

# for adding the meta data in a way that gets synth
host.node.default_child.add_overide('Metadata.AWS::CloudFormation::Init', meta_data)

This is using the CfnInstance object you get from host.node.default_child

You can access those pseudo-params by using the core module:

from aws_cdk import core

# other code...

EC2InstanceUserData = aws_ec2.UserData.for_windows()
EC2InstanceUserData.add_commands(
   "cfn-signal.exe --exit-code 0 ",
   f"--stack {core.Aws.STACK_ID}",
   f" --resource {EC2Instance}",  # Without more context, I'm not sure if this is exactly what you're wanting
   f" --region {core.Aws.REGION}",
)

# other code ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM