describe 'Kumogata::Client#convert' do it 'convert Ruby template to JSON template' do template = <<-EOS Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS json_template = run_client(:convert, :template => template) expect(json_template).to eq((<<-EOS).chomp) { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS end it 'convert Ruby template to YAML template' do template = <<-EOS Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS json_template = run_client(:convert, :template => template, :options => {:output_format => :yaml}) expect(json_template).to eq((<<-EOS)) --- Resources: myEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-XXXXXXXX InstanceType: t1.micro Outputs: AZ: Value: Fn::GetAtt: - myEC2Instance - AvailabilityZone EOS end it 'convert YAML template to Ruby template' do template = <<-EOS --- Resources: myEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-XXXXXXXX InstanceType: t1.micro Outputs: AZ: Value: Fn::GetAtt: - myEC2Instance - AvailabilityZone EOS ruby_template = run_client(:convert, :template => template, :template_ext => '.yml', :options => {:output_format => :ruby}) expect(ruby_template).to eq((<<-EOS).chomp) Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS end it 'convert Ruby template to JavaScript template' do template = <<-EOS Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS js_template = run_client(:convert, :template => template, :options => {:output_format => :js}) expect(js_template).to eq <<-EOS.strip ({ "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } }) EOS end it 'convert JavaScript template to Ruby template' do template = <<-EOS function fetch_ami() { return "ami-XXXXXXXX"; } ({ Resources: { /* comment */ myEC2Instance: { Type: "AWS::EC2::Instance", Properties: { ImageId: fetch_ami(), InstanceType: "t1.micro" } } }, Outputs: { AZ: { /* comment */ Value: { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } }) EOS ruby_template = run_client(:convert, :template => template, :template_ext => '.js', :options => {:output_format => :ruby}) expect(ruby_template).to eq((<<-EOS).chomp) Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS end it 'convert Ruby template to JSON5 template' do template = <<-EOS Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS js_template = run_client(:convert, :template => template, :options => {:output_format => :json5}) expect(js_template).to eq <<-EOS.strip { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS end it 'convert JSON5 template to Ruby template' do template = <<-EOS { Resources: { /* comment */ myEC2Instance: { Type: "AWS::EC2::Instance", Properties: { ImageId: "ami-XXXXXXXX", InstanceType: "t1.micro" } } }, Outputs: { AZ: { /* comment */ Value: { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS ruby_template = run_client(:convert, :template => template, :template_ext => '.json5', :options => {:output_format => :ruby}) expect(ruby_template).to eq((<<-EOS).chomp) Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS end it 'convert JavaScript template to Ruby template' do template = <<-EOS fetch_ami = () -> "ami-XXXXXXXX" # comment return { Resources: myEC2Instance: Type: "AWS::EC2::Instance", Properties: ImageId: fetch_ami(), InstanceType: "t1.micro" Outputs: AZ: # comment Value: "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } EOS ruby_template = run_client(:convert, :template => template, :template_ext => '.coffee', :options => {:output_format => :ruby}) expect(ruby_template).to eq((<<-EOS).chomp) Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS end it 'convert YAML template to JSON template' do template = <<-EOS --- Resources: myEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-XXXXXXXX InstanceType: t1.micro Outputs: AZ: Value: Fn::GetAtt: - myEC2Instance - AvailabilityZone EOS ruby_template = run_client(:convert, :template => template, :template_ext => '.yml', :options => {:output_format => :json}) expect(ruby_template).to eq((<<-EOS).chomp) { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS end it 'convert JSON template to Ruby template' do template = <<-EOS { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS ruby_template = run_client(:convert, :template => template, :template_ext => '.template') expect(ruby_template).to eq((<<-EOS).chomp) Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS end it 'convert JSON template to YAML template' do template = <<-EOS { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS ruby_template = run_client(:convert, :template => template, :template_ext => '.template', :options => {:output_format => :yaml}) expect(ruby_template).to eq((<<-EOS)) --- Resources: myEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-XXXXXXXX InstanceType: t1.micro Outputs: AZ: Value: Fn::GetAtt: - myEC2Instance - AvailabilityZone EOS end it 'convert Ruby template to JSON template with fn_join()' do template = <<-TEMPLATE Parameters do Password do NoEcho true Type "String" end end Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" UserData do Fn__Base64 (<<-EOS).fn_join #!/bin/bash echo START | logger /opt/aws/bin/cfn-init -s <%= Ref "AWS::StackName" %> -r myEC2Instance --region <%= Ref "AWS::Region" %> echo END | logger EOS end end Metadata do AWS__CloudFormation__Init do config do packages do yum({"httpd"=>[]}) end services do sysvinit do httpd do enabled "true" ensureRunning "true" end end end commands do any_name do command (<<-EOS).fn_join echo <%= Ref "Password" %> > /tmp/my-password EOS end end end # config end # AWS__CloudFormation__Init end # Metadata end end TEMPLATE json_template = run_client(:convert, :template => template) expect(json_template).to eq((<<-'EOS').chomp) { "Parameters": { "Password": { "NoEcho": "true", "Type": "String" } }, "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro", "UserData": { "Fn::Base64": { "Fn::Join": [ "", [ "#!/bin/bash\n", "echo START | logger\n", "/opt/aws/bin/cfn-init -s ", { "Ref": "AWS::StackName" }, " -r myEC2Instance --region ", { "Ref": "AWS::Region" }, "\n", "echo END | logger\n" ] ] } } }, "Metadata": { "AWS::CloudFormation::Init": { "config": { "packages": { "yum": { "httpd": [ ] } }, "services": { "sysvinit": { "httpd": { "enabled": "true", "ensureRunning": "true" } } }, "commands": { "any:name": { "command": { "Fn::Join": [ "", [ "echo ", { "Ref": "Password" }, " > /tmp/my-password\n" ] ] } } } } } } } } } EOS end it 'convert Ruby template to JSON template with converting user_data' do template = <<-TEMPLATE Parameters do Password do NoEcho true Type "String" end end Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" UserData (<<-EOS).undent.encode64 #!/bin/bash yum install -y httpd services start httpd EOS end end end TEMPLATE json_template = run_client(:convert, :template => template) # UserData: IyEvYmluL2Jhc2gKeXVtIGluc3RhbGwgLXkgaHR0cGQKc2VydmljZXMgc3RhcnQgaHR0cGQK # => #!/bin/bash # yum install -y httpd # services start httpd expect(json_template).to eq((<<-EOS).chomp) { "Parameters": { "Password": { "NoEcho": "true", "Type": "String" } }, "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro", "UserData": "IyEvYmluL2Jhc2gKeXVtIGluc3RhbGwgLXkgaHR0cGQKc2VydmljZXMgc3RhcnQgaHR0cGQK" } } } } EOS end it 'convert Ruby template to JSON template with block args' do template = <<-'TEMPLATE' Parameters do Password do NoEcho true Type "String" end end Resources do myEC2Instance do |resource_name| Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" UserData do Fn__Base64 (<<-EOS).fn_join #!/bin/bash echo START | logger /opt/aws/bin/cfn-init -s <%= Ref "AWS::StackName" %> -r #{resource_name} --region <%= Ref "AWS::Region" %> echo END | logger EOS end end Metadata do AWS__CloudFormation__Init do config do packages do yum({"httpd"=>[]}) end services do sysvinit do httpd do enabled "true" ensureRunning "true" end end end commands do any_name do command (<<-EOS).fn_join echo <%= Ref "Password" %> > /tmp/my-password EOS end end end # config end # AWS__CloudFormation__Init end # Metadata end end Outputs do WebsiteURL do Value (<<-EOS).fn_join http://<%= Fn__GetAtt "myEC2Instance", "PublicDnsName" %> EOS end Base64Str do Value (<<-EOS).fn_join <%= Fn__Base64 "AWS CloudFormation" %> EOS end MappedValue do Value (<<-EOS).fn_join <%= Fn__FindInMap "RegionMap", _{ Ref "AWS::Region" }, 32 %> EOS end AZ do Value (<<-EOS).fn_join <%= Fn__GetAZs "us-east-1" %> EOS end ConditionalValue do Value (<<-EOS).fn_join <%= Fn__If ["Tokyo", "ap-northeast-1", _{ Ref "AWS::Region" }] %> EOS end SelectedValue do Value (<<-EOS).fn_join <%= Fn__Select [1, _ { Fn__GetAZs "ap-northeast-1" }] %> EOS end end TEMPLATE json_template = run_client(:convert, :template => template) expect(json_template).to eq((<<-'EOS').chomp) { "Parameters": { "Password": { "NoEcho": "true", "Type": "String" } }, "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro", "UserData": { "Fn::Base64": { "Fn::Join": [ "", [ "#!/bin/bash\n", "echo START | logger\n", "/opt/aws/bin/cfn-init -s ", { "Ref": "AWS::StackName" }, " -r myEC2Instance --region ", { "Ref": "AWS::Region" }, "\n", "echo END | logger\n" ] ] } } }, "Metadata": { "AWS::CloudFormation::Init": { "config": { "packages": { "yum": { "httpd": [ ] } }, "services": { "sysvinit": { "httpd": { "enabled": "true", "ensureRunning": "true" } } }, "commands": { "any:name": { "command": { "Fn::Join": [ "", [ "echo ", { "Ref": "Password" }, " > /tmp/my-password\n" ] ] } } } } } } } }, "Outputs": { "WebsiteURL": { "Value": { "Fn::Join": [ "", [ "http://", { "Fn::GetAtt": [ "myEC2Instance", "PublicDnsName" ] }, "\n" ] ] } }, "Base64Str": { "Value": { "Fn::Join": [ "", [ { "Fn::Base64": "AWS CloudFormation" }, "\n" ] ] } }, "MappedValue": { "Value": { "Fn::Join": [ "", [ { "Fn::FindInMap": [ "RegionMap", { "Ref": "AWS::Region" }, "32" ] }, "\n" ] ] } }, "AZ": { "Value": { "Fn::Join": [ "", [ { "Fn::GetAZs": "us-east-1" }, "\n" ] ] } }, "ConditionalValue": { "Value": { "Fn::Join": [ "", [ { "Fn::If": [ "Tokyo", "ap-northeast-1", { "Ref": "AWS::Region" } ] }, "\n" ] ] } }, "SelectedValue": { "Value": { "Fn::Join": [ "", [ { "Fn::Select": [ 1, { "Fn::GetAZs": "ap-northeast-1" } ] }, "\n" ] ] } } } } EOS end it 'convert splitted Ruby template to JSON template' do json_template = nil part_of_template = <<-EOS myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end EOS tempfile(part_of_template, '.rb') do |f| template = <<-EOS Resources do _include #{f.path.inspect} end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS json_template = run_client(:convert, :template => template) end expect(json_template).to eq((<<-EOS).chomp) { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS end it 'convert splitted Ruby template to JSON template with args' do json_template = nil part_of_template = <<-EOS myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId args[:ami_id] InstanceType "t1.micro" end end EOS tempfile(part_of_template, '.rb') do |f| template = <<-EOS Resources do _include #{f.path.inspect}, {:ami_id => "ami-XXXXXXXX"} end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS json_template = run_client(:convert, :template => template) end expect(json_template).to eq((<<-EOS).chomp) { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS end it 'convert Ruby template to JSON template with require' do template = <<-EOS require 'fileutils' Resources do myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS json_template = run_client(:convert, :template => template) expect(json_template).to eq((<<-EOS).chomp) { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS end it 'convert splitted Ruby template to JSON template' do json_template = nil part_of_template = <<-EOS myEC2Instance do Type "AWS::EC2::Instance" Properties do ImageId "ami-XXXXXXXX" InstanceType "t1.micro" end end EOS tempfile(part_of_template, '.rb') do |f| template = <<-EOS Resources do _include #{f.path.inspect} end Outputs do AZ do Value do Fn__GetAtt "myEC2Instance", "AvailabilityZone" end end end EOS json_template = run_client(:convert, :template => template) end expect(json_template).to eq((<<-EOS).chomp) { "Resources": { "myEC2Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-XXXXXXXX", "InstanceType": "t1.micro" } } }, "Outputs": { "AZ": { "Value": { "Fn::GetAtt": [ "myEC2Instance", "AvailabilityZone" ] } } } } EOS end let(:drupal_single_instance_template) do path = File.expand_path('../Drupal_Single_Instance.template', __FILE__) open(path) {|f| f.read } end let(:drupal_single_instance_template_rb) do path = File.expand_path('../Drupal_Single_Instance.template.rb', __FILE__) open(path) {|f| f.read } end it 'Ruby templates and JSON template should be same' do json_template = JSON.parse(drupal_single_instance_template) ruby_template = run_client(:convert, :template => drupal_single_instance_template_rb) ruby_template = JSON.parse(ruby_template) expect(ruby_template).to eq(json_template) end let(:vpc_knowhow_2014_04_template) do path = File.expand_path('../vpc-knowhow-2014-04.template', __FILE__) open(path) {|f| f.read } end it 'convert JSON template to Ruby template (include yum key)' do ruby_template = run_client(:convert, :template => vpc_knowhow_2014_04_template, :template_ext => '.template') expect(ruby_template).to eq <<-'EOS'.strip AWSTemplateFormatVersion "2010-09-09" Description "VPC knowhow template" Parameters do KeyName do Description "Name of an existing EC2 KeyPair to enable SSH access to the instances" Type "String" MinLength 1 MaxLength 64 AllowedPattern "[-_ a-zA-Z0-9]*" ConstraintDescription "can contain only alphanumeric characters, spaces, dashes and underscores." end SSHFrom do Description "Lockdown SSH access to the bastion host (default can be accessed from anywhere)" Type "String" MinLength 9 MaxLength 18 Default "0.0.0.0/0" AllowedPattern "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" ConstraintDescription "must be a valid CIDR range of the form x.x.x.x/x." end DBInstanceType do Description "EC2 instance type for the Blue environment" Default "db.t1.micro" Type "String" end DBSnapshotName do Default "" Description "The name of a DB snapshot (optional)" Type "String" end DBAllocatedStorage do Default 5 Description "DB instance disk size" Type "Number" end DBUsername do Default "admin" Description "The database master account username" Type "String" MinLength 1 MaxLength 16 AllowedPattern "[a-zA-Z][a-zA-Z0-9]*" ConstraintDescription "must begin with a letter and contain only alphanumeric characters." end DBPassword do Description "Password of RDS master password" Type "String" NoEcho "true" MinLength 4 end DBName do Default "" Description "The name of a DB01 database" Type "String" end WebInstanceType do Description "EC2 instance type for the web server" Default "t1.micro" Type "String" end WebFleetSize do Description "Number of EC2 instances to launch for the web server" Default 2 Type "Number" MaxValue 100 MinValue 1 end HostedZone do Description "The DNS name of an existing Amazon Route 53 hosted zone" Type "String" end end Conditions do UseDBSnapshot do Fn__Not [ _{ Fn__Equals [ _{ Ref "DBSnapshotName" }, "" ] } ] end end Mappings do AWSAmazonLinuxAMI( {"us-east-1"=> {"name"=>"Virginia", "201303"=>"ami-3275ee5b", "201309"=>"ami-35792c5c", "201403"=>"ami-2f726546"}, "us-west-2"=> {"name"=>"Oregon", "201303"=>"ami-ecbe2adc", "201309"=>"ami-d03ea1e0", "201403"=>"ami-b8f69f88"}, "us-west-1"=> {"name"=>"California", "201303"=>"ami-66d1fc23", "201309"=>"ami-687b4f2d", "201403"=>"ami-84f1cfc1"}, "eu-west-1"=> {"name"=>"Ireland", "201303"=>"ami-44939930", "201309"=>"ami-149f7863", "201403"=>"ami-a921dfde"}, "ap-southeast-1"=> {"name"=>"Singapole", "201303"=>"ami-aa9ed2f8", "201309"=>"ami-14f2b946", "201403"=>"ami-787c2c2a"}, "ap-southeast-2"=> {"name"=>"Sydney", "201303"=>"ami-363eaf0c", "201309"=>"ami-a148d59b", "201403"=>"ami-0bc85031"}, "ap-northeast-1"=> {"name"=>"Tokyo", "201303"=>"ami-173fbf16", "201309"=>"ami-3561fe34", "201403"=>"ami-a1bec3a0"}, "sa-east-1"=> {"name"=>"SaoPaulo", "201303"=>"ami-dd6bb0c0", "201309"=>"ami-9f6ec982", "201403"=>"ami-89de7c94"}}) ELBLogger( {"us-east-1"=>{"AccountID"=>"127311923021"}, "us-west-2"=>{"AccountID"=>"797873946194"}, "us-west-1"=>{"AccountID"=>"027434742980"}, "eu-west-1"=>{"AccountID"=>"156460612806"}, "ap-southeast-1"=>{"AccountID"=>"114774131450"}, "ap-southeast-2"=>{"AccountID"=>"783225319266"}, "ap-northeast-1"=>{"AccountID"=>"582318560864"}, "sa-east-1"=>{"AccountID"=>"507241528517"}, "us-gov-west-1"=>{"AccountID"=>"048591011584"}}) StackConfig do VPC do CIDR "10.0.0.0/16" end FrontendSubnet1 do CIDR "10.0.0.0/24" end FrontendSubnet2 do CIDR "10.0.1.0/24" end ApplicationSubnet1 do CIDR "10.0.100.0/24" end ApplicationSubnet2 do CIDR "10.0.101.0/24" end DatastoreSubnet1 do CIDR "10.0.200.0/24" end DatastoreSubnet2 do CIDR "10.0.201.0/24" end BastionServer do InstanceType "t1.micro" end end end Resources do PowerUserRole do Type "AWS::IAM::Role" Properties do AssumeRolePolicyDocument do Statement [ _{ Effect "Allow" Principal do Service ["ec2.amazonaws.com"] end Action ["sts:AssumeRole"] } ] end Path "/" Policies [ _{ PolicyName "PowerUserPolicy" PolicyDocument do Statement [ _{ Sid "PowerUserStmt" Effect "Allow" NotAction "iam:*" Resource "*" } ] end } ] end end PowerUserProfile do Type "AWS::IAM::InstanceProfile" Properties do Path "/" Roles [ _{ Ref "PowerUserRole" } ] end end LogBucket do Type "AWS::S3::Bucket" DeletionPolicy "Retain" end LogBucketPolicy do Type "AWS::S3::BucketPolicy" Properties do Bucket do Ref "LogBucket" end PolicyDocument do Id "LogBucketPolicy" Statement [ _{ Sid "WriteAccess" Action ["s3:PutObject"] Effect "Allow" Resource do Fn__Join [ "", [ "arn:aws:s3:::", _{ Ref "LogBucket" }, "/AWSLogs/", _{ Ref "AWS::AccountId" }, "/*" ] ] end Principal do AWS do Fn__FindInMap [ "ELBLogger", _{ Ref "AWS::Region" }, "AccountID" ] end end } ] end end end VPC do Type "AWS::EC2::VPC" Properties do CidrBlock do Fn__FindInMap "StackConfig", "VPC", "CIDR" end EnableDnsSupport "true" EnableDnsHostnames "true" InstanceTenancy "default" Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Public" } ] end end InternetGateway do Type "AWS::EC2::InternetGateway" Properties do Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Public" } ] end end AttachGateway do Type "AWS::EC2::VPCGatewayAttachment" Properties do VpcId do Ref "VPC" end InternetGatewayId do Ref "InternetGateway" end end end PublicRouteTable do Type "AWS::EC2::RouteTable" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Public" } ] end end PrivateRouteTable do Type "AWS::EC2::RouteTable" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Private" } ] end end PublicRoute do Type "AWS::EC2::Route" DependsOn "AttachGateway" Properties do RouteTableId do Ref "PublicRouteTable" end DestinationCidrBlock "0.0.0.0/0" GatewayId do Ref "InternetGateway" end end end FrontendSubnet1 do Type "AWS::EC2::Subnet" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end AvailabilityZone do Fn__Select [ "0", _{ Fn__GetAZs do Ref "AWS::Region" end } ] end CidrBlock do Fn__FindInMap "StackConfig", "FrontendSubnet1", "CIDR" end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Public" } ] end end FrontendSubnet2 do Type "AWS::EC2::Subnet" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end AvailabilityZone do Fn__Select [ "1", _{ Fn__GetAZs do Ref "AWS::Region" end } ] end CidrBlock do Fn__FindInMap "StackConfig", "FrontendSubnet2", "CIDR" end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Public" } ] end end ApplicationSubnet1 do Type "AWS::EC2::Subnet" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end CidrBlock do Fn__FindInMap "StackConfig", "ApplicationSubnet1", "CIDR" end AvailabilityZone do Fn__Select [ "0", _{ Fn__GetAZs do Ref "AWS::Region" end } ] end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Public" } ] end end ApplicationSubnet2 do Type "AWS::EC2::Subnet" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end CidrBlock do Fn__FindInMap "StackConfig", "ApplicationSubnet2", "CIDR" end AvailabilityZone do Fn__Select [ "1", _{ Fn__GetAZs do Ref "AWS::Region" end } ] end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Public" } ] end end DatastoreSubnet1 do Type "AWS::EC2::Subnet" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end CidrBlock do Fn__FindInMap "StackConfig", "DatastoreSubnet1", "CIDR" end AvailabilityZone do Fn__Select [ "0", _{ Fn__GetAZs do Ref "AWS::Region" end } ] end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Private" } ] end end DatastoreSubnet2 do Type "AWS::EC2::Subnet" DependsOn "AttachGateway" Properties do VpcId do Ref "VPC" end CidrBlock do Fn__FindInMap "StackConfig", "DatastoreSubnet2", "CIDR" end AvailabilityZone do Fn__Select [ "1", _{ Fn__GetAZs do Ref "AWS::Region" end } ] end Tags [ _{ Key "Application" Value do Ref "AWS::StackId" end }, _{ Key "Network" Value "Private" } ] end end FrontendSubnet1RouteTableAssociation do Type "AWS::EC2::SubnetRouteTableAssociation" Properties do SubnetId do Ref "FrontendSubnet1" end RouteTableId do Ref "PublicRouteTable" end end end FrontendSubnet2RouteTableAssociation do Type "AWS::EC2::SubnetRouteTableAssociation" Properties do SubnetId do Ref "FrontendSubnet2" end RouteTableId do Ref "PublicRouteTable" end end end ApplicationSubnet1RouteTableAssociation do Type "AWS::EC2::SubnetRouteTableAssociation" Properties do SubnetId do Ref "ApplicationSubnet1" end RouteTableId do Ref "PublicRouteTable" end end end ApplicationSubnet2RouteTableAssociation do Type "AWS::EC2::SubnetRouteTableAssociation" Properties do SubnetId do Ref "ApplicationSubnet2" end RouteTableId do Ref "PublicRouteTable" end end end DatastoreSubnet1RouteTableAssociation do Type "AWS::EC2::SubnetRouteTableAssociation" Properties do SubnetId do Ref "DatastoreSubnet1" end RouteTableId do Ref "PrivateRouteTable" end end end DatastoreSubnet2RouteTableAssociation do Type "AWS::EC2::SubnetRouteTableAssociation" Properties do SubnetId do Ref "DatastoreSubnet2" end RouteTableId do Ref "PrivateRouteTable" end end end VPCDefaultSecurityGroup do Type "AWS::EC2::SecurityGroup" Properties do VpcId do Ref "VPC" end GroupDescription "Allow all communications in VPC" SecurityGroupIngress [ _{ IpProtocol "tcp" FromPort 0 ToPort 65535 CidrIp do Fn__FindInMap "StackConfig", "VPC", "CIDR" end }, _{ IpProtocol "udp" FromPort 0 ToPort 65535 CidrIp do Fn__FindInMap "StackConfig", "VPC", "CIDR" end }, _{ IpProtocol "icmp" FromPort "-1" ToPort "-1" CidrIp do Fn__FindInMap "StackConfig", "VPC", "CIDR" end } ] end end SSHSecurityGroup do Type "AWS::EC2::SecurityGroup" Properties do VpcId do Ref "VPC" end GroupDescription "Enable SSH access via port 22" SecurityGroupIngress [ _{ IpProtocol "tcp" FromPort 22 ToPort 22 CidrIp do Ref "SSHFrom" end } ] end end PublicWebSecurityGroup do Type "AWS::EC2::SecurityGroup" Properties do VpcId do Ref "VPC" end GroupDescription "Public Security Group with HTTP access on port 443 from the internet" SecurityGroupIngress [ _{ IpProtocol "tcp" FromPort 80 ToPort 80 CidrIp "0.0.0.0/0" }, _{ IpProtocol "tcp" FromPort 443 ToPort 443 CidrIp "0.0.0.0/0" } ] end end ApplicationSecurityGroup do Type "AWS::EC2::SecurityGroup" Properties do VpcId do Ref "VPC" end GroupDescription "Marker security group for Application server." end end MySQLSecurityGroup do Type "AWS::EC2::SecurityGroup" Properties do VpcId do Ref "VPC" end GroupDescription "Marker security group for MySQL server." end end BastionWaitHandle do Type "AWS::CloudFormation::WaitConditionHandle" end BastionWaitCondition do Type "AWS::CloudFormation::WaitCondition" DependsOn "BastionInstance" Properties do Handle do Ref "BastionWaitHandle" end Timeout 900 end end BastionInstance do Type "AWS::EC2::Instance" Properties do InstanceType do Fn__FindInMap "StackConfig", "BastionServer", "InstanceType" end KeyName do Ref "KeyName" end SubnetId do Ref "FrontendSubnet1" end ImageId do Fn__FindInMap [ "AWSAmazonLinuxAMI", _{ Ref "AWS::Region" }, "201403" ] end IamInstanceProfile do Ref "PowerUserProfile" end SecurityGroupIds [ _{ Ref "SSHSecurityGroup" }, _{ Ref "VPCDefaultSecurityGroup" } ] Tags [ _{ Key "Name" Value "Bastion" } ] UserData do Fn__Base64 do Fn__Join [ "", [ "#! /bin/bash -v\n", "yum update -y\n", "# Helper function\n", "function error_exit\n", "{\n", " /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", _{ Ref "BastionWaitHandle" }, "'\n", " exit 1\n", "}\n", "# Install packages\n", "/opt/aws/bin/cfn-init -s ", _{ Ref "AWS::StackId" }, " -r BastionInstance ", " --region ", _{ Ref "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n", "# All is well so signal success\n", "/opt/aws/bin/cfn-signal -e $? -r \"BastionInstance setup complete\" '", _{ Ref "BastionWaitHandle" }, "'\n" ] ] end end end Metadata do AWS__CloudFormation__Init do config do packages do yum( {"mysql55"=>[], "jq"=>[], "python-magic"=>[]}) end end end end end BastionInstanceEIP do Type "AWS::EC2::EIP" DependsOn "AttachGateway" Properties do Domain "vpc" InstanceId do Ref "BastionInstance" end end end BastionDNSRecord do Type "AWS::Route53::RecordSet" Properties do HostedZoneName do Fn__Join [ "", [ _{ Ref "HostedZone" }, "." ] ] end Comment "A record for the Bastion instance." Name do Fn__Join [ "", [ "bastion.", _{ Ref "HostedZone" }, "." ] ] end Type "A" TTL 300 ResourceRecords [ _{ Ref "BastionInstanceEIP" } ] end end BastionLocalDNSRecord do Type "AWS::Route53::RecordSet" Properties do HostedZoneName do Fn__Join [ "", [ _{ Ref "HostedZone" }, "." ] ] end Comment "A record for the private IP address of Bastion instance." Name do Fn__Join [ "", [ "bastion.local.", _{ Ref "HostedZone" }, "." ] ] end Type "A" TTL 300 ResourceRecords [ _{ Fn__GetAtt "BastionInstance", "PrivateIp" } ] end end DBParamGroup do Type "AWS::RDS::DBParameterGroup" Properties do Description "Default parameter group for Portnoy" Family "MySQL5.6" Parameters( {"character_set_database"=>"utf8mb4", "character_set_client"=>"utf8mb4", "character_set_connection"=>"utf8mb4", "character_set_results"=>"utf8mb4", "character_set_server"=>"utf8mb4", "skip-character-set-client-handshake"=>"TRUE"}) end end DBSubnetGroup do Type "AWS::RDS::DBSubnetGroup" Properties do DBSubnetGroupDescription "Database subnets for RDS" SubnetIds [ _{ Ref "DatastoreSubnet1" }, _{ Ref "DatastoreSubnet2" } ] end end DBInstance do Type "AWS::RDS::DBInstance" DeletionPolicy "Snapshot" Properties do DBInstanceClass do Ref "DBInstanceType" end AllocatedStorage do Ref "DBAllocatedStorage" end Engine "MySQL" MultiAZ "true" EngineVersion "5.6.13" MasterUsername do Ref "DBUsername" end MasterUserPassword do Ref "DBPassword" end BackupRetentionPeriod 35 DBParameterGroupName do Ref "DBParamGroup" end DBSubnetGroupName do Ref "DBSubnetGroup" end DBSnapshotIdentifier do Fn__If [ "UseDBSnapshot", _{ Ref "DBSnapshotName" }, _{ Ref "AWS::NoValue" } ] end PreferredBackupWindow "19:00-19:30" PreferredMaintenanceWindow "sat:20:00-sat:20:30" VPCSecurityGroups [ _{ Ref "VPCDefaultSecurityGroup" }, _{ Ref "MySQLSecurityGroup" } ] end end DatabaseDNSRecord do Type "AWS::Route53::RecordSet" Properties do HostedZoneName do Fn__Join [ "", [ _{ Ref "HostedZone" }, "." ] ] end Comment "CNAME for the database." Name do Fn__Join [ "", [ "db.local.", _{ Ref "HostedZone" }, "." ] ] end Type "CNAME" TTL 300 ResourceRecords [ _{ Fn__GetAtt "DBInstance", "Endpoint.Address" } ] end end ApplicationFleet do Type "AWS::AutoScaling::AutoScalingGroup" UpdatePolicy do AutoScalingRollingUpdate do MaxBatchSize 1 MinInstancesInService 1 PauseTime "PT2M30S" end end Properties do AvailabilityZones [ _{ Fn__GetAtt "ApplicationSubnet1", "AvailabilityZone" }, _{ Fn__GetAtt "ApplicationSubnet2", "AvailabilityZone" } ] VPCZoneIdentifier [ _{ Ref "ApplicationSubnet1" }, _{ Ref "ApplicationSubnet2" } ] LaunchConfigurationName do Ref "ApplicationServerLaunchConfig" end MinSize do Ref "WebFleetSize" end MaxSize do Ref "WebFleetSize" end DesiredCapacity do Ref "WebFleetSize" end LoadBalancerNames [ _{ Ref "ElasticLoadBalancer" } ] Tags [ _{ Key "Name" Value "Application" PropagateAtLaunch "true" } ] end end ApplicationServerLaunchConfig do Type "AWS::AutoScaling::LaunchConfiguration" Properties do InstanceType do Ref "WebInstanceType" end KeyName do Ref "KeyName" end ImageId do Fn__FindInMap [ "AWSAmazonLinuxAMI", _{ Ref "AWS::Region" }, "201403" ] end SecurityGroups [ _{ Ref "VPCDefaultSecurityGroup" }, _{ Ref "ApplicationSecurityGroup" } ] AssociatePublicIpAddress "true" IamInstanceProfile do Ref "PowerUserProfile" end InstanceMonitoring "false" UserData do Fn__Base64 do Fn__Join [ "", [ "#! /bin/bash -v\n", "yum update -y\n", "# Install packages\n", "/opt/aws/bin/cfn-init -s ", _{ Ref "AWS::StackId" }, " -r ApplicationServerLaunchConfig ", " --region ", _{ Ref "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n" ] ] end end end Metadata do AWS__CloudFormation__Init do config do packages do yum( {"httpd"=>[], "mysql55"=>[]}) end files do _path("/var/www/html/index.html") do content "HelloHello, world!" mode "000644" owner "apache" group "apache" end end services do sysvinit do httpd do enabled "true" ensureRunning "true" end end end end end end end ElasticLoadBalancer do Type "AWS::ElasticLoadBalancing::LoadBalancer" DependsOn "AttachGateway" Properties do Subnets [ _{ Ref "FrontendSubnet1" }, _{ Ref "FrontendSubnet2" } ] Listeners [ _{ LoadBalancerPort 80 InstancePort 80 Protocol "HTTP" } ] HealthCheck do Target "HTTP:80/index.html" HealthyThreshold 2 UnhealthyThreshold 2 Interval 6 Timeout 5 end SecurityGroups [ _{ Ref "PublicWebSecurityGroup" } ] end end LoadBalancerDNSRecord do Type "AWS::Route53::RecordSetGroup" Properties do HostedZoneName do Fn__Join [ "", [ _{ Ref "HostedZone" }, "." ] ] end Comment "Zone apex alias targeted to LoadBalancer." RecordSets [ _{ Name do Fn__Join [ "", [ _{ Ref "HostedZone" }, "." ] ] end Type "A" AliasTarget do HostedZoneId do Fn__GetAtt "ElasticLoadBalancer", "CanonicalHostedZoneNameID" end DNSName do Fn__GetAtt "ElasticLoadBalancer", "CanonicalHostedZoneName" end end } ] end end end Outputs do JdbcConnectionString do Value do Fn__Join [ "", [ "jdbc:mysql://", _{ Ref "DatabaseDNSRecord" }, ":", _{ Fn__GetAtt "DBInstance", "Endpoint.Port" }, "/", _{ Ref "DBName" } ] ] end Description "-" end SSHToBackendServer do Value do Fn__Join [ "", [ "ssh -i /path/to/", _{ Ref "KeyName" }, ".pem", " -oProxyCommand='ssh -i /path/to/", _{ Ref "KeyName" }, ".pem -W %h:%p ec2-user@", _{ Ref "BastionDNSRecord" }, "'", " ec2-user@" ] ] end Description "SSH command to connect to the backend servers" end end EOS end end