from copy import deepcopy
from typing import Any, Dict, List, Optional
from moto.core.utils import camelcase_to_underscores
from moto.ec2.exceptions import (
InvalidParameterCombination,
InvalidRequest,
MissingParameterError,
)
from moto.ec2.utils import filter_iam_instance_profiles
from ._base_response import EC2BaseResponse
class InstanceResponse(EC2BaseResponse):
def describe_instances(self) -> str:
self.error_on_dryrun()
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_instances.html
# You cannot specify this(MaxResults) parameter and the instance IDs parameter in the same request.
if "InstanceId.1" in self.data and "MaxResults" in self.data:
raise InvalidParameterCombination(
"The parameter instancesSet cannot be used with the parameter maxResults"
)
filter_dict = self._filters_from_querystring()
instance_ids = self._get_multi_param("InstanceId")
token = self._get_param("NextToken")
if instance_ids:
reservations = self.ec2_backend.get_reservations_by_instance_ids(
instance_ids, filters=filter_dict
)
else:
reservations = self.ec2_backend.describe_instances(filters=filter_dict)
reservation_ids = [reservation.id for reservation in reservations]
if token:
start = reservation_ids.index(token) + 1
else:
start = 0
max_results = int(self._get_param("MaxResults", 100))
reservations_resp = reservations[start : start + max_results]
next_token = None
if max_results and len(reservations) > (start + max_results):
next_token = reservations_resp[-1].id
template = self.response_template(EC2_DESCRIBE_INSTANCES)
return (
template.render(
account_id=self.current_account,
reservations=reservations_resp,
next_token=next_token,
run_instances=False,
)
.replace("True", "true")
.replace("False", "false")
)
def run_instances(self) -> str:
min_count = int(self._get_param("MinCount", if_none="1"))
image_id = self._get_param("ImageId")
owner_id = self._get_param("OwnerId")
user_data = self._get_param("UserData")
security_group_names = self._get_multi_param("SecurityGroup")
kwargs = {
"instance_type": self._get_param("InstanceType", if_none="m1.small"),
"is_instance_type_default": not self._get_param("InstanceType"),
"placement": self._get_param("Placement.AvailabilityZone"),
"placement_hostid": self._get_param("Placement.HostId"),
"region_name": self.region,
"subnet_id": self._get_param("SubnetId"),
"owner_id": owner_id,
"key_name": self._get_param("KeyName"),
"security_group_ids": self._get_multi_param("SecurityGroupId"),
"nics": self._get_multi_param("NetworkInterface."),
"private_ip": self._get_param("PrivateIpAddress"),
"associate_public_ip": self._get_param("AssociatePublicIpAddress"),
"tags": self._parse_tag_specification(),
"ebs_optimized": self._get_param("EbsOptimized") or False,
"instance_market_options": self._get_param(
"InstanceMarketOptions.MarketType"
)
or {},
"instance_initiated_shutdown_behavior": self._get_param(
"InstanceInitiatedShutdownBehavior"
),
"launch_template": self._get_multi_param_dict("LaunchTemplate"),
"hibernation_options": self._get_multi_param_dict("HibernationOptions"),
"iam_instance_profile_name": self._get_param("IamInstanceProfile.Name")
or None,
"iam_instance_profile_arn": self._get_param("IamInstanceProfile.Arn")
or None,
"monitoring_state": "enabled"
if self._get_param("Monitoring.Enabled") == "true"
else "disabled",
}
if len(kwargs["nics"]) and kwargs["subnet_id"]:
raise InvalidParameterCombination(
msg="Network interfaces and an instance-level subnet ID may not be specified on the same request"
)
mappings = self._parse_block_device_mapping()
if mappings:
kwargs["block_device_mappings"] = mappings
iam_instance_profile_name = kwargs.get("iam_instance_profile_name")
iam_instance_profile_arn = kwargs.get("iam_instance_profile_arn")
if iam_instance_profile_arn or iam_instance_profile_name:
# Validate the profile exists, before we error_on_dryrun and run_instances
filter_iam_instance_profiles(
self.current_account,
iam_instance_profile_arn=iam_instance_profile_arn,
iam_instance_profile_name=iam_instance_profile_name,
)
self.error_on_dryrun()
new_reservation = self.ec2_backend.run_instances(
image_id, min_count, user_data, security_group_names, **kwargs
)
if iam_instance_profile_name:
self.ec2_backend.associate_iam_instance_profile(
instance_id=new_reservation.instances[0].id,
iam_instance_profile_name=iam_instance_profile_name,
)
if iam_instance_profile_arn:
self.ec2_backend.associate_iam_instance_profile(
instance_id=new_reservation.instances[0].id,
iam_instance_profile_arn=iam_instance_profile_arn,
)
template = self.response_template(EC2_RUN_INSTANCES)
return template.render(
account_id=self.current_account,
reservation=new_reservation,
run_instances=True,
)
def terminate_instances(self) -> str:
instance_ids = self._get_multi_param("InstanceId")
self.error_on_dryrun()
instances = self.ec2_backend.terminate_instances(instance_ids)
from moto.autoscaling import autoscaling_backends
from moto.elbv2 import elbv2_backends
autoscaling_backends[self.current_account][
self.region
].notify_terminate_instances(instance_ids)
elbv2_backends[self.current_account][self.region].notify_terminate_instances(
instance_ids
)
template = self.response_template(EC2_TERMINATE_INSTANCES)
return template.render(instances=instances)
def reboot_instances(self) -> str:
instance_ids = self._get_multi_param("InstanceId")
self.error_on_dryrun()
instances = self.ec2_backend.reboot_instances(instance_ids)
template = self.response_template(EC2_REBOOT_INSTANCES)
return template.render(instances=instances)
def stop_instances(self) -> str:
instance_ids = self._get_multi_param("InstanceId")
self.error_on_dryrun()
instances = self.ec2_backend.stop_instances(instance_ids)
template = self.response_template(EC2_STOP_INSTANCES)
return template.render(instances=instances)
def start_instances(self) -> str:
instance_ids = self._get_multi_param("InstanceId")
self.error_on_dryrun()
instances = self.ec2_backend.start_instances(instance_ids)
template = self.response_template(EC2_START_INSTANCES)
return template.render(instances=instances)
def _get_list_of_dict_params(
self, param_prefix: str, _dct: Dict[str, Any]
) -> List[Any]:
"""
Simplified version of _get_dict_param
Allows you to pass in a custom dict instead of using self.querystring by default
"""
params = []
for key, value in _dct.items():
if key.startswith(param_prefix):
params.append(value)
return params
def describe_instance_status(self) -> str:
instance_ids = self._get_multi_param("InstanceId")
include_all_instances = self._get_param("IncludeAllInstances") == "true"
filters = self._get_list_prefix("Filter")
filters = [
{"name": f["name"], "values": self._get_list_of_dict_params("value.", f)}
for f in filters
]
instances = self.ec2_backend.describe_instance_status(
instance_ids, include_all_instances, filters
)
template = self.response_template(EC2_INSTANCE_STATUS)
return template.render(instances=instances)
def describe_instance_types(self) -> str:
instance_type_filters = self._get_multi_param("InstanceType")
filter_dict = self._filters_from_querystring()
instance_types = self.ec2_backend.describe_instance_types(
instance_type_filters, filter_dict
)
template = self.response_template(EC2_DESCRIBE_INSTANCE_TYPES)
return template.render(instance_types=instance_types)
def describe_instance_type_offerings(self) -> str:
location_type_filters = self._get_param("LocationType")
filter_dict = self._filters_from_querystring()
offerings = self.ec2_backend.describe_instance_type_offerings(
location_type_filters, filter_dict
)
template = self.response_template(EC2_DESCRIBE_INSTANCE_TYPE_OFFERINGS)
return template.render(instance_type_offerings=offerings)
def describe_instance_attribute(self) -> str:
# TODO this and modify below should raise IncorrectInstanceState if
# instance not in stopped state
attribute = self._get_param("Attribute")
instance_id = self._get_param("InstanceId")
instance, value = self.ec2_backend.describe_instance_attribute(
instance_id, attribute
)
if attribute == "groupSet":
template = self.response_template(EC2_DESCRIBE_INSTANCE_GROUPSET_ATTRIBUTE)
else:
template = self.response_template(EC2_DESCRIBE_INSTANCE_ATTRIBUTE)
return template.render(instance=instance, attribute=attribute, value=value)
def describe_instance_credit_specifications(self) -> str:
instance_ids = self._get_multi_param("InstanceId")
instance = self.ec2_backend.describe_instance_credit_specifications(
instance_ids
)
template = self.response_template(EC2_DESCRIBE_INSTANCE_CREDIT_SPECIFICATIONS)
return template.render(instances=instance)
def modify_instance_attribute(self) -> str:
handlers = [
self._attribute_value_handler,
self._dot_value_instance_attribute_handler,
self._block_device_mapping_handler,
self._security_grp_instance_attribute_handler,
]
for handler in handlers:
success = handler()
if success:
return success
msg = (
"This specific call to ModifyInstanceAttribute has not been"
" implemented in Moto yet. Feel free to open an issue at"
" https://github.com/getmoto/moto/issues"
)
raise NotImplementedError(msg)
def _block_device_mapping_handler(self) -> Optional[str]:
"""
Handles requests which are generated by code similar to:
instance.modify_attribute(
BlockDeviceMappings=[{
'DeviceName': '/dev/sda1',
'Ebs': {'DeleteOnTermination': True}
}]
)
The querystring contains information similar to:
BlockDeviceMapping.1.Ebs.DeleteOnTermination : ['true']
BlockDeviceMapping.1.DeviceName : ['/dev/sda1']
For now we only support the "BlockDeviceMapping.1.Ebs.DeleteOnTermination"
configuration, but it should be trivial to add anything else.
"""
mapping_counter = 1
mapping_device_name_fmt = "BlockDeviceMapping.%s.DeviceName"
mapping_del_on_term_fmt = "BlockDeviceMapping.%s.Ebs.DeleteOnTermination"
while True:
mapping_device_name = mapping_device_name_fmt % mapping_counter
if mapping_device_name not in self.querystring.keys():
break
mapping_del_on_term = mapping_del_on_term_fmt % mapping_counter
del_on_term_value_str = self.querystring[mapping_del_on_term][0]
del_on_term_value = True if "true" == del_on_term_value_str else False
device_name_value = self.querystring[mapping_device_name][0]
instance_id = self._get_param("InstanceId")
instance = self.ec2_backend.get_instance(instance_id)
self.error_on_dryrun()
block_device_type = instance.block_device_mapping[device_name_value]
block_device_type.delete_on_termination = del_on_term_value
# +1 for the next device
mapping_counter += 1
if mapping_counter > 1:
return EC2_MODIFY_INSTANCE_ATTRIBUTE
return None
def _dot_value_instance_attribute_handler(self) -> Optional[str]:
attribute_key = None
for key, value in self.querystring.items():
if ".Value" in key:
attribute_key = key
break
if not attribute_key:
return None
self.error_on_dryrun()
value = self.querystring.get(attribute_key)[0] # type: ignore
normalized_attribute = camelcase_to_underscores(attribute_key.split(".")[0])
instance_id = self._get_param("InstanceId")
self.ec2_backend.modify_instance_attribute(
instance_id, normalized_attribute, value
)
return EC2_MODIFY_INSTANCE_ATTRIBUTE
def _attribute_value_handler(self) -> Optional[str]:
attribute_key = self._get_param("Attribute")
if attribute_key is None:
return None
self.error_on_dryrun()
value = self._get_param("Value")
normalized_attribute = camelcase_to_underscores(attribute_key)
instance_id = self._get_param("InstanceId")
self.ec2_backend.modify_instance_attribute(
instance_id, normalized_attribute, value
)
return EC2_MODIFY_INSTANCE_ATTRIBUTE
def _security_grp_instance_attribute_handler(self) -> str:
new_security_grp_list = []
for key in self.querystring:
if "GroupId." in key:
new_security_grp_list.append(self.querystring.get(key)[0]) # type: ignore
instance_id = self._get_param("InstanceId")
self.error_on_dryrun()
self.ec2_backend.modify_instance_security_groups(
instance_id, new_security_grp_list
)
return EC2_MODIFY_INSTANCE_ATTRIBUTE
def _parse_block_device_mapping(self) -> List[Dict[str, Any]]:
device_mappings = self._get_list_prefix("BlockDeviceMapping")
mappings = []
for device_mapping in device_mappings:
self._validate_block_device_mapping(device_mapping)
device_template: Dict[str, Any] = deepcopy(BLOCK_DEVICE_MAPPING_TEMPLATE)
device_template["VirtualName"] = device_mapping.get("virtual_name")
device_template["DeviceName"] = device_mapping.get("device_name")
device_template["Ebs"]["SnapshotId"] = device_mapping.get(
"ebs._snapshot_id"
)
device_template["Ebs"]["VolumeSize"] = device_mapping.get(
"ebs._volume_size"
)
device_template["Ebs"]["DeleteOnTermination"] = self._convert_to_bool(
device_mapping.get("ebs._delete_on_termination", False)
)
device_template["Ebs"]["VolumeType"] = device_mapping.get(
"ebs._volume_type"
)
device_template["Ebs"]["Iops"] = device_mapping.get("ebs._iops")
device_template["Ebs"]["Encrypted"] = self._convert_to_bool(
device_mapping.get("ebs._encrypted", False)
)
device_template["Ebs"]["KmsKeyId"] = device_mapping.get("ebs._kms_key_id")
device_template["NoDevice"] = device_mapping.get("no_device")
mappings.append(device_template)
return mappings
@staticmethod
def _validate_block_device_mapping(device_mapping: Dict[str, Any]) -> None: # type: ignore[misc]
from botocore import __version__ as botocore_version
if "no_device" in device_mapping:
assert isinstance(
device_mapping["no_device"], str
), f"botocore {botocore_version} isn't limiting NoDevice to str type anymore, it is type:{type(device_mapping['no_device'])}"
if device_mapping["no_device"] == "":
# the only legit value it can have is empty string
# and none of the other checks here matter if NoDevice
# is being used
return
else:
raise InvalidRequest()
if not any(mapping for mapping in device_mapping if mapping.startswith("ebs.")):
raise MissingParameterError("ebs")
if (
"ebs._volume_size" not in device_mapping
and "ebs._snapshot_id" not in device_mapping
):
raise MissingParameterError("size or snapshotId")
@staticmethod
def _convert_to_bool(bool_str: Any) -> bool: # type: ignore[misc]
if isinstance(bool_str, bool):
return bool_str
if isinstance(bool_str, str):
return str(bool_str).lower() == "true"
return False
BLOCK_DEVICE_MAPPING_TEMPLATE = {
"VirtualName": None,
"DeviceName": None,
"NoDevice": None,
"Ebs": {
"SnapshotId": None,
"VolumeSize": None,
"DeleteOnTermination": None,
"VolumeType": None,
"Iops": None,
"Encrypted": None,
},
}
INSTANCE_TEMPLATE = """-
{{ instance.id }}
{{ instance.image_id }}
{% if run_instances %}
0
pending
{% else %}
{{ instance._state.code }}
{{ instance._state.name }}
{% endif %}
{{ instance.private_dns }}
{{ instance.public_dns }}
{{ instance.public_dns }}
{{ instance._reason }}
{% if instance.key_name is not none %}
{{ instance.key_name }}
{% endif %}
{{ instance.ebs_optimized }}
{{ instance.ami_launch_index }}
{{ instance.instance_type }}
{% if instance.iam_instance_profile %}
{{ instance.iam_instance_profile['Arn'] }}
{{ instance.iam_instance_profile['Id'] }}
{% endif %}
{{ instance.launch_time }}
{% if instance.lifecycle %}
{{ instance.lifecycle }}
{% endif %}
{% if instance.placement_hostid %}{{ instance.placement_hostid }}{% endif %}
{{ instance.placement}}
default
{{ instance.monitoring_state }}
{% if instance.subnet_id %}
{{ instance.subnet_id }}
{% elif instance.nics[0].subnet.id %}
{{ instance.nics[0].subnet.id }}
{% endif %}
{% if instance.vpc_id %}
{{ instance.vpc_id }}
{% elif instance.nics[0].subnet.vpc_id %}
{{ instance.nics[0].subnet.vpc_id }}
{% endif %}
{{ instance.private_ip }}
{% if instance.nics[0].public_ip %}
{{ instance.nics[0].public_ip }}
{% endif %}
{{ instance.source_dest_check }}
{% for group in instance.dynamic_group_list %}
-
{% if group.id %}
{{ group.id }}
{{ group.name }}
{% else %}
{{ group }}
{% endif %}
{% endfor %}
{% if instance.platform %}
{{ instance.platform }}
{% endif %}
{{ instance.virtualization_type }}
{{ instance._state_reason.code }}
{{ instance._state_reason.message }}
{{ instance.architecture }}
{{ instance.kernel }}
ebs
/dev/sda1
{% for device_name,deviceobject in instance.get_block_device_mapping %}
-
{{ device_name }}
{{ deviceobject.volume_id }}
{{ deviceobject.status }}
{{ deviceobject.attach_time }}
{{ deviceobject.delete_on_termination }}
{{deviceobject.size}}
{% endfor %}
ABCDE{{ account_id }}3
xen
{% if instance.hibernation_options %}
{{ instance.hibernation_options.get("Configured") }}
{% endif %}
{% if instance.get_tags() %}
{% for tag in instance.get_tags() %}
-
{{ tag.resource_id }}
{{ tag.resource_type }}
{{ tag.key }}
{{ tag.value }}
{% endfor %}
{% endif %}
{% for nic in instance.nics.values() %}
-
{{ nic.id }}
{% if nic.subnet %}
{{ nic.subnet.id }}
{{ nic.subnet.vpc_id }}
{% endif %}
Primary network interface
{{ account_id }}
in-use
1b:2b:3c:4d:5e:6f
{{ nic.private_ip_address }}
{{ instance.source_dest_check }}
{% for group in nic.group_set %}
-
{% if group.id %}
{{ group.id }}
{{ group.name }}
{% else %}
{{ group }}
{% endif %}
{% endfor %}
{{ nic.attachment_id }}
{{ nic.device_index }}
attached
2015-01-01T00:00:00Z
true
{% if nic.public_ip %}
{{ nic.public_ip }}
{{ account_id }}
{% endif %}
-
{{ nic.private_ip_address }}
true
{% if nic.public_ip %}
{{ nic.public_ip }}
{{ account_id }}
{% endif %}
{% endfor %}
"""
EC2_RUN_INSTANCES = (
"""
59dbff89-35bd-4eac-99ed-be587EXAMPLE
{{ reservation.id }}
{{ account_id }}
-
sg-245f6a01
default
{% for instance in reservation.instances %}
"""
+ INSTANCE_TEMPLATE
+ """
{% endfor %}
"""
)
EC2_DESCRIBE_INSTANCES = (
"""
fdcdcab1-ae5c-489e-9c33-4637c5dda355
{% for reservation in reservations %}
-
{{ reservation.id }}
{{ account_id }}
{% for group in reservation.dynamic_group_list %}
-
{% if group.id %}
{{ group.id }}
{{ group.name }}
{% else %}
{{ group }}
{% endif %}
{% endfor %}
{% for instance in reservation.instances %}
"""
+ INSTANCE_TEMPLATE
+ """
{% endfor %}
{% endfor %}
{% if next_token %}
{{ next_token }}
{% endif %}
"""
)
EC2_TERMINATE_INSTANCES = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
{% for instance, previous_state in instances %}
-
{{ instance.id }}
{{ previous_state.code }}
{{ previous_state.name }}
32
shutting-down
{% endfor %}
"""
EC2_STOP_INSTANCES = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
{% for instance, previous_state in instances %}
-
{{ instance.id }}
{{ previous_state.code }}
{{ previous_state.name }}
64
stopping
{% endfor %}
"""
EC2_START_INSTANCES = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
{% for instance, previous_state in instances %}
-
{{ instance.id }}
{{ previous_state.code }}
{{ previous_state.name }}
0
pending
{% endfor %}
"""
EC2_REBOOT_INSTANCES = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
true
"""
EC2_DESCRIBE_INSTANCE_ATTRIBUTE = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
{{ instance.id }}
<{{ attribute }}>
{% if value is not none %}
{{ value }}
{% endif %}
{{ attribute }}>
"""
EC2_DESCRIBE_INSTANCE_CREDIT_SPECIFICATIONS = """
1b234b5c-d6ef-7gh8-90i1-j2345678901
{% for instance in instances %}
-
{{ instance.id }}
standard
{% endfor %}
"""
EC2_DESCRIBE_INSTANCE_GROUPSET_ATTRIBUTE = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
{{ instance.id }}
<{{ attribute }}>
{% for sg in value %}
-
{{ sg.id }}
{% endfor %}
{{ attribute }}>
"""
EC2_MODIFY_INSTANCE_ATTRIBUTE = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
true
"""
EC2_INSTANCE_STATUS = """
59dbff89-35bd-4eac-99ed-be587EXAMPLE
{% for instance in instances %}
-
{{ instance.id }}
{{ instance.placement }}
{{ instance.state_code }}
{{ instance.state }}
{% if instance.state_code == 16 %}
ok
-
reachability
passed
ok
-
reachability
passed
{% else %}
not-applicable
not-applicable
{% endif %}
{% endfor %}
"""
EC2_DESCRIBE_INSTANCE_TYPES = """
f8b86168-d034-4e65-b48d-3b84c78e64af
{% for instance_type in instance_types %}
-
{{ instance_type.AutoRecoverySupported }}
{{ instance_type.BareMetal }}
{{ instance_type.BurstablePerformanceSupported }}
{{ instance_type.CurrentGeneration }}
{{ instance_type.DedicatedHostsSupported }}
{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('BaselineBandwidthInMbps', 0) | int }}
{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('BaselineIops', 0) | int }}
{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('BaselineThroughputInMBps', 0.0) | float }}
{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('MaximumBandwidthInMbps', 0) | int }}
{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('MaximumIops', 0) | int }}
{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedInfo', {}).get('MaximumThroughputInMBps', 0.0) | float }}
{{ instance_type.get('EbsInfo', {}).get('EbsOptimizedSupport', 'default') }}
{{ instance_type.get('EbsInfo', {}).get('EncryptionSupport', 'supported') }}
{{ instance_type.get('EbsInfo', {}).get('NvmeSupport', 'required') }}
{{ instance_type.get('NetworkInfo', {}).get('DefaultNetworkCardIndex', 0) | int }}
{{ instance_type.get('NetworkInfo', {}).get('EfaSupported', False) }}
{{ instance_type.get('NetworkInfo', {}).get('EnaSrdSupported', False) }}
{{ instance_type.get('NetworkInfo', {}).get('EnaSupport', 'unsupported') }}
{{ instance_type.get('NetworkInfo', {}).get('EncryptionInTransitSupported', False) }}
{{ instance_type.get('NetworkInfo', {}).get('Ipv4AddressesPerInterface', 0) | int }}
{{ instance_type.get('NetworkInfo', {}).get('Ipv6AddressesPerInterface', 0) | int }}
{{ instance_type.get('NetworkInfo', {}).get('Ipv6Supported', False) }}
{{ instance_type.get('NetworkInfo', {}).get('MaximumNetworkCards', 0) | int }}
{{ instance_type.get('NetworkInfo', {}).get('MaximumNetworkInterfaces', 0) | int }}
{% for network_card in instance_type.get('NetworkInfo', {}).get('NetworkCards', []) %}
-
{{ network_card.get('BaselineBandwidthInGbps', 0.0) | float }}
{{ network_card.get('MaximumNetworkInterfaces', 0) | int }}
{{ network_card.get('NetworkCardIndex', 0) | int }}
{{ network_card.get('NetworkPerformance', 'Up to 25 Schmeckles') }}
{{ network_card.get('PeakBandwidthInGbps', 0.0) | float }}
{% endfor %}
{{ instance_type.get('NetworkInfo', {}).get('NetworkPerformance', 'Up to 25 Schmeckles') }}
{{ instance_type.FreeTierEligible }}
{{ instance_type.HibernationSupported }}
{{ instance_type.get('Hypervisor', 'motovisor') }}
{{ instance_type.InstanceStorageSupported }}
{% for strategy in instance_type.get('PlacementGroupInfo', {}).get('SupportedStrategies', []) %}
- {{ strategy }}
{% endfor %}
{% for dev_type in instance_type.get('SupportedRootDeviceTypes', []) %}
- {{ dev_type }}
{% endfor %}
{% for usage_class in instance_type.get('SupportedUsageClasses', []) %}
- {{ usage_class }}
{% endfor %}
{% for supported_vtype in instance_type.get('SupportedVirtualizationTypes', []) %}
- {{ supported_vtype }}
{% endfor %}
{{ instance_type.InstanceType }}
{{ instance_type.get('VCpuInfo', {}).get('DefaultVCpus', 0)|int }}
{{ instance_type.get('VCpuInfo', {}).get('DefaultCores', 0)|int }}
{{ instance_type.get('VCpuInfo').get('DefaultThreadsPerCore', 0)|int }}
{% for valid_core in instance_type.get("VCpuInfo", {}).get('ValidCores', []) %}
- {{ valid_core }}
{% endfor %}
{% for threads_per_core in instance_type.get("VCpuInfo", {}).get('ValidThreadsPerCore', []) %}
- {{ threads_per_core }}
{% endfor %}
{{ instance_type.get('MemoryInfo', {}).get('SizeInMiB', 0)|int }}
{{ instance_type.get('InstanceStorageInfo', {}).get('TotalSizeInGB', 0)|int }}
{% for arch in instance_type.get('ProcessorInfo', {}).get('SupportedArchitectures', []) %}
-
{{ arch }}
{% endfor %}
{{ instance_type.get('ProcessorInfo', {}).get('SustainedClockSpeedInGhz', 0.0) | float }}
{% if instance_type.get('GpuInfo', {})|length > 0 %}
{% for gpu in instance_type.get('GpuInfo').get('Gpus') %}
-
{{ gpu['Count']|int }}
{{ gpu['Manufacturer'] }}
{{ gpu['MemoryInfo']['SizeInMiB']|int }}
{{ gpu['Name'] }}
{% endfor %}
{{ instance_type['GpuInfo']['TotalGpuMemoryInMiB']|int }}
{% endif %}
{% endfor %}
"""
EC2_DESCRIBE_INSTANCE_TYPE_OFFERINGS = """
f8b86168-d034-4e65-b48d-3b84c78e64af
{% for offering in instance_type_offerings %}
-
{{ offering.InstanceType }}
{{ offering.Location }}
{{ offering.LocationType }}
{% endfor %}
"""