class AWSDSL::CfnBuilder
Public Class Methods
build(stack)
click to toggle source
# File lib/awsdsl/cfn_builder.rb, line 13 def self.build(stack) CfnBuilder.new(stack).build end
new(stack)
click to toggle source
# File lib/awsdsl/cfn_builder.rb, line 9 def initialize(stack) @stack = stack end
Public Instance Methods
build()
click to toggle source
# File lib/awsdsl/cfn_builder.rb, line 17 def build @t = CfnDsl::CloudFormationTemplate.new stack = @stack @t.declare do Description stack.description end AWS.memoize do build_vpcs build_elasticaches build_buckets build_roles end @t end
build_buckets()
click to toggle source
# File lib/awsdsl/cfn_builder.rb, line 374 def build_buckets stack = @stack stack.buckets.each do |bucket| @t.declare do Bucket "#{bucket.name.capitalize}Bucket" do BucketName bucket.bucket_name if bucket.bucket_name AccessControl bucket.access_control if bucket.access_control end end end end
build_elasticaches()
click to toggle source
# File lib/awsdsl/cfn_builder.rb, line 231 def build_elasticaches default_ports = { 'redis' => 6379, 'memcached' => 11211 } stack = @stack stack.elasticaches.each do |cache| # Default to Redis, also set default port if unset. engine ||= 'redis' port ||= default_ports[engine] num_nodes ||= 1 cache_vpc = resolve_vpc(cache.vpc) cache_name = "#{cache.name.capitalize}Cache" # SG @t.declare do EC2_SecurityGroup "#{cache_name}SG" do GroupDescription "#{cache.name.capitalize} Cache Security Group" VpcId cache_vpc cache.allows.each do |rule| SecurityGroupIngress IpProtocol: 'tcp', FromPort: port, ToPort: port, SourceSecurityGroupId: Ref("#{rule[:role].capitalize}SG") end end end # ElastiCacheSubnetGroup cache_subnets = resolve_subnets(cache.vpc, cache.subnets) @t.declare do ElastiCache_SubnetGroup "#{cache_name}SubnetGroup" do Description "SubnetGroup for #{cache_name}" SubnetIds cache_subnets end end # CacheCluster @t.declare do CacheCluster cache_name do CacheNodeType cache.node_type NumCacheNodes num_nodes Engine engine Port port CacheSubnetGroupName Ref("#{cache_name}SubnetGroup") VpcSecurityGroupIds [FnGetAtt("#{cache_name}SG", 'GroupId')] end end # Add additional policy to each Role that can access this Cache # This will allow said Role to discover the Cache nodes cache.allows.each do |rule| role = stack.roles.find { |r| r.name = rule[:role] } role.policy_statement effect: 'Allow', action: 'elasticache:Describe*', resource: '*' end end end
build_roles()
click to toggle source
# File lib/awsdsl/cfn_builder.rb, line 32 def build_roles stack = @stack stack.roles.each do |role| role_name = role.name.capitalize role_vpc = resolve_vpc(role.vpc) # Create ELBs and appropriate security groups etc. role.load_balancers.each do |lb| listeners = lb.listeners.map { |l| format_listener(l) } health_check = health_check_defaults(lb.health_check) if lb.health_check lb_name = "#{role_name}#{lb.name.capitalize}ELB" subnets = lb.subnets.empty? ? role.subnets : lb.subnets lb_subnets = resolve_subnets(role.vpc, subnets) # ELB @t.declare do LoadBalancer lb_name do Listeners listeners ConnectionSettings lb.connection_settings if lb.connection_settings HealthCheck health_check if health_check CrossZone true Subnets lb_subnets SecurityGroups [Ref("#{lb_name}SG")] end end # ELB SG @t.declare do EC2_SecurityGroup "#{lb_name}SG" do GroupDescription "#{lb.name.capitalize} ELB Security Group" VpcId role_vpc listeners.map { |l| l[:LoadBalancerPort] }.each do |port| SecurityGroupIngress IpProtocol: 'tcp', FromPort: port, ToPort: port, CidrIp: '0.0.0.0/0' end lb.allows.each do |rule| ports = rule[:ports].is_a?(Array) ? rule[:ports] : [rule[:ports]] ports.each do |port| SecurityGroupIngress IpProtocol: rule[:proto] || 'tcp', FromPort: port, ToPort: port, SourceSecurityGroupId: Ref("#{rule[:role].capitalize}SG") end end end end # ELB DNS records lb.dns_records.each do |record| zone = record[:zone] || get_zone_for_record(record[:name]).id record_name = record[:name].split('.').map(&:capitalize).join @t.declare do RecordSet record_name do HostedZoneId zone Name record Type 'A' AliasTarget HostedZoneId: FnGetAtt(lb_name, 'CanonicalHostedZoneNameID'), DNSName: FnGetAtt(lb_name, 'CanonicalHostedZoneName') end end end end # end load_balancers # IAM Role @t.declare do IAM_Role "#{role_name}Role" do AssumeRolePolicyDocument Statement: [{ Effect: 'Allow', Principal: { Service: ['ec2.amazonaws.com'] }, Action: ['sts:AssumeRole'] }] Path '/' end end # Policy statements = role.policy_statements.map { |s| format_policy_statement(s) } if statements.count > 1 policy_name = "#{role_name}Policy" @t.declare do Policy policy_name do PolicyName policy_name PolicyDocument Statement: statements Roles [Ref("#{role_name}Role")] end end end # Instance Profile @t.declare do InstanceProfile "#{role_name}InstanceProfile" do Path '/' Roles [Ref("#{role_name}Role")] end end # Autoscaling Group update_policy = update_policy_defaults(role) lb_names = role.load_balancers.map { |lb| "#{role_name}#{lb.name.capitalize}ELB" } subnets = resolve_subnets(role.vpc, role.subnets) min = role.min_size || 0 max = role.max_size || 1 tgt = role.tgt_size || 1 @t.declare do AutoScalingGroup "#{role_name}ASG" do LaunchConfigurationName Ref("#{role.name.capitalize}LaunchConfig") UpdatePolicy 'AutoScalingRollingUpdate', update_policy if update_policy MinSize min MaxSize max DesiredCapacity tgt LoadBalancerNames lb_names.map { |name| Ref(name) } VPCZoneIdentifier subnets AvailabiltityZones FnGetAZs('') end end # Launch Configuration security_groups = resolve_security_groups(role.vpc, role.security_groups) block_devices = format_block_devices(role.block_devices) vars = (stack.vars || {}).merge(role.vars || {}) @t.declare do LaunchConfiguration "#{role_name}LaunchConfig" do ImageId role.ami # TODO(jpg): Should support NAT at some stage even though it's nasty on AWS AssociatePublicIpAddress true InstanceType role.instance_type # TODO(jpg): Need to resolve this to IDs or Refs as necessary SecurityGroups [Ref("#{role_name}SG")] + security_groups IamInstanceProfile Ref("#{role_name}InstanceProfile") BlockDeviceMappings block_devices KeyName role.key_pair if role.key_pair vars.each do |k,v| Metadata k.to_s, v end end end lb_ingress_rules = role.load_balancers.map do |lb| lb.listeners.map do |l| h = listener_defaults(l) h[:sg] = "#{role_name}#{lb.name.capitalize}ELBSG" h end end.flatten # Security Group @t.declare do EC2_SecurityGroup "#{role_name}SG" do GroupDescription "#{role_name} Security Group" VpcId role_vpc # TODO(jpg): Better way of offering up defaults SecurityGroupIngress IpProtocol: 'tcp', FromPort: 22, ToPort: 22, CidrIp: '0.0.0.0/0' # Access from configured load_balancers lb_ingress_rules.each do |rule| SecurityGroupIngress IpProtocol: 'tcp', FromPort: rule[:instance_port], ToPort: rule[:instance_port], SourceSecurityGroupId: Ref(rule[:sg]) end # Access from other roles # TODO(jpg): catch undefined roles before template generation role.allows.select { |r| r[:role] != role.name }.each do |rule| ports = rule[:ports].is_a?(Array) ? rule[:ports] : [rule[:ports]] ports.each do |port| SecurityGroupIngress IpProtocol: rule[:proto] || 'tcp', FromPort: port, ToPort: port, SourceSecurityGroupId: Ref("#{rule[:role].capitalize}SG") end end end # Intracluster communication role.allows.select { |r| r[:role] == role.name }.each do |rule| ports = rule[:ports].is_a?(Array) ? rule[:ports] : [rule[:ports]] proto = rule[:proto] || 'tcp' ports.each do |port| EC2_SecurityGroupIngress "#{role_name}SG#{proto.upcase}#{port}" do GroupId Ref("#{role_name}SG") IpProtocol proto FromPort port ToPort port SourceSecurityGroupId Ref("#{role_name}SG") end end end end end end
build_vpcs()
click to toggle source
# File lib/awsdsl/cfn_builder.rb, line 291 def build_vpcs stack = @stack stack.vpcs.each do |vpc| igw = vpc.igw || true dns = vpc.dns || true cidr = vpc.cidr || '10.0.0.0/16' subnet_bits = vpc.subnet_bits || 24 dns_hostnames = vpc.dns_hostnames || true cidr = NetAddr::CIDR.create(cidr) subnets = cidr.subnet(Bits: subnet_bits).to_enum # VPC vpc_name = "#{vpc.name.capitalize}VPC" @t.declare do VPC vpc_name do CidrBlock cidr EnableDnsSupport dns EnableDnsHostnames dns_hostnames end end if igw # Don't create internet facing stuff if igw is not enabled igw_name = "#{vpc.name.capitalize}IGW" # IGW @t.declare do InternetGateway igw_name end # Attach to VPC @t.declare do VPCGatewayAttachment "#{vpc.name.capitalize}GWAttachment" do VpcId Ref(vpc_name) InternetGatewayId Ref(igw_name) end end # RouteTable rt_name = "#{vpc.name.capitalize}RouteTable" @t.declare do RouteTable rt_name do VpcId Ref(vpc_name) end end # Default route for RouteTable @t.declare do Route "#{vpc.name.capitalize}DefaultRoute" do RouteTableId Ref(rt_name) DestinationCidrBlock '0.0.0.0/0' GatewayId Ref(igw_name) # TODO(jpg): DependsOn rt_name end end end vpc.subnets.each do |subnet| subnet_igw = subnet.igw || igw azs = subnet.azs || fetch_availability_zones(vpc.region) subnet_name = "#{vpc.name.capitalize}#{subnet.name.capitalize}Subnet" azs.each do |az| subnet_name_az = "#{subnet_name}#{az.capitalize}" @t.declare do Subnet subnet_name_az do AvailabilityZone "#{vpc.region}#{az}" CidrBlock subnets.next VpcId Ref(vpc_name) end if subnet_igw SubnetRouteTableAssociation "#{subnet_name_az}DefaultRTAssoc" do SubnetId Ref(subnet_name_az) RouteTableId Ref(rt_name) # TODO(jpg): DependsOn rt_name end end end end end end end