Merge pull request #2369 from dkuntz2/implement-launch-templates
Add basic endpoints for EC2 Launch Templates
This commit is contained in:
commit
d5e7334e5b
@ -523,3 +523,11 @@ class OperationNotPermitted3(EC2ClientError):
|
|||||||
pcx_id,
|
pcx_id,
|
||||||
acceptor_region)
|
acceptor_region)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidLaunchTemplateNameError(EC2ClientError):
|
||||||
|
def __init__(self):
|
||||||
|
super(InvalidLaunchTemplateNameError, self).__init__(
|
||||||
|
"InvalidLaunchTemplateName.AlreadyExistsException",
|
||||||
|
"Launch template name already in use."
|
||||||
|
)
|
||||||
|
@ -20,7 +20,6 @@ from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
|
|||||||
from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
|
from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
|
||||||
from boto.ec2.launchspecification import LaunchSpecification
|
from boto.ec2.launchspecification import LaunchSpecification
|
||||||
|
|
||||||
|
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
from moto.core.models import Model, BaseModel
|
from moto.core.models import Model, BaseModel
|
||||||
@ -49,6 +48,7 @@ from .exceptions import (
|
|||||||
InvalidKeyPairDuplicateError,
|
InvalidKeyPairDuplicateError,
|
||||||
InvalidKeyPairFormatError,
|
InvalidKeyPairFormatError,
|
||||||
InvalidKeyPairNameError,
|
InvalidKeyPairNameError,
|
||||||
|
InvalidLaunchTemplateNameError,
|
||||||
InvalidNetworkAclIdError,
|
InvalidNetworkAclIdError,
|
||||||
InvalidNetworkAttachmentIdError,
|
InvalidNetworkAttachmentIdError,
|
||||||
InvalidNetworkInterfaceIdError,
|
InvalidNetworkInterfaceIdError,
|
||||||
@ -98,6 +98,7 @@ from .utils import (
|
|||||||
random_internet_gateway_id,
|
random_internet_gateway_id,
|
||||||
random_ip,
|
random_ip,
|
||||||
random_ipv6_cidr,
|
random_ipv6_cidr,
|
||||||
|
random_launch_template_id,
|
||||||
random_nat_gateway_id,
|
random_nat_gateway_id,
|
||||||
random_key_pair,
|
random_key_pair,
|
||||||
random_private_ip,
|
random_private_ip,
|
||||||
@ -4113,6 +4114,92 @@ class NatGatewayBackend(object):
|
|||||||
return self.nat_gateways.pop(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_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_template_name_to_ids:
|
||||||
|
raise InvalidLaunchTemplateNameError()
|
||||||
|
template = LaunchTemplate(self, name, template_data, description)
|
||||||
|
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(self, template_id):
|
||||||
|
return self.launch_templates[template_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[tid] for tid in template_ids]
|
||||||
|
else:
|
||||||
|
templates = list(self.launch_templates.values())
|
||||||
|
|
||||||
|
return generic_filter(filters, templates)
|
||||||
|
|
||||||
|
|
||||||
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend,
|
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend,
|
||||||
RegionsAndZonesBackend, SecurityGroupBackend, AmiBackend,
|
RegionsAndZonesBackend, SecurityGroupBackend, AmiBackend,
|
||||||
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
|
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
|
||||||
@ -4122,7 +4209,7 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend,
|
|||||||
VPCGatewayAttachmentBackend, SpotFleetBackend,
|
VPCGatewayAttachmentBackend, SpotFleetBackend,
|
||||||
SpotRequestBackend, ElasticAddressBackend, KeyPairBackend,
|
SpotRequestBackend, ElasticAddressBackend, KeyPairBackend,
|
||||||
DHCPOptionsSetBackend, NetworkAclBackend, VpnGatewayBackend,
|
DHCPOptionsSetBackend, NetworkAclBackend, VpnGatewayBackend,
|
||||||
CustomerGatewayBackend, NatGatewayBackend):
|
CustomerGatewayBackend, NatGatewayBackend, LaunchTemplateBackend):
|
||||||
def __init__(self, region_name):
|
def __init__(self, region_name):
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
super(EC2Backend, self).__init__()
|
super(EC2Backend, self).__init__()
|
||||||
@ -4177,6 +4264,8 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, EBSBackend,
|
|||||||
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['internet-gateway']:
|
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['internet-gateway']:
|
||||||
self.describe_internet_gateways(
|
self.describe_internet_gateways(
|
||||||
internet_gateway_ids=[resource_id])
|
internet_gateway_ids=[resource_id])
|
||||||
|
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['launch-template']:
|
||||||
|
self.get_launch_template(resource_id)
|
||||||
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-acl']:
|
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-acl']:
|
||||||
self.get_all_network_acls()
|
self.get_all_network_acls()
|
||||||
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-interface']:
|
elif resource_prefix == EC2_RESOURCE_TO_PREFIX['network-interface']:
|
||||||
|
@ -14,6 +14,7 @@ from .instances import InstanceResponse
|
|||||||
from .internet_gateways import InternetGateways
|
from .internet_gateways import InternetGateways
|
||||||
from .ip_addresses import IPAddresses
|
from .ip_addresses import IPAddresses
|
||||||
from .key_pairs import KeyPairs
|
from .key_pairs import KeyPairs
|
||||||
|
from .launch_templates import LaunchTemplates
|
||||||
from .monitoring import Monitoring
|
from .monitoring import Monitoring
|
||||||
from .network_acls import NetworkACLs
|
from .network_acls import NetworkACLs
|
||||||
from .placement_groups import PlacementGroups
|
from .placement_groups import PlacementGroups
|
||||||
@ -49,6 +50,7 @@ class EC2Response(
|
|||||||
InternetGateways,
|
InternetGateways,
|
||||||
IPAddresses,
|
IPAddresses,
|
||||||
KeyPairs,
|
KeyPairs,
|
||||||
|
LaunchTemplates,
|
||||||
Monitoring,
|
Monitoring,
|
||||||
NetworkACLs,
|
NetworkACLs,
|
||||||
PlacementGroups,
|
PlacementGroups,
|
||||||
|
252
moto/ec2/responses/launch_templates.py
Normal file
252
moto/ec2/responses/launch_templates.py
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
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):
|
||||||
|
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)
|
||||||
|
|
||||||
|
if isinstance(value, (str, int, float, six.text_type)):
|
||||||
|
node.text = str(value)
|
||||||
|
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 is None:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Don't know how to serialize \"{}\" to xml".format(value.__class__))
|
||||||
|
|
||||||
|
|
||||||
|
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("_")
|
||||||
|
key_len = len(key_fix_splits)
|
||||||
|
|
||||||
|
new_key = ""
|
||||||
|
for i in range(0, key_len):
|
||||||
|
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._parse_tag_specification("TagSpecification")
|
||||||
|
|
||||||
|
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:
|
||||||
|
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()
|
||||||
|
|
||||||
|
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(tmpl_id)
|
||||||
|
|
||||||
|
version_description = self._get_param('VersionDescription')
|
||||||
|
|
||||||
|
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(template_id)
|
||||||
|
|
||||||
|
max_results = self._get_int_param("MaxResults", 15)
|
||||||
|
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")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
vMin = min_version - 1
|
||||||
|
ret_versions = template.versions[vMin:vMax]
|
||||||
|
elif max_version:
|
||||||
|
vMax = max_version
|
||||||
|
ret_versions = template.versions[:vMax]
|
||||||
|
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
|
@ -20,6 +20,7 @@ EC2_RESOURCE_TO_PREFIX = {
|
|||||||
'image': 'ami',
|
'image': 'ami',
|
||||||
'instance': 'i',
|
'instance': 'i',
|
||||||
'internet-gateway': 'igw',
|
'internet-gateway': 'igw',
|
||||||
|
'launch-template': 'lt',
|
||||||
'nat-gateway': 'nat',
|
'nat-gateway': 'nat',
|
||||||
'network-acl': 'acl',
|
'network-acl': 'acl',
|
||||||
'network-acl-subnet-assoc': 'aclassoc',
|
'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)
|
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():
|
def random_public_ip():
|
||||||
return '54.214.{0}.{1}'.format(random.choice(range(255)),
|
return '54.214.{0}.{1}'.format(random.choice(range(255)),
|
||||||
random.choice(range(255)))
|
random.choice(range(255)))
|
||||||
|
415
tests/test_ec2/test_launch_templates.py
Normal file
415
tests/test_ec2/test_launch_templates.py
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
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", region_name="us-east-1")
|
||||||
|
|
||||||
|
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", region_name="us-east-1")
|
||||||
|
|
||||||
|
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", 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(
|
||||||
|
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_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(
|
||||||
|
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_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")
|
||||||
|
|
||||||
|
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", region_name="us-east-1")
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
@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"}
|
||||||
|
]
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user