Skip to content

Commit 3f657e5

Browse files
authored
EC2: integrate AMI responses with core serializer (#9108)
1 parent 6023a9a commit 3f657e5

File tree

3 files changed

+47
-154
lines changed

3 files changed

+47
-154
lines changed

moto/ec2/models/amis.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from moto import settings
88
from moto.utilities.utils import load_resource
99

10+
from ...core.utils import utcnow
1011
from ..exceptions import (
1112
InvalidAMIAttributeItemValueError,
1213
InvalidAMIIdError,
@@ -17,7 +18,6 @@
1718
from ..utils import (
1819
generic_filter,
1920
random_ami_id,
20-
utc_date_and_time,
2121
)
2222
from .core import TaggedEC2Resource
2323
from .instances import Instance
@@ -75,7 +75,7 @@ def __init__(
7575
self.root_device_name = root_device_name
7676
self.root_device_type = root_device_type
7777
self.sriov = sriov
78-
self.creation_date = creation_date or utc_date_and_time()
78+
self.creation_date = creation_date or utcnow()
7979
self.product_codes = product_codes
8080
self.boot_mode = boot_mode
8181
self.instance_id: Optional[str] = None
@@ -126,8 +126,18 @@ def is_public(self) -> bool:
126126
return {"Group": "all"} in self.launch_permissions
127127

128128
@property
129-
def is_public_string(self) -> str:
130-
return str(self.is_public).lower()
129+
def block_device_mappings(self) -> List[Dict[str, Any]]:
130+
return [
131+
{
132+
"DeviceName": self.root_device_name,
133+
"Ebs": {
134+
"SnapshotId": self.ebs_snapshot.id,
135+
"VolumeSize": 15,
136+
"DeleteOnTermination": False,
137+
"VolumeType": "standard",
138+
},
139+
}
140+
]
131141

132142
@property
133143
def source_instance_id(self) -> Optional[str]:
@@ -145,7 +155,7 @@ def get_filter_value(
145155
elif filter_name == "image-id":
146156
return self.id
147157
elif filter_name == "is-public":
148-
return self.is_public_string
158+
return self.is_public
149159
elif filter_name == "state":
150160
return self.state
151161
elif filter_name == "name":

moto/ec2/responses/amis.py

Lines changed: 32 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from moto.core.responses import ActionResult, EmptyResult
2+
13
from ..exceptions import AuthFailureRestricted, InvalidRequest
24
from ._base_response import EC2BaseResponse
35

46

57
class AmisResponse(EC2BaseResponse):
6-
def create_image(self) -> str:
8+
def create_image(self) -> ActionResult:
79
name = self.querystring.get("Name")[0] # type: ignore[index]
810
description = self._get_param("Description", if_none="")
911
instance_id = self._get_param("InstanceId")
@@ -17,10 +19,10 @@ def create_image(self) -> str:
1719
description,
1820
tag_specifications=tag_specifications,
1921
)
20-
template = self.response_template(CREATE_IMAGE_RESPONSE)
21-
return template.render(image=image)
22+
result = {"ImageId": image.id}
23+
return ActionResult(result)
2224

23-
def copy_image(self) -> str:
25+
def copy_image(self) -> ActionResult:
2426
source_image_id = self._get_param("SourceImageId")
2527
source_region = self._get_param("SourceRegion")
2628
name = self._get_param("Name")
@@ -31,19 +33,19 @@ def copy_image(self) -> str:
3133
image = self.ec2_backend.copy_image(
3234
source_image_id, source_region, name, description
3335
)
34-
template = self.response_template(COPY_IMAGE_RESPONSE)
35-
return template.render(image=image)
36+
result = {"ImageId": image.id}
37+
return ActionResult(result)
3638

37-
def deregister_image(self) -> str:
39+
def deregister_image(self) -> ActionResult:
3840
ami_id = self._get_param("ImageId")
3941

4042
self.error_on_dryrun()
4143

4244
self.ec2_backend.deregister_image(ami_id)
43-
template = self.response_template(DEREGISTER_IMAGE_RESPONSE)
44-
return template.render(success="true")
45+
result = {"Return": True}
46+
return ActionResult(result)
4547

46-
def describe_images(self) -> str:
48+
def describe_images(self) -> ActionResult:
4749
self.error_on_dryrun()
4850
ami_ids = self._get_multi_param("ImageId")
4951
filters = self._filters_from_querystring()
@@ -52,10 +54,10 @@ def describe_images(self) -> str:
5254
images = self.ec2_backend.describe_images(
5355
ami_ids=ami_ids, filters=filters, exec_users=exec_users, owners=owners
5456
)
55-
template = self.response_template(DESCRIBE_IMAGES_RESPONSE)
56-
return template.render(images=images)
57+
result = {"Images": images}
58+
return ActionResult(result)
5759

58-
def describe_image_attribute(self) -> str:
60+
def describe_image_attribute(self) -> ActionResult:
5961
ami_id = self._get_param("ImageId")
6062
attribute_name = self._get_param("Attribute")
6163

@@ -93,15 +95,20 @@ def describe_image_attribute(self) -> str:
9395
ami_id, valid_attributes_list[attribute_name]
9496
)
9597

96-
template = self.response_template(DESCRIBE_IMAGE_ATTRIBUTES_RESPONSE)
97-
return template.render(
98-
ami_id=ami_id,
99-
launch_permissions=launch_permissions,
100-
attribute_name=attribute_name,
101-
attribute_value=attribute_value,
102-
)
98+
result = {"ImageId": ami_id}
99+
if attribute_name == "productCodes":
100+
result["ProductCodes"] = [
101+
{"ProductCodeId": code, "ProductCodeType": "marketplace"}
102+
for code in attribute_value # type: ignore[union-attr]
103+
]
104+
elif attribute_name == "launchPermission":
105+
result["LaunchPermissions"] = launch_permissions
106+
else:
107+
result[attribute_name] = {"Value": attribute_value}
103108

104-
def modify_image_attribute(self) -> str:
109+
return ActionResult(result)
110+
111+
def modify_image_attribute(self) -> ActionResult:
105112
ami_id = self._get_param("ImageId")
106113
launch_permissions_to_add = list(
107114
self._get_params().get("LaunchPermission", {}).get("Add", {}).values()
@@ -139,141 +146,19 @@ def modify_image_attribute(self) -> str:
139146
launch_permissions_to_add=launch_permissions_to_add,
140147
launch_permissions_to_remove=launch_permissions_to_remove,
141148
)
142-
return MODIFY_IMAGE_ATTRIBUTE_RESPONSE
149+
return EmptyResult()
143150

144-
def register_image(self) -> str:
151+
def register_image(self) -> ActionResult:
145152
name = self.querystring.get("Name")[0] # type: ignore[index]
146153
description = self._get_param("Description", if_none="")
147154

148155
self.error_on_dryrun()
149156

150157
image = self.ec2_backend.register_image(name, description)
151-
template = self.response_template(REGISTER_IMAGE_RESPONSE)
152-
return template.render(image=image)
158+
result = {"ImageId": image.id}
159+
return ActionResult(result)
153160

154161
def reset_image_attribute(self) -> str:
155162
self.error_on_dryrun()
156163

157164
raise NotImplementedError("AMIs.reset_image_attribute is not yet implemented")
158-
159-
160-
CREATE_IMAGE_RESPONSE = """<CreateImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
161-
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
162-
<imageId>{{ image.id }}</imageId>
163-
</CreateImageResponse>"""
164-
165-
COPY_IMAGE_RESPONSE = """<CopyImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
166-
<requestId>60bc441d-fa2c-494d-b155-5d6a3EXAMPLE</requestId>
167-
<imageId>{{ image.id }}</imageId>
168-
</CopyImageResponse>"""
169-
170-
# TODO almost all of these params should actually be templated based on
171-
# the ec2 image
172-
DESCRIBE_IMAGES_RESPONSE = """<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
173-
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
174-
<imagesSet>
175-
{% for image in images %}
176-
<item>
177-
<imageId>{{ image.id }}</imageId>
178-
<imageLocation>{{ image.image_location }}</imageLocation>
179-
<imageState>{{ image.state }}</imageState>
180-
<imageOwnerId>{{ image.owner_id }}</imageOwnerId>
181-
<isPublic>{{ image.is_public_string }}</isPublic>
182-
<architecture>{{ image.architecture }}</architecture>
183-
<imageType>{{ image.image_type }}</imageType>
184-
<kernelId>{{ image.kernel_id }}</kernelId>
185-
<ramdiskId>ari-1a2b3c4d</ramdiskId>
186-
<imageOwnerAlias>amazon</imageOwnerAlias>
187-
<creationDate>{{ image.creation_date }}</creationDate>
188-
<name>{{ image.name }}</name>
189-
{% if image.platform %}
190-
<platform>{{ image.platform }}</platform>
191-
{% endif %}
192-
<description>{{ image.description }}</description>
193-
<rootDeviceType>{{ image.root_device_type }}</rootDeviceType>
194-
<rootDeviceName>{{ image.root_device_name }}</rootDeviceName>
195-
<blockDeviceMapping>
196-
<item>
197-
<deviceName>{{ image.root_device_name }}</deviceName>
198-
<ebs>
199-
<snapshotId>{{ image.ebs_snapshot.id }}</snapshotId>
200-
<volumeSize>15</volumeSize>
201-
<deleteOnTermination>false</deleteOnTermination>
202-
<volumeType>standard</volumeType>
203-
</ebs>
204-
</item>
205-
</blockDeviceMapping>
206-
<virtualizationType>{{ image.virtualization_type }}</virtualizationType>
207-
<tagSet>
208-
{% for tag in image.get_tags() %}
209-
<item>
210-
<resourceId>{{ tag.resource_id }}</resourceId>
211-
<resourceType>{{ tag.resource_type }}</resourceType>
212-
<key>{{ tag.key }}</key>
213-
<value>{{ tag.value }}</value>
214-
</item>
215-
{% endfor %}
216-
</tagSet>
217-
<hypervisor>xen</hypervisor>
218-
{% if image.source_instance_id %}
219-
<sourceInstanceId>{{ image.source_instance_id }}</sourceInstanceId>
220-
{% endif %}
221-
</item>
222-
{% endfor %}
223-
</imagesSet>
224-
</DescribeImagesResponse>"""
225-
226-
DESCRIBE_IMAGE_RESPONSE = """<DescribeImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
227-
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
228-
<imageId>{{ image.id }}</imageId>
229-
<{{ key }}>
230-
<value>{{ value }}</value>
231-
</{{key }}>
232-
</DescribeImageAttributeResponse>"""
233-
234-
DEREGISTER_IMAGE_RESPONSE = """<DeregisterImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
235-
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
236-
<return>{{ success }}</return>
237-
</DeregisterImageResponse>"""
238-
239-
DESCRIBE_IMAGE_ATTRIBUTES_RESPONSE = """
240-
<DescribeImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
241-
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
242-
<imageId>{{ ami_id }}</imageId>
243-
<{{ attribute_name }}>
244-
{% if attribute_name == 'productCodes' %}
245-
{% for value in attribute_value %}
246-
<item>
247-
<productCode>{{ value }}</productCode>
248-
<type>marketplace</type>
249-
</item>
250-
{% endfor %}
251-
{% endif %}
252-
{% if attribute_name == 'launchPermission' %}
253-
{% if launch_permissions %}
254-
{% for lp in launch_permissions %}
255-
<item>
256-
{% if lp['UserId'] %}<userId>{{ lp['UserId'] }}</userId>{% endif %}
257-
{% if lp['Group'] %}<group>{{ lp['Group'] }}</group>{% endif %}
258-
{% if lp['OrganizationArn'] %}<organizationArn>{{ lp['OrganizationArn'] }}</organizationArn>{% endif %}
259-
{% if lp['OrganizationalUnitArn'] %}<organizationalUnitArn>{{ lp['OrganizationalUnitArn'] }}</organizationalUnitArn>{% endif %}
260-
</item>
261-
{% endfor %}
262-
{% endif %}
263-
{% endif %}
264-
{% if attribute_name not in ['launchPermission', 'productCodes'] %}
265-
<value>{{ attribute_value }}</value>
266-
{% endif %}
267-
</{{ attribute_name }}>
268-
</DescribeImageAttributeResponse>"""
269-
270-
MODIFY_IMAGE_ATTRIBUTE_RESPONSE = """
271-
<ModifyImageAttributeResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
272-
<return>true</return>
273-
</ModifyImageAttributeResponse>
274-
"""
275-
276-
REGISTER_IMAGE_RESPONSE = """<RegisterImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
277-
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
278-
<imageId>{{ image.id }}</imageId>
279-
</RegisterImageResponse>"""

tests/test_ec2/test_amis.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ def test_ami_create_and_delete():
7171
assert retrieved_image["ImageId"] == image_id
7272
assert retrieved_image["VirtualizationType"] == instance["VirtualizationType"]
7373
assert retrieved_image["Architecture"] == instance["Architecture"]
74-
assert retrieved_image["KernelId"] == instance["KernelId"]
7574
assert retrieved_image["Platform"] == instance["Platform"]
7675
assert "CreationDate" in retrieved_image
7776
ec2.terminate_instances(InstanceIds=[instance_id])
@@ -224,7 +223,6 @@ def test_ami_copy():
224223
assert copy_image["ImageId"] == copy_image_id
225224
assert copy_image["VirtualizationType"] == source_image["VirtualizationType"]
226225
assert copy_image["Architecture"] == source_image["Architecture"]
227-
assert copy_image["KernelId"] == source_image["KernelId"]
228226
assert copy_image["Platform"] == source_image["Platform"]
229227

230228
# Validate auto-created snapshot

0 commit comments

Comments
 (0)