Templates ========= Sceptre uses CloudFormation Templates to launch AWS Stacks. Templates must be stored in a directory named ``templates/``, in the same directory as the ``config/`` directory: .. code-block:: text . ├── config │   └── dev │   ├── config.yaml │   └── vpc.yaml └── templates └── vpc.py CloudFormation -------------- Templates with ``.json`` or ``.yaml`` extensions are treated as CloudFormation Templates. They are read in and used without modification. Jinja ----- Templates with ``.j2`` extensions are treated as Jinja2 Templates. These are rendered and should create a raw JSON or YAML CloudFormation Template. Sceptre User Data is accessible within Templates as ``sceptre_user_data``. For example ``{{ sceptre_user_data.some_variable }}``. ``sceptre_user_data`` accesses the ``sceptre_user_data`` key in the Stack Config file. Example ~~~~~~~ While rendering templates with Jinja2, some characters aren't supported by AWS. A workaround for this is to use Jinja to replace `-`, `.` and `_` with other characters to ensure that we produce a valid template. With this we had some plasticity to the template and it becomes easier to add/remove entries on a particular stack. With this templating some problems will arise, such empty strings as parameters. In the following example you can find a work around for this issues. This represents a stack to deploy route53 records, it's only showing CNAME and ALIAS records to not get too large. Stack: .. code-block:: yaml template path: templates/dns-extras.j2 type: file dependencies: - prod/route53/domain-zone.yaml parameters: DomainName: "example.com"!stack_output prod/route53/example-com-zone.yaml::FullDomainName Zone: !stack_output prod/route53/example-com-zone.yaml::HostedZoneID sceptre_user_data: CNAMErecords: - record: "example01" address: "example01.otherdomain.com." ttl: 600 - record: "example02" address: "example01.otherdomain.com." ttl: 600 ALIASrecords: - record: "" hostedzoneid: "ZYOURZONEIDDDD" dnsnamealias: "ELB07-Sites-000000000.us-east-1.elb.amazonaws.com" ttl: 600 - record: "www" hostedzoneid: "Z32O12XQLNTSW2" dnsnamealias: "ELB07-Sit es-000000000.us-east-1.elb.amazonaws.com" ttl: 600 Template `dns-extras.j2`: .. code-block:: yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Add Route53 - CNAME and ALIAS records' Parameters: DomainName: Type: String Default: example.net Zone: Type: String {% if sceptre_user_data.CNAMErecords is defined %}{% for rule in sceptre_user_data.CNAMErecords %} {{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}cnamerecord: Type: String Default: "{{rule.record}}"{% endfor %}{% endif %} {% if sceptre_user_data.ALIASrecords is defined %}{% for rule in sceptre_user_data.ALIASrecords %} {{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}aliasrecord: Type: String Default: "{{rule.record}}" {{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}aliasvalue: Type: String Default: "{{rule.dnsnamealias}}" {{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}aliaszoneid: Type: String Default: "{{rule.hostedzoneid}}" {% endfor %}{% endif %} Resources: {% if sceptre_user_data.CNAMErecords is defined %}{% for rule in sceptre_user_data.CNAMErecords %}add{{ rule.record | replace("-","d")|replace("_","s")|replace('.',"p")}}cnamerecord: {% set record = rule.record %} Type: 'AWS::Route53::RecordSet' Properties: Name: !Join - "" - [ !Sub '${ {{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}cnamerecord }','.', !Ref DomainName, '.'] HostedZoneId: !Sub '${Zone}' Type: CNAME TTL: {{ rule.ttl }} ResourceRecords: - {{ rule.address }} {% endfor %}{% endif %} {% if sceptre_user_data.ALIASrecords is defined %}{% for rule in sceptre_user_data.ALIASrecords %} {% set entry = rule.record |replace("-","d")|replace("_","s")|replace('.',"p")%}add{{entry}}aliasrecord: Type: AWS::Route53::RecordSet Properties: {% if rule.record == "" %} Name: !Ref DomainName {% else %} Name: !Join - "" - [ !Sub '${ {{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}aliasrecord }','.', !Ref DomainName, '.'] {% endif %} Type: A HostedZoneId: !Ref Zone AliasTarget: DNSName: "{{ rule.dnsnamealias }}" HostedZoneId: "{{ rule.hostedzoneid }}" {% endfor %}{% endif %} Outputs: {% if sceptre_user_data.CNAMErecords is defined %}{% for rule in sceptre_user_data.CNAMErecords %}add{{ rule.record | replace("-","d")|replace("_","s")|replace('.',"p")}}cnamerecord: Value: !Ref 'add{{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}cnamerecord' Description: '{{ rule.address }}' {% endfor %}{% endif %} {% if sceptre_user_data.ALIASrecords is defined %}{% for rule in sceptre_user_data.ALIASrecords %}add{{ rule.record | replace("-","d")|replace("_","s")|replace('.',"p")}}aliasrecord: Value: !Ref 'add{{ rule.record |replace("-","d")|replace("_","s")|replace('.',"p")}}aliasrecord' Description: '{{ rule.dnsnamealias }}' {% endfor %}{% endif %} StackName: Description: 'Stack name.' Value: !Sub '${AWS::StackName}' Export: Name: !Sub '${AWS::StackName}' Python ------ Templates with a ``.py`` extension are treated as Python Templates. They should implement a function named ``sceptre_handler(sceptre_user_data)`` which returns the CloudFormation Template as a ``string``. Sceptre User Data is passed to this function as an argument. If Sceptre User Data is not defined in the Stack Config file, Sceptre passes an empty ``dict``. Example ~~~~~~~ Troposphere ^^^^^^^^^^^ This example is using `troposphere`_ to generate CloudFormation Template as a `json` string. .. code-block:: python from troposphere import Template from troposphere.ec2 import VPC def vpc(sceptre_user_data): """AWS VPC CloudFormationTemplate""" template = Template() template.add_resource(VPC( "VirtualPrivateCloud", CidrBlock=sceptre_user_data["cidr_block"] )) return template.to_yaml() def sceptre_handler(sceptre_user_data): return vpc(sceptre_user_data) .. note:: To generate templates using Troposphere you must install the Troposphere library by running ``pip install sceptre[troposphere]`` .. _troposphere: https://github.com/cloudtools/troposphere/ AWS CDK ^^^^^^^ This example generates a cloudformation template from `AWS CDK`_ code. .. code-block:: yaml sceptre_user_data: BucketName: my-bucket .. .. code-block:: python import yaml from aws_cdk import ( aws_s3 as s3, core as cdk ) class S3CdkStack(cdk.Stack): def __init__(self, scope: cdk.Stack, construct_id: str, sceptre_user_data, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) bucket_name = sceptre_user_data['BucketName'] bucket = s3.Bucket(self, "S3Bucket", bucket_name=bucket_name) def sceptre_handler(sceptre_user_data): app = cdk.App() S3CdkStack(app, "S3CdkStack", sceptre_user_data) template = app.synth().get_stack_by_name("S3CdkStack").template return yaml.safe_dump(template) .. _AWS_CDK: https://github.com/aws/aws-cdk Generate cloudformation: ``sceptre generate dev/S3CdkStack.py`` .. code-block:: yaml Resources: S3Bucket07682993: DeletionPolicy: Retain Properties: BucketName: my-bucket Type: AWS::S3::Bucket UpdateReplacePolicy: Retain ..