1

I am using CDK v2, with Typescript.

I want my bastion machine to log stuff to Cloudwatch. The specific LogGroup I want it to write to is also created via CDK (so that I can customise the retention).

How can I customise the userData script with knowledge about other AWS resources, which are also created by CDK - so I can't know their names?

My CDK stuff is being deployed via a CDK pipeline.

Here is my CDK script:

export class StoBastion extends cdk.Stack {

  constructor(scope: Construct, id: string, props: cdk.StackProps){
    super(scope, id, props);

    // actual name: DemoStage-StoBastion-StoBastionLogGroup5EEB3DE8-AdkaWy0ELoeF
    const logGroup = new LogGroup(this, "StoBastionLogGroup", {
      retention: RetentionDays.TWO_WEEKS,
    });

    let initScriptPath = 'lib/script/bastion-linux2-asg-provision.sh';
    const userDataText = readFileSync(initScriptPath, 'utf8');

    const autoScalingGroup = new AutoScalingGroup(this, 'StoAsg', {
      ...   
      userData: UserData.custom(userDataText),
    })    
    
  }
}

And the shell script I want to use as the userData for the instance:

#!/bin/sh

### cloudwatch ###
# This goes as early as possible in the script, so we can see what's going
# on from Cloudwatch ASAP.
echo " >>bastion>> installing cloudwatch package $(date)"
yum install -y awslogs

echo " >>bastion>> configuring cloudwatch - ${TF_APP_LOG_GROUP} $(date)"

## overwrite awscli.conf ##
cat > /etc/awslogs/awscli.conf <<EOL
[plugins]
cwlogs = cwlogs
[default]
region = ${TF_APP_REGION}
EOL
## end of overwrite awscli.conf ##

## overwrite awslogs.conf ##
cat > /etc/awslogs/awslogs.conf <<EOL
[general]
state_file = /var/lib/awslogs/agent-state

[cloudinit]
datetime_format = %b %d %H:%M:%S
file = /var/log/cloud-init-output.log
buffer_duration = 5000
log_group_name = ${TF_APP_LOG_GROUP}
log_stream_name = linux2-cloud-init-output-{instance_id}
initial_position = start_of_file

EOL
## of overwrite awslogs.conf ##

echo " >>bastion>> start awslogs daemon  $(date)"
systemctl start awslogsd

echo " >>bastion>> make sure awslogs starts up on boot"
systemctl enable awslogsd.service

### end cloudwatch ###

I want to somehow replace the variable references in the userData script like ${TF_APP_LOG_GROUP} with values populated at CDK deploy time so they have the correct values.

I'm doing cloudwatch stuff at the moment, but there will be other stuff I need to do like this, so this question isn't about cloudwatch - it's about "how can I configure my userData with values known only at CDK deploy time"?

Shorn
  • 19,077
  • 15
  • 90
  • 168
  • Use string formatting and simply place your variables in the user data script. It will be inserted as tokens that will be resolved during deployment. – gshpychka Aug 15 '22 at 05:49
  • @gshpychka - "use string formatting" - do you have an example of what the would look like, or a link to some doco that describes it? – Shorn Aug 15 '22 at 07:01
  • Just insert your variable into your text the same way you would insert any text variable. – gshpychka Aug 15 '22 at 09:10

2 Answers2

4

You can do this with conventional string formatting tools, as if the log group were a regular string:

const userDataText = readFileSync(
    initScriptPath,
    'utf8'
).replaceAll(
    '${TF_APP_LOG_GROUP}',
    logGroup.logGroupName
);

What this will do behind the scenes is replace all occurences of ${TF_APP_LOG_GROUP} in the text string with a token (a special string that looks something like ${TOKEN[LogGroup.Name.1234]}), and CloudFormation will in turn replace it with the actual value during deployment.

For reference: https://docs.aws.amazon.com/cdk/v2/guide/tokens.html

gshpychka
  • 8,523
  • 1
  • 11
  • 31
  • So easy. I just figured it would be tricky - started googling and finding all these weird cloudformation configurations instead of just trying the obvious thing. Thanks! – Shorn Aug 16 '22 at 00:43
-1

This was all incorrect. Leaving it for posterity purposes, but not valid as it was the wrong direction

Wherever you are synthing your template - be that in a SimpleSynth Pipelines step or in a CodeBuild if using a standard CodePipeline/CodeBuild, you can include context variables in the synth.

The command cdk deploy can be followed with any context variables you want: cdk deploy -c VariableName=value - if your bash script is returning to the shell the answers, you can store them as shell variables and use them in the cdk deploy.

you can then reference these variables within the actual stacks with const bucket_name = this.node.tryGetContext('VariableName');

See this documentation or this SO post for more information.

lynkfox
  • 2,003
  • 1
  • 8
  • 16
  • The question is about CloudFormation-generated strings - in this case, the name of a Log group - not user-provided deploy-time parameters. – gshpychka Aug 15 '22 at 13:29
  • Ooops. You are correct. I have the post entirely backwards. I was coming from the direction that the userData script was *getting* the data not using it. My fault for answering questions before being fully awake! – lynkfox Aug 15 '22 at 13:41