Correctly generate resource name for target groups when using cloudformation
They need to have less than 32 character names, so when you don't specify a name cloudformation generates a name that is less than 32 characters. And make sure that flake8 passes
This commit is contained in:
		
							parent
							
								
									8092929258
								
							
						
					
					
						commit
						81381cd035
					
				| @ -96,6 +96,7 @@ NAME_TYPE_MAP = { | |||||||
|     "AWS::ElasticBeanstalk::Application": "ApplicationName", |     "AWS::ElasticBeanstalk::Application": "ApplicationName", | ||||||
|     "AWS::ElasticBeanstalk::Environment": "EnvironmentName", |     "AWS::ElasticBeanstalk::Environment": "EnvironmentName", | ||||||
|     "AWS::ElasticLoadBalancing::LoadBalancer": "LoadBalancerName", |     "AWS::ElasticLoadBalancing::LoadBalancer": "LoadBalancerName", | ||||||
|  |     "AWS::ElasticLoadBalancingV2::TargetGroup": "Name", | ||||||
|     "AWS::RDS::DBInstance": "DBInstanceIdentifier", |     "AWS::RDS::DBInstance": "DBInstanceIdentifier", | ||||||
|     "AWS::S3::Bucket": "BucketName", |     "AWS::S3::Bucket": "BucketName", | ||||||
|     "AWS::SNS::Topic": "TopicName", |     "AWS::SNS::Topic": "TopicName", | ||||||
| @ -244,6 +245,18 @@ def resource_name_property_from_type(resource_type): | |||||||
|     return NAME_TYPE_MAP.get(resource_type) |     return NAME_TYPE_MAP.get(resource_type) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def generate_resource_name(resource_type, stack_name, logical_id): | ||||||
|  |     if resource_type == "AWS::ElasticLoadBalancingV2::TargetGroup": | ||||||
|  |         # Target group names need to be less than 32 characters, so when cloudformation creates a name for you | ||||||
|  |         # it makes sure to stay under that limit | ||||||
|  |         name_prefix = '{0}-{1}'.format(stack_name, logical_id) | ||||||
|  |         my_random_suffix = random_suffix() | ||||||
|  |         truncated_name_prefix = name_prefix[0:32 - (len(my_random_suffix) + 1)] | ||||||
|  |         return '{0}-{1}'.format(truncated_name_prefix, my_random_suffix) | ||||||
|  |     else: | ||||||
|  |         return '{0}-{1}-{2}'.format(stack_name, logical_id, random_suffix()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def parse_resource(logical_id, resource_json, resources_map): | def parse_resource(logical_id, resource_json, resources_map): | ||||||
|     resource_type = resource_json['Type'] |     resource_type = resource_json['Type'] | ||||||
|     resource_class = resource_class_from_type(resource_type) |     resource_class = resource_class_from_type(resource_type) | ||||||
| @ -258,15 +271,12 @@ def parse_resource(logical_id, resource_json, resources_map): | |||||||
|         if 'Properties' not in resource_json: |         if 'Properties' not in resource_json: | ||||||
|             resource_json['Properties'] = dict() |             resource_json['Properties'] = dict() | ||||||
|         if resource_name_property not in resource_json['Properties']: |         if resource_name_property not in resource_json['Properties']: | ||||||
|             resource_json['Properties'][resource_name_property] = '{0}-{1}-{2}'.format( |             resource_json['Properties'][resource_name_property] = generate_resource_name( | ||||||
|                 resources_map.get('AWS::StackName'), |                 resource_type, resources_map.get('AWS::StackName'), logical_id) | ||||||
|                 logical_id, |  | ||||||
|                 random_suffix()) |  | ||||||
|         resource_name = resource_json['Properties'][resource_name_property] |         resource_name = resource_json['Properties'][resource_name_property] | ||||||
|     else: |     else: | ||||||
|         resource_name = '{0}-{1}-{2}'.format(resources_map.get('AWS::StackName'), |         resource_name = generate_resource_name(resource_type, resources_map.get('AWS::StackName'), logical_id) | ||||||
|                                              logical_id, | 
 | ||||||
|                                              random_suffix()) |  | ||||||
|     return resource_class, resource_json, resource_name |     return resource_class, resource_json, resource_name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -124,10 +124,7 @@ class FakeTargetGroup(BaseModel): | |||||||
| 
 | 
 | ||||||
|         elbv2_backend = elbv2_backends[region_name] |         elbv2_backend = elbv2_backends[region_name] | ||||||
| 
 | 
 | ||||||
|         # per cloudformation docs: |         name = properties.get('Name') | ||||||
|         # The target group name should be shorter than 22 characters because |  | ||||||
|         # AWS CloudFormation uses the target group name to create the name of the load balancer. |  | ||||||
|         name = properties.get('Name', resource_name[:22]) |  | ||||||
|         vpc_id = properties.get("VpcId") |         vpc_id = properties.get("VpcId") | ||||||
|         protocol = properties.get('Protocol') |         protocol = properties.get('Protocol') | ||||||
|         port = properties.get("Port") |         port = properties.get("Port") | ||||||
| @ -437,7 +434,7 @@ class ELBv2Backend(BaseBackend): | |||||||
|     def create_target_group(self, name, **kwargs): |     def create_target_group(self, name, **kwargs): | ||||||
|         if len(name) > 32: |         if len(name) > 32: | ||||||
|             raise InvalidTargetGroupNameError( |             raise InvalidTargetGroupNameError( | ||||||
|                 "Target group name '%s' cannot be longer than '22' characters" % name |                 "Target group name '%s' cannot be longer than '32' characters" % name | ||||||
|             ) |             ) | ||||||
|         if not re.match('^[a-zA-Z0-9\-]+$', name): |         if not re.match('^[a-zA-Z0-9\-]+$', name): | ||||||
|             raise InvalidTargetGroupNameError( |             raise InvalidTargetGroupNameError( | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
|  | 
 | ||||||
|  | import json | ||||||
| import os | import os | ||||||
| import boto3 | import boto3 | ||||||
| import botocore | import botocore | ||||||
| @ -6,7 +8,7 @@ from botocore.exceptions import ClientError | |||||||
| from nose.tools import assert_raises | from nose.tools import assert_raises | ||||||
| import sure  # noqa | import sure  # noqa | ||||||
| 
 | 
 | ||||||
| from moto import mock_elbv2, mock_ec2, mock_acm | from moto import mock_elbv2, mock_ec2, mock_acm, mock_cloudformation | ||||||
| from moto.elbv2 import elbv2_backends | from moto.elbv2 import elbv2_backends | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -416,6 +418,7 @@ def test_create_target_group_and_listeners(): | |||||||
|     response = conn.describe_target_groups() |     response = conn.describe_target_groups() | ||||||
|     response.get('TargetGroups').should.have.length_of(0) |     response.get('TargetGroups').should.have.length_of(0) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| @mock_elbv2 | @mock_elbv2 | ||||||
| @mock_ec2 | @mock_ec2 | ||||||
| def test_create_target_group_without_non_required_parameters(): | def test_create_target_group_without_non_required_parameters(): | ||||||
| @ -454,6 +457,7 @@ def test_create_target_group_without_non_required_parameters(): | |||||||
|     target_group = response.get('TargetGroups')[0] |     target_group = response.get('TargetGroups')[0] | ||||||
|     target_group.should_not.be.none |     target_group.should_not.be.none | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| @mock_elbv2 | @mock_elbv2 | ||||||
| @mock_ec2 | @mock_ec2 | ||||||
| def test_create_invalid_target_group(): | def test_create_invalid_target_group(): | ||||||
| @ -1105,6 +1109,50 @@ def test_describe_invalid_target_group(): | |||||||
|         conn.describe_target_groups(Names=['invalid']) |         conn.describe_target_groups(Names=['invalid']) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @mock_elbv2 | ||||||
|  | @mock_ec2 | ||||||
|  | def test_describe_target_groups_no_arguments(): | ||||||
|  |     conn = boto3.client('elbv2', region_name='us-east-1') | ||||||
|  |     ec2 = boto3.resource('ec2', region_name='us-east-1') | ||||||
|  | 
 | ||||||
|  |     security_group = ec2.create_security_group( | ||||||
|  |         GroupName='a-security-group', Description='First One') | ||||||
|  |     vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') | ||||||
|  |     subnet1 = ec2.create_subnet( | ||||||
|  |         VpcId=vpc.id, | ||||||
|  |         CidrBlock='172.28.7.192/26', | ||||||
|  |         AvailabilityZone='us-east-1a') | ||||||
|  |     subnet2 = ec2.create_subnet( | ||||||
|  |         VpcId=vpc.id, | ||||||
|  |         CidrBlock='172.28.7.192/26', | ||||||
|  |         AvailabilityZone='us-east-1b') | ||||||
|  | 
 | ||||||
|  |     response = conn.create_load_balancer( | ||||||
|  |         Name='my-lb', | ||||||
|  |         Subnets=[subnet1.id, subnet2.id], | ||||||
|  |         SecurityGroups=[security_group.id], | ||||||
|  |         Scheme='internal', | ||||||
|  |         Tags=[{'Key': 'key_name', 'Value': 'a_value'}]) | ||||||
|  | 
 | ||||||
|  |     response.get('LoadBalancers')[0].get('LoadBalancerArn') | ||||||
|  | 
 | ||||||
|  |     conn.create_target_group( | ||||||
|  |         Name='a-target', | ||||||
|  |         Protocol='HTTP', | ||||||
|  |         Port=8080, | ||||||
|  |         VpcId=vpc.id, | ||||||
|  |         HealthCheckProtocol='HTTP', | ||||||
|  |         HealthCheckPort='8080', | ||||||
|  |         HealthCheckPath='/', | ||||||
|  |         HealthCheckIntervalSeconds=5, | ||||||
|  |         HealthCheckTimeoutSeconds=5, | ||||||
|  |         HealthyThresholdCount=5, | ||||||
|  |         UnhealthyThresholdCount=2, | ||||||
|  |         Matcher={'HttpCode': '200'}) | ||||||
|  | 
 | ||||||
|  |     assert len(conn.describe_target_groups()['TargetGroups']) == 1 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @mock_elbv2 | @mock_elbv2 | ||||||
| def test_describe_account_limits(): | def test_describe_account_limits(): | ||||||
|     client = boto3.client('elbv2', region_name='eu-central-1') |     client = boto3.client('elbv2', region_name='eu-central-1') | ||||||
| @ -1473,3 +1521,68 @@ def test_modify_listener_http_to_https(): | |||||||
|                 {'Type': 'forward', 'TargetGroupArn': target_group_arn} |                 {'Type': 'forward', 'TargetGroupArn': target_group_arn} | ||||||
|             ] |             ] | ||||||
|         ) |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_ec2 | ||||||
|  | @mock_elbv2 | ||||||
|  | @mock_cloudformation | ||||||
|  | def test_create_target_groups_through_cloudformation(): | ||||||
|  |     cfn_conn = boto3.client('cloudformation', region_name='us-east-1') | ||||||
|  |     elbv2_client = boto3.client('elbv2', region_name='us-east-1') | ||||||
|  | 
 | ||||||
|  |     # test that setting a name manually as well as letting cloudformation create a name both work | ||||||
|  |     # this is a special case because test groups have a name length limit of 22 characters, and must be unique | ||||||
|  |     # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-targetgroup.html#cfn-elasticloadbalancingv2-targetgroup-name | ||||||
|  |     template = { | ||||||
|  |         "AWSTemplateFormatVersion": "2010-09-09", | ||||||
|  |         "Description": "ECS Cluster Test CloudFormation", | ||||||
|  |         "Resources": { | ||||||
|  |             "testVPC": { | ||||||
|  |                 "Type": "AWS::EC2::VPC", | ||||||
|  |                 "Properties": { | ||||||
|  |                     "CidrBlock": "10.0.0.0/16", | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             "testGroup1": { | ||||||
|  |                 "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", | ||||||
|  |                 "Properties": { | ||||||
|  |                     "Port": 80, | ||||||
|  |                     "Protocol": "HTTP", | ||||||
|  |                     "VpcId": {"Ref": "testVPC"}, | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             "testGroup2": { | ||||||
|  |                 "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", | ||||||
|  |                 "Properties": { | ||||||
|  |                     "Port": 90, | ||||||
|  |                     "Protocol": "HTTP", | ||||||
|  |                     "VpcId": {"Ref": "testVPC"}, | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             "testGroup3": { | ||||||
|  |                 "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", | ||||||
|  |                 "Properties": { | ||||||
|  |                     "Name": "MyTargetGroup", | ||||||
|  |                     "Port": 70, | ||||||
|  |                     "Protocol": "HTTPS", | ||||||
|  |                     "VpcId": {"Ref": "testVPC"}, | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     template_json = json.dumps(template) | ||||||
|  |     cfn_conn.create_stack( | ||||||
|  |         StackName="test-stack", | ||||||
|  |         TemplateBody=template_json, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     describe_target_groups_response = elbv2_client.describe_target_groups() | ||||||
|  |     target_group_dicts = describe_target_groups_response['TargetGroups'] | ||||||
|  |     assert len(target_group_dicts) == 3 | ||||||
|  | 
 | ||||||
|  |     # there should be 2 target groups with the same prefix of 10 characters (since the random suffix is 12) | ||||||
|  |     # and one named MyTargetGroup | ||||||
|  |     assert len([tg for tg in target_group_dicts if tg['TargetGroupName'] == 'MyTargetGroup']) == 1 | ||||||
|  |     assert len( | ||||||
|  |         [tg for tg in target_group_dicts if tg['TargetGroupName'].startswith('test-stack-test')] | ||||||
|  |     ) == 2 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user