def test_get_merged_policies(monkeypatch): role = MagicMock() role.policies.all = MagicMock(return_value=[MagicMock(policy_name='pol1', policy_document={'foo': 'bar'})]) iam = MagicMock() iam.Role.return_value = role monkeypatch.setattr('boto3.resource', lambda x: iam) assert [{'PolicyDocument': {'foo': 'bar'}, 'PolicyName': 'pol1'}] == get_merged_policies(['RoleA'])
def test_get_merged_policies(monkeypatch): iam = MagicMock() iam.list_role_policies.return_value = { "list_role_policies_response": {"list_role_policies_result": {"policy_names": ["pol1"]}} } iam.get_role_policy.return_value = { "get_role_policy_response": {"get_role_policy_result": {"policy_document": '{"foo":"bar"}'}} } monkeypatch.setattr("boto.iam.connect_to_region", lambda x: iam) assert [{"PolicyDocument": {"foo": "bar"}, "PolicyName": "pol1"}] == get_merged_policies(["RoleA"], "myregion")
def handle_iam_roles(definition, configuration, args): """ This function resolves Senza's IAMRoles attribute and creates the CF InstanceProfile resources """ logical_id = configuration["Name"] + "InstanceProfile" roles = configuration.pop("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 }, } return logical_id
def handle_iam_roles(definition, configuration, args): """ This function resolves Senza's IAMRoles attribute and creates the CF InstanceProfile resources """ logical_id = configuration['Name'] + 'InstanceProfile' roles = configuration.pop("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 } } return logical_id
def test_get_merged_policies(monkeypatch): iam = MagicMock() iam.list_role_policies.return_value = { 'list_role_policies_response': { 'list_role_policies_result': { 'policy_names': ['pol1'] } } } iam.get_role_policy.return_value = { 'get_role_policy_response': { 'get_role_policy_result': { 'policy_document': '{"foo":"bar"}' } } } monkeypatch.setattr('boto.iam.connect_to_region', lambda x: iam) assert [{ 'PolicyDocument': { 'foo': 'bar' }, 'PolicyName': 'pol1' }] == get_merged_policies(['RoleA'], 'myregion')
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, 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_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): 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
def test_get_merged_policies(monkeypatch): iam = MagicMock() iam.list_role_policies.return_value = {'list_role_policies_response': {'list_role_policies_result': {'policy_names': ['pol1']}}} iam.get_role_policy.return_value = {'get_role_policy_response': {'get_role_policy_result': {'policy_document': '{"foo":"bar"}'}}} monkeypatch.setattr('boto.iam.connect_to_region', lambda x: iam) assert [{'PolicyDocument': {'foo': 'bar'}, 'PolicyName': 'pol1'}] == get_merged_policies(['RoleA'], 'myregion')