From 3b68be55d3f97130effedcbe7b01b561d3191df6 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sun, 1 May 2022 19:27:25 +0000 Subject: [PATCH] EC2 - Pass LaunchTemplate tags to new Instances (#5085) --- moto/core/responses.py | 14 -------------- moto/ec2/_models/instances.py | 8 ++++++++ moto/ec2/_models/spot_requests.py | 17 +++-------------- moto/ec2/responses/_base_response.py | 7 +++++++ moto/ec2/utils.py | 13 +++++++++++++ tests/test_ec2/test_instances.py | 23 +++++++++++++++++++++++ 6 files changed, 54 insertions(+), 28 deletions(-) diff --git a/moto/core/responses.py b/moto/core/responses.py index 2555ba2cd..d65848ca0 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -725,20 +725,6 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): return results - def _parse_tag_specification(self): - # [{"ResourceType": _type, "Tag": [{"Key": k, "Value": v}, ..]}] - tag_spec = self._get_multi_param("TagSpecification") - # {_type: {k: v, ..}} - tags = {} - for spec in tag_spec: - if spec["ResourceType"] not in tags: - tags[spec["ResourceType"]] = {} - tags[spec["ResourceType"]].update( - {tag["Key"]: tag["Value"] for tag in spec["Tag"]} - ) - - return tags - def _get_object_map(self, prefix, name="Name", value="Value"): """ Given a query dict like diff --git a/moto/ec2/_models/instances.py b/moto/ec2/_models/instances.py index 406bf3b14..d99a47316 100644 --- a/moto/ec2/_models/instances.py +++ b/moto/ec2/_models/instances.py @@ -22,6 +22,7 @@ from ..utils import ( random_reservation_id, filter_reservations, utc_date_and_time, + convert_tag_spec, ) @@ -70,6 +71,13 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel): self.image_id = template_version.image_id else: self.image_id = image_id + # Check if we have tags to process + if launch_template_arg: + template_version = ec2_backend._get_template_from_args(launch_template_arg) + tag_spec_set = template_version.data.get("TagSpecification", {}) + tags = convert_tag_spec(tag_spec_set) + instance_tags = tags.get("instance", {}) + self.add_tags(instance_tags) self._state = InstanceState("running", 16) self._reason = "" diff --git a/moto/ec2/_models/spot_requests.py b/moto/ec2/_models/spot_requests.py index 5688aeca7..3a753e316 100644 --- a/moto/ec2/_models/spot_requests.py +++ b/moto/ec2/_models/spot_requests.py @@ -11,6 +11,7 @@ from ..utils import ( random_spot_fleet_request_id, random_spot_request_id, generic_filter, + convert_tag_spec, ) @@ -249,7 +250,8 @@ class SpotFleetRequest(TaggedEC2Resource, CloudFormationModel): launch_specs_from_config.append(new_launch_template) for spec in (launch_specs or []) + launch_specs_from_config: - tags = self._extract_tags(spec) + tag_spec_set = spec.get("TagSpecificationSet", []) + tags = convert_tag_spec(tag_spec_set) self.launch_specs.append( SpotFleetLaunchSpec( ebs_optimized=spec.get("EbsOptimized"), @@ -270,19 +272,6 @@ class SpotFleetRequest(TaggedEC2Resource, CloudFormationModel): self.spot_requests = [] self.create_spot_requests(self.target_capacity) - def _extract_tags(self, spec): - # IN: [{"ResourceType": _type, "Tag": [{"Key": k, "Value": v}, ..]}] - # OUT: {_type: {k: v, ..}} - tag_spec_set = spec.get("TagSpecificationSet", []) - tags = {} - for tag_spec in tag_spec_set: - if tag_spec["ResourceType"] not in tags: - tags[tag_spec["ResourceType"]] = {} - tags[tag_spec["ResourceType"]].update( - {tag["Key"]: tag["Value"] for tag in tag_spec["Tag"]} - ) - return tags - @property def physical_resource_id(self): return self.id diff --git a/moto/ec2/responses/_base_response.py b/moto/ec2/responses/_base_response.py index ccb299bcb..2b25c2b3f 100644 --- a/moto/ec2/responses/_base_response.py +++ b/moto/ec2/responses/_base_response.py @@ -1,4 +1,5 @@ from moto.core.responses import BaseResponse +from ..utils import convert_tag_spec class EC2BaseResponse(BaseResponse): @@ -7,3 +8,9 @@ class EC2BaseResponse(BaseResponse): _filters = self._get_multi_param("Filter.") # return {x1: y1, ...} return {f["Name"]: f["Value"] for f in _filters} + + def _parse_tag_specification(self): + # [{"ResourceType": _type, "Tag": [{"Key": k, "Value": v}, ..]}] + tag_spec_set = self._get_multi_param("TagSpecification") + # {_type: {k: v, ..}} + return convert_tag_spec(tag_spec_set) diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 658f26429..7dcce32c6 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -773,3 +773,16 @@ def gen_moto_amis(described_images, drop_images_missing_keys=True): raise err return result + + +def convert_tag_spec(tag_spec_set): + # IN: [{"ResourceType": _type, "Tag": [{"Key": k, "Value": v}, ..]}] + # OUT: {_type: {k: v, ..}} + tags = {} + for tag_spec in tag_spec_set: + if tag_spec["ResourceType"] not in tags: + tags[tag_spec["ResourceType"]] = {} + tags[tag_spec["ResourceType"]].update( + {tag["Key"]: tag["Value"] for tag in tag_spec["Tag"]} + ) + return tags diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index a4d3848f0..77b16cb5e 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -2170,6 +2170,29 @@ def test_create_instance_with_launch_template_id_produces_no_warning( assert len(captured_warnings) == 0 +@mock_ec2 +def test_create_instance_from_launch_template__process_tags(): + client = boto3.client("ec2", region_name="us-west-1") + + template = client.create_launch_template( + LaunchTemplateName=str(uuid4()), + LaunchTemplateData={ + "ImageId": EXAMPLE_AMI_ID, + "TagSpecifications": [ + {"ResourceType": "instance", "Tags": [{"Key": "k", "Value": "v"}]} + ], + }, + )["LaunchTemplate"] + + instance = client.run_instances( + MinCount=1, + MaxCount=1, + LaunchTemplate={"LaunchTemplateId": template["LaunchTemplateId"]}, + )["Instances"][0] + + instance.should.have.key("Tags").equals([{"Key": "k", "Value": "v"}]) + + @mock_ec2 def test_run_instance_and_associate_public_ip(): ec2 = boto3.resource("ec2", "us-west-1")