diff --git a/macro/Makefile b/macro/Makefile new file mode 100644 index 000000000..893872025 --- /dev/null +++ b/macro/Makefile @@ -0,0 +1,10 @@ +BUCKET=macro-template-default-831650818513-us-east-1 + +macro: + pip install -r source/requirements.txt -t source + aws cloudformation package --template-file template.yaml --s3-bucket $(BUCKET) --output-template-file template.output + aws cloudformation deploy --template-file template.output --stack-name TroposphereMacro --capabilities CAPABILITY_IAM + rm -rf template.output + +sample: + aws cloudformation deploy --stack-name template-macro-example --template-file example.py --parameter-overrides InstanceName=NewName \ No newline at end of file diff --git a/macro/README.md b/macro/README.md new file mode 100644 index 000000000..23d91f077 --- /dev/null +++ b/macro/README.md @@ -0,0 +1,76 @@ +# Troposphere Macro + +The `Troposphere` macro adds the ability to create CloudFormation resources from troposphere stacks. + +# How to install and use the Macro in your AWS account + +## Deploying + +1. You will need an S3 bucket to store the CloudFormation artifacts: + * If you don't have one already, create one with `aws s3 mb s3://` + +2. Install all python requirements + + ```shell + pip install -r source/requirements.txt -t source + ``` + +3. Package the CloudFormation template. The provided template uses [the AWS Serverless Application Model](https://aws.amazon.com/about-aws/whats-new/2016/11/introducing-the-aws-serverless-application-model/) so must be transformed before you can deploy it. + + ```shell + aws cloudformation package \ + --template-file template.yaml \ + --s3-bucket \ + --output-template-file template.output + ``` + +4. Deploy the packaged CloudFormation template to a CloudFormation stack: + + ```shell + aws cloudformation deploy \ + --stack-name troposphere-macro \ + --template-file template.output \ + --capabilities CAPABILITY_IAM + ``` + +5. To test out the macro's capabilities, try launching the provided example template: + + ```shell + aws cloudformation deploy \ + --stack-name template-macro-example \ + --template-file example.py + ``` + +## Usage + +Just add your resources to macro_template object, the template object is created by macro itself. +You can provide your troposphere code in Troposphere tag. + +``` +Transform: [Troposhere] +Description: Example Macro Troposhere. + +Parameters: + InstanceName: + Type: String + Default: "MyInstance" + ImageId: + Type: String + Default: "ami-951945d0" + InstanceType: + Type: String + Default: "t1.micro" + +Troposhere: | + from troposphere import Ref + import troposphere.ec2 as ec2 + + instance = ec2.Instance('MyInstance') + + instance.ImageId = Ref('ImageId') + instance.InstanceType = Ref('InstanceType') + instance.Tags = ec2.Tags(Name = Ref('InstanceName')) + + macro_template.add_resource(instance) +``` + diff --git a/macro/example.py b/macro/example.py new file mode 100644 index 000000000..0baff7d97 --- /dev/null +++ b/macro/example.py @@ -0,0 +1,30 @@ +Transform: [Troposhere] +Description: Example Macro Troposhere. + +Parameters: + InstanceName: + Type: String + Default: "MyInstance" + ImageId: + Type: String + Default: "ami-eb0c7791" + InstanceType: + Type: String + Default: "t1.micro" + +Troposhere: | + from troposphere import Ref + import troposphere.ec2 as ec2 + + list_instances = ["One", "Two", "Three", "Four"] + + instance_name = macro_parameters["InstanceName"] + + for el in list_instances: + instance = ec2.Instance('MyInstance{}'.format(el)) + + instance.ImageId = Ref('ImageId') + instance.InstanceType = Ref('InstanceType') + instance.Tags = ec2.Tags(Name = instance_name + el) + + macro_template.add_resource(instance) \ No newline at end of file diff --git a/macro/source/macro.py b/macro/source/macro.py new file mode 100755 index 000000000..b55a464b0 --- /dev/null +++ b/macro/source/macro.py @@ -0,0 +1,33 @@ +from troposphere.template_generator import TemplateGenerator +import traceback + +def handle_template(event): + template = event['fragment'] + macro_parameters = event['templateParameterValues'] + + troposphere_code = template["Troposhere"] + del template["Troposhere"] + + macro_template = TemplateGenerator(template) + exec(troposphere_code) + + return macro_template.to_dict() + +def handler(event, context): + request_id = event['requestId'] + + macro_response = { + 'status': 'success', + 'requestId': request_id + } + + try: + macro_response['fragment'] = handle_template(event) + except Exception as e: + traceback.print_exc() + macro_response['status'] = 'failure' + macro_response['errorMessage'] = str(e) + + return macro_response + + diff --git a/macro/source/requirements.txt b/macro/source/requirements.txt new file mode 100644 index 000000000..2adba948d --- /dev/null +++ b/macro/source/requirements.txt @@ -0,0 +1 @@ +troposphere[policy] \ No newline at end of file diff --git a/macro/template.yaml b/macro/template.yaml new file mode 100644 index 000000000..4ea01a8ae --- /dev/null +++ b/macro/template.yaml @@ -0,0 +1,40 @@ +Transform: AWS::Serverless-2016-10-31 + +Resources: + TransformExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'logs:*' + Resource: 'arn:aws:logs:*:*:*' + + MacroFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.6 + CodeUri: source/ + Handler: macro.handler + Role: !GetAtt TransformExecutionRole.Arn + Timeout: 300 + + Macro: + Type: AWS::CloudFormation::Macro + Properties: + Name: Troposhere + FunctionName: !GetAtt MacroFunction.Arn