awspub 0.0.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- awspub/__init__.py +3 -0
- awspub/api.py +165 -0
- awspub/cli/__init__.py +146 -0
- awspub/common.py +64 -0
- awspub/configmodels.py +216 -0
- awspub/context.py +108 -0
- awspub/exceptions.py +28 -0
- awspub/image.py +656 -0
- awspub/image_marketplace.py +120 -0
- awspub/s3.py +262 -0
- awspub/snapshot.py +241 -0
- awspub/sns.py +105 -0
- awspub/tests/__init__.py +0 -0
- awspub/tests/fixtures/config-invalid-s3-extra.yaml +12 -0
- awspub/tests/fixtures/config-minimal.yaml +12 -0
- awspub/tests/fixtures/config-valid-nonawspub.yaml +13 -0
- awspub/tests/fixtures/config1.vmdk +0 -0
- awspub/tests/fixtures/config1.yaml +171 -0
- awspub/tests/fixtures/config2-mapping.yaml +2 -0
- awspub/tests/fixtures/config2.yaml +48 -0
- awspub/tests/fixtures/config3-duplicate-keys.yaml +18 -0
- awspub/tests/test_api.py +89 -0
- awspub/tests/test_cli.py +0 -0
- awspub/tests/test_common.py +34 -0
- awspub/tests/test_context.py +88 -0
- awspub/tests/test_image.py +556 -0
- awspub/tests/test_image_marketplace.py +44 -0
- awspub/tests/test_s3.py +74 -0
- awspub/tests/test_snapshot.py +122 -0
- awspub/tests/test_sns.py +189 -0
- awspub-0.0.10.dist-info/LICENSE +675 -0
- awspub-0.0.10.dist-info/METADATA +46 -0
- awspub-0.0.10.dist-info/RECORD +35 -0
- awspub-0.0.10.dist-info/WHEEL +4 -0
- awspub-0.0.10.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,556 @@
|
|
1
|
+
import pathlib
|
2
|
+
from unittest.mock import MagicMock, patch
|
3
|
+
|
4
|
+
import botocore.exceptions
|
5
|
+
import pytest
|
6
|
+
|
7
|
+
from awspub import context, exceptions, image
|
8
|
+
|
9
|
+
curdir = pathlib.Path(__file__).parent.resolve()
|
10
|
+
|
11
|
+
|
12
|
+
@pytest.mark.parametrize(
|
13
|
+
"imagename,snapshotname",
|
14
|
+
[
|
15
|
+
# test-image-1 without any separate snapshot or billing products.
|
16
|
+
# so snapshotname should match source sha256sum
|
17
|
+
("test-image-1", "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8"),
|
18
|
+
# test-image-2 with separate snapshot but without billing products.
|
19
|
+
# so snapshotname should be the shasum of the concatenated string of:
|
20
|
+
# - 6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8
|
21
|
+
# - echo -n test-image-2 | sha256sum
|
22
|
+
("test-image-2", "0c274a96fe840cdd9cf65b0bf8e4d755d94fddf00916aa6f26ee3f08e412c88f"),
|
23
|
+
# test-image-3 with separate snapshot and billing products
|
24
|
+
("test-image-3", "ef7c5bbbc2816c60acfa4f3954e431c849054f7370bf351055f6d665b60623e7"),
|
25
|
+
# test-image-4 without separate snapshot but with billing products
|
26
|
+
("test-image-4", "bf795c602d53ff9c9548cc6305aa1240bd0f3d4429869abe4c96bcef65c4e48d"),
|
27
|
+
# test-image-5 without separate snapshot but with multiple billing products
|
28
|
+
("test-image-5", "8171cd4d36d06150a5ff8bb519439c5efd4e91841be62f50736db3b82e4aaedc"),
|
29
|
+
],
|
30
|
+
)
|
31
|
+
def test_snapshot_names(imagename, snapshotname):
|
32
|
+
"""
|
33
|
+
Test the snapshot name calculation based on the image properties
|
34
|
+
"""
|
35
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
36
|
+
assert ctx.conf["source"]["path"] == curdir / "fixtures/config1.vmdk"
|
37
|
+
assert ctx.source_sha256 == "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8"
|
38
|
+
|
39
|
+
img = image.Image(ctx, imagename)
|
40
|
+
assert img.snapshot_name == snapshotname
|
41
|
+
|
42
|
+
|
43
|
+
@pytest.mark.parametrize(
|
44
|
+
"imagename,regions_in_partition,regions_expected",
|
45
|
+
[
|
46
|
+
# test-image-1 has 2 regions defined
|
47
|
+
("test-image-1", ["region1", "region2"], ["region1", "region2"]),
|
48
|
+
# test-image-1 has 2 regions defined and there are more regions in the partition
|
49
|
+
("test-image-1", ["region1", "region2", "region3"], ["region1", "region2"]),
|
50
|
+
("test-image-1", ["region1", "region2"], ["region1", "region2"]),
|
51
|
+
# test-image-2 has no regions defined, so whatever the ec2 client returns should be valid
|
52
|
+
("test-image-2", ["all-region-1", "all-region-2"], ["all-region-1", "all-region-2"]),
|
53
|
+
# test-image-1 has 2 regions defined, but those regions are not in the partition
|
54
|
+
("test-image-1", ["region3", "region4"], []),
|
55
|
+
# test-image-1 has 2 regions defined, but those regions are not partially in the partition
|
56
|
+
("test-image-1", ["region2", "region4"], ["region2"]),
|
57
|
+
# test-image-2 has no regions defined and the ec2 client doesn't return any regions
|
58
|
+
("test-image-2", [], []),
|
59
|
+
],
|
60
|
+
)
|
61
|
+
@patch("awspub.s3.S3.bucket_region", return_value="region1")
|
62
|
+
def test_image_regions(s3_region_mock, imagename, regions_in_partition, regions_expected):
|
63
|
+
"""
|
64
|
+
Test the regions for a given image
|
65
|
+
"""
|
66
|
+
with patch("boto3.client") as bclient_mock:
|
67
|
+
instance = bclient_mock.return_value
|
68
|
+
instance.describe_regions.return_value = {"Regions": [{"RegionName": r} for r in regions_in_partition]}
|
69
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
70
|
+
img = image.Image(ctx, imagename)
|
71
|
+
assert sorted(img.image_regions) == sorted(regions_expected)
|
72
|
+
|
73
|
+
|
74
|
+
@pytest.mark.parametrize(
|
75
|
+
"imagename,cleanup",
|
76
|
+
[
|
77
|
+
("test-image-1", True),
|
78
|
+
("test-image-2", False),
|
79
|
+
],
|
80
|
+
)
|
81
|
+
def test_image_cleanup(imagename, cleanup):
|
82
|
+
"""
|
83
|
+
Test the cleanup for a given image
|
84
|
+
"""
|
85
|
+
with patch("boto3.client") as bclient_mock:
|
86
|
+
instance = bclient_mock.return_value
|
87
|
+
instance.describe_images.return_value = {"Images": [{"Name": imagename, "Public": False, "ImageId": "ami-123"}]}
|
88
|
+
instance.describe_regions.return_value = {"Regions": [{"RegionName": "region1"}, {"RegionName": "region2"}]}
|
89
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
90
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
91
|
+
img = image.Image(ctx, imagename)
|
92
|
+
img.cleanup()
|
93
|
+
assert instance.deregister_image.called == cleanup
|
94
|
+
|
95
|
+
|
96
|
+
@pytest.mark.parametrize(
|
97
|
+
"root_device_name,block_device_mappings,snapshot_id",
|
98
|
+
[
|
99
|
+
("", [], None),
|
100
|
+
("/dev/sda1", [], None),
|
101
|
+
(
|
102
|
+
"/dev/sda1",
|
103
|
+
[
|
104
|
+
{
|
105
|
+
"DeviceName": "/dev/sda1",
|
106
|
+
"Ebs": {
|
107
|
+
"DeleteOnTermination": True,
|
108
|
+
"SnapshotId": "snap-0be0763f84af34e05",
|
109
|
+
"VolumeSize": 17,
|
110
|
+
"VolumeType": "gp2",
|
111
|
+
"Encrypted": False,
|
112
|
+
},
|
113
|
+
},
|
114
|
+
{"DeviceName": "/dev/sdb", "VirtualName": "ephemeral0"},
|
115
|
+
],
|
116
|
+
"snap-0be0763f84af34e05",
|
117
|
+
),
|
118
|
+
],
|
119
|
+
)
|
120
|
+
def test_image___get_root_device_snapshot_id(root_device_name, block_device_mappings, snapshot_id):
|
121
|
+
"""
|
122
|
+
Test the _get_root_device_snapshot_id() method
|
123
|
+
"""
|
124
|
+
i = {"RootDeviceName": root_device_name, "BlockDeviceMappings": block_device_mappings}
|
125
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
126
|
+
img = image.Image(ctx, "test-image-1")
|
127
|
+
assert img._get_root_device_snapshot_id(i) == snapshot_id
|
128
|
+
|
129
|
+
|
130
|
+
@pytest.mark.parametrize(
|
131
|
+
(
|
132
|
+
"imagename",
|
133
|
+
"partition",
|
134
|
+
"called_mod_image",
|
135
|
+
"called_mod_snapshot",
|
136
|
+
"called_start_change_set",
|
137
|
+
"called_put_parameter",
|
138
|
+
"called_sns_publish",
|
139
|
+
),
|
140
|
+
[
|
141
|
+
("test-image-6", "aws", True, True, False, False, False),
|
142
|
+
("test-image-7", "aws", False, False, False, False, False),
|
143
|
+
("test-image-8", "aws", True, True, True, True, False),
|
144
|
+
("test-image-8", "aws-cn", True, True, False, True, False),
|
145
|
+
("test-image-10", "aws", False, False, False, False, True),
|
146
|
+
("test-image-11", "aws", False, False, False, False, True),
|
147
|
+
("test-image-12", "aws", False, False, False, False, True),
|
148
|
+
],
|
149
|
+
)
|
150
|
+
def test_image_publish(
|
151
|
+
imagename,
|
152
|
+
partition,
|
153
|
+
called_mod_image,
|
154
|
+
called_mod_snapshot,
|
155
|
+
called_start_change_set,
|
156
|
+
called_put_parameter,
|
157
|
+
called_sns_publish,
|
158
|
+
):
|
159
|
+
"""
|
160
|
+
Test the publish() for a given image
|
161
|
+
"""
|
162
|
+
with patch("boto3.client") as bclient_mock:
|
163
|
+
instance = bclient_mock.return_value
|
164
|
+
instance.meta.partition = partition
|
165
|
+
instance.describe_images.return_value = {
|
166
|
+
"Images": [
|
167
|
+
{
|
168
|
+
"Name": imagename,
|
169
|
+
"ImageId": "ami-abc",
|
170
|
+
"RootDeviceName": "/dev/sda1",
|
171
|
+
"BlockDeviceMappings": [
|
172
|
+
{
|
173
|
+
"DeviceName": "/dev/sda1",
|
174
|
+
"Ebs": {
|
175
|
+
"DeleteOnTermination": True,
|
176
|
+
"SnapshotId": "snap-0be0763f84af34e05",
|
177
|
+
},
|
178
|
+
},
|
179
|
+
],
|
180
|
+
}
|
181
|
+
]
|
182
|
+
}
|
183
|
+
instance.get_parameters.return_value = {"Parameters": []}
|
184
|
+
instance.describe_regions.return_value = {
|
185
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
186
|
+
}
|
187
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
188
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
189
|
+
img = image.Image(ctx, imagename)
|
190
|
+
img.publish()
|
191
|
+
assert instance.modify_image_attribute.called == called_mod_image
|
192
|
+
assert instance.modify_snapshot_attribute.called == called_mod_snapshot
|
193
|
+
assert instance.start_change_set.called == called_start_change_set
|
194
|
+
assert instance.put_parameter.called == called_put_parameter
|
195
|
+
assert instance.publish.called == called_sns_publish
|
196
|
+
|
197
|
+
|
198
|
+
def test_image__get_zero_images():
|
199
|
+
"""
|
200
|
+
Test the Image._get() method with zero matching image
|
201
|
+
"""
|
202
|
+
with patch("boto3.client") as bclient_mock:
|
203
|
+
instance = bclient_mock.return_value
|
204
|
+
instance.describe_images.return_value = {"Images": []}
|
205
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
206
|
+
img = image.Image(ctx, "test-image-1")
|
207
|
+
assert img._get(instance) is None
|
208
|
+
|
209
|
+
|
210
|
+
def test_image__get_one_images():
|
211
|
+
"""
|
212
|
+
Test the Image._get() method with a single matching image
|
213
|
+
"""
|
214
|
+
with patch("boto3.client") as bclient_mock:
|
215
|
+
instance = bclient_mock.return_value
|
216
|
+
instance.describe_images.return_value = {
|
217
|
+
"Images": [
|
218
|
+
{
|
219
|
+
"Name": "test-image-1",
|
220
|
+
"ImageId": "ami-abc",
|
221
|
+
"RootDeviceName": "/dev/sda1",
|
222
|
+
"BlockDeviceMappings": [
|
223
|
+
{
|
224
|
+
"DeviceName": "/dev/sda1",
|
225
|
+
"Ebs": {
|
226
|
+
"DeleteOnTermination": True,
|
227
|
+
"SnapshotId": "snap-abc",
|
228
|
+
},
|
229
|
+
},
|
230
|
+
],
|
231
|
+
}
|
232
|
+
]
|
233
|
+
}
|
234
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
235
|
+
img = image.Image(ctx, "test-image-1")
|
236
|
+
assert img._get(instance) == image._ImageInfo("ami-abc", "snap-abc")
|
237
|
+
|
238
|
+
|
239
|
+
def test_image__get_multiple_images():
|
240
|
+
"""
|
241
|
+
Test the Image._get() method with a multiple matching image
|
242
|
+
"""
|
243
|
+
with patch("boto3.client") as bclient_mock:
|
244
|
+
instance = bclient_mock.return_value
|
245
|
+
instance.describe_images.return_value = {
|
246
|
+
"Images": [
|
247
|
+
{
|
248
|
+
"Name": "test-image-1",
|
249
|
+
"ImageId": "ami-1,",
|
250
|
+
},
|
251
|
+
{
|
252
|
+
"Name": "test-image-1",
|
253
|
+
"ImageId": "ami-2,",
|
254
|
+
},
|
255
|
+
]
|
256
|
+
}
|
257
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
258
|
+
img = image.Image(ctx, "test-image-1")
|
259
|
+
with pytest.raises(exceptions.MultipleImagesException):
|
260
|
+
img._get(instance)
|
261
|
+
|
262
|
+
|
263
|
+
@pytest.mark.parametrize(
|
264
|
+
"imagename,expected_tags",
|
265
|
+
[
|
266
|
+
# no image specific tags - assume the common tags
|
267
|
+
(
|
268
|
+
"test-image-1",
|
269
|
+
[
|
270
|
+
{"Key": "awspub:source:filename", "Value": "config1.vmdk"},
|
271
|
+
{"Key": "awspub:source:architecture", "Value": "x86_64"},
|
272
|
+
{
|
273
|
+
"Key": "awspub:source:sha256",
|
274
|
+
"Value": "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8",
|
275
|
+
},
|
276
|
+
{"Key": "name", "Value": "foobar"},
|
277
|
+
],
|
278
|
+
),
|
279
|
+
# with image specific tag but no override
|
280
|
+
(
|
281
|
+
"test-image-6",
|
282
|
+
[
|
283
|
+
{"Key": "awspub:source:filename", "Value": "config1.vmdk"},
|
284
|
+
{"Key": "awspub:source:architecture", "Value": "x86_64"},
|
285
|
+
{
|
286
|
+
"Key": "awspub:source:sha256",
|
287
|
+
"Value": "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8",
|
288
|
+
},
|
289
|
+
{"Key": "name", "Value": "foobar"},
|
290
|
+
{"Key": "key1", "Value": "value1"},
|
291
|
+
],
|
292
|
+
),
|
293
|
+
# with image specific tag which overrides common tag
|
294
|
+
(
|
295
|
+
"test-image-7",
|
296
|
+
[
|
297
|
+
{"Key": "awspub:source:filename", "Value": "config1.vmdk"},
|
298
|
+
{"Key": "awspub:source:architecture", "Value": "x86_64"},
|
299
|
+
{
|
300
|
+
"Key": "awspub:source:sha256",
|
301
|
+
"Value": "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8",
|
302
|
+
},
|
303
|
+
{"Key": "name", "Value": "not-foobar"},
|
304
|
+
{"Key": "key2", "Value": "name"},
|
305
|
+
],
|
306
|
+
),
|
307
|
+
],
|
308
|
+
)
|
309
|
+
def test_image__tags(imagename, expected_tags):
|
310
|
+
"""
|
311
|
+
Test the Image._tags() method
|
312
|
+
"""
|
313
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
314
|
+
img = image.Image(ctx, imagename)
|
315
|
+
assert img._tags == expected_tags
|
316
|
+
|
317
|
+
|
318
|
+
@pytest.mark.parametrize(
|
319
|
+
"available_images,expected",
|
320
|
+
[
|
321
|
+
# image available
|
322
|
+
([{"Name": "test-image-6", "ImageId": "ami-123"}], {"eu-central-1": image._ImageInfo("ami-123", None)}),
|
323
|
+
# image not available
|
324
|
+
([], {}),
|
325
|
+
],
|
326
|
+
)
|
327
|
+
def test_image_list(available_images, expected):
|
328
|
+
"""
|
329
|
+
Test the list for a given image
|
330
|
+
"""
|
331
|
+
with patch("boto3.client") as bclient_mock:
|
332
|
+
instance = bclient_mock.return_value
|
333
|
+
instance.describe_images.return_value = {"Images": available_images}
|
334
|
+
instance.describe_regions.return_value = {
|
335
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
336
|
+
}
|
337
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
338
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
339
|
+
img = image.Image(ctx, "test-image-6")
|
340
|
+
assert img.list() == expected
|
341
|
+
|
342
|
+
|
343
|
+
@patch("awspub.s3.S3.bucket_region", return_value="region1")
|
344
|
+
def test_image_create_existing(s3_bucket_mock):
|
345
|
+
"""
|
346
|
+
Test the create() method for a given image that already exist
|
347
|
+
"""
|
348
|
+
with patch("boto3.client") as bclient_mock:
|
349
|
+
instance = bclient_mock.return_value
|
350
|
+
instance.describe_snapshots.return_value = {"Snapshots": [{"SnapshotId": "snap-123"}]}
|
351
|
+
instance.describe_images.return_value = {
|
352
|
+
"Images": [
|
353
|
+
{
|
354
|
+
"Name": "test-image-6",
|
355
|
+
"ImageId": "ami-123",
|
356
|
+
"RootDeviceName": "/dev/sda1",
|
357
|
+
"BlockDeviceMappings": [
|
358
|
+
{
|
359
|
+
"DeviceName": "/dev/sda1",
|
360
|
+
"Ebs": {
|
361
|
+
"DeleteOnTermination": True,
|
362
|
+
"SnapshotId": "snap-123",
|
363
|
+
},
|
364
|
+
},
|
365
|
+
],
|
366
|
+
}
|
367
|
+
]
|
368
|
+
}
|
369
|
+
instance.describe_regions.return_value = {
|
370
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
371
|
+
}
|
372
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
373
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
374
|
+
img = image.Image(ctx, "test-image-6")
|
375
|
+
assert img.create() == {"eu-central-1": image._ImageInfo(image_id="ami-123", snapshot_id="snap-123")}
|
376
|
+
# register and create_tags shouldn't be called given that the image was already there
|
377
|
+
assert not instance.register_image.called
|
378
|
+
assert not instance.create_tags.called
|
379
|
+
|
380
|
+
|
381
|
+
@pytest.mark.parametrize(
|
382
|
+
"imagename,describe_images,get_parameters,get_parameters_called,put_parameter_called",
|
383
|
+
[
|
384
|
+
# no image, no parameters (this should actually never happen but test it anyway)
|
385
|
+
("test-image-8", [], [], False, False),
|
386
|
+
# with image, no parameter, no overwrite
|
387
|
+
("test-image-8", [{"Name": "test-image-8", "ImageId": "ami-123"}], [], True, True),
|
388
|
+
# with image, with parameter, no overwrite
|
389
|
+
(
|
390
|
+
"test-image-8",
|
391
|
+
[{"Name": "test-image-8", "ImageId": "ami-123"}],
|
392
|
+
[{"Name": "/awspub-test/param1", "Value": "ami-123"}],
|
393
|
+
True,
|
394
|
+
False,
|
395
|
+
),
|
396
|
+
# with image, no parameter, with overwrite
|
397
|
+
("test-image-9", [{"Name": "test-image-8", "ImageId": "ami-123"}], [], False, True),
|
398
|
+
# with image, with parameter, with overwrite
|
399
|
+
(
|
400
|
+
"test-image-9",
|
401
|
+
[{"Name": "test-image-8", "ImageId": "ami-123"}],
|
402
|
+
[{"Name": "/awspub-test/param1", "Value": "ami-123"}],
|
403
|
+
False,
|
404
|
+
True,
|
405
|
+
),
|
406
|
+
],
|
407
|
+
)
|
408
|
+
def test_image__put_ssm_parameters(
|
409
|
+
imagename, describe_images, get_parameters, get_parameters_called, put_parameter_called
|
410
|
+
):
|
411
|
+
"""
|
412
|
+
Test the _put_ssm_parameters() method
|
413
|
+
"""
|
414
|
+
with patch("boto3.client") as bclient_mock:
|
415
|
+
instance = bclient_mock.return_value
|
416
|
+
instance.describe_images.return_value = {"Images": describe_images}
|
417
|
+
instance.get_parameters.return_value = {"Parameters": get_parameters}
|
418
|
+
instance.describe_regions.return_value = {
|
419
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
420
|
+
}
|
421
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
422
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
423
|
+
img = image.Image(ctx, imagename)
|
424
|
+
img._put_ssm_parameters()
|
425
|
+
assert instance.get_parameters.called == get_parameters_called
|
426
|
+
assert instance.put_parameter.called == put_parameter_called
|
427
|
+
|
428
|
+
|
429
|
+
@pytest.mark.parametrize(
|
430
|
+
"image_found,config,config_image_name, expected_problems",
|
431
|
+
[
|
432
|
+
# image not available
|
433
|
+
([], "fixtures/config1.yaml", "test-image-6", [image.ImageVerificationErrors.NOT_EXIST]),
|
434
|
+
# image matches expectations from config
|
435
|
+
(
|
436
|
+
[
|
437
|
+
{
|
438
|
+
"Name": "test-image-6",
|
439
|
+
"State": "available",
|
440
|
+
"ImageId": "ami-123",
|
441
|
+
"RootDeviceName": "/dev/sda1",
|
442
|
+
"RootDeviceType": "ebs",
|
443
|
+
"BootMode": "uefi-preferred",
|
444
|
+
"BlockDeviceMappings": [
|
445
|
+
{
|
446
|
+
"DeviceName": "/dev/sda1",
|
447
|
+
"Ebs": {
|
448
|
+
"DeleteOnTermination": True,
|
449
|
+
"VolumeType": "gp3",
|
450
|
+
"VolumeSize": 8,
|
451
|
+
"SnapshotId": "snap-123",
|
452
|
+
},
|
453
|
+
},
|
454
|
+
],
|
455
|
+
"Tags": [
|
456
|
+
{"Key": "name", "Value": "foobar"},
|
457
|
+
],
|
458
|
+
}
|
459
|
+
],
|
460
|
+
"fixtures/config1.yaml",
|
461
|
+
"test-image-6",
|
462
|
+
[],
|
463
|
+
),
|
464
|
+
],
|
465
|
+
)
|
466
|
+
def test_image__verify(image_found, config, config_image_name, expected_problems):
|
467
|
+
"""
|
468
|
+
Test _verify() for a given image and configuration
|
469
|
+
"""
|
470
|
+
with patch("boto3.client") as bclient_mock:
|
471
|
+
instance = bclient_mock.return_value
|
472
|
+
instance.describe_images.return_value = {"Images": image_found}
|
473
|
+
instance.describe_snapshots.return_value = {"Snapshots": [{"State": "completed"}]}
|
474
|
+
ctx = context.Context(curdir / config, None)
|
475
|
+
img = image.Image(ctx, config_image_name)
|
476
|
+
problems = img._verify("eu-central-1")
|
477
|
+
assert problems == expected_problems
|
478
|
+
|
479
|
+
|
480
|
+
@pytest.mark.parametrize(
|
481
|
+
"partition,imagename,share_list_expected",
|
482
|
+
[
|
483
|
+
("aws", "test-image-8", [{"UserId": "123456789123"}, {"UserId": "221020170000"}, {"UserId": "290620200000"}]),
|
484
|
+
("aws-cn", "test-image-8", [{"UserId": "334455667788"}]),
|
485
|
+
("aws-us-gov", "test-image-8", []),
|
486
|
+
],
|
487
|
+
)
|
488
|
+
def test_image__share_list_filtered(partition, imagename, share_list_expected):
|
489
|
+
"""
|
490
|
+
Test _share_list_filtered() for a given image
|
491
|
+
"""
|
492
|
+
with patch("boto3.client") as bclient_mock:
|
493
|
+
instance = bclient_mock.return_value
|
494
|
+
instance.meta.partition = partition
|
495
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
496
|
+
img = image.Image(ctx, imagename)
|
497
|
+
assert img._share_list_filtered(img.conf["share"]) == share_list_expected
|
498
|
+
|
499
|
+
|
500
|
+
@patch("awspub.s3.S3.bucket_region", return_value="region1")
|
501
|
+
def test_create__should_allow_partial_registration(s3_bucket_mock):
|
502
|
+
"""
|
503
|
+
Test that the create() method allows a partial upload set
|
504
|
+
"""
|
505
|
+
with patch("boto3.client") as bclient_mock:
|
506
|
+
instance = bclient_mock.return_value
|
507
|
+
|
508
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
509
|
+
img = image.Image(ctx, "test-image-6")
|
510
|
+
img._image_regions = ["region1", "region2"]
|
511
|
+
img._image_regions_cached = True
|
512
|
+
with patch.object(img, "_get") as get_mock, patch.object(img._snapshot, "copy") as copy_mock:
|
513
|
+
copy_mock.return_value = {r: f"snapshot{i}" for i, r in enumerate(img.image_regions)}
|
514
|
+
get_mock.return_value = None
|
515
|
+
instance.register_image.side_effect = [
|
516
|
+
botocore.exceptions.ClientError(
|
517
|
+
{
|
518
|
+
"Error": {
|
519
|
+
"Code": "OperationNotPermitted",
|
520
|
+
"Message": "Intentional permission failure for snapshot0",
|
521
|
+
}
|
522
|
+
},
|
523
|
+
"awspub Testing",
|
524
|
+
),
|
525
|
+
{"ImageId": "id1"},
|
526
|
+
]
|
527
|
+
with pytest.raises(exceptions.IncompleteImageSetException):
|
528
|
+
img.create() == {"region2": image._ImageInfo("id1", "snapshot1")}
|
529
|
+
# register and create_tags should be called since at least one snapshot made it
|
530
|
+
assert instance.register_image.called
|
531
|
+
assert instance.create_tags.called
|
532
|
+
|
533
|
+
|
534
|
+
def test_register_image__should_return_none_on_permission_failures():
|
535
|
+
instance = MagicMock()
|
536
|
+
|
537
|
+
instance.register_image.side_effect = botocore.exceptions.ClientError(
|
538
|
+
{"Error": {"Code": "OperationNotPermitted", "Message": "Testing"}}, "Testing"
|
539
|
+
)
|
540
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
541
|
+
img = image.Image(ctx, "test-image-6")
|
542
|
+
snapshot_ids = {"eu-central-1": "my-snapshot"}
|
543
|
+
assert img._register_image(snapshot_ids["eu-central-1"], instance) is None
|
544
|
+
|
545
|
+
|
546
|
+
def test_register_image__should_raise_on_unhandled_client_error():
|
547
|
+
instance = MagicMock()
|
548
|
+
|
549
|
+
instance.register_image.side_effect = botocore.exceptions.ClientError(
|
550
|
+
{"Error": {"Code": "UnsupportedOperation", "Message": "Testing"}}, "Testing"
|
551
|
+
)
|
552
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
553
|
+
img = image.Image(ctx, "test-image-6")
|
554
|
+
snapshot_ids = {"eu-central-1": "my-snapshot"}
|
555
|
+
with pytest.raises(botocore.exceptions.ClientError):
|
556
|
+
img._register_image(snapshot_ids["eu-central-1"], instance) is None
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import pathlib
|
2
|
+
from unittest.mock import patch
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from awspub import context, image_marketplace
|
7
|
+
|
8
|
+
curdir = pathlib.Path(__file__).parent.resolve()
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.mark.parametrize(
|
12
|
+
"imagename,new_version,called_start_change_set",
|
13
|
+
[
|
14
|
+
# same version that already exists
|
15
|
+
("test-image-8", "1.0.0", False),
|
16
|
+
# new version
|
17
|
+
("test-image-8", "2.0.0", True),
|
18
|
+
],
|
19
|
+
)
|
20
|
+
def test_image_marketplace_request_new_version(imagename, new_version, called_start_change_set):
|
21
|
+
"""
|
22
|
+
Test the request_new_version logic
|
23
|
+
"""
|
24
|
+
with patch("boto3.client") as bclient_mock:
|
25
|
+
instance = bclient_mock.return_value
|
26
|
+
instance.describe_entity.return_value = {"DetailsDocument": {"Versions": [{"VersionTitle": new_version}]}}
|
27
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
28
|
+
img = image_marketplace.ImageMarketplace(ctx, imagename)
|
29
|
+
img.request_new_version("ami-123")
|
30
|
+
assert instance.start_change_set.called == called_start_change_set
|
31
|
+
|
32
|
+
|
33
|
+
@pytest.mark.parametrize(
|
34
|
+
"name,expected",
|
35
|
+
[
|
36
|
+
("1.0.0", "1.0.0"),
|
37
|
+
("1.0.0 (testing)", "1.0.0 testing"),
|
38
|
+
("a sentence with spaces", "a sentence with spaces"),
|
39
|
+
("_+=.:@-", "_+=.:@-"),
|
40
|
+
("(parens) [brackets] |pipes|", "parens brackets pipes"),
|
41
|
+
],
|
42
|
+
)
|
43
|
+
def test_changeset_name_sanitization(name, expected):
|
44
|
+
assert image_marketplace.ImageMarketplace.sanitize_changeset_name(name) == expected
|
awspub/tests/test_s3.py
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
import pathlib
|
2
|
+
from unittest.mock import patch
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from awspub import context, s3
|
7
|
+
from awspub.exceptions import BucketDoesNotExistException
|
8
|
+
|
9
|
+
curdir = pathlib.Path(__file__).parent.resolve()
|
10
|
+
|
11
|
+
|
12
|
+
@pytest.mark.parametrize(
|
13
|
+
"list_multipart_uploads_resp,create_multipart_upload_called",
|
14
|
+
[
|
15
|
+
# no available uploads - create one
|
16
|
+
([], True),
|
17
|
+
# one available upload with non-matching key
|
18
|
+
([{"UploadId": "abc", "Key": "does-not-match"}], True),
|
19
|
+
# multiple available upload with non-matching key
|
20
|
+
([{"UploadId": "abc", "Key": "does-not-match"}, {"UploadId": "def", "Key": "does-not-match2"}], True),
|
21
|
+
# one available upload with matching key
|
22
|
+
([{"UploadId": "abc", "Key": "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8"}], False),
|
23
|
+
# multiple available upload with one matching key
|
24
|
+
(
|
25
|
+
[
|
26
|
+
{"UploadId": "abc", "Key": "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8"},
|
27
|
+
{"UploadId": "abc", "Key": "does-not-match"},
|
28
|
+
],
|
29
|
+
False,
|
30
|
+
),
|
31
|
+
# multiple available upload with multiple matching keys
|
32
|
+
(
|
33
|
+
[
|
34
|
+
{"UploadId": "abc", "Key": "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8"},
|
35
|
+
{"UploadId": "def", "Key": "6252475408b9f9ee64452b611d706a078831a99b123db69d144d878a0488a0a8"},
|
36
|
+
],
|
37
|
+
False,
|
38
|
+
),
|
39
|
+
],
|
40
|
+
)
|
41
|
+
def test_s3__get_multipart_upload_id(list_multipart_uploads_resp, create_multipart_upload_called):
|
42
|
+
"""
|
43
|
+
test the _get_multipart_upload_id() function
|
44
|
+
"""
|
45
|
+
|
46
|
+
with patch("boto3.client") as bclient_mock:
|
47
|
+
instance = bclient_mock.return_value
|
48
|
+
instance.list_multipart_uploads.return_value = {"Uploads": list_multipart_uploads_resp}
|
49
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
50
|
+
sthree = s3.S3(ctx)
|
51
|
+
sthree._get_multipart_upload_id()
|
52
|
+
assert instance.create_multipart_upload.called == create_multipart_upload_called
|
53
|
+
|
54
|
+
|
55
|
+
@patch("awspub.s3.S3._bucket_exists", return_value=True)
|
56
|
+
@patch("awspub.s3.boto3")
|
57
|
+
def test_s3_bucket_region_bucket_exists(boto3_mock, bucket_exists_mock):
|
58
|
+
region_name = "sample-region-1"
|
59
|
+
head_bucket = {"BucketRegion": region_name}
|
60
|
+
boto3_mock.client.return_value.head_bucket.return_value = head_bucket
|
61
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
62
|
+
sthree = s3.S3(ctx)
|
63
|
+
|
64
|
+
assert sthree.bucket_region == region_name
|
65
|
+
|
66
|
+
|
67
|
+
@patch("awspub.s3.S3._bucket_exists", return_value=False)
|
68
|
+
@patch("boto3.client")
|
69
|
+
def test_s3_bucket_region_bucket_not_exists(bclient_mock, bucket_exists_mock):
|
70
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
71
|
+
sthree = s3.S3(ctx)
|
72
|
+
|
73
|
+
with pytest.raises(BucketDoesNotExistException):
|
74
|
+
sthree.bucket_region()
|