cfn-pyplates and cfn-init magic

For some, this information may be old hat, but for a while I’ve been looking for a good way to run cfn-init within UserData during an instance launch. I had a catch 22 scenario happening when building UserData with references to AWS::StackId because I’m using cfn-pyplates to build the CloudFormation json. Until recently, I couldn’t find a good way to use resource references while base64 encoding a build script in LaunchConfiguration…

The solution turned out to be a combination of str and join to build the head of the script while reading in the remainder with an open call:

interpreter = str('#!/bin/bash\n')
default_region = join('', str('export AWS_DEFAULT_REGION='), ref('AWS::Region'), str('\n'))
cfn_init_cmd = join('', str('/opt/aws/bin/cfn-init -s '), ref('AWS::StackId'), str(' -r WebLaunchConfiguration --region '), ref('AWS::Region'), str('\n'))
build_script = open('./').read()
user_data_script = join('', interpreter, default_region, cfn_init_cmd, build_script)

and in my LaunchConfiguration resource:

  Resource('WebLaunchConfiguration', 'AWS::AutoScaling::LaunchConfiguration',
      'ImageId': ref('InstanceAMI'),
      'InstanceType': ref('EC2InstanceType'),
      'UserData': base64(user_data_script),
      'SecurityGroups': [ref('WebSG'), ref('AdminSG')],
      'KeyName': 'default',
      'IamInstanceProfile': 'WebInstanceRole',
            'config': {
              'files': {
                '/etc/': {
                  'content': join('',
                    "export DBName=", ref('DBName'), "\n",
                    "export DBUsername=", ref('DBUsername'), "\n",
                    "export DBPassword=", ref('DBPassword'), "\n",
                    "export DBAddress=", get_att('RDSInstance', 'Endpoint.Address'), "\n",
                    "export DBPort=", get_att('RDSInstance', 'Endpoint.Port'), "\n",
                    "export DBPrefix=", ref('DBPrefix'), "\n",
                'mode': '000400',
                'owner': 'root',
                'group': 'root'

Boom! Now I could source /etc/ in UserData and have a secure way to pass all my parameters to the instances…