def extract_security_group_ids(configuration, elastigroup_config: dict, args): """ This function identifies whether a senza formatted EC2-sg (by name) is configured, if so it transforms it into a Spotinst Elastigroup EC2-sq (by id) API configuration If there's already a compute.launchSpecification.securityGroupIds config it's left unchanged """ elastigroup_config = ensure_keys(elastigroup_config, "compute", "launchSpecification") launch_spec_config = elastigroup_config["compute"]["launchSpecification"] security_group_ids = [] if "securityGroupIds" not in launch_spec_config.keys(): if "SecurityGroups" in configuration.keys(): security_groups_ref = configuration.pop("SecurityGroups") if isinstance(security_groups_ref, str): security_group_ids = resolve_security_groups( [security_groups_ref], args.region) elif isinstance(security_groups_ref, list): security_group_ids = resolve_security_groups( security_groups_ref, args.region) if len(security_group_ids) > 0: launch_spec_config["securityGroupIds"] = security_group_ids
def component_redis_cluster(definition, configuration, args, info, force): name = configuration["Name"] definition = ensure_keys(definition, "Resources") number_of_nodes = int(configuration.get('NumberOfNodes', '2')) definition["Resources"]["RedisReplicationGroup"] = { "Type": "AWS::ElastiCache::ReplicationGroup", "Properties": { "AutomaticFailoverEnabled": True, "CacheNodeType": configuration.get('CacheNodeType', 'cache.t2.small'), "CacheSubnetGroupName": { "Ref": "RedisSubnetGroup" }, "Engine": "redis", "EngineVersion": configuration.get('EngineVersion', '2.8.19'), "CacheParameterGroupName": configuration.get('CacheParameterGroupName', 'default.redis2.8'), "NumCacheClusters": number_of_nodes, "CacheNodeType": configuration.get('CacheNodeType', 'cache.t2.small'), "SecurityGroupIds": resolve_security_groups(configuration["SecurityGroups"], args.region), "ReplicationGroupDescription": "Redis replicated cache cluster: " + name, } } definition["Resources"]["RedisSubnetGroup"] = { "Type": "AWS::ElastiCache::SubnetGroup", "Properties": { "Description": "Redis cluster subnet group", "SubnetIds": {"Fn::FindInMap": ["ServerSubnets", {"Ref": "AWS::Region"}, "Subnets"]} } } return definition
def component_redis_node(definition, configuration, args, info, force, account_info): name = configuration["Name"] definition = ensure_keys(definition, "Resources") definition["Resources"]["RedisCacheCluster"] = { "Type": "AWS::ElastiCache::CacheCluster", "Properties": { "ClusterName": name, "Engine": "redis", "EngineVersion": configuration.get('EngineVersion', '2.8.19'), "CacheParameterGroupName": configuration.get('CacheParameterGroupName', 'default.redis2.8'), "NumCacheNodes": 1, "CacheNodeType": configuration.get('CacheNodeType', 'cache.t2.small'), "CacheSubnetGroupName": { "Ref": "RedisSubnetGroup" }, "VpcSecurityGroupIds": resolve_security_groups(configuration["SecurityGroups"], args.region) } } definition["Resources"]["RedisSubnetGroup"] = { "Type": "AWS::ElastiCache::SubnetGroup", "Properties": { "Description": "Redis cluster subnet group", "SubnetIds": {"Fn::FindInMap": ["ServerSubnets", {"Ref": "AWS::Region"}, "Subnets"]} } } return definition
def component_redis_cluster(definition, configuration, args, info, force, account_info): name = configuration["Name"] definition = ensure_keys(definition, "Resources") number_of_nodes = int(configuration.get('NumberOfNodes', '2')) definition["Resources"]["RedisReplicationGroup"] = { "Type": "AWS::ElastiCache::ReplicationGroup", "Properties": { "AutomaticFailoverEnabled": True, "CacheNodeType": configuration.get('CacheNodeType', 'cache.t2.small'), "CacheSubnetGroupName": { "Ref": "RedisSubnetGroup" }, "Engine": "redis", "EngineVersion": configuration.get('EngineVersion', '2.8.19'), "CacheParameterGroupName": configuration.get('CacheParameterGroupName', 'default.redis2.8'), "NumCacheClusters": number_of_nodes, "CacheNodeType": configuration.get('CacheNodeType', 'cache.t2.small'), "SecurityGroupIds": resolve_security_groups(configuration["SecurityGroups"], args.region), "ReplicationGroupDescription": "Redis replicated cache cluster: " + name, } } definition["Resources"]["RedisSubnetGroup"] = { "Type": "AWS::ElastiCache::SubnetGroup", "Properties": { "Description": "Redis cluster subnet group", "SubnetIds": {"Fn::FindInMap": ["ServerSubnets", {"Ref": "AWS::Region"}, "Subnets"]} } } return definition
def extract_security_group_ids(configuration, elastigroup_config: dict, args): """ This function identifies whether a senza formatted EC2-sg (by name) is configured, if so it transforms it into a Spotinst Elastigroup EC2-sq (by id) API configuration If there's already a compute.launchSpecification.securityGroupIds config it's left unchanged """ elastigroup_config = ensure_keys(elastigroup_config, "compute", "launchSpecification") launch_spec_config = elastigroup_config["compute"]["launchSpecification"] security_group_ids = [] if "securityGroupIds" not in launch_spec_config.keys(): if "SecurityGroups" in configuration.keys(): security_groups_ref = configuration.pop("SecurityGroups") if isinstance(security_groups_ref, str): security_group_ids = resolve_security_groups([security_groups_ref], args.region) elif isinstance(security_groups_ref, list): security_group_ids = resolve_security_groups(security_groups_ref, args.region) if len(security_group_ids) > 0: launch_spec_config["securityGroupIds"] = security_group_ids
def test_resolve_security_groups(monkeypatch): ec2 = MagicMock() ec2.security_groups.filter.return_value = [MagicMock(name='app-test', id='sg-test')] monkeypatch.setattr('boto3.resource', MagicMock(return_value=ec2)) security_groups = [] security_groups.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) security_groups.append('sg-007') security_groups.append('app-test') result = [] result.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) result.append('sg-007') result.append('sg-test') assert result == resolve_security_groups(security_groups, 'myregion')
def test_resolve_security_groups(monkeypatch): ec2 = MagicMock() sg = boto.ec2.securitygroup.SecurityGroup(name='app-test', id='sg-test') ec2.get_all_security_groups.return_value = [sg] monkeypatch.setattr('boto.ec2.connect_to_region', MagicMock(return_value=ec2)) security_groups = [] security_groups.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) security_groups.append('sg-007') security_groups.append('app-test') result = [] result.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) result.append('sg-007') result.append('sg-test') assert result == resolve_security_groups(security_groups, 'myregion')
def test_resolve_security_groups(monkeypatch): ec2 = MagicMock() ec2.security_groups.filter = MagicMock( side_effect=[ [MagicMock(name="app-test", id="sg-test")], [MagicMock(name="physical-resource-id", id="sg-resource")], ] ) def my_resource(rtype, *args): if rtype == "ec2": return ec2 else: return MagicMock() def my_client(rtype, *args): if rtype == "cloudformation": cf = MagicMock() resource = { "StackResourceDetail": { "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::EC2::SecurityGroup", "PhysicalResourceId": "physical-resource-id", } } cf.describe_stack_resource.return_value = resource return cf else: return MagicMock() monkeypatch.setattr("boto3.resource", my_resource) monkeypatch.setattr("boto3.client", my_client) security_groups = [] security_groups.append({"Fn::GetAtt": ["RefSecGroup", "GroupId"]}) security_groups.append("sg-007") security_groups.append("app-test") security_groups.append({"Stack": "stack", "LogicalId": "id"}) result = [] result.append({"Fn::GetAtt": ["RefSecGroup", "GroupId"]}) result.append("sg-007") result.append("sg-test") result.append("sg-resource") assert result == resolve_security_groups(security_groups, "myregion")
def test_resolve_security_groups(monkeypatch): ec2 = MagicMock() ec2.security_groups.filter = MagicMock(side_effect=[[ MagicMock(name='app-test', id='sg-test') ], [MagicMock(name='physical-resource-id', id='sg-resource')]]) def my_resource(rtype, *args): if rtype == 'ec2': return ec2 else: return MagicMock() def my_client(rtype, *args): if rtype == 'cloudformation': cf = MagicMock() resource = { 'StackResourceDetail': { 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::EC2::SecurityGroup', 'PhysicalResourceId': 'physical-resource-id' } } cf.describe_stack_resource.return_value = resource return cf else: return MagicMock() monkeypatch.setattr('boto3.resource', my_resource) monkeypatch.setattr('boto3.client', my_client) security_groups = [] security_groups.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) security_groups.append('sg-007') security_groups.append('app-test') security_groups.append({'Stack': 'stack', 'LogicalId': 'id'}) result = [] result.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) result.append('sg-007') result.append('sg-test') result.append('sg-resource') assert result == resolve_security_groups(security_groups, 'myregion')
def test_resolve_security_groups(monkeypatch): ec2 = MagicMock() ec2.security_groups.filter = MagicMock(side_effect=[ [MagicMock(name='app-test', id='sg-test')], [MagicMock(name='physical-resource-id', id='sg-resource')]]) def my_resource(rtype, *args): if rtype == 'ec2': return ec2 else: return MagicMock() def my_client(rtype, *args): if rtype == 'cloudformation': cf = MagicMock() resource = { 'StackResourceDetail': {'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::EC2::SecurityGroup', 'PhysicalResourceId': 'physical-resource-id'}} cf.describe_stack_resource.return_value = resource return cf else: return MagicMock() monkeypatch.setattr('boto3.resource', my_resource) monkeypatch.setattr('boto3.client', my_client) security_groups = [] security_groups.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) security_groups.append('sg-007') security_groups.append('app-test') security_groups.append({'Stack': 'stack', 'LogicalId': 'id'}) result = [] result.append({'Fn::GetAtt': ['RefSecGroup', 'GroupId']}) result.append('sg-007') result.append('sg-test') result.append('sg-resource') assert result == resolve_security_groups(security_groups, 'myregion')
def component_elastic_load_balancer_v2(definition, configuration: dict, args: TemplateArguments, info: dict, force, account_info: AccountArguments): lb_name = configuration["Name"] # domains pointing to the load balancer subdomain = '' main_zone = None for name, domain in configuration.get('Domains', {}).items(): name = '{}{}'.format(lb_name, name) domain_name = "{0}.{1}".format(domain["Subdomain"], domain["Zone"]) convert_cname_records_to_alias(domain_name) properties = {"Type": "A", "Name": domain_name, "HostedZoneName": domain["Zone"], "AliasTarget": {"HostedZoneId": {"Fn::GetAtt": [lb_name, "CanonicalHostedZoneID"]}, "DNSName": {"Fn::GetAtt": [lb_name, "DNSName"]}}} definition["Resources"][name] = {"Type": "AWS::Route53::RecordSet", "Properties": properties} if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"]['SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) subdomain = domain['Subdomain'] main_zone = domain['Zone'] # type: str target_group_name = lb_name + 'TargetGroup' listeners = configuration.get('Listeners') or get_listeners( lb_name, target_group_name, subdomain, main_zone, configuration, account_info) health_check_protocol = configuration.get('HealthCheckProtocol') or 'HTTP' if health_check_protocol not in ALLOWED_HEALTH_CHECK_PROTOCOLS: raise click.UsageError('Protocol "{}" is not supported for LoadBalancer'.format(health_check_protocol)) health_check_path = configuration.get("HealthCheckPath") or '/health' health_check_port = configuration.get("HealthCheckPort") or configuration["HTTPPort"] if configuration.get('LoadBalancerName'): loadbalancer_name = generate_valid_cloud_name(configuration["LoadBalancerName"], 32) elif configuration.get('NameSuffix'): version = '{}-{}'.format(info["StackVersion"], configuration['NameSuffix']) loadbalancer_name = get_load_balancer_name(info["StackName"], version) del(configuration['NameSuffix']) else: loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = configuration.get('Scheme') or 'internal' if loadbalancer_scheme == 'internet-facing': click.secho('You are deploying an internet-facing ELB that will be ' 'publicly accessible! You should have OAUTH2 and HTTPS ' 'in place!', bold=True, err=True) if loadbalancer_scheme not in ALLOWED_LOADBALANCER_SCHEMES: raise click.UsageError('Scheme "{}" is not supported for LoadBalancer'.format(loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" vpc_id = configuration.get("VpcId") or account_info.VpcID tags = [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { 'Name': loadbalancer_name, 'Scheme': loadbalancer_scheme, 'SecurityGroups': resolve_security_groups(configuration["SecurityGroups"], args.region), 'Subnets': {"Fn::FindInMap": [loadbalancer_subnet_map, {"Ref": "AWS::Region"}, "Subnets"]}, 'LoadBalancerAttributes': [ { "Key": "idle_timeout.timeout_seconds", "Value": "60" } ], "Tags": tags } } definition["Resources"][target_group_name] = { 'Type': 'AWS::ElasticLoadBalancingV2::TargetGroup', 'Properties': { 'Name': loadbalancer_name, 'HealthCheckIntervalSeconds': '10', 'HealthCheckPath': health_check_path, 'HealthCheckPort': health_check_port, 'HealthCheckProtocol': health_check_protocol, 'HealthCheckTimeoutSeconds': '5', 'HealthyThresholdCount': '2', 'Port': configuration['HTTPPort'], 'Protocol': 'HTTP', 'UnhealthyThresholdCount': '2', 'VpcId': vpc_id, 'Tags': tags, 'TargetGroupAttributes': [{'Key': 'deregistration_delay.timeout_seconds', 'Value': '60'}] } } resource_names = set([lb_name, target_group_name]) for i, listener in enumerate(listeners): if i == 0: suffix = '' else: suffix = str(i + 1) resource_name = lb_name + 'Listener' + suffix definition['Resources'][resource_name] = listener resource_names.add(resource_name) for key, val in configuration.items(): # overwrite any specified properties, but only properties which were defined by us already for res in resource_names: if key in definition['Resources'][res]['Properties'] and key not in SENZA_PROPERTIES: definition['Resources'][res]['Properties'][key] = val return definition
def component_elastic_load_balancer(definition, configuration, args, info, force, account_info): lb_name = configuration["Name"] # domains pointing to the load balancer main_zone = None for name, domain in configuration.get('Domains', {}).items(): name = '{}{}'.format(lb_name, name) definition["Resources"][name] = { "Type": "AWS::Route53::RecordSet", "Properties": { "Type": "CNAME", "TTL": 20, "ResourceRecords": [{ "Fn::GetAtt": [lb_name, "DNSName"] }], "Name": "{0}.{1}".format(domain["Subdomain"], domain["Zone"]), "HostedZoneName": "{0}".format(domain["Zone"]) }, } if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"][ 'SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) main_zone = domain['Zone'] ssl_cert = configuration.get('SSLCertificateId') pattern = None if not ssl_cert: if main_zone: pattern = main_zone.lower().rstrip('.').replace('.', '-') else: pattern = '' elif not ssl_cert.startswith('arn:'): pattern = ssl_cert if pattern is not None: ssl_cert = find_ssl_certificate_arn(args.region, pattern) if not ssl_cert: fatal_error( 'Could not find any matching SSL certificate for "{}"'.format( pattern)) health_check_protocol = "HTTP" allowed_health_check_protocols = ("HTTP", "TCP", "UDP", "SSL") if "HealthCheckProtocol" in configuration: health_check_protocol = configuration["HealthCheckProtocol"] if health_check_protocol not in allowed_health_check_protocols: raise click.UsageError( 'Protocol "{}" is not supported for LoadBalancer'.format( health_check_protocol)) health_check_path = "/ui/" if "HealthCheckPath" in configuration: health_check_path = configuration["HealthCheckPath"] health_check_port = configuration["HTTPPort"] if "HealthCheckPort" in configuration: health_check_port = configuration["HealthCheckPort"] health_check_target = "{0}:{1}{2}".format(health_check_protocol, health_check_port, health_check_path) if configuration.get('NameSufix'): loadbalancer_name = get_load_balancer_name( info["StackName"], '{}-{}'.format(info["StackVersion"], configuration['NameSufix'])) del (configuration['NameSufix']) else: loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = "internal" allowed_loadbalancer_schemes = ("internet-facing", "internal") if "Scheme" in configuration: loadbalancer_scheme = configuration["Scheme"] else: configuration["Scheme"] = loadbalancer_scheme if loadbalancer_scheme == 'internet-facing': click.secho( 'You are deploying an internet-facing ELB that will be publicly accessible! ' + 'You should have OAUTH2 and HTTPS in place!', fg='red', bold=True, err=True) if loadbalancer_scheme not in allowed_loadbalancer_schemes: raise click.UsageError( 'Scheme "{}" is not supported for LoadBalancer'.format( loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Subnets": { "Fn::FindInMap": [loadbalancer_subnet_map, { "Ref": "AWS::Region" }, "Subnets"] }, "HealthCheck": { "HealthyThreshold": "2", "UnhealthyThreshold": "2", "Interval": "10", "Timeout": "5", "Target": health_check_target }, "Listeners": [{ "PolicyNames": [], "SSLCertificateId": ssl_cert, "Protocol": "HTTPS", "InstancePort": configuration["HTTPPort"], "LoadBalancerPort": 443 }], "CrossZone": "true", "LoadBalancerName": loadbalancer_name, "SecurityGroups": resolve_security_groups(configuration["SecurityGroups"], args.region), "Tags": [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] } } for key, val in configuration.items(): # overwrite any specified properties, but # ignore our special Senza properties as they are not supported by CF if key not in SENZA_PROPERTIES: definition['Resources'][lb_name]['Properties'][key] = val return definition
def component_elastic_load_balancer(definition, configuration: dict, args: TemplateArguments, info: dict, force, account_info: AccountArguments): lb_name = configuration["Name"] # domains pointing to the load balancer subdomain = '' main_zone = None for name, domain in configuration.get('Domains', {}).items(): name = '{}{}'.format(lb_name, name) domain_name = "{0}.{1}".format(domain["Subdomain"], domain["Zone"]) convert_cname_records_to_alias(domain_name) properties = { "Type": "A", "Name": domain_name, "HostedZoneName": domain["Zone"], "AliasTarget": { "HostedZoneId": { "Fn::GetAtt": [lb_name, "CanonicalHostedZoneNameID"] }, "DNSName": { "Fn::GetAtt": [lb_name, "DNSName"] } } } definition["Resources"][name] = { "Type": "AWS::Route53::RecordSet", "Properties": properties } if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"][ 'SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) subdomain = domain['Subdomain'] main_zone = domain['Zone'] # type: str listeners = configuration.get('Listeners') or get_listeners(configuration) listeners = resolve_ssl_certificates(listeners, subdomain, main_zone, account_info) health_check_protocol = configuration.get('HealthCheckProtocol') or 'HTTP' if health_check_protocol not in ALLOWED_HEALTH_CHECK_PROTOCOLS: raise click.UsageError( 'Protocol "{}" is not supported for LoadBalancer'.format( health_check_protocol)) if health_check_protocol in ['HTTP', 'HTTPS']: health_check_path = configuration.get("HealthCheckPath") or '/health' else: health_check_path = '' health_check_port = configuration.get( "HealthCheckPort") or configuration["HTTPPort"] health_check_target = "{0}:{1}{2}".format(health_check_protocol, health_check_port, health_check_path) if configuration.get('NameSuffix'): version = '{}-{}'.format(info["StackVersion"], configuration['NameSuffix']) loadbalancer_name = get_load_balancer_name(info["StackName"], version) del (configuration['NameSuffix']) else: loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = configuration.get('Scheme') or 'internal' if loadbalancer_scheme == 'internet-facing': click.secho( 'You are deploying an internet-facing ELB that will be ' 'publicly accessible! You should have OAUTH2 and HTTPS ' 'in place!', bold=True, err=True) if loadbalancer_scheme not in ALLOWED_LOADBALANCER_SCHEMES: raise click.UsageError( 'Scheme "{}" is not supported for LoadBalancer'.format( loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Scheme": loadbalancer_scheme, "Subnets": { "Fn::FindInMap": [loadbalancer_subnet_map, { "Ref": "AWS::Region" }, "Subnets"] }, "HealthCheck": { "HealthyThreshold": "2", "UnhealthyThreshold": "2", "Interval": "10", "Timeout": "5", "Target": health_check_target }, "Listeners": listeners, "ConnectionDrainingPolicy": { "Enabled": True, "Timeout": 60 }, "CrossZone": "true", "LoadBalancerName": loadbalancer_name, "SecurityGroups": resolve_security_groups(configuration["SecurityGroups"], args.region), "Tags": [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] } } for key, val in configuration.items(): # overwrite any specified properties, but # ignore our special Senza properties as they are not supported by CF if key not in SENZA_PROPERTIES: definition['Resources'][lb_name]['Properties'][key] = val return definition
def component_elastic_load_balancer(definition, configuration, args, info, force): lb_name = configuration["Name"] # domains pointing to the load balancer main_zone = None for name, domain in configuration.get('Domains', {}).items(): definition["Resources"][name] = { "Type": "AWS::Route53::RecordSet", "Properties": { "Type": "CNAME", "TTL": 20, "ResourceRecords": [ {"Fn::GetAtt": [lb_name, "DNSName"]} ], "Name": "{0}.{1}".format(domain["Subdomain"], domain["Zone"]), "HostedZoneName": "{0}.".format(domain["Zone"]) }, } if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"]['SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) main_zone = domain['Zone'] ssl_cert = configuration.get('SSLCertificateId') pattern = None if not ssl_cert: if main_zone: pattern = main_zone.lower().replace('.', '-') else: pattern = '' elif not ssl_cert.startswith('arn:'): pattern = ssl_cert if pattern is not None: ssl_cert = find_ssl_certificate_arn(args.region, pattern) if not ssl_cert: raise click.UsageError('Could not find any matching SSL certificate for "{}"'.format(pattern)) health_check_protocol = "HTTP" allowed_health_check_protocols = ("HTTP", "TCP", "UDP", "SSL") if "HealthCheckProtocol" in configuration: health_check_protocol = configuration["HealthCheckProtocol"] if health_check_protocol not in allowed_health_check_protocols: raise click.UsageError('Protocol "{}" is not supported for LoadBalancer'.format(health_check_protocol)) health_check_path = "/ui/" if "HealthCheckPath" in configuration: health_check_path = configuration["HealthCheckPath"] health_check_port = configuration["HTTPPort"] if "HealthCheckPort" in configuration: health_check_port = configuration["HealthCheckPort"] health_check_target = "{0}:{1}{2}".format(health_check_protocol, health_check_port, health_check_path) loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = "internet-facing" allowed_loadbalancer_schemes = ("internet-facing", "internal") if "Scheme" in configuration: loadbalancer_scheme = configuration["Scheme"] if loadbalancer_scheme not in allowed_loadbalancer_schemes: raise click.UsageError('Scheme "{}" is not supported for LoadBalancer'.format(loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Scheme": loadbalancer_scheme, "Subnets": {"Fn::FindInMap": [loadbalancer_subnet_map, {"Ref": "AWS::Region"}, "Subnets"]}, "HealthCheck": { "HealthyThreshold": "2", "UnhealthyThreshold": "2", "Interval": "10", "Timeout": "5", "Target": health_check_target }, "Listeners": [ { "PolicyNames": [], "SSLCertificateId": ssl_cert, "Protocol": "HTTPS", "InstancePort": configuration["HTTPPort"], "LoadBalancerPort": 443 } ], "CrossZone": "true", "LoadBalancerName": loadbalancer_name, "SecurityGroups": resolve_security_groups(configuration["SecurityGroups"], args.region), "Tags": [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] } } return definition
def component_elastic_load_balancer_v2(definition, configuration: dict, args: TemplateArguments, info: dict, force, account_info: AccountArguments): lb_name = configuration["Name"] # domains pointing to the load balancer subdomain = '' main_zone = None for name, domain in configuration.get('Domains', {}).items(): name = '{}{}'.format(lb_name, name) domain_name = "{0}.{1}".format(domain["Subdomain"], domain["Zone"]) convert_cname_records_to_alias(domain_name) properties = { "Type": "A", "Name": domain_name, "HostedZoneName": domain["Zone"], "AliasTarget": { "HostedZoneId": { "Fn::GetAtt": [lb_name, "CanonicalHostedZoneID"] }, "DNSName": { "Fn::GetAtt": [lb_name, "DNSName"] } } } definition["Resources"][name] = { "Type": "AWS::Route53::RecordSet", "Properties": properties } if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"][ 'SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) subdomain = domain['Subdomain'] main_zone = domain['Zone'] # type: str target_group_name = lb_name + 'TargetGroup' listeners = configuration.get('Listeners') or get_listeners( lb_name, target_group_name, subdomain, main_zone, configuration, account_info) health_check_protocol = configuration.get('HealthCheckProtocol') or 'HTTP' if health_check_protocol not in ALLOWED_HEALTH_CHECK_PROTOCOLS: raise click.UsageError( 'Protocol "{}" is not supported for LoadBalancer'.format( health_check_protocol)) health_check_path = configuration.get("HealthCheckPath") or '/health' health_check_port = configuration.get( "HealthCheckPort") or configuration["HTTPPort"] if configuration.get('NameSuffix'): version = '{}-{}'.format(info["StackVersion"], configuration['NameSuffix']) loadbalancer_name = get_load_balancer_name(info["StackName"], version) del (configuration['NameSuffix']) else: loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = configuration.get('Scheme') or 'internal' if loadbalancer_scheme == 'internet-facing': click.secho( 'You are deploying an internet-facing ELB that will be ' 'publicly accessible! You should have OAUTH2 and HTTPS ' 'in place!', bold=True, err=True) if loadbalancer_scheme not in ALLOWED_LOADBALANCER_SCHEMES: raise click.UsageError( 'Scheme "{}" is not supported for LoadBalancer'.format( loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" vpc_id = configuration.get("VpcId") or account_info.VpcID tags = [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { 'Name': loadbalancer_name, 'Scheme': loadbalancer_scheme, 'SecurityGroups': resolve_security_groups(configuration["SecurityGroups"], args.region), 'Subnets': { "Fn::FindInMap": [loadbalancer_subnet_map, { "Ref": "AWS::Region" }, "Subnets"] }, "Tags": tags } } definition["Resources"][target_group_name] = { 'Type': 'AWS::ElasticLoadBalancingV2::TargetGroup', 'Properties': { 'Name': loadbalancer_name, 'HealthCheckIntervalSeconds': '10', 'HealthCheckPath': health_check_path, 'HealthCheckPort': health_check_port, 'HealthCheckProtocol': health_check_protocol, 'HealthCheckTimeoutSeconds': '5', 'HealthyThresholdCount': '2', 'Port': configuration['HTTPPort'], 'Protocol': 'HTTP', 'UnhealthyThresholdCount': '2', 'VpcId': vpc_id, 'Tags': tags, 'TargetGroupAttributes': [{ 'Key': 'deregistration_delay.timeout_seconds', 'Value': '60' }] } } resource_names = set([lb_name, target_group_name]) for i, listener in enumerate(listeners): if i == 0: suffix = '' else: suffix = str(i + 1) resource_name = lb_name + 'Listener' + suffix definition['Resources'][resource_name] = listener resource_names.add(resource_name) for key, val in configuration.items(): # overwrite any specified properties, but only properties which were defined by us already for res in resource_names: if key in definition['Resources'][res][ 'Properties'] and key not in SENZA_PROPERTIES: definition['Resources'][res]['Properties'][key] = val return definition
def component_elastic_load_balancer(definition, configuration: dict, args: TemplateArguments, info: dict, force, account_info: AccountArguments): lb_name = configuration["Name"] # domains pointing to the load balancer subdomain = '' main_zone = None for name, domain in configuration.get('Domains', {}).items(): name = '{}{}'.format(lb_name, name) domain_name = "{0}.{1}".format(domain["Subdomain"], domain["Zone"]) convert_cname_records_to_alias(domain_name) properties = {"Type": "A", "Name": domain_name, "HostedZoneName": domain["Zone"], "AliasTarget": {"HostedZoneId": {"Fn::GetAtt": [lb_name, "CanonicalHostedZoneNameID"]}, "DNSName": {"Fn::GetAtt": [lb_name, "DNSName"]}}} definition["Resources"][name] = {"Type": "AWS::Route53::RecordSet", "Properties": properties} if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"]['SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) subdomain = domain['Subdomain'] main_zone = domain['Zone'] # type: str listeners = configuration.get('Listeners') or get_listeners(configuration) listeners = resolve_ssl_certificates(listeners, subdomain, main_zone, account_info) health_check_protocol = configuration.get('HealthCheckProtocol') or 'HTTP' if health_check_protocol not in ALLOWED_HEALTH_CHECK_PROTOCOLS: raise click.UsageError('Protocol "{}" is not supported for LoadBalancer'.format(health_check_protocol)) if health_check_protocol in ['HTTP', 'HTTPS']: health_check_path = configuration.get("HealthCheckPath") or '/health' else: health_check_path = '' health_check_port = configuration.get("HealthCheckPort") or configuration["HTTPPort"] health_check_target = "{0}:{1}{2}".format(health_check_protocol, health_check_port, health_check_path) if configuration.get('NameSuffix'): version = '{}-{}'.format(info["StackVersion"], configuration['NameSuffix']) loadbalancer_name = get_load_balancer_name(info["StackName"], version) del(configuration['NameSuffix']) else: loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = configuration.get('Scheme') or 'internal' if loadbalancer_scheme == 'internet-facing': click.secho('You are deploying an internet-facing ELB that will be ' 'publicly accessible! You should have OAUTH2 and HTTPS ' 'in place!', bold=True, err=True) if loadbalancer_scheme not in ALLOWED_LOADBALANCER_SCHEMES: raise click.UsageError('Scheme "{}" is not supported for LoadBalancer'.format(loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Scheme": loadbalancer_scheme, "Subnets": {"Fn::FindInMap": [loadbalancer_subnet_map, {"Ref": "AWS::Region"}, "Subnets"]}, "HealthCheck": { "HealthyThreshold": "2", "UnhealthyThreshold": "2", "Interval": "10", "Timeout": "5", "Target": health_check_target }, "Listeners": listeners, "ConnectionDrainingPolicy": { "Enabled": True, "Timeout": 60 }, "CrossZone": "true", "LoadBalancerName": loadbalancer_name, "SecurityGroups": resolve_security_groups(configuration["SecurityGroups"], args.region), "Tags": [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] } } for key, val in configuration.items(): # overwrite any specified properties, but # ignore our special Senza properties as they are not supported by CF if key not in SENZA_PROPERTIES: definition['Resources'][lb_name]['Properties'][key] = val return definition
def component_elastic_load_balancer(definition, configuration, args, info, force, account_info): lb_name = configuration["Name"] # domains pointing to the load balancer subdomain = '' main_zone = None for name, domain in configuration.get('Domains', {}).items(): name = '{}{}'.format(lb_name, name) definition["Resources"][name] = { "Type": "AWS::Route53::RecordSet", "Properties": { "Type": "CNAME", "TTL": 20, "ResourceRecords": [ {"Fn::GetAtt": [lb_name, "DNSName"]} ], "Name": "{0}.{1}".format(domain["Subdomain"], domain["Zone"]), "HostedZoneName": "{0}".format(domain["Zone"]) }, } if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"]['SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) subdomain = domain['Subdomain'] main_zone = domain['Zone'] # type: str ssl_cert = configuration.get('SSLCertificateId') if ACMCertificate.arn_is_acm_certificate(ssl_cert): # check if certificate really exists try: ACMCertificate.get_by_arn(ssl_cert) except ClientError as e: error_msg = e.response['Error']['Message'] fatal_error(error_msg) elif IAMServerCertificate.arn_is_server_certificate(ssl_cert): # TODO check if certificate exists pass elif ssl_cert is not None: certificate = IAMServerCertificate.get_by_name(ssl_cert) ssl_cert = certificate.arn elif main_zone is not None: if main_zone: iam_pattern = main_zone.lower().rstrip('.').replace('.', '-') name = '{sub}.{zone}'.format(sub=subdomain, zone=main_zone.rstrip('.')) acm_certificates = sorted(ACM.get_certificates(domain_name=name), reverse=True) else: iam_pattern = '' acm_certificates = [] iam_certificates = sorted(IAM.get_certificates(name=iam_pattern)) if not iam_certificates: # if there are no iam certificates matching the pattern # try to use any certificate iam_certificates = sorted(IAM.get_certificates(), reverse=True) # the priority is acm_certificate first and iam_certificate second certificates = (acm_certificates + iam_certificates) # type: List[Union[ACMCertificate, IAMServerCertificate]] try: certificate = certificates[0] ssl_cert = certificate.arn except IndexError: if main_zone: fatal_error('Could not find any matching ' 'SSL certificate for "{}"'.format(name)) else: fatal_error('Could not find any SSL certificate') health_check_protocol = "HTTP" allowed_health_check_protocols = ("HTTP", "TCP", "UDP", "SSL") if "HealthCheckProtocol" in configuration: health_check_protocol = configuration["HealthCheckProtocol"] if health_check_protocol not in allowed_health_check_protocols: raise click.UsageError('Protocol "{}" is not supported for LoadBalancer'.format(health_check_protocol)) health_check_path = "/ui/" if "HealthCheckPath" in configuration: health_check_path = configuration["HealthCheckPath"] health_check_port = configuration["HTTPPort"] if "HealthCheckPort" in configuration: health_check_port = configuration["HealthCheckPort"] health_check_target = "{0}:{1}{2}".format(health_check_protocol, health_check_port, health_check_path) if configuration.get('LoadBalancerName'): loadbalancer_name = info["LoadBalancerName"] elif configuration.get('NameSuffix'): version = '{}-{}'.format(info["StackVersion"], configuration['NameSuffix']) loadbalancer_name = get_load_balancer_name(info["StackName"], version) del(configuration['NameSuffix']) else: loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = "internal" allowed_loadbalancer_schemes = ("internet-facing", "internal") if "Scheme" in configuration: loadbalancer_scheme = configuration["Scheme"] else: configuration["Scheme"] = loadbalancer_scheme if loadbalancer_scheme == 'internet-facing': click.secho('You are deploying an internet-facing ELB that will be ' 'publicly accessible! You should have OAUTH2 and HTTPS ' 'in place!', bold=True, err=True) if loadbalancer_scheme not in allowed_loadbalancer_schemes: raise click.UsageError('Scheme "{}" is not supported for LoadBalancer'.format(loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Subnets": {"Fn::FindInMap": [loadbalancer_subnet_map, {"Ref": "AWS::Region"}, "Subnets"]}, "HealthCheck": { "HealthyThreshold": "2", "UnhealthyThreshold": "2", "Interval": "10", "Timeout": "5", "Target": health_check_target }, "Listeners": [ { "PolicyNames": [], "SSLCertificateId": ssl_cert, "Protocol": "HTTPS", "InstancePort": configuration["HTTPPort"], "LoadBalancerPort": 443 } ], "ConnectionDrainingPolicy": { "Enabled": True, "Timeout": 60 }, "CrossZone": "true", "LoadBalancerName": loadbalancer_name, "SecurityGroups": resolve_security_groups(configuration["SecurityGroups"], args.region), "Tags": [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] } } for key, val in configuration.items(): # overwrite any specified properties, but # ignore our special Senza properties as they are not supported by CF if key not in SENZA_PROPERTIES: definition['Resources'][lb_name]['Properties'][key] = val return definition
def component_auto_scaling_group(definition, configuration, args, info, force, account_info): definition = ensure_keys(definition, "Resources") # launch configuration config_name = configuration["Name"] + "Config" definition["Resources"][config_name] = { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "InstanceType": configuration["InstanceType"], "ImageId": {"Fn::FindInMap": ["Images", {"Ref": "AWS::Region"}, configuration["Image"]]}, "AssociatePublicIpAddress": configuration.get('AssociatePublicIpAddress', False), "EbsOptimized": configuration.get('EbsOptimized', False) } } if 'BlockDeviceMappings' in configuration: definition['Resources'][config_name]['Properties']['BlockDeviceMappings'] = configuration['BlockDeviceMappings'] if "IamInstanceProfile" in configuration: definition["Resources"][config_name]["Properties"]["IamInstanceProfile"] = configuration["IamInstanceProfile"] if 'IamRoles' in configuration: logical_id = configuration['Name'] + 'InstanceProfile' roles = configuration['IamRoles'] if len(roles) > 1: for role in roles: if isinstance(role, dict): raise click.UsageError('Cannot merge policies of Cloud Formation references ({"Ref": ".."}): ' + 'You can use at most one IAM role with "Ref".') logical_role_id = configuration['Name'] + 'Role' definition['Resources'][logical_role_id] = { 'Type': 'AWS::IAM::Role', 'Properties': { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": ["ec2.amazonaws.com"] }, "Action": ["sts:AssumeRole"] } ] }, 'Path': '/', 'Policies': get_merged_policies(roles) } } instance_profile_roles = [{'Ref': logical_role_id}] else: instance_profile_roles = roles definition['Resources'][logical_id] = { 'Type': 'AWS::IAM::InstanceProfile', 'Properties': { 'Path': '/', 'Roles': instance_profile_roles } } definition["Resources"][config_name]["Properties"]["IamInstanceProfile"] = {'Ref': logical_id} if "SecurityGroups" in configuration: definition["Resources"][config_name]["Properties"]["SecurityGroups"] = \ resolve_security_groups(configuration["SecurityGroups"], args.region) if "UserData" in configuration: definition["Resources"][config_name]["Properties"]["UserData"] = { "Fn::Base64": configuration["UserData"] } # auto scaling group asg_name = configuration["Name"] definition["Resources"][asg_name] = { "Type": "AWS::AutoScaling::AutoScalingGroup", # wait up to 15 minutes to get a signal from at least one server that it booted "CreationPolicy": { "ResourceSignal": { "Count": "1", "Timeout": "PT15M" } }, "Properties": { # for our operator some notifications "LaunchConfigurationName": {"Ref": config_name}, "VPCZoneIdentifier": {"Fn::FindInMap": ["ServerSubnets", {"Ref": "AWS::Region"}, "Subnets"]}, "Tags": [ # Tag "Name" { "Key": "Name", "PropagateAtLaunch": True, "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "PropagateAtLaunch": True, "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "PropagateAtLaunch": True, "Value": info["StackVersion"] } ] } } if "OperatorTopicId" in info: definition["Resources"][asg_name]["Properties"]["NotificationConfiguration"] = { "NotificationTypes": [ "autoscaling:EC2_INSTANCE_LAUNCH", "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", "autoscaling:EC2_INSTANCE_TERMINATE", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" ], "TopicARN": resolve_topic_arn(args.region, info["OperatorTopicId"]) } default_health_check_type = 'EC2' if "ElasticLoadBalancer" in configuration: if isinstance(configuration["ElasticLoadBalancer"], str): definition["Resources"][asg_name]["Properties"]["LoadBalancerNames"] = [ {"Ref": configuration["ElasticLoadBalancer"]}] elif isinstance(configuration["ElasticLoadBalancer"], list): definition["Resources"][asg_name]["Properties"]["LoadBalancerNames"] = [] for ref in configuration["ElasticLoadBalancer"]: definition["Resources"][asg_name]["Properties"]["LoadBalancerNames"].append({'Ref': ref}) # use ELB health check by default default_health_check_type = 'ELB' definition["Resources"][asg_name]['Properties']['HealthCheckType'] = \ configuration.get('HealthCheckType', default_health_check_type) definition["Resources"][asg_name]['Properties']['HealthCheckGracePeriod'] = \ configuration.get('HealthCheckGracePeriod', 300) if "AutoScaling" in configuration: definition["Resources"][asg_name]["Properties"]["MaxSize"] = configuration["AutoScaling"]["Maximum"] definition["Resources"][asg_name]["Properties"]["MinSize"] = configuration["AutoScaling"]["Minimum"] # ScaleUp policy definition["Resources"][asg_name + "ScaleUp"] = { "Type": "AWS::AutoScaling::ScalingPolicy", "Properties": { "AdjustmentType": "ChangeInCapacity", "ScalingAdjustment": "1", "Cooldown": "60", "AutoScalingGroupName": { "Ref": asg_name } } } # ScaleDown policy definition["Resources"][asg_name + "ScaleDown"] = { "Type": "AWS::AutoScaling::ScalingPolicy", "Properties": { "AdjustmentType": "ChangeInCapacity", "ScalingAdjustment": "-1", "Cooldown": "60", "AutoScalingGroupName": { "Ref": asg_name } } } metric_type = configuration["AutoScaling"]["MetricType"] metricfn = globals().get('metric_{}'.format(metric_type.lower())) if not metricfn: raise click.UsageError('Auto scaling MetricType "{}" not supported.'.format(metric_type)) definition = metricfn(asg_name, definition, configuration["AutoScaling"], args, info, force) else: definition["Resources"][asg_name]["Properties"]["MaxSize"] = 1 definition["Resources"][asg_name]["Properties"]["MinSize"] = 1 return definition
def component_auto_scaling_group(definition, configuration, args, info, force, account_info): definition = ensure_keys(definition, "Resources") # launch configuration config_name = configuration["Name"] + "Config" definition["Resources"][config_name] = { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "InstanceType": configuration["InstanceType"], "ImageId": { "Fn::FindInMap": [ "Images", { "Ref": "AWS::Region" }, configuration["Image"], ] }, "AssociatePublicIpAddress": configuration.get("AssociatePublicIpAddress", False), "EbsOptimized": configuration.get("EbsOptimized", False), }, } if "IamRoles" in configuration: logical_id = handle_iam_roles(definition, configuration, args) definition["Resources"][config_name]["Properties"][ "IamInstanceProfile"] = { "Ref": logical_id } if "SecurityGroups" in configuration: definition["Resources"][config_name]["Properties"][ "SecurityGroups"] = resolve_security_groups( configuration["SecurityGroups"], args.region) if "UserData" in configuration: definition["Resources"][config_name]["Properties"]["UserData"] = { "Fn::Base64": configuration["UserData"] } # auto scaling group asg_name = configuration["Name"] asg_success = ["1", "PT15M"] if "AutoScaling" in configuration: if "SuccessRequires" in configuration["AutoScaling"]: asg_success = normalize_asg_success( configuration["AutoScaling"]["SuccessRequires"]) tags = [ # Tag "Name" { "Key": "Name", "PropagateAtLaunch": True, "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]), }, # Tag "StackName" { "Key": "StackName", "PropagateAtLaunch": True, "Value": info["StackName"] }, # Tag "StackVersion" { "Key": "StackVersion", "PropagateAtLaunch": True, "Value": info["StackVersion"], }, ] if "Tags" in configuration: for tag in configuration["Tags"]: tags.append({ "Key": tag["Key"], "PropagateAtLaunch": True, "Value": tag["Value"] }) definition["Resources"][asg_name] = { "Type": "AWS::AutoScaling::AutoScalingGroup", # wait to get a signal from an amount of servers to signal that it booted "CreationPolicy": { "ResourceSignal": { "Count": asg_success[0], "Timeout": asg_success[1] } }, "Properties": { # for our operator some notifications "LaunchConfigurationName": { "Ref": config_name }, "VPCZoneIdentifier": { "Fn::FindInMap": ["ServerSubnets", { "Ref": "AWS::Region" }, "Subnets"] }, "Tags": tags, }, } asg_properties = definition["Resources"][asg_name]["Properties"] if "OperatorTopicId" in info: asg_properties["NotificationConfiguration"] = { "NotificationTypes": [ "autoscaling:EC2_INSTANCE_LAUNCH", "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", "autoscaling:EC2_INSTANCE_TERMINATE", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR", ], "TopicARN": resolve_topic_arn(args.region, info["OperatorTopicId"]), } default_health_check_type = "EC2" if "ElasticLoadBalancer" in configuration: if isinstance(configuration["ElasticLoadBalancer"], str): asg_properties["LoadBalancerNames"] = [{ "Ref": configuration["ElasticLoadBalancer"] }] elif isinstance(configuration["ElasticLoadBalancer"], list): asg_properties["LoadBalancerNames"] = [{ "Ref": ref } for ref in configuration["ElasticLoadBalancer"]] # use ELB health check by default default_health_check_type = "ELB" if "ElasticLoadBalancerV2" in configuration: if isinstance(configuration["ElasticLoadBalancerV2"], str): asg_properties["TargetGroupARNs"] = [{ "Ref": configuration["ElasticLoadBalancerV2"] + "TargetGroup" }] elif isinstance(configuration["ElasticLoadBalancerV2"], list): asg_properties["TargetGroupARNs"] = [{ "Ref": ref + "TargetGroup" } for ref in configuration["ElasticLoadBalancerV2"]] # use ELB health check by default default_health_check_type = "ELB" asg_properties["HealthCheckType"] = configuration.get( "HealthCheckType", default_health_check_type) asg_properties["HealthCheckGracePeriod"] = configuration.get( "HealthCheckGracePeriod", 300) if "AutoScaling" in configuration: as_conf = configuration["AutoScaling"] asg_properties["MaxSize"] = as_conf["Maximum"] asg_properties["MinSize"] = as_conf["Minimum"] asg_properties["DesiredCapacity"] = max( int(as_conf["Minimum"]), int(as_conf.get("DesiredCapacity", 1))) default_scaling_adjustment = as_conf.get("ScalingAdjustment", 1) default_cooldown = as_conf.get("Cooldown", "60") # ScaleUp policy scale_up_name = asg_name + "ScaleUp" scale_up_adjustment = int( as_conf.get("ScaleUpAdjustment", default_scaling_adjustment)) scale_up_cooldown = as_conf.get("ScaleUpCooldown", default_cooldown) definition["Resources"][scale_up_name] = create_autoscaling_policy( asg_name, scale_up_name, scale_up_adjustment, scale_up_cooldown, definition) # ScaleDown policy scale_down_name = asg_name + "ScaleDown" scale_down_adjustment = (-1) * int( as_conf.get("ScaleDownAdjustment", default_scaling_adjustment)) scale_down_cooldown = as_conf.get("ScaleDownCooldown", default_cooldown) definition["Resources"][scale_down_name] = create_autoscaling_policy( asg_name, scale_down_name, scale_down_adjustment, scale_down_cooldown, definition, ) if "MetricType" in as_conf: metric_type = as_conf["MetricType"] metricfns = { "CPU": metric_cpu, "NetworkIn": metric_network, "NetworkOut": metric_network, } # lowercase cpu is an acceptable metric, be compatible if metric_type.lower() not in map(lambda t: t.lower(), metricfns.keys()): raise click.UsageError( 'Auto scaling MetricType "{}" not supported.'.format( metric_type)) metricfn = metricfns[metric_type] definition = metricfn(asg_name, definition, as_conf, args, info, force) else: asg_properties["MaxSize"] = 1 asg_properties["MinSize"] = 1 for res in (config_name, asg_name): props = definition["Resources"][res]["Properties"] additional_cf_properties = ADDITIONAL_PROPERTIES.get( definition["Resources"][res]["Type"]) properties_allowed_to_overwrite = ( set(props.keys()) - SENZA_PROPERTIES) | additional_cf_properties for key in properties_allowed_to_overwrite: if key in configuration: props[key] = configuration[key] return definition
def component_auto_scaling_group(definition, configuration, args, info, force, account_info): definition = ensure_keys(definition, "Resources") # launch configuration config_name = configuration["Name"] + "Config" definition["Resources"][config_name] = { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "InstanceType": configuration["InstanceType"], "ImageId": {"Fn::FindInMap": ["Images", {"Ref": "AWS::Region"}, configuration["Image"]]}, "AssociatePublicIpAddress": configuration.get('AssociatePublicIpAddress', False), "EbsOptimized": configuration.get('EbsOptimized', False) } } if 'IamRoles' in configuration: logical_id = handle_iam_roles(definition, configuration, args) definition["Resources"][config_name]["Properties"]["IamInstanceProfile"] = {'Ref': logical_id} if "SecurityGroups" in configuration: definition["Resources"][config_name]["Properties"]["SecurityGroups"] = \ resolve_security_groups(configuration["SecurityGroups"], args.region) if "UserData" in configuration: definition["Resources"][config_name]["Properties"]["UserData"] = { "Fn::Base64": configuration["UserData"] } # auto scaling group asg_name = configuration["Name"] asg_success = ["1", "PT15M"] if "AutoScaling" in configuration: if "SuccessRequires" in configuration["AutoScaling"]: asg_success = normalize_asg_success(configuration["AutoScaling"]["SuccessRequires"]) tags = [ # Tag "Name" { "Key": "Name", "PropagateAtLaunch": True, "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "PropagateAtLaunch": True, "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "PropagateAtLaunch": True, "Value": info["StackVersion"] } ] if "Tags" in configuration: for tag in configuration["Tags"]: tags.append({ "Key": tag["Key"], "PropagateAtLaunch": True, "Value": tag["Value"] }) definition["Resources"][asg_name] = { "Type": "AWS::AutoScaling::AutoScalingGroup", # wait to get a signal from an amount of servers to signal that it booted "CreationPolicy": { "ResourceSignal": { "Count": asg_success[0], "Timeout": asg_success[1] } }, "Properties": { # for our operator some notifications "LaunchConfigurationName": {"Ref": config_name}, "VPCZoneIdentifier": {"Fn::FindInMap": ["ServerSubnets", {"Ref": "AWS::Region"}, "Subnets"]}, "Tags": tags } } asg_properties = definition["Resources"][asg_name]["Properties"] if "OperatorTopicId" in info: asg_properties["NotificationConfiguration"] = { "NotificationTypes": [ "autoscaling:EC2_INSTANCE_LAUNCH", "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", "autoscaling:EC2_INSTANCE_TERMINATE", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" ], "TopicARN": resolve_topic_arn(args.region, info["OperatorTopicId"]) } default_health_check_type = 'EC2' if "ElasticLoadBalancer" in configuration: if isinstance(configuration["ElasticLoadBalancer"], str): asg_properties["LoadBalancerNames"] = [{"Ref": configuration["ElasticLoadBalancer"]}] elif isinstance(configuration["ElasticLoadBalancer"], list): asg_properties["LoadBalancerNames"] = [{'Ref': ref} for ref in configuration["ElasticLoadBalancer"]] # use ELB health check by default default_health_check_type = 'ELB' if "ElasticLoadBalancerV2" in configuration: if isinstance(configuration["ElasticLoadBalancerV2"], str): asg_properties["TargetGroupARNs"] = [{"Ref": configuration["ElasticLoadBalancerV2"] + 'TargetGroup'}] elif isinstance(configuration["ElasticLoadBalancerV2"], list): asg_properties["TargetGroupARNs"] = [ {'Ref': ref + 'TargetGroup'} for ref in configuration["ElasticLoadBalancerV2"] ] # use ELB health check by default default_health_check_type = 'ELB' asg_properties['HealthCheckType'] = configuration.get('HealthCheckType', default_health_check_type) asg_properties['HealthCheckGracePeriod'] = configuration.get('HealthCheckGracePeriod', 300) if "AutoScaling" in configuration: as_conf = configuration["AutoScaling"] asg_properties["MaxSize"] = as_conf["Maximum"] asg_properties["MinSize"] = as_conf["Minimum"] asg_properties["DesiredCapacity"] = max(int(as_conf["Minimum"]), int(as_conf.get('DesiredCapacity', 1))) default_scaling_adjustment = as_conf.get("ScalingAdjustment", 1) default_cooldown = as_conf.get("Cooldown", "60") # ScaleUp policy scale_up_name = asg_name + "ScaleUp" scale_up_adjustment = int( as_conf.get("ScaleUpAdjustment", default_scaling_adjustment)) scale_up_cooldown = as_conf.get( "ScaleUpCooldown", default_cooldown) definition["Resources"][scale_up_name] = create_autoscaling_policy( asg_name, scale_up_name, scale_up_adjustment, scale_up_cooldown, definition) # ScaleDown policy scale_down_name = asg_name + "ScaleDown" scale_down_adjustment = (-1) * int( as_conf.get("ScaleDownAdjustment", default_scaling_adjustment)) scale_down_cooldown = as_conf.get( "ScaleDownCooldown", default_cooldown) definition["Resources"][scale_down_name] = create_autoscaling_policy( asg_name, scale_down_name, scale_down_adjustment, scale_down_cooldown, definition) if "MetricType" in as_conf: metric_type = as_conf["MetricType"] metricfns = { "CPU": metric_cpu, "NetworkIn": metric_network, "NetworkOut": metric_network } # lowercase cpu is an acceptable metric, be compatible if metric_type.lower() not in map(lambda t: t.lower(), metricfns.keys()): raise click.UsageError('Auto scaling MetricType "{}" not supported.'.format(metric_type)) metricfn = metricfns[metric_type] definition = metricfn(asg_name, definition, as_conf, args, info, force) else: asg_properties["MaxSize"] = 1 asg_properties["MinSize"] = 1 for res in (config_name, asg_name): props = definition['Resources'][res]['Properties'] additional_cf_properties = ADDITIONAL_PROPERTIES.get(definition['Resources'][res]['Type']) properties_allowed_to_overwrite = (set(props.keys()) - SENZA_PROPERTIES) | additional_cf_properties for key in properties_allowed_to_overwrite: if key in configuration: props[key] = configuration[key] return definition
def component_auto_scaling_group(definition, configuration, args, info, force, account_info): definition = ensure_keys(definition, "Resources") # launch configuration config_name = configuration["Name"] + "Config" definition["Resources"][config_name] = { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "InstanceType": configuration["InstanceType"], "ImageId": { "Fn::FindInMap": ["Images", { "Ref": "AWS::Region" }, configuration["Image"]] }, "AssociatePublicIpAddress": configuration.get('AssociatePublicIpAddress', False), "EbsOptimized": configuration.get('EbsOptimized', False) } } if 'IamRoles' in configuration: logical_id = configuration['Name'] + 'InstanceProfile' roles = configuration['IamRoles'] if len(roles) > 1: for role in roles: if isinstance(role, dict): raise click.UsageError( 'Cannot merge policies of Cloud Formation references ({"Ref": ".."}): ' + 'You can use at most one IAM role with "Ref".') logical_role_id = configuration['Name'] + 'Role' definition['Resources'][logical_role_id] = { 'Type': 'AWS::IAM::Role', 'Properties': { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Service": ["ec2.amazonaws.com"] }, "Action": ["sts:AssumeRole"] }] }, 'Path': '/', 'Policies': get_merged_policies(roles) } } instance_profile_roles = [{'Ref': logical_role_id}] elif isinstance(roles[0], dict): instance_profile_roles = [ resolve_referenced_resource(roles[0], args.region) ] else: instance_profile_roles = roles definition['Resources'][logical_id] = { 'Type': 'AWS::IAM::InstanceProfile', 'Properties': { 'Path': '/', 'Roles': instance_profile_roles } } definition["Resources"][config_name]["Properties"][ "IamInstanceProfile"] = { 'Ref': logical_id } if "SecurityGroups" in configuration: definition["Resources"][config_name]["Properties"]["SecurityGroups"] = \ resolve_security_groups(configuration["SecurityGroups"], args.region) if "UserData" in configuration: definition["Resources"][config_name]["Properties"]["UserData"] = { "Fn::Base64": configuration["UserData"] } # auto scaling group asg_name = configuration["Name"] asg_success = ["1", "PT15M"] if "AutoScaling" in configuration: if "SuccessRequires" in configuration["AutoScaling"]: asg_success = normalize_asg_success( configuration["AutoScaling"]["SuccessRequires"]) tags = [ # Tag "Name" { "Key": "Name", "PropagateAtLaunch": True, "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "PropagateAtLaunch": True, "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "PropagateAtLaunch": True, "Value": info["StackVersion"] } ] if "Tags" in configuration: for tag in configuration["Tags"]: tags.append({ "Key": tag["Key"], "PropagateAtLaunch": True, "Value": tag["Value"] }) definition["Resources"][asg_name] = { "Type": "AWS::AutoScaling::AutoScalingGroup", # wait to get a signal from an amount of servers to signal that it booted "CreationPolicy": { "ResourceSignal": { "Count": asg_success[0], "Timeout": asg_success[1] } }, "Properties": { # for our operator some notifications "LaunchConfigurationName": { "Ref": config_name }, "VPCZoneIdentifier": { "Fn::FindInMap": ["ServerSubnets", { "Ref": "AWS::Region" }, "Subnets"] }, "Tags": tags } } asg_properties = definition["Resources"][asg_name]["Properties"] if "OperatorTopicId" in info: asg_properties["NotificationConfiguration"] = { "NotificationTypes": [ "autoscaling:EC2_INSTANCE_LAUNCH", "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", "autoscaling:EC2_INSTANCE_TERMINATE", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" ], "TopicARN": resolve_topic_arn(args.region, info["OperatorTopicId"]) } default_health_check_type = 'EC2' if "ElasticLoadBalancer" in configuration: if isinstance(configuration["ElasticLoadBalancer"], str): asg_properties["LoadBalancerNames"] = [{ "Ref": configuration["ElasticLoadBalancer"] }] elif isinstance(configuration["ElasticLoadBalancer"], list): asg_properties["LoadBalancerNames"] = [{ 'Ref': ref } for ref in configuration["ElasticLoadBalancer"]] # use ELB health check by default default_health_check_type = 'ELB' if "ElasticLoadBalancerV2" in configuration: if isinstance(configuration["ElasticLoadBalancerV2"], str): asg_properties["TargetGroupARNs"] = [{ "Ref": configuration["ElasticLoadBalancerV2"] + 'TargetGroup' }] elif isinstance(configuration["ElasticLoadBalancerV2"], list): asg_properties["TargetGroupARNs"] = [{ 'Ref': ref + 'TargetGroup' } for ref in configuration["ElasticLoadBalancerV2"]] # use ELB health check by default default_health_check_type = 'ELB' asg_properties['HealthCheckType'] = configuration.get( 'HealthCheckType', default_health_check_type) asg_properties['HealthCheckGracePeriod'] = configuration.get( 'HealthCheckGracePeriod', 300) if "AutoScaling" in configuration: as_conf = configuration["AutoScaling"] asg_properties["MaxSize"] = as_conf["Maximum"] asg_properties["MinSize"] = as_conf["Minimum"] asg_properties["DesiredCapacity"] = max( int(as_conf["Minimum"]), int(as_conf.get('DesiredCapacity', 1))) default_scaling_adjustment = as_conf.get("ScalingAdjustment", 1) default_cooldown = as_conf.get("Cooldown", "60") # ScaleUp policy scale_up_name = asg_name + "ScaleUp" scale_up_adjustment = int( as_conf.get("ScaleUpAdjustment", default_scaling_adjustment)) scale_up_cooldown = as_conf.get("ScaleUpCooldown", default_cooldown) definition["Resources"][scale_up_name] = create_autoscaling_policy( asg_name, scale_up_name, scale_up_adjustment, scale_up_cooldown, definition) # ScaleDown policy scale_down_name = asg_name + "ScaleDown" scale_down_adjustment = (-1) * int( as_conf.get("ScaleDownAdjustment", default_scaling_adjustment)) scale_down_cooldown = as_conf.get("ScaleDownCooldown", default_cooldown) definition["Resources"][scale_down_name] = create_autoscaling_policy( asg_name, scale_down_name, scale_down_adjustment, scale_down_cooldown, definition) if "MetricType" in as_conf: metric_type = as_conf["MetricType"] metricfns = { "CPU": metric_cpu, "NetworkIn": metric_network, "NetworkOut": metric_network } # lowercase cpu is an acceptable metric, be compatible if metric_type.lower() not in map(lambda t: t.lower(), metricfns.keys()): raise click.UsageError( 'Auto scaling MetricType "{}" not supported.'.format( metric_type)) metricfn = metricfns[metric_type] definition = metricfn(asg_name, definition, as_conf, args, info, force) else: asg_properties["MaxSize"] = 1 asg_properties["MinSize"] = 1 for res in (config_name, asg_name): props = definition['Resources'][res]['Properties'] additional_cf_properties = ADDITIONAL_PROPERTIES.get( definition['Resources'][res]['Type']) properties_allowed_to_overwrite = ( set(props.keys()) - SENZA_PROPERTIES) | additional_cf_properties for key in properties_allowed_to_overwrite: if key in configuration: props[key] = configuration[key] return definition
def component_elastic_load_balancer(definition, configuration, args, info, force): lb_name = configuration["Name"] # domains pointing to the load balancer main_zone = None for name, domain in configuration.get('Domains', {}).items(): definition["Resources"][name] = { "Type": "AWS::Route53::RecordSet", "Properties": { "Type": "CNAME", "TTL": 20, "ResourceRecords": [ {"Fn::GetAtt": [lb_name, "DNSName"]} ], "Name": "{0}.{1}".format(domain["Subdomain"], domain["Zone"]), "HostedZoneName": "{0}.".format(domain["Zone"]) }, } if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"]['SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) main_zone = domain['Zone'] ssl_cert = configuration.get('SSLCertificateId') pattern = None if not ssl_cert: if main_zone: pattern = main_zone.lower().replace('.', '-') else: pattern = '' elif not ssl_cert.startswith('arn:'): pattern = ssl_cert if pattern is not None: ssl_cert = find_ssl_certificate_arn(args.region, pattern) if not ssl_cert: raise click.UsageError('Could not find any matching SSL certificate for "{}"'.format(pattern)) health_check_protocol = "HTTP" allowed_health_check_protocols = ("HTTP", "TCP", "UDP", "SSL") if "HealthCheckProtocol" in configuration: health_check_protocol = configuration["HealthCheckProtocol"] if health_check_protocol not in allowed_health_check_protocols: raise click.UsageError('Protocol "{}" is not supported for LoadBalancer'.format(health_check_protocol)) health_check_path = "/ui/" if "HealthCheckPath" in configuration: health_check_path = configuration["HealthCheckPath"] health_check_port = configuration["HTTPPort"] if "HealthCheckPort" in configuration: health_check_port = configuration["HealthCheckPort"] health_check_target = "{0}:{1}{2}".format(health_check_protocol, health_check_port, health_check_path) loadbalancer_name = get_load_balancer_name(info["StackName"], info["StackVersion"]) loadbalancer_scheme = "internal" allowed_loadbalancer_schemes = ("internet-facing", "internal") if "Scheme" in configuration: loadbalancer_scheme = configuration["Scheme"] else: configuration["Scheme"] = loadbalancer_scheme if loadbalancer_scheme == 'internet-facing': click.secho('You are deploying an internet-facing ELB that will be publicly accessible! ' + 'You should have OAUTH2 and HTTPS in place!', fg='red', bold=True, err=True) if loadbalancer_scheme not in allowed_loadbalancer_schemes: raise click.UsageError('Scheme "{}" is not supported for LoadBalancer'.format(loadbalancer_scheme)) if loadbalancer_scheme == "internal": loadbalancer_subnet_map = "LoadBalancerInternalSubnets" else: loadbalancer_subnet_map = "LoadBalancerSubnets" # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Subnets": {"Fn::FindInMap": [loadbalancer_subnet_map, {"Ref": "AWS::Region"}, "Subnets"]}, "HealthCheck": { "HealthyThreshold": "2", "UnhealthyThreshold": "2", "Interval": "10", "Timeout": "5", "Target": health_check_target }, "Listeners": [ { "PolicyNames": [], "SSLCertificateId": ssl_cert, "Protocol": "HTTPS", "InstancePort": configuration["HTTPPort"], "LoadBalancerPort": 443 } ], "CrossZone": "true", "LoadBalancerName": loadbalancer_name, "SecurityGroups": resolve_security_groups(configuration["SecurityGroups"], args.region), "Tags": [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] } } for key, val in configuration.items(): # overwrite any specified properties, but # ignore our special Senza properties as they are not supported by CF if key not in SENZA_PROPERTIES: definition['Resources'][lb_name]['Properties'][key] = val return definition
def component_elastic_load_balancer(definition, configuration, args, info, force): lb_name = configuration["Name"] # domains pointing to the load balancer main_zone = None for name, domain in configuration.get('Domains', {}).items(): definition["Resources"][name] = { "Type": "AWS::Route53::RecordSet", "Properties": { "Type": "CNAME", "TTL": 20, "ResourceRecords": [ {"Fn::GetAtt": [lb_name, "DNSName"]} ], "Name": "{0}.{1}".format(domain["Subdomain"], domain["Zone"]), "HostedZoneName": "{0}.".format(domain["Zone"]) }, } if domain["Type"] == "weighted": definition["Resources"][name]["Properties"]['Weight'] = 0 definition["Resources"][name]["Properties"]['SetIdentifier'] = "{0}-{1}".format(info["StackName"], info["StackVersion"]) main_zone = domain['Zone'] ssl_cert = configuration.get('SSLCertificateId') pattern = None if not ssl_cert: if main_zone: pattern = main_zone.lower().replace('.', '-') else: pattern = '' elif not ssl_cert.startswith('arn:'): pattern = ssl_cert if pattern is not None: ssl_cert = find_ssl_certificate_arn(args.region, pattern) if not ssl_cert: raise click.UsageError('Could not find any matching SSL certificate for "{}"'.format(pattern)) health_check_protocol = "HTTP" allowed_health_check_protocols = ("HTTP", "TCP", "UDP", "SSL") if "HealthCheckProtocol" in configuration: health_check_protocol = configuration["HealthCheckProtocol"] if health_check_protocol not in allowed_health_check_protocols: raise click.UsageError('Protocol "{}" is not supported for LoadBalancer'.format(health_check_protocol)) health_check_path = "/ui/" if "HealthCheckPath" in configuration: health_check_path = configuration["HealthCheckPath"] health_check_target = "{0}:{1}{2}".format(health_check_protocol, configuration["HTTPPort"], health_check_path) # load balancer definition["Resources"][lb_name] = { "Type": "AWS::ElasticLoadBalancing::LoadBalancer", "Properties": { "Scheme": "internet-facing", "Subnets": {"Fn::FindInMap": ["LoadBalancerSubnets", {"Ref": "AWS::Region"}, "Subnets"]}, "HealthCheck": { "HealthyThreshold": "2", "UnhealthyThreshold": "2", "Interval": "10", "Timeout": "5", "Target": health_check_target }, "Listeners": [ { "PolicyNames": [], "SSLCertificateId": ssl_cert, "Protocol": "HTTPS", "InstancePort": configuration["HTTPPort"], "LoadBalancerPort": 443 } ], "CrossZone": "true", "LoadBalancerName": "{0}-{1}".format(info["StackName"], info["StackVersion"]), "SecurityGroups": resolve_security_groups(configuration["SecurityGroups"], args.region), "Tags": [ # Tag "Name" { "Key": "Name", "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "Value": info["StackVersion"] } ] } } return definition
def component_auto_scaling_group(definition, configuration, args, info, force, account_info): definition = ensure_keys(definition, "Resources") # launch configuration config_name = configuration["Name"] + "Config" definition["Resources"][config_name] = { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "InstanceType": configuration["InstanceType"], "ImageId": {"Fn::FindInMap": ["Images", {"Ref": "AWS::Region"}, configuration["Image"]]}, "AssociatePublicIpAddress": configuration.get('AssociatePublicIpAddress', False), "EbsOptimized": configuration.get('EbsOptimized', False) } } if 'BlockDeviceMappings' in configuration: definition['Resources'][config_name]['Properties']['BlockDeviceMappings'] = configuration['BlockDeviceMappings'] if "IamInstanceProfile" in configuration: definition["Resources"][config_name]["Properties"]["IamInstanceProfile"] = configuration["IamInstanceProfile"] if 'IamRoles' in configuration: logical_id = configuration['Name'] + 'InstanceProfile' roles = configuration['IamRoles'] if len(roles) > 1: for role in roles: if isinstance(role, dict): raise click.UsageError('Cannot merge policies of Cloud Formation references ({"Ref": ".."}): ' + 'You can use at most one IAM role with "Ref".') logical_role_id = configuration['Name'] + 'Role' definition['Resources'][logical_role_id] = { 'Type': 'AWS::IAM::Role', 'Properties': { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": ["ec2.amazonaws.com"] }, "Action": ["sts:AssumeRole"] } ] }, 'Path': '/', 'Policies': get_merged_policies(roles) } } instance_profile_roles = [{'Ref': logical_role_id}] elif isinstance(roles[0], dict): instance_profile_roles = [resolve_referenced_resource(roles[0], args.region)] else: instance_profile_roles = roles definition['Resources'][logical_id] = { 'Type': 'AWS::IAM::InstanceProfile', 'Properties': { 'Path': '/', 'Roles': instance_profile_roles } } definition["Resources"][config_name]["Properties"]["IamInstanceProfile"] = {'Ref': logical_id} if "SecurityGroups" in configuration: definition["Resources"][config_name]["Properties"]["SecurityGroups"] = \ resolve_security_groups(configuration["SecurityGroups"], args.region) if "UserData" in configuration: definition["Resources"][config_name]["Properties"]["UserData"] = { "Fn::Base64": configuration["UserData"] } # auto scaling group asg_name = configuration["Name"] asg_success = ["1", "PT15M"] if "AutoScaling" in configuration: if "SuccessRequires" in configuration["AutoScaling"]: asg_success = normalize_asg_success(configuration["AutoScaling"]["SuccessRequires"]) definition["Resources"][asg_name] = { "Type": "AWS::AutoScaling::AutoScalingGroup", # wait to get a signal from an amount of servers to signal that it booted "CreationPolicy": { "ResourceSignal": { "Count": asg_success[0], "Timeout": asg_success[1] } }, "Properties": { # for our operator some notifications "LaunchConfigurationName": {"Ref": config_name}, "VPCZoneIdentifier": {"Fn::FindInMap": ["ServerSubnets", {"Ref": "AWS::Region"}, "Subnets"]}, "Tags": [ # Tag "Name" { "Key": "Name", "PropagateAtLaunch": True, "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "PropagateAtLaunch": True, "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "PropagateAtLaunch": True, "Value": info["StackVersion"] } ] } } asg_properties = definition["Resources"][asg_name]["Properties"] if "OperatorTopicId" in info: asg_properties["NotificationConfiguration"] = { "NotificationTypes": [ "autoscaling:EC2_INSTANCE_LAUNCH", "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", "autoscaling:EC2_INSTANCE_TERMINATE", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" ], "TopicARN": resolve_topic_arn(args.region, info["OperatorTopicId"]) } default_health_check_type = 'EC2' if "ElasticLoadBalancer" in configuration: if isinstance(configuration["ElasticLoadBalancer"], str): asg_properties["LoadBalancerNames"] = [{"Ref": configuration["ElasticLoadBalancer"]}] elif isinstance(configuration["ElasticLoadBalancer"], list): asg_properties["LoadBalancerNames"] = [{'Ref': ref} for ref in configuration["ElasticLoadBalancer"]] # use ELB health check by default default_health_check_type = 'ELB' asg_properties['HealthCheckType'] = configuration.get('HealthCheckType', default_health_check_type) asg_properties['HealthCheckGracePeriod'] = configuration.get('HealthCheckGracePeriod', 300) if "AutoScaling" in configuration: as_conf = configuration["AutoScaling"] asg_properties["MaxSize"] = as_conf["Maximum"] asg_properties["MinSize"] = as_conf["Minimum"] asg_properties["DesiredCapacity"] = max(int(as_conf["Minimum"]), int(as_conf.get('DesiredCapacity', 1))) scaling_adjustment = int(as_conf.get("ScalingAdjustment", 1)) # ScaleUp policy definition["Resources"][asg_name + "ScaleUp"] = { "Type": "AWS::AutoScaling::ScalingPolicy", "Properties": { "AdjustmentType": "ChangeInCapacity", "ScalingAdjustment": str(scaling_adjustment), "Cooldown": str(as_conf.get("Cooldown", "60")), "AutoScalingGroupName": { "Ref": asg_name } } } # ScaleDown policy definition["Resources"][asg_name + "ScaleDown"] = { "Type": "AWS::AutoScaling::ScalingPolicy", "Properties": { "AdjustmentType": "ChangeInCapacity", "ScalingAdjustment": str((-1) * scaling_adjustment), "Cooldown": str(as_conf.get("Cooldown", "60")), "AutoScalingGroupName": { "Ref": asg_name } } } if "MetricType" in as_conf: metric_type = as_conf["MetricType"] metricfns = { "CPU": metric_cpu, "NetworkIn": metric_network, "NetworkOut": metric_network } # lowercase cpu is an acceptable metric, be compatible if metric_type.lower() not in map(lambda t: t.lower(), metricfns.keys()): raise click.UsageError('Auto scaling MetricType "{}" not supported.'.format(metric_type)) metricfn = metricfns[metric_type] definition = metricfn(asg_name, definition, as_conf, args, info, force) else: asg_properties["MaxSize"] = 1 asg_properties["MinSize"] = 1 return definition
def component_auto_scaling_group(definition, configuration, args, info, force): definition = ensure_keys(definition, "Resources") # launch configuration config_name = configuration["Name"] + "Config" definition["Resources"][config_name] = { "Type": "AWS::AutoScaling::LaunchConfiguration", "Properties": { "InstanceType": configuration["InstanceType"], "ImageId": {"Fn::FindInMap": ["Images", {"Ref": "AWS::Region"}, configuration["Image"]]}, "AssociatePublicIpAddress": configuration.get('AssociatePublicIpAddress', False), "EbsOptimized": configuration.get('EbsOptimized', False) } } if 'BlockDeviceMappings' in configuration: definition['Resources'][config_name]['Properties']['BlockDeviceMappings'] = configuration['BlockDeviceMappings'] if "IamInstanceProfile" in configuration: definition["Resources"][config_name]["Properties"]["IamInstanceProfile"] = configuration["IamInstanceProfile"] if 'IamRoles' in configuration: logical_id = configuration['Name'] + 'InstanceProfile' roles = configuration['IamRoles'] if len(roles) > 1: for role in roles: if isinstance(role, dict): raise click.UsageError('Cannot merge policies of Cloud Formation references ({"Ref": ".."}): ' + 'You can use at most one IAM role with "Ref".') logical_role_id = configuration['Name'] + 'Role' definition['Resources'][logical_role_id] = { 'Type': 'AWS::IAM::Role', 'Properties': { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": ["ec2.amazonaws.com"] }, "Action": ["sts:AssumeRole"] } ] }, 'Path': '/', 'Policies': get_merged_policies(roles, args.region) } } instance_profile_roles = [{'Ref': logical_role_id}] else: instance_profile_roles = roles definition['Resources'][logical_id] = { 'Type': 'AWS::IAM::InstanceProfile', 'Properties': { 'Path': '/', 'Roles': instance_profile_roles } } definition["Resources"][config_name]["Properties"]["IamInstanceProfile"] = {'Ref': logical_id} if "SecurityGroups" in configuration: definition["Resources"][config_name]["Properties"]["SecurityGroups"] = \ resolve_security_groups(configuration["SecurityGroups"], args.region) if "UserData" in configuration: definition["Resources"][config_name]["Properties"]["UserData"] = { "Fn::Base64": configuration["UserData"] } # auto scaling group asg_name = configuration["Name"] definition["Resources"][asg_name] = { "Type": "AWS::AutoScaling::AutoScalingGroup", # wait up to 15 minutes to get a signal from at least one server that it booted "CreationPolicy": { "ResourceSignal": { "Count": "1", "Timeout": "PT15M" } }, "Properties": { # for our operator some notifications "LaunchConfigurationName": {"Ref": config_name}, "VPCZoneIdentifier": {"Fn::FindInMap": ["ServerSubnets", {"Ref": "AWS::Region"}, "Subnets"]}, "Tags": [ # Tag "Name" { "Key": "Name", "PropagateAtLaunch": True, "Value": "{0}-{1}".format(info["StackName"], info["StackVersion"]) }, # Tag "StackName" { "Key": "StackName", "PropagateAtLaunch": True, "Value": info["StackName"], }, # Tag "StackVersion" { "Key": "StackVersion", "PropagateAtLaunch": True, "Value": info["StackVersion"] } ] } } if "OperatorTopicId" in info: definition["Resources"][asg_name]["Properties"]["NotificationConfiguration"] = { "NotificationTypes": [ "autoscaling:EC2_INSTANCE_LAUNCH", "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", "autoscaling:EC2_INSTANCE_TERMINATE", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" ], "TopicARN": resolve_topic_arn(args.region, info["OperatorTopicId"]) } default_health_check_type = 'EC2' if "ElasticLoadBalancer" in configuration: definition["Resources"][asg_name]["Properties"]["LoadBalancerNames"] = [ {"Ref": configuration["ElasticLoadBalancer"]}] # use ELB health check by default default_health_check_type = 'ELB' definition["Resources"][asg_name]['Properties']['HealthCheckType'] = \ configuration.get('HealthCheckType', default_health_check_type) definition["Resources"][asg_name]['Properties']['HealthCheckGracePeriod'] = \ configuration.get('HealthCheckGracePeriod', 300) if "AutoScaling" in configuration: definition["Resources"][asg_name]["Properties"]["MaxSize"] = configuration["AutoScaling"]["Maximum"] definition["Resources"][asg_name]["Properties"]["MinSize"] = configuration["AutoScaling"]["Minimum"] # ScaleUp policy definition["Resources"][asg_name + "ScaleUp"] = { "Type": "AWS::AutoScaling::ScalingPolicy", "Properties": { "AdjustmentType": "ChangeInCapacity", "ScalingAdjustment": "1", "Cooldown": "60", "AutoScalingGroupName": { "Ref": asg_name } } } # ScaleDown policy definition["Resources"][asg_name + "ScaleDown"] = { "Type": "AWS::AutoScaling::ScalingPolicy", "Properties": { "AdjustmentType": "ChangeInCapacity", "ScalingAdjustment": "-1", "Cooldown": "60", "AutoScalingGroupName": { "Ref": asg_name } } } metric_type = configuration["AutoScaling"]["MetricType"] metricfn = globals().get('metric_{}'.format(metric_type.lower())) if not metricfn: raise click.UsageError('Auto scaling MetricType "{}" not supported.'.format(metric_type)) definition = metricfn(asg_name, definition, configuration["AutoScaling"], args, info, force) else: definition["Resources"][asg_name]["Properties"]["MaxSize"] = 1 definition["Resources"][asg_name]["Properties"]["MinSize"] = 1 return definition