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 %} """ 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 %} """ 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 %} """