From aa3b6085d1cd535a319c1f08192b5ed1bfa5db6b Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Wed, 14 Aug 2019 16:11:05 -0500 Subject: [PATCH 01/10] Add basic endpoints for EC2 Launch Templates Specifically, add the CreateLaunchTemplate, CreateLaunchTemplateVersion, DescribeLaunchTemplates, and DescribeLaunchTemplateVersions endpoints. --- moto/ec2/exceptions.py | 7 + moto/ec2/models.py | 87 ++++++++- moto/ec2/responses/__init__.py | 2 + moto/ec2/responses/launch_templates.py | 243 ++++++++++++++++++++++++ moto/ec2/utils.py | 5 + tests/test_ec2/test_launch_templates.py | 215 +++++++++++++++++++++ 6 files changed, 557 insertions(+), 2 deletions(-) create mode 100644 moto/ec2/responses/launch_templates.py create mode 100644 tests/test_ec2/test_launch_templates.py diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 5d5ccd844..453f75d1d 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -523,3 +523,10 @@ class OperationNotPermitted3(EC2ClientError): pcx_id, acceptor_region) ) + +class InvalidLaunchTemplateNameError(EC2ClientError): + def __init__(self): + super(InvalidLaunchTemplateNameError, self).__init__( + "InvalidLaunchTemplateName.AlreadyExistsException", + "Launch template name already in use." + ) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 41a84ec48..2310585ac 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -19,7 +19,8 @@ from boto.ec2.instance import Instance as BotoInstance, Reservation from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest from boto.ec2.launchspecification import LaunchSpecification - +from xml.etree import ElementTree +from xml.dom import minidom from moto.compat import OrderedDict from moto.core import BaseBackend @@ -49,6 +50,7 @@ from .exceptions import ( InvalidKeyPairDuplicateError, InvalidKeyPairFormatError, InvalidKeyPairNameError, + InvalidLaunchTemplateNameError, InvalidNetworkAclIdError, InvalidNetworkAttachmentIdError, InvalidNetworkInterfaceIdError, @@ -98,6 +100,7 @@ from .utils import ( random_internet_gateway_id, random_ip, random_ipv6_cidr, + random_launch_template_id, random_nat_gateway_id, random_key_pair, random_private_ip, @@ -4112,6 +4115,84 @@ class NatGatewayBackend(object): def delete_nat_gateway(self, nat_gateway_id): return self.nat_gateways.pop(nat_gateway_id) +class LaunchTemplateVersion(object): + def __init__(self, template, number, data, description): + self.template = template + self.number = number + self.data = data + self.description = description + self.create_time = utc_date_and_time() + +class LaunchTemplate(TaggedEC2Resource): + def __init__(self, backend, name, template_data, version_description): + self.ec2_backend = backend + self.name = name + self.id = random_launch_template_id() + self.create_time = utc_date_and_time() + + self.versions = [] + self.create_version(template_data, version_description) + self.default_version_number = 1 + + def create_version(self, data, description): + num = len(self.versions) + 1 + version = LaunchTemplateVersion(self, num, data, description) + self.versions.append(version) + return version + + def is_default(self, version): + return self.default_version == version.number + + def get_version(self, num): + return self.versions[num-1] + + def default_version(self): + return self.versions[self.default_version_number-1] + + def latest_version(self): + return self.versions[-1] + + @property + def latest_version_number(self): + return self.latest_version().number + + def get_filter_value(self, filter_name): + if filter_name == 'launch-template-name': + return self.name + else: + return super(LaunchTemplate, self).get_filter_value( + filter_name, "DescribeLaunchTemplates") + +class LaunchTemplateBackend(object): + def __init__(self): + self.launch_templates_by_name = {} + self.launch_templates_by_id = {} + super(LaunchTemplateBackend, self).__init__() + + def create_launch_template(self, name, description, template_data): + if name in self.launch_templates_by_name: + raise InvalidLaunchTemplateNameError() + template = LaunchTemplate(self, name, template_data, description) + self.launch_templates_by_id[template.id] = template + self.launch_templates_by_name[template.name] = template + return template + + def get_launch_template_by_name(self, name): + return self.launch_templates_by_name[name] + + def get_launch_template_by_id(self, templ_id): + return self.launch_templates_by_id[templ_id] + + def get_launch_templates(self, template_names=None, template_ids=None, filters=None): + if template_ids: + templates = [self.launch_templates_by_id[tid] for tid in template_ids] + elif template_names: + templates = [self.launch_templates_by_name[name] for name in template_names] + else: + templates = list(self.launch_templates_by_name.values()) + + return generic_filter(filters, templates) + class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend, RegionsAndZonesBackend, SecurityGroupBackend, AmiBackend, @@ -4122,7 +4203,7 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend, VPCGatewayAttachmentBackend, SpotFleetBackend, SpotRequestBackend, ElasticAddressBackend, KeyPairBackend, DHCPOptionsSetBackend, NetworkAclBackend, VpnGatewayBackend, - CustomerGatewayBackend, NatGatewayBackend): + CustomerGatewayBackend, NatGatewayBackend, LaunchTemplateBackend): def __init__(self, region_name): self.region_name = region_name super(EC2Backend, self).__init__() @@ -4177,6 +4258,8 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend, elif resource_prefix == EC2_RESOURCE_TO_PREFIX['internet-gateway']: self.describe_internet_gateways( internet_gateway_ids=[resource_id]) + elif resource_prefix == EC2_RESOURCE_TO_PREFIX['launch-template']: + self.get_launch_template_by_id(resource_id) elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-acl']: self.get_all_network_acls() elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-interface']: diff --git a/moto/ec2/responses/__init__.py b/moto/ec2/responses/__init__.py index 1222a7ef8..d0648eb50 100644 --- a/moto/ec2/responses/__init__.py +++ b/moto/ec2/responses/__init__.py @@ -14,6 +14,7 @@ from .instances import InstanceResponse from .internet_gateways import InternetGateways from .ip_addresses import IPAddresses from .key_pairs import KeyPairs +from .launch_templates import LaunchTemplates from .monitoring import Monitoring from .network_acls import NetworkACLs from .placement_groups import PlacementGroups @@ -49,6 +50,7 @@ class EC2Response( InternetGateways, IPAddresses, KeyPairs, + LaunchTemplates, Monitoring, NetworkACLs, PlacementGroups, diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py new file mode 100644 index 000000000..ebce54294 --- /dev/null +++ b/moto/ec2/responses/launch_templates.py @@ -0,0 +1,243 @@ +import json +import six +import uuid +from moto.core.responses import BaseResponse +from moto.ec2.models import OWNER_ID +from moto.ec2.exceptions import FilterNotImplementedError +from moto.ec2.utils import filters_from_querystring + +from xml.etree import ElementTree +from xml.dom import minidom + + +def xml_root(name): + root = ElementTree.Element(name, { + "xmlns": "http://ec2.amazonaws.com/doc/2016-11-15/" + }) + request_id = str(uuid.uuid4()) + "example" + ElementTree.SubElement(root, "requestId").text = request_id + + return root + +def xml_serialize(tree, key, value): + if key: + name = key[0].lower() + key[1:] + if isinstance(value, list): + if name[-1] == 's': + name = name[:-1] + + name = name + 'Set' + + node = ElementTree.SubElement(tree, name) + else: + node = tree + + if isinstance(value, (str, int, float)): + node.text = str(value) + elif isinstance(value, bool): + node.text = str(value).lower() + elif isinstance(value, dict): + for dictkey, dictvalue in six.iteritems(value): + xml_serialize(node, dictkey, dictvalue) + elif isinstance(value, list): + for item in value: + xml_serialize(node, 'item', item) + +def pretty_xml(tree): + rough = ElementTree.tostring(tree, 'utf-8') + parsed = minidom.parseString(rough) + return parsed.toprettyxml(indent=' ') + +def parse_object(raw_data): + out_data = {} + for key, value in six.iteritems(raw_data): + key_fix_splits = key.split("_") + l = len(key_fix_splits) + + new_key = "" + for i in range(0, l): + new_key += key_fix_splits[i][0].upper() + key_fix_splits[i][1:] + + data = out_data + splits = new_key.split(".") + for split in splits[:-1]: + if split not in data: + data[split] = {} + data = data[split] + + data[splits[-1]] = value + + out_data = parse_lists(out_data) + return out_data + +def parse_lists(data): + for key, value in six.iteritems(data): + if isinstance(value, dict): + keys = data[key].keys() + is_list = all(map(lambda k: k.isnumeric(), keys)) + + if is_list: + new_value = [] + keys = sorted(list(keys)) + for k in keys: + lvalue = value[k] + if isinstance(lvalue, dict): + lvalue = parse_lists(lvalue) + new_value.append(lvalue) + data[key] = new_value + return data + +class LaunchTemplates(BaseResponse): + def create_launch_template(self): + name = self._get_param('LaunchTemplateName') + version_description = self._get_param('VersionDescription') + tag_spec = self._get_param('TagSpecifications') + + raw_template_data = self._get_dict_param('LaunchTemplateData.') + parsed_template_data = parse_object(raw_template_data) + + if tag_spec: + if 'TagSpecifications' not in parsed_template_data: + parsed_template_data['TagSpecifications'] = [] + parsed_template_data['TagSpecifications'].extend(tag_spec) + + if self.is_not_dryrun('CreateLaunchTemplate'): + template = self.ec2_backend.create_launch_template(name, version_description, parsed_template_data) + version = template.default_version() + + tree = xml_root("CreateLaunchTemplateResponse") + xml_serialize(tree, "launchTemplate", { + "createTime": version.create_time, + "createdBy": "arn:aws:iam::{OWNER_ID}:root".format(OWNER_ID=OWNER_ID), + "defaultVersionNumber": template.default_version_number, + "latestVersionNumber": version.number, + "launchTemplateId": template.id, + "launchTemplateName": template.name + }) + + return pretty_xml(tree) + + def create_launch_template_version(self): + name = self._get_param('LaunchTemplateName') + tmpl_id = self._get_param('LaunchTemplateId') + if name: + template = self.ec2_backend.get_launch_template_by_name(name) + if tmpl_id: + template = self.ec2_backend.get_launch_template_by_id(tmpl_id) + + version_description = self._get_param('VersionDescription') + tag_spec = self._get_param('TagSpecifications') + # source_version = self._get_int_param('SourceVersion') + + raw_template_data = self._get_dict_param('LaunchTemplateData.') + template_data = parse_object(raw_template_data) + + if self.is_not_dryrun('CreateLaunchTemplate'): + version = template.create_version(template_data, version_description) + + tree = xml_root("CreateLaunchTemplateVersionResponse") + xml_serialize(tree, "launchTemplateVersion", { + "createTime": version.create_time, + "createdBy": "arn:aws:iam::{OWNER_ID}:root".format(OWNER_ID=OWNER_ID), + "defaultVersion": template.is_default(version), + "launchTemplateData": version.data, + "launchTemplateId": template.id, + "launchTemplateName": template.name, + "versionDescription": version.description, + "versionNumber": version.number, + }) + return pretty_xml(tree) + + + # def delete_launch_template(self): + # pass + + # def delete_launch_template_versions(self): + # pass + + def describe_launch_template_versions(self): + name = self._get_param('LaunchTemplateName') + template_id = self._get_param('LaunchTemplateId') + if name: + template = self.ec2_backend.get_launch_template_by_name(name) + if template_id: + template = self.ec2_backend.get_launch_template_by_id(template_id) + + max_results = self._get_int_param("MaxResults", 15) + versions = self._get_multi_param("Versions") + min_version = self._get_int_param("MinVersion") + max_version = self._get_int_param("MaxVersion") + + filters = filters_from_querystring(self.querystring) + if filters: + raise FilterNotImplementedError("all filters", "DescribeLaunchTemplateVersions") + + if self.is_not_dryrun('DescribeLaunchTemplateVersions'): + tree = ElementTree.Element("DescribeLaunchTemplateVersionsResponse", { + "xmlns": "http://ec2.amazonaws.com/doc/2016-11-15/", + }) + request_id = ElementTree.SubElement(tree, "requestId") + request_id.text = "65cadec1-b364-4354-8ca8-4176dexample" + + versions_node = ElementTree.SubElement(tree, "launchTemplateVersionSet") + + ret_versions = [] + if versions: + for v in versions: + ret_versions.append(template.get_version(int(v))) + elif min_version: + if max_version: + vMax = max_version + else: + vMax = min_version + max_results + + ret_versions = template.versions[min_version-1:vMax-1] + elif max_version: + ret_versions = template.versions[0:max_version-1] + else: + ret_versions = template.versions + + ret_versions = ret_versions[:max_results] + + for version in ret_versions: + xml_serialize(versions_node, "item", { + "createTime": version.create_time, + "createdBy": "arn:aws:iam::{OWNER_ID}:root".format(OWNER_ID=OWNER_ID), + "defaultVersion": True, + "launchTemplateData": version.data, + "launchTemplateId": template.id, + "launchTemplateName": template.name, + "versionDescription": version.description, + "versionNumber": version.number, + }) + + return pretty_xml(tree) + + def describe_launch_templates(self): + max_results = self._get_int_param("MaxResults", 15) + template_names = self._get_multi_param("LaunchTemplateName") + template_ids = self._get_multi_param("LaunchTemplateId") + filters = filters_from_querystring(self.querystring) + + if self.is_not_dryrun("DescribeLaunchTemplates"): + tree = ElementTree.Element("DescribeLaunchTemplatesResponse") + templates_node = ElementTree.SubElement(tree, "launchTemplates") + + templates = self.ec2_backend.get_launch_templates(template_names=template_names, template_ids=template_ids, filters=filters) + + templates = templates[:max_results] + + for template in templates: + xml_serialize(templates_node, "item", { + "createTime": template.create_time, + "createdBy": "arn:aws:iam::{OWNER_ID}:root".format(OWNER_ID=OWNER_ID), + "defaultVersionNumber": template.default_version_number, + "latestVersionNumber": template.latest_version_number, + "launchTemplateId": template.id, + "launchTemplateName": template.name, + }) + + return pretty_xml(tree) + + # def modify_launch_template(self): + # pass diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index a998f18ef..e67cb39f4 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -20,6 +20,7 @@ EC2_RESOURCE_TO_PREFIX = { 'image': 'ami', 'instance': 'i', 'internet-gateway': 'igw', + 'launch-template': 'lt', 'nat-gateway': 'nat', 'network-acl': 'acl', 'network-acl-subnet-assoc': 'aclassoc', @@ -161,6 +162,10 @@ def random_nat_gateway_id(): return random_id(prefix=EC2_RESOURCE_TO_PREFIX['nat-gateway'], size=17) +def random_launch_template_id(): + return random_id(prefix=EC2_RESOURCE_TO_PREFIX['launch-template'], size=17) + + def random_public_ip(): return '54.214.{0}.{1}'.format(random.choice(range(255)), random.choice(range(255))) diff --git a/tests/test_ec2/test_launch_templates.py b/tests/test_ec2/test_launch_templates.py new file mode 100644 index 000000000..e2e160b6f --- /dev/null +++ b/tests/test_ec2/test_launch_templates.py @@ -0,0 +1,215 @@ +import boto3 +import sure # noqa + +from nose.tools import assert_raises +from botocore.client import ClientError + +from moto import mock_ec2 + +@mock_ec2 +def test_launch_template_create(): + cli = boto3.client("ec2") + + resp = cli.create_launch_template( + LaunchTemplateName="test-template", + + # the absolute minimum needed to create a template without other resources + LaunchTemplateData={ + "TagSpecifications": [{ + "ResourceType": "instance", + "Tags": [{ + "Key": "test", + "Value": "value", + }], + }], + }, + ) + + resp.should.have.key("LaunchTemplate") + lt = resp["LaunchTemplate"] + lt["LaunchTemplateName"].should.equal("test-template") + lt["DefaultVersionNumber"].should.equal(1) + lt["LatestVersionNumber"].should.equal(1) + + with assert_raises(ClientError) as ex: + cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "TagSpecifications": [{ + "ResourceType": "instance", + "Tags": [{ + "Key": "test", + "Value": "value", + }], + }], + }, + ) + + str(ex.exception).should.equal( + 'An error occurred (InvalidLaunchTemplateName.AlreadyExistsException) when calling the CreateLaunchTemplate operation: Launch template name already in use.') + +@mock_ec2 +def test_describe_launch_template_versions(): + template_data = { + "ImageId": "ami-abc123", + "DisableApiTermination": False, + "TagSpecifications": [{ + "ResourceType": "instance", + "Tags": [{ + "Key": "test", + "Value": "value", + }], + }], + "SecurityGroupIds": [ + "sg-1234", + "sg-ab5678", + ], + } + + cli = boto3.client("ec2") + + create_resp = cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData=template_data) + + # test using name + resp = cli.describe_launch_template_versions( + LaunchTemplateName="test-template", + Versions=['1']) + + templ = resp["LaunchTemplateVersions"][0]["LaunchTemplateData"] + templ.should.equal(template_data) + + # test using id + resp = cli.describe_launch_template_versions( + LaunchTemplateId=create_resp["LaunchTemplate"]["LaunchTemplateId"], + Versions=['1']) + + templ = resp["LaunchTemplateVersions"][0]["LaunchTemplateData"] + templ.should.equal(template_data) + +@mock_ec2 +def test_create_launch_template_version(): + cli = boto3.client("ec2") + + create_resp = cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + version_resp = cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-def456" + }, + VersionDescription="new ami") + + version_resp.should.have.key("LaunchTemplateVersion") + version = version_resp["LaunchTemplateVersion"] + version["DefaultVersion"].should.equal(False) + version["LaunchTemplateId"].should.equal(create_resp["LaunchTemplate"]["LaunchTemplateId"]) + version["VersionDescription"].should.equal("new ami") + version["VersionNumber"].should.equal(2) + +@mock_ec2 +def test_describe_template_versions_with_multiple_versions(): + cli = boto3.client("ec2") + + cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-def456" + }, + VersionDescription="new ami") + + resp = cli.describe_launch_template_versions( + LaunchTemplateName="test-template") + + resp["LaunchTemplateVersions"].should.have.length_of(2) + resp["LaunchTemplateVersions"][0]["LaunchTemplateData"]["ImageId"].should.equal("ami-abc123") + resp["LaunchTemplateVersions"][1]["LaunchTemplateData"]["ImageId"].should.equal("ami-def456") + +@mock_ec2 +def test_describe_launch_templates(): + cli = boto3.client("ec2") + + lt_ids = [] + r = cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + lt_ids.append(r["LaunchTemplate"]["LaunchTemplateId"]) + + r = cli.create_launch_template( + LaunchTemplateName="test-template2", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + lt_ids.append(r["LaunchTemplate"]["LaunchTemplateId"]) + + # general call, all templates + resp = cli.describe_launch_templates() + resp.should.have.key("LaunchTemplates") + resp["LaunchTemplates"].should.have.length_of(2) + resp["LaunchTemplates"][0]["LaunchTemplateName"].should.equal("test-template") + resp["LaunchTemplates"][1]["LaunchTemplateName"].should.equal("test-template2") + + # filter by names + resp = cli.describe_launch_templates( + LaunchTemplateNames=["test-template2", "test-template"]) + resp.should.have.key("LaunchTemplates") + resp["LaunchTemplates"].should.have.length_of(2) + resp["LaunchTemplates"][0]["LaunchTemplateName"].should.equal("test-template2") + resp["LaunchTemplates"][1]["LaunchTemplateName"].should.equal("test-template") + + # filter by ids + resp = cli.describe_launch_templates(LaunchTemplateIds=lt_ids) + resp.should.have.key("LaunchTemplates") + resp["LaunchTemplates"].should.have.length_of(2) + resp["LaunchTemplates"][0]["LaunchTemplateName"].should.equal("test-template") + resp["LaunchTemplates"][1]["LaunchTemplateName"].should.equal("test-template2") + +@mock_ec2 +def test_describe_launch_templates_with_filters(): + cli = boto3.client("ec2") + + r = cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + cli.create_tags( + Resources=[r["LaunchTemplate"]["LaunchTemplateId"]], + Tags=[ + {"Key": "tag1", "Value": "a value"}, + {"Key": "another-key", "Value": "this value"}, + ]) + + cli.create_launch_template( + LaunchTemplateName="no-tags", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + resp = cli.describe_launch_templates(Filters=[{ + "Name": "tag:tag1", "Values": ["a value"] + }]) + + resp["LaunchTemplates"].should.have.length_of(1) + resp["LaunchTemplates"][0]["LaunchTemplateName"].should.equal("test-template") + + resp = cli.describe_launch_templates(Filters=[{ + "Name": "launch-template-name", "Values": ["no-tags"] + }]) + resp["LaunchTemplates"].should.have.length_of(1) + resp["LaunchTemplates"][0]["LaunchTemplateName"].should.equal("no-tags") + From f939531ae9299c8c0b40be84d9bc335dd3b590c4 Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Wed, 14 Aug 2019 16:19:30 -0500 Subject: [PATCH 02/10] Fun with whitespace (flake8 violation fixes) --- moto/ec2/exceptions.py | 1 + moto/ec2/models.py | 9 +++++---- moto/ec2/responses/launch_templates.py | 20 ++++++++++++-------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 453f75d1d..b7a49cc57 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -524,6 +524,7 @@ class OperationNotPermitted3(EC2ClientError): acceptor_region) ) + class InvalidLaunchTemplateNameError(EC2ClientError): def __init__(self): super(InvalidLaunchTemplateNameError, self).__init__( diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 2310585ac..3cb0bad93 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -19,8 +19,6 @@ from boto.ec2.instance import Instance as BotoInstance, Reservation from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest from boto.ec2.launchspecification import LaunchSpecification -from xml.etree import ElementTree -from xml.dom import minidom from moto.compat import OrderedDict from moto.core import BaseBackend @@ -4115,6 +4113,7 @@ class NatGatewayBackend(object): def delete_nat_gateway(self, nat_gateway_id): return self.nat_gateways.pop(nat_gateway_id) + class LaunchTemplateVersion(object): def __init__(self, template, number, data, description): self.template = template @@ -4123,6 +4122,7 @@ class LaunchTemplateVersion(object): self.description = description self.create_time = utc_date_and_time() + class LaunchTemplate(TaggedEC2Resource): def __init__(self, backend, name, template_data, version_description): self.ec2_backend = backend @@ -4144,10 +4144,10 @@ class LaunchTemplate(TaggedEC2Resource): return self.default_version == version.number def get_version(self, num): - return self.versions[num-1] + return self.versions[num - 1] def default_version(self): - return self.versions[self.default_version_number-1] + return self.versions[self.default_version_number - 1] def latest_version(self): return self.versions[-1] @@ -4163,6 +4163,7 @@ class LaunchTemplate(TaggedEC2Resource): return super(LaunchTemplate, self).get_filter_value( filter_name, "DescribeLaunchTemplates") + class LaunchTemplateBackend(object): def __init__(self): self.launch_templates_by_name = {} diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index ebce54294..d1bfaa6d4 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -1,4 +1,3 @@ -import json import six import uuid from moto.core.responses import BaseResponse @@ -19,6 +18,7 @@ def xml_root(name): return root + def xml_serialize(tree, key, value): if key: name = key[0].lower() + key[1:] @@ -43,19 +43,21 @@ def xml_serialize(tree, key, value): for item in value: xml_serialize(node, 'item', item) + def pretty_xml(tree): rough = ElementTree.tostring(tree, 'utf-8') parsed = minidom.parseString(rough) return parsed.toprettyxml(indent=' ') + def parse_object(raw_data): out_data = {} for key, value in six.iteritems(raw_data): key_fix_splits = key.split("_") - l = len(key_fix_splits) + key_len = len(key_fix_splits) new_key = "" - for i in range(0, l): + for i in range(0, key_len): new_key += key_fix_splits[i][0].upper() + key_fix_splits[i][1:] data = out_data @@ -70,6 +72,7 @@ def parse_object(raw_data): out_data = parse_lists(out_data) return out_data + def parse_lists(data): for key, value in six.iteritems(data): if isinstance(value, dict): @@ -87,6 +90,7 @@ def parse_lists(data): data[key] = new_value return data + class LaunchTemplates(BaseResponse): def create_launch_template(self): name = self._get_param('LaunchTemplateName') @@ -126,8 +130,6 @@ class LaunchTemplates(BaseResponse): template = self.ec2_backend.get_launch_template_by_id(tmpl_id) version_description = self._get_param('VersionDescription') - tag_spec = self._get_param('TagSpecifications') - # source_version = self._get_int_param('SourceVersion') raw_template_data = self._get_dict_param('LaunchTemplateData.') template_data = parse_object(raw_template_data) @@ -148,7 +150,6 @@ class LaunchTemplates(BaseResponse): }) return pretty_xml(tree) - # def delete_launch_template(self): # pass @@ -191,9 +192,12 @@ class LaunchTemplates(BaseResponse): else: vMax = min_version + max_results - ret_versions = template.versions[min_version-1:vMax-1] + vMin = min_version - 1 + vMax = vMax - 1 + ret_versions = template.versions[vMin:vMax] elif max_version: - ret_versions = template.versions[0:max_version-1] + vMax = max_version - 1 + ret_versions = template.versions[:vMax] else: ret_versions = template.versions From 1de63b1691f4f338ee01a8e0d17affc6853f1c9f Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Wed, 14 Aug 2019 16:32:01 -0500 Subject: [PATCH 03/10] Specify region in launch template tests --- tests/test_ec2/test_launch_templates.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_ec2/test_launch_templates.py b/tests/test_ec2/test_launch_templates.py index e2e160b6f..ae5214c0d 100644 --- a/tests/test_ec2/test_launch_templates.py +++ b/tests/test_ec2/test_launch_templates.py @@ -8,7 +8,7 @@ from moto import mock_ec2 @mock_ec2 def test_launch_template_create(): - cli = boto3.client("ec2") + cli = boto3.client("ec2", region_name="us-east-1") resp = cli.create_launch_template( LaunchTemplateName="test-template", @@ -66,7 +66,7 @@ def test_describe_launch_template_versions(): ], } - cli = boto3.client("ec2") + cli = boto3.client("ec2", region_name="us-east-1") create_resp = cli.create_launch_template( LaunchTemplateName="test-template", @@ -90,7 +90,7 @@ def test_describe_launch_template_versions(): @mock_ec2 def test_create_launch_template_version(): - cli = boto3.client("ec2") + cli = boto3.client("ec2", region_name="us-east-1") create_resp = cli.create_launch_template( LaunchTemplateName="test-template", @@ -114,7 +114,7 @@ def test_create_launch_template_version(): @mock_ec2 def test_describe_template_versions_with_multiple_versions(): - cli = boto3.client("ec2") + cli = boto3.client("ec2", region_name="us-east-1") cli.create_launch_template( LaunchTemplateName="test-template", @@ -138,7 +138,7 @@ def test_describe_template_versions_with_multiple_versions(): @mock_ec2 def test_describe_launch_templates(): - cli = boto3.client("ec2") + cli = boto3.client("ec2", region_name="us-east-1") lt_ids = [] r = cli.create_launch_template( @@ -179,7 +179,7 @@ def test_describe_launch_templates(): @mock_ec2 def test_describe_launch_templates_with_filters(): - cli = boto3.client("ec2") + cli = boto3.client("ec2", region_name="us-east-1") r = cli.create_launch_template( LaunchTemplateName="test-template", From 5f80014332a3303d54be7189bba31d7ba10f28af Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Wed, 14 Aug 2019 17:32:59 -0500 Subject: [PATCH 04/10] Serialize unicode as string in python2 --- moto/ec2/responses/launch_templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index d1bfaa6d4..14337d17f 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -32,7 +32,7 @@ def xml_serialize(tree, key, value): else: node = tree - if isinstance(value, (str, int, float)): + if isinstance(value, (str, int, float, six.text_type)): node.text = str(value) elif isinstance(value, bool): node.text = str(value).lower() From ed82264806b0d47146853a683b0ae5a879ef35a0 Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Wed, 14 Aug 2019 17:31:57 -0500 Subject: [PATCH 05/10] Rework LaunchTemplateBackend to be keep only one copy of a template, and be ordered The original LaunchTemplateBackend kept two copies of a template, one for referencing it by name and one for referencing it by id. This change switches to using one copy, by id, and adding a lookup dict for mapping names to ids. Additionally, to fix the python2 test ordering issues, the launch template dict was changed to an OrderedDict. --- moto/ec2/models.py | 33 +++++++++++++++----------- moto/ec2/responses/launch_templates.py | 4 ++-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 3cb0bad93..10d6f2b28 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -4166,31 +4166,36 @@ class LaunchTemplate(TaggedEC2Resource): class LaunchTemplateBackend(object): def __init__(self): - self.launch_templates_by_name = {} - self.launch_templates_by_id = {} + self.launch_template_name_to_ids = {} + self.launch_templates = OrderedDict() + self.launch_template_insert_order = [] super(LaunchTemplateBackend, self).__init__() def create_launch_template(self, name, description, template_data): - if name in self.launch_templates_by_name: + if name in self.launch_template_name_to_ids: raise InvalidLaunchTemplateNameError() template = LaunchTemplate(self, name, template_data, description) - self.launch_templates_by_id[template.id] = template - self.launch_templates_by_name[template.name] = template + self.launch_templates[template.id] = template + self.launch_template_name_to_ids[template.name] = template.id + self.launch_template_insert_order.append(template.id) return template - def get_launch_template_by_name(self, name): - return self.launch_templates_by_name[name] + def get_launch_template(self, template_id): + return self.launch_templates[template_id] - def get_launch_template_by_id(self, templ_id): - return self.launch_templates_by_id[templ_id] + def get_launch_template_by_name(self, name): + return self.get_launch_template(self.launch_template_name_to_ids[name]) def get_launch_templates(self, template_names=None, template_ids=None, filters=None): + if template_names and not template_ids: + template_ids = [] + for name in template_names: + template_ids.append(self.launch_template_name_to_ids[name]) + if template_ids: - templates = [self.launch_templates_by_id[tid] for tid in template_ids] - elif template_names: - templates = [self.launch_templates_by_name[name] for name in template_names] + templates = [self.launch_templates[tid] for tid in template_ids] else: - templates = list(self.launch_templates_by_name.values()) + templates = list(self.launch_templates.values()) return generic_filter(filters, templates) @@ -4260,7 +4265,7 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend, self.describe_internet_gateways( internet_gateway_ids=[resource_id]) elif resource_prefix == EC2_RESOURCE_TO_PREFIX['launch-template']: - self.get_launch_template_by_id(resource_id) + self.get_launch_template(resource_id) elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-acl']: self.get_all_network_acls() elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-interface']: diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index 14337d17f..ab6f54be1 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -127,7 +127,7 @@ class LaunchTemplates(BaseResponse): if name: template = self.ec2_backend.get_launch_template_by_name(name) if tmpl_id: - template = self.ec2_backend.get_launch_template_by_id(tmpl_id) + template = self.ec2_backend.get_launch_template(tmpl_id) version_description = self._get_param('VersionDescription') @@ -162,7 +162,7 @@ class LaunchTemplates(BaseResponse): if name: template = self.ec2_backend.get_launch_template_by_name(name) if template_id: - template = self.ec2_backend.get_launch_template_by_id(template_id) + template = self.ec2_backend.get_launch_template(template_id) max_results = self._get_int_param("MaxResults", 15) versions = self._get_multi_param("Versions") From 154b4ef84483626d028590e6e22c0ce9901abaaa Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Mon, 19 Aug 2019 17:54:35 -0500 Subject: [PATCH 06/10] Simplify xml_serialize, warn when unknown type used --- moto/ec2/responses/launch_templates.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index ab6f54be1..36c10eea1 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -20,28 +20,27 @@ def xml_root(name): def xml_serialize(tree, key, value): - if key: - name = key[0].lower() + key[1:] - if isinstance(value, list): - if name[-1] == 's': - name = name[:-1] + name = key[0].lower() + key[1:] + if isinstance(value, list): + if name[-1] == 's': + name = name[:-1] - name = name + 'Set' + name = name + 'Set' - node = ElementTree.SubElement(tree, name) - else: - node = tree + node = ElementTree.SubElement(tree, name) if isinstance(value, (str, int, float, six.text_type)): node.text = str(value) - elif isinstance(value, bool): - node.text = str(value).lower() elif isinstance(value, dict): for dictkey, dictvalue in six.iteritems(value): xml_serialize(node, dictkey, dictvalue) elif isinstance(value, list): for item in value: xml_serialize(node, 'item', item) + elif value == None: + pass + else: + raise NotImplementedError("Don't know how to serialize \"{}\" to xml".format(value.__class__)) def pretty_xml(tree): From 743e5be4d3368fd4f69b31bbf4da2bcb58c9a6b8 Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Mon, 19 Aug 2019 17:57:39 -0500 Subject: [PATCH 07/10] Confirm describe_launch_template_versions works with Versions, MinVersion, and MaxVersion options --- moto/ec2/responses/launch_templates.py | 6 +- tests/test_ec2/test_launch_templates.py | 143 ++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 3 deletions(-) diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index 36c10eea1..870a8be74 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -164,10 +164,11 @@ class LaunchTemplates(BaseResponse): template = self.ec2_backend.get_launch_template(template_id) max_results = self._get_int_param("MaxResults", 15) - versions = self._get_multi_param("Versions") + versions = self._get_multi_param("LaunchTemplateVersion") min_version = self._get_int_param("MinVersion") max_version = self._get_int_param("MaxVersion") + filters = filters_from_querystring(self.querystring) if filters: raise FilterNotImplementedError("all filters", "DescribeLaunchTemplateVersions") @@ -192,10 +193,9 @@ class LaunchTemplates(BaseResponse): vMax = min_version + max_results vMin = min_version - 1 - vMax = vMax - 1 ret_versions = template.versions[vMin:vMax] elif max_version: - vMax = max_version - 1 + vMax = max_version ret_versions = template.versions[:vMax] else: ret_versions = template.versions diff --git a/tests/test_ec2/test_launch_templates.py b/tests/test_ec2/test_launch_templates.py index ae5214c0d..afe0488ce 100644 --- a/tests/test_ec2/test_launch_templates.py +++ b/tests/test_ec2/test_launch_templates.py @@ -48,6 +48,7 @@ def test_launch_template_create(): str(ex.exception).should.equal( 'An error occurred (InvalidLaunchTemplateName.AlreadyExistsException) when calling the CreateLaunchTemplate operation: Launch template name already in use.') + @mock_ec2 def test_describe_launch_template_versions(): template_data = { @@ -136,6 +137,148 @@ def test_describe_template_versions_with_multiple_versions(): resp["LaunchTemplateVersions"][0]["LaunchTemplateData"]["ImageId"].should.equal("ami-abc123") resp["LaunchTemplateVersions"][1]["LaunchTemplateData"]["ImageId"].should.equal("ami-def456") + +@mock_ec2 +def test_describe_launch_template_versions_with_versions_option(): + cli = boto3.client("ec2", region_name="us-east-1") + + cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-def456" + }, + VersionDescription="new ami") + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-hij789" + }, + VersionDescription="new ami, again") + + resp = cli.describe_launch_template_versions( + LaunchTemplateName="test-template", + Versions=["2","3"]) + + resp["LaunchTemplateVersions"].should.have.length_of(2) + resp["LaunchTemplateVersions"][0]["LaunchTemplateData"]["ImageId"].should.equal("ami-def456") + resp["LaunchTemplateVersions"][1]["LaunchTemplateData"]["ImageId"].should.equal("ami-hij789") + + +@mock_ec2 +def test_describe_launch_template_versions_with_min(): + cli = boto3.client("ec2", region_name="us-east-1") + + cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-def456" + }, + VersionDescription="new ami") + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-hij789" + }, + VersionDescription="new ami, again") + + resp = cli.describe_launch_template_versions( + LaunchTemplateName="test-template", + MinVersion="2") + + resp["LaunchTemplateVersions"].should.have.length_of(2) + resp["LaunchTemplateVersions"][0]["LaunchTemplateData"]["ImageId"].should.equal("ami-def456") + resp["LaunchTemplateVersions"][1]["LaunchTemplateData"]["ImageId"].should.equal("ami-hij789") + + +@mock_ec2 +def test_describe_launch_template_versions_with_max(): + cli = boto3.client("ec2", region_name="us-east-1") + + cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-def456" + }, + VersionDescription="new ami") + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-hij789" + }, + VersionDescription="new ami, again") + + resp = cli.describe_launch_template_versions( + LaunchTemplateName="test-template", + MaxVersion="2") + + resp["LaunchTemplateVersions"].should.have.length_of(2) + resp["LaunchTemplateVersions"][0]["LaunchTemplateData"]["ImageId"].should.equal("ami-abc123") + resp["LaunchTemplateVersions"][1]["LaunchTemplateData"]["ImageId"].should.equal("ami-def456") + + +@mock_ec2 +def test_describe_launch_template_versions_with_min_and_max(): + cli = boto3.client("ec2", region_name="us-east-1") + + cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-def456" + }, + VersionDescription="new ami") + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-hij789" + }, + VersionDescription="new ami, again") + + cli.create_launch_template_version( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-345abc" + }, + VersionDescription="new ami, because why not") + + resp = cli.describe_launch_template_versions( + LaunchTemplateName="test-template", + MinVersion="2", + MaxVersion="3") + + resp["LaunchTemplateVersions"].should.have.length_of(2) + resp["LaunchTemplateVersions"][0]["LaunchTemplateData"]["ImageId"].should.equal("ami-def456") + resp["LaunchTemplateVersions"][1]["LaunchTemplateData"]["ImageId"].should.equal("ami-hij789") + + + @mock_ec2 def test_describe_launch_templates(): cli = boto3.client("ec2", region_name="us-east-1") From a1aa08771850b27afafaaa1c821528b02adf5d9c Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Mon, 19 Aug 2019 17:58:19 -0500 Subject: [PATCH 08/10] Add test for creating launch templates with TagSpecifications option --- moto/ec2/responses/launch_templates.py | 18 ++++++++++----- tests/test_ec2/test_launch_templates.py | 29 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index 870a8be74..4863492bb 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -94,17 +94,25 @@ class LaunchTemplates(BaseResponse): def create_launch_template(self): name = self._get_param('LaunchTemplateName') version_description = self._get_param('VersionDescription') - tag_spec = self._get_param('TagSpecifications') + tag_spec = self._parse_tag_specification("TagSpecification") raw_template_data = self._get_dict_param('LaunchTemplateData.') parsed_template_data = parse_object(raw_template_data) - if tag_spec: - if 'TagSpecifications' not in parsed_template_data: - parsed_template_data['TagSpecifications'] = [] - parsed_template_data['TagSpecifications'].extend(tag_spec) if self.is_not_dryrun('CreateLaunchTemplate'): + if tag_spec: + if 'TagSpecifications' not in parsed_template_data: + parsed_template_data['TagSpecifications'] = [] + converted_tag_spec = [] + for resource_type, tags in six.iteritems(tag_spec): + converted_tag_spec.append({ + "ResourceType": resource_type, + "Tags": [{"Key": key, "Value": value} for key, value in six.iteritems(tags)], + }) + + parsed_template_data['TagSpecifications'].extend(converted_tag_spec) + template = self.ec2_backend.create_launch_template(name, version_description, parsed_template_data) version = template.default_version() diff --git a/tests/test_ec2/test_launch_templates.py b/tests/test_ec2/test_launch_templates.py index afe0488ce..0cdd7ae31 100644 --- a/tests/test_ec2/test_launch_templates.py +++ b/tests/test_ec2/test_launch_templates.py @@ -356,3 +356,32 @@ def test_describe_launch_templates_with_filters(): resp["LaunchTemplates"].should.have.length_of(1) resp["LaunchTemplates"][0]["LaunchTemplateName"].should.equal("no-tags") + +@mock_ec2 +def test_create_launch_template_with_tag_spec(): + cli = boto3.client("ec2", region_name="us-east-1") + + cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={"ImageId":"ami-abc123"}, + TagSpecifications=[{ + "ResourceType": "instance", + "Tags": [ + {"Key": "key", "Value": "value"} + ] + }], + ) + + resp = cli.describe_launch_template_versions( + LaunchTemplateName="test-template", + Versions=["1"]) + version = resp["LaunchTemplateVersions"][0] + + version["LaunchTemplateData"].should.have.key("TagSpecifications") + version["LaunchTemplateData"]["TagSpecifications"].should.have.length_of(1) + version["LaunchTemplateData"]["TagSpecifications"][0].should.equal({ + "ResourceType": "instance", + "Tags": [ + {"Key": "key", "Value": "value"} + ] + }) From 4929298f1f339a0e9110a4ce87b2f1052e08d9cb Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Mon, 19 Aug 2019 17:58:48 -0500 Subject: [PATCH 09/10] Test create_launch_template_version using launch_template id --- tests/test_ec2/test_launch_templates.py | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/test_ec2/test_launch_templates.py b/tests/test_ec2/test_launch_templates.py index 0cdd7ae31..bac98f8fc 100644 --- a/tests/test_ec2/test_launch_templates.py +++ b/tests/test_ec2/test_launch_templates.py @@ -114,7 +114,32 @@ def test_create_launch_template_version(): version["VersionNumber"].should.equal(2) @mock_ec2 -def test_describe_template_versions_with_multiple_versions(): +def test_create_launch_template_version_by_id(): + cli = boto3.client("ec2", region_name="us-east-1") + + create_resp = cli.create_launch_template( + LaunchTemplateName="test-template", + LaunchTemplateData={ + "ImageId": "ami-abc123" + }) + + version_resp = cli.create_launch_template_version( + LaunchTemplateId=create_resp["LaunchTemplate"]["LaunchTemplateId"], + LaunchTemplateData={ + "ImageId": "ami-def456" + }, + VersionDescription="new ami") + + version_resp.should.have.key("LaunchTemplateVersion") + version = version_resp["LaunchTemplateVersion"] + version["DefaultVersion"].should.equal(False) + version["LaunchTemplateId"].should.equal(create_resp["LaunchTemplate"]["LaunchTemplateId"]) + version["VersionDescription"].should.equal("new ami") + version["VersionNumber"].should.equal(2) + + +@mock_ec2 +def test_describe_launch_template_versions_with_multiple_versions(): cli = boto3.client("ec2", region_name="us-east-1") cli.create_launch_template( From d2ce3a9e043ba8f7b22eea6cda1a5c09624fc354 Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Mon, 19 Aug 2019 18:01:44 -0500 Subject: [PATCH 10/10] Flake8 fixes --- moto/ec2/responses/launch_templates.py | 4 +--- tests/test_ec2/test_launch_templates.py | 9 ++++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/moto/ec2/responses/launch_templates.py b/moto/ec2/responses/launch_templates.py index 4863492bb..a8d92a928 100644 --- a/moto/ec2/responses/launch_templates.py +++ b/moto/ec2/responses/launch_templates.py @@ -37,7 +37,7 @@ def xml_serialize(tree, key, value): elif isinstance(value, list): for item in value: xml_serialize(node, 'item', item) - elif value == None: + elif value is None: pass else: raise NotImplementedError("Don't know how to serialize \"{}\" to xml".format(value.__class__)) @@ -99,7 +99,6 @@ class LaunchTemplates(BaseResponse): raw_template_data = self._get_dict_param('LaunchTemplateData.') parsed_template_data = parse_object(raw_template_data) - if self.is_not_dryrun('CreateLaunchTemplate'): if tag_spec: if 'TagSpecifications' not in parsed_template_data: @@ -176,7 +175,6 @@ class LaunchTemplates(BaseResponse): min_version = self._get_int_param("MinVersion") max_version = self._get_int_param("MaxVersion") - filters = filters_from_querystring(self.querystring) if filters: raise FilterNotImplementedError("all filters", "DescribeLaunchTemplateVersions") diff --git a/tests/test_ec2/test_launch_templates.py b/tests/test_ec2/test_launch_templates.py index bac98f8fc..87e1d3986 100644 --- a/tests/test_ec2/test_launch_templates.py +++ b/tests/test_ec2/test_launch_templates.py @@ -6,6 +6,7 @@ from botocore.client import ClientError from moto import mock_ec2 + @mock_ec2 def test_launch_template_create(): cli = boto3.client("ec2", region_name="us-east-1") @@ -89,6 +90,7 @@ def test_describe_launch_template_versions(): templ = resp["LaunchTemplateVersions"][0]["LaunchTemplateData"] templ.should.equal(template_data) + @mock_ec2 def test_create_launch_template_version(): cli = boto3.client("ec2", region_name="us-east-1") @@ -113,6 +115,7 @@ def test_create_launch_template_version(): version["VersionDescription"].should.equal("new ami") version["VersionNumber"].should.equal(2) + @mock_ec2 def test_create_launch_template_version_by_id(): cli = boto3.client("ec2", region_name="us-east-1") @@ -189,7 +192,7 @@ def test_describe_launch_template_versions_with_versions_option(): resp = cli.describe_launch_template_versions( LaunchTemplateName="test-template", - Versions=["2","3"]) + Versions=["2", "3"]) resp["LaunchTemplateVersions"].should.have.length_of(2) resp["LaunchTemplateVersions"][0]["LaunchTemplateData"]["ImageId"].should.equal("ami-def456") @@ -303,7 +306,6 @@ def test_describe_launch_template_versions_with_min_and_max(): resp["LaunchTemplateVersions"][1]["LaunchTemplateData"]["ImageId"].should.equal("ami-hij789") - @mock_ec2 def test_describe_launch_templates(): cli = boto3.client("ec2", region_name="us-east-1") @@ -345,6 +347,7 @@ def test_describe_launch_templates(): resp["LaunchTemplates"][0]["LaunchTemplateName"].should.equal("test-template") resp["LaunchTemplates"][1]["LaunchTemplateName"].should.equal("test-template2") + @mock_ec2 def test_describe_launch_templates_with_filters(): cli = boto3.client("ec2", region_name="us-east-1") @@ -388,7 +391,7 @@ def test_create_launch_template_with_tag_spec(): cli.create_launch_template( LaunchTemplateName="test-template", - LaunchTemplateData={"ImageId":"ami-abc123"}, + LaunchTemplateData={"ImageId": "ami-abc123"}, TagSpecifications=[{ "ResourceType": "instance", "Tags": [