From 519b8e59aaf348d34cdf3bffe64e5de4183ab3da Mon Sep 17 00:00:00 2001 From: Lars Fronius Date: Tue, 15 Mar 2016 15:36:02 +0100 Subject: [PATCH 1/2] Propagate stack-level tags to resources According to http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html "All stack-level tags, including automatically created tags, are propagated to resources that AWS CloudFormation supports. Currently, tags are not propagated to Amazon EBS volumes that are created from block device mappings." --- moto/cloudformation/models.py | 2 +- moto/cloudformation/parsing.py | 13 +++++++------ .../test_cloudformation_stack_integration.py | 9 +++++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index d8793b8ea..bf11bc7ce 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -26,7 +26,7 @@ class FakeStack(object): self.output_map = self._create_output_map() def _create_resource_map(self): - resource_map = ResourceMap(self.stack_id, self.name, self.parameters, self.region_name, self.template_dict) + resource_map = ResourceMap(self.stack_id, self.name, self.parameters, self.tags, self.region_name, self.template_dict) resource_map.create() return resource_map diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 26eab6e87..126cf7991 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import collections import functools import logging +import copy from moto.autoscaling import models as autoscaling_models from moto.awslambda import models as lambda_models @@ -264,11 +265,12 @@ class ResourceMap(collections.Mapping): each resources is passed this lazy map that it can grab dependencies from. """ - def __init__(self, stack_id, stack_name, parameters, region_name, template): + def __init__(self, stack_id, stack_name, parameters, tags, region_name, template): self._template = template self._resource_json_map = template['Resources'] self._region_name = region_name self.input_parameters = parameters + self.tags = copy.deepcopy(tags) self.resolved_parameters = {} # Create the default resources @@ -339,13 +341,12 @@ class ResourceMap(collections.Mapping): # Since this is a lazy map, to create every object we just need to # iterate through self. - tags = {'aws:cloudformation:stack-name': self.get('AWS::StackName'), - 'aws:cloudformation:stack-id': self.get('AWS::StackId')} + self.tags.update({'aws:cloudformation:stack-name': self.get('AWS::StackName'), + 'aws:cloudformation:stack-id': self.get('AWS::StackId')}) for resource in self.resources: - self[resource] if isinstance(self[resource], ec2_models.TaggedEC2Resource): - tags['aws:cloudformation:logical-id'] = resource - ec2_models.ec2_backends[self._region_name].create_tags([self[resource].physical_resource_id], tags) + self.tags['aws:cloudformation:logical-id'] = resource + ec2_models.ec2_backends[self._region_name].create_tags([self[resource].physical_resource_id], self.tags) def update(self, template): self.load_mapping() diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 5f52bb9a9..609ab4a21 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -363,6 +363,12 @@ def test_stack_security_groups(): "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "My security group", + "Tags": [ + { + "Key": "bar", + "Value": "baz" + } + ], "SecurityGroupIngress": [{ "IpProtocol": "tcp", "FromPort": "22", @@ -384,6 +390,7 @@ def test_stack_security_groups(): conn.create_stack( "security_group_stack", template_body=security_group_template_json, + tags={"foo":"bar"} ) ec2_conn = boto.ec2.connect_to_region("us-west-1") @@ -395,6 +402,8 @@ def test_stack_security_groups(): ec2_instance.groups[0].id.should.equal(instance_group.id) instance_group.description.should.equal("My security group") + instance_group.tags.should.have.key('foo').which.should.equal('bar') + instance_group.tags.should.have.key('bar').which.should.equal('baz') rule1, rule2 = instance_group.rules int(rule1.to_port).should.equal(22) int(rule1.from_port).should.equal(22) From c94f49cc1ed4dee9bcf96bb509ee0489f72ce200 Mon Sep 17 00:00:00 2001 From: Lars Fronius Date: Tue, 15 Mar 2016 16:50:57 +0100 Subject: [PATCH 2/2] Only count user-assigned tags for TagLimitExceeded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/allocation-tag-restrictions.html "Reserved prefix—aws: AWS-assigned tag names and values are automatically assigned the aws: prefix, which the user cannot assign. AWS-assigned tag names do not count towards the tag limit of 10. User-assigned tag names have the prefix user: in the Cost Allocation Report." --- moto/ec2/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index db2b71bcb..9707eb973 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -774,9 +774,9 @@ class TagBackend(object): raise InvalidParameterValueErrorTagNull() for resource_id in resource_ids: if resource_id in self.tags: - if len(self.tags[resource_id]) + len(tags) > 10: + if len(self.tags[resource_id]) + len([tag for tag in tags if not tag.startswith("aws:")]) > 10: raise TagLimitExceeded() - elif len(tags) > 10: + elif len([tag for tag in tags if not tag.startswith("aws:")]) > 10: raise TagLimitExceeded() for resource_id in resource_ids: for tag in tags: