awspub 0.0.4__py3-none-any.whl → 0.0.6__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/common.py +18 -0
- awspub/configmodels.py +24 -3
- awspub/image.py +41 -8
- awspub/s3.py +1 -1
- awspub/tests/fixtures/config1.yaml +5 -0
- awspub/tests/test_common.py +16 -0
- awspub/tests/test_image.py +53 -8
- {awspub-0.0.4.dist-info → awspub-0.0.6.dist-info}/METADATA +1 -1
- {awspub-0.0.4.dist-info → awspub-0.0.6.dist-info}/RECORD +12 -10
- {awspub-0.0.4.dist-info → awspub-0.0.6.dist-info}/LICENSE +0 -0
- {awspub-0.0.4.dist-info → awspub-0.0.6.dist-info}/WHEEL +0 -0
- {awspub-0.0.4.dist-info → awspub-0.0.6.dist-info}/entry_points.txt +0 -0
awspub/common.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from typing import Tuple
|
2
|
+
|
3
|
+
|
4
|
+
def _split_partition(val: str) -> Tuple[str, str]:
|
5
|
+
"""
|
6
|
+
Split a string into partition and resource, separated by a colon. If no partition is given, assume "aws"
|
7
|
+
:param val: the string to split
|
8
|
+
:type val: str
|
9
|
+
:return: the partition and the resource
|
10
|
+
:rtype: Tuple[str, str]
|
11
|
+
"""
|
12
|
+
if ":" in val:
|
13
|
+
partition, resource = val.split(":")
|
14
|
+
else:
|
15
|
+
# if no partition is given, assume default commercial partition "aws"
|
16
|
+
partition = "aws"
|
17
|
+
resource = val
|
18
|
+
return partition, resource
|
awspub/configmodels.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import pathlib
|
2
2
|
from typing import Dict, List, Literal, Optional
|
3
3
|
|
4
|
-
from pydantic import BaseModel, ConfigDict, Field
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
5
|
+
|
6
|
+
from awspub.common import _split_partition
|
5
7
|
|
6
8
|
|
7
9
|
class ConfigS3Model(BaseModel):
|
@@ -101,7 +103,9 @@ class ConfigImageModel(BaseModel):
|
|
101
103
|
|
102
104
|
description: Optional[str] = Field(description="Optional image description", default=None)
|
103
105
|
regions: Optional[List[str]] = Field(
|
104
|
-
description="Optional list of regions for this image. If not given, all available regions will
|
106
|
+
description="Optional list of regions for this image. If not given, all available regions will"
|
107
|
+
"be used from the currently used partition. If a region doesn't exist in the currently used partition,"
|
108
|
+
" it will be ignored.",
|
105
109
|
default=None,
|
106
110
|
)
|
107
111
|
separate_snapshot: bool = Field(description="Use a separate snapshot for this image?", default=False)
|
@@ -122,7 +126,9 @@ class ConfigImageModel(BaseModel):
|
|
122
126
|
)
|
123
127
|
imds_support: Optional[Literal["v2.0"]] = Field(description="Optional IMDS support", default=None)
|
124
128
|
share: Optional[List[str]] = Field(
|
125
|
-
description="Optional list of account IDs the image and snapshot will be shared with"
|
129
|
+
description="Optional list of account IDs the image and snapshot will be shared with. The account"
|
130
|
+
"ID can be prefixed with the partition and separated by ':'. Eg 'aws-cn:123456789123'",
|
131
|
+
default=None,
|
126
132
|
)
|
127
133
|
temporary: Optional[bool] = Field(
|
128
134
|
description="Optional boolean field indicates that a image is only temporary", default=False
|
@@ -143,6 +149,21 @@ class ConfigImageModel(BaseModel):
|
|
143
149
|
groups: Optional[List[str]] = Field(description="Optional list of groups this image is part of", default=[])
|
144
150
|
tags: Optional[Dict[str, str]] = Field(description="Optional Tags to apply to this image only", default={})
|
145
151
|
|
152
|
+
@field_validator("share")
|
153
|
+
@classmethod
|
154
|
+
def check_share(cls, v: Optional[List[str]]) -> Optional[List[str]]:
|
155
|
+
"""
|
156
|
+
Make sure the account IDs are valid and if given the partition is correct
|
157
|
+
"""
|
158
|
+
if v is not None:
|
159
|
+
for val in v:
|
160
|
+
partition, account_id = _split_partition(val)
|
161
|
+
if len(account_id) != 12:
|
162
|
+
raise ValueError("Account ID must be 12 characters long")
|
163
|
+
if partition not in ["aws", "aws-cn", "aws-us-gov"]:
|
164
|
+
raise ValueError("Partition must be one of 'aws', 'aws-cn', 'aws-us-gov'")
|
165
|
+
return v
|
166
|
+
|
146
167
|
|
147
168
|
class ConfigModel(BaseModel):
|
148
169
|
"""
|
awspub/image.py
CHANGED
@@ -9,6 +9,7 @@ from mypy_boto3_ec2.client import EC2Client
|
|
9
9
|
from mypy_boto3_ssm import SSMClient
|
10
10
|
|
11
11
|
from awspub import exceptions
|
12
|
+
from awspub.common import _split_partition
|
12
13
|
from awspub.context import Context
|
13
14
|
from awspub.image_marketplace import ImageMarketplace
|
14
15
|
from awspub.s3 import S3
|
@@ -54,6 +55,7 @@ class Image:
|
|
54
55
|
self._ctx: Context = context
|
55
56
|
self._image_name: str = image_name
|
56
57
|
self._image_regions: List[str] = []
|
58
|
+
self._image_regions_cached: bool = False
|
57
59
|
|
58
60
|
if self._image_name not in self._ctx.conf["images"].keys():
|
59
61
|
raise ValueError(f"image '{self._image_name}' not found in context configuration")
|
@@ -119,15 +121,28 @@ class Image:
|
|
119
121
|
def image_regions(self) -> List[str]:
|
120
122
|
"""
|
121
123
|
Get the image regions. Either configured in the image configuration
|
122
|
-
or all available regions
|
124
|
+
or all available regions.
|
125
|
+
If a region is listed that is not available in the currently used partition,
|
126
|
+
that region will be ignored (eg. having us-east-1 configured but running in the aws-cn
|
127
|
+
partition doesn't include us-east-1 here).
|
123
128
|
"""
|
124
|
-
if not self.
|
129
|
+
if not self._image_regions_cached:
|
130
|
+
# get all available regions
|
131
|
+
ec2client: EC2Client = boto3.client("ec2", region_name=self._s3.bucket_region)
|
132
|
+
resp = ec2client.describe_regions()
|
133
|
+
image_regions_all = [r["RegionName"] for r in resp["Regions"]]
|
134
|
+
|
125
135
|
if self.conf["regions"]:
|
126
|
-
|
136
|
+
# filter out regions that are not available in the current partition
|
137
|
+
image_regions_configured_set = set(self.conf["regions"])
|
138
|
+
image_regions_all_set = set(image_regions_all)
|
139
|
+
self._image_regions = list(image_regions_configured_set.intersection(image_regions_all_set))
|
140
|
+
diff = image_regions_configured_set.difference(image_regions_all_set)
|
141
|
+
if diff:
|
142
|
+
logger.warning(f"configured regions {diff} not available in the current partition. Ignoring those.")
|
127
143
|
else:
|
128
|
-
|
129
|
-
|
130
|
-
self._image_regions = [r["RegionName"] for r in resp["Regions"]]
|
144
|
+
self._image_regions = image_regions_all
|
145
|
+
self._image_regions_cached = True
|
131
146
|
return self._image_regions
|
132
147
|
|
133
148
|
@property
|
@@ -145,16 +160,34 @@ class Image:
|
|
145
160
|
tags.append({"Key": name, "Value": value})
|
146
161
|
return tags
|
147
162
|
|
163
|
+
def _share_list_filtered(self, share_conf: List[str]) -> List[Dict[str, str]]:
|
164
|
+
"""
|
165
|
+
Get a filtered list of share configurations based on the current partition
|
166
|
+
:param share_conf: the share configuration
|
167
|
+
:type share_conf: List[str]
|
168
|
+
:return: a List of share configurations that is usable by modify_image_attribute()
|
169
|
+
:rtype: List[Dict[str, str]]
|
170
|
+
"""
|
171
|
+
# the current partition
|
172
|
+
partition_current = boto3.client("ec2").meta.partition
|
173
|
+
|
174
|
+
share_list: List[Dict[str, str]] = []
|
175
|
+
for share in share_conf:
|
176
|
+
partition, account_id = _split_partition(share)
|
177
|
+
if partition == partition_current:
|
178
|
+
share_list.append({"UserId": account_id})
|
179
|
+
return share_list
|
180
|
+
|
148
181
|
def _share(self, share_conf: List[str], images: Dict[str, _ImageInfo]):
|
149
182
|
"""
|
150
183
|
Share images with accounts
|
151
184
|
|
152
|
-
:param share_conf: the share configuration
|
185
|
+
:param share_conf: the share configuration containing list
|
153
186
|
:type share_conf: List[str]
|
154
187
|
:param images: a Dict with region names as keys and _ImageInfo objects as values
|
155
188
|
:type images: Dict[str, _ImageInfo]
|
156
189
|
"""
|
157
|
-
share_list
|
190
|
+
share_list = self._share_list_filtered(share_conf)
|
158
191
|
|
159
192
|
for region, image_info in images.items():
|
160
193
|
ec2client: EC2Client = boto3.client("ec2", region_name=region)
|
awspub/s3.py
CHANGED
@@ -78,6 +78,11 @@ awspub:
|
|
78
78
|
public: true
|
79
79
|
tags:
|
80
80
|
key1: value1
|
81
|
+
share:
|
82
|
+
- "123456789123"
|
83
|
+
- "221020170000"
|
84
|
+
- "aws:290620200000"
|
85
|
+
- "aws-cn:334455667788"
|
81
86
|
marketplace:
|
82
87
|
entity_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
83
88
|
access_role_arn: "arn:aws:iam::xxxxxxxxxxxx:role/AWSMarketplaceAccess"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from awspub.common import _split_partition
|
4
|
+
|
5
|
+
|
6
|
+
@pytest.mark.parametrize(
|
7
|
+
"input,expected_output",
|
8
|
+
[
|
9
|
+
("123456789123", ("aws", "123456789123")),
|
10
|
+
("aws:123456789123", ("aws", "123456789123")),
|
11
|
+
("aws-cn:123456789123", ("aws-cn", "123456789123")),
|
12
|
+
("aws-us-gov:123456789123", ("aws-us-gov", "123456789123")),
|
13
|
+
],
|
14
|
+
)
|
15
|
+
def test_common__split_partition(input, expected_output):
|
16
|
+
assert _split_partition(input) == expected_output
|
awspub/tests/test_image.py
CHANGED
@@ -40,27 +40,34 @@ def test_snapshot_names(imagename, snapshotname):
|
|
40
40
|
|
41
41
|
|
42
42
|
@pytest.mark.parametrize(
|
43
|
-
"imagename,
|
43
|
+
"imagename,regions_in_partition,regions_expected",
|
44
44
|
[
|
45
45
|
# test-image-1 has 2 regions defined
|
46
|
-
("test-image-1", ["region1", "region2"]),
|
46
|
+
("test-image-1", ["region1", "region2"], ["region1", "region2"]),
|
47
|
+
# test-image-1 has 2 regions defined and there are more regions in the partition
|
48
|
+
("test-image-1", ["region1", "region2", "region3"], ["region1", "region2"]),
|
49
|
+
("test-image-1", ["region1", "region2"], ["region1", "region2"]),
|
47
50
|
# test-image-2 has no regions defined, so whatever the ec2 client returns should be valid
|
48
|
-
("test-image-2", ["all-region-1", "all-region-2"]),
|
51
|
+
("test-image-2", ["all-region-1", "all-region-2"], ["all-region-1", "all-region-2"]),
|
52
|
+
# test-image-1 has 2 regions defined, but those regions are not in the partition
|
53
|
+
("test-image-1", ["region3", "region4"], []),
|
54
|
+
# test-image-1 has 2 regions defined, but those regions are not partially in the partition
|
55
|
+
("test-image-1", ["region2", "region4"], ["region2"]),
|
56
|
+
# test-image-2 has no regions defined and the ec2 client doesn't return any regions
|
57
|
+
("test-image-2", [], []),
|
49
58
|
],
|
50
59
|
)
|
51
60
|
@patch("awspub.s3.S3.bucket_region", return_value="region1")
|
52
|
-
def test_image_regions(s3_region_mock, imagename,
|
61
|
+
def test_image_regions(s3_region_mock, imagename, regions_in_partition, regions_expected):
|
53
62
|
"""
|
54
63
|
Test the regions for a given image
|
55
64
|
"""
|
56
65
|
with patch("boto3.client") as bclient_mock:
|
57
66
|
instance = bclient_mock.return_value
|
58
|
-
instance.describe_regions.return_value = {
|
59
|
-
"Regions": [{"RegionName": "all-region-1"}, {"RegionName": "all-region-2"}]
|
60
|
-
}
|
67
|
+
instance.describe_regions.return_value = {"Regions": [{"RegionName": r} for r in regions_in_partition]}
|
61
68
|
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
62
69
|
img = image.Image(ctx, imagename)
|
63
|
-
assert img.image_regions ==
|
70
|
+
assert sorted(img.image_regions) == sorted(regions_expected)
|
64
71
|
|
65
72
|
|
66
73
|
@pytest.mark.parametrize(
|
@@ -77,6 +84,8 @@ def test_image_cleanup(imagename, cleanup):
|
|
77
84
|
with patch("boto3.client") as bclient_mock:
|
78
85
|
instance = bclient_mock.return_value
|
79
86
|
instance.describe_images.return_value = {"Images": [{"Name": imagename, "Public": False, "ImageId": "ami-123"}]}
|
87
|
+
instance.describe_regions.return_value = {"Regions": [{"RegionName": "region1"}, {"RegionName": "region2"}]}
|
88
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
80
89
|
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
81
90
|
img = image.Image(ctx, imagename)
|
82
91
|
img.cleanup()
|
@@ -154,6 +163,10 @@ def test_image_publish(
|
|
154
163
|
]
|
155
164
|
}
|
156
165
|
instance.get_parameters.return_value = {"Parameters": []}
|
166
|
+
instance.describe_regions.return_value = {
|
167
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
168
|
+
}
|
169
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
157
170
|
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
158
171
|
img = image.Image(ctx, imagename)
|
159
172
|
img.publish()
|
@@ -299,6 +312,10 @@ def test_image_list(available_images, expected):
|
|
299
312
|
with patch("boto3.client") as bclient_mock:
|
300
313
|
instance = bclient_mock.return_value
|
301
314
|
instance.describe_images.return_value = {"Images": available_images}
|
315
|
+
instance.describe_regions.return_value = {
|
316
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
317
|
+
}
|
318
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
302
319
|
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
303
320
|
img = image.Image(ctx, "test-image-6")
|
304
321
|
assert img.list() == expected
|
@@ -330,6 +347,10 @@ def test_image_create_existing(s3_bucket_mock):
|
|
330
347
|
}
|
331
348
|
]
|
332
349
|
}
|
350
|
+
instance.describe_regions.return_value = {
|
351
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
352
|
+
}
|
353
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
333
354
|
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
334
355
|
img = image.Image(ctx, "test-image-6")
|
335
356
|
assert img.create() == {"eu-central-1": image._ImageInfo(image_id="ami-123", snapshot_id="snap-123")}
|
@@ -375,6 +396,10 @@ def test_image__put_ssm_parameters(
|
|
375
396
|
instance = bclient_mock.return_value
|
376
397
|
instance.describe_images.return_value = {"Images": describe_images}
|
377
398
|
instance.get_parameters.return_value = {"Parameters": get_parameters}
|
399
|
+
instance.describe_regions.return_value = {
|
400
|
+
"Regions": [{"RegionName": "eu-central-1"}, {"RegionName": "us-east-1"}]
|
401
|
+
}
|
402
|
+
instance.list_buckets.return_value = {"Buckets": [{"Name": "bucket1"}]}
|
378
403
|
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
379
404
|
img = image.Image(ctx, imagename)
|
380
405
|
img._put_ssm_parameters()
|
@@ -431,3 +456,23 @@ def test_image__verify(image_found, config, config_image_name, expected_problems
|
|
431
456
|
img = image.Image(ctx, config_image_name)
|
432
457
|
problems = img._verify("eu-central-1")
|
433
458
|
assert problems == expected_problems
|
459
|
+
|
460
|
+
|
461
|
+
@pytest.mark.parametrize(
|
462
|
+
"partition,imagename,share_list_expected",
|
463
|
+
[
|
464
|
+
("aws", "test-image-8", [{"UserId": "123456789123"}, {"UserId": "221020170000"}, {"UserId": "290620200000"}]),
|
465
|
+
("aws-cn", "test-image-8", [{"UserId": "334455667788"}]),
|
466
|
+
("aws-us-gov", "test-image-8", []),
|
467
|
+
],
|
468
|
+
)
|
469
|
+
def test_image__share_list_filtered(partition, imagename, share_list_expected):
|
470
|
+
"""
|
471
|
+
Test _share_list_filtered() for a given image
|
472
|
+
"""
|
473
|
+
with patch("boto3.client") as bclient_mock:
|
474
|
+
instance = bclient_mock.return_value
|
475
|
+
instance.meta.partition = partition
|
476
|
+
ctx = context.Context(curdir / "fixtures/config1.yaml", None)
|
477
|
+
img = image.Image(ctx, imagename)
|
478
|
+
assert img._share_list_filtered(img.conf["share"]) == share_list_expected
|
@@ -1,31 +1,33 @@
|
|
1
1
|
awspub/__init__.py,sha256=7hgLrq6k53yaJrjFe7X5Cm45z3SIc1Vxocb5k3G8xPc,124
|
2
2
|
awspub/api.py,sha256=d1gx9LdqdYXRLf8yZ_spIz_93WhB2GNnCG_x3ABrMkI,6497
|
3
3
|
awspub/cli/__init__.py,sha256=-zCBEbnt5zbvSZ8PxQALpPAy0CiQUf-qZnikJ7U4Sf0,5621
|
4
|
-
awspub/
|
4
|
+
awspub/common.py,sha256=M_Ibw8DoAHG3oLoK5qRUggEjI7kJSSslC7r9VySe8vk,562
|
5
|
+
awspub/configmodels.py,sha256=DMrC3N8V_zj2SuBRJu27dxxY4Um5rcemWFFqMlA6j9E,7824
|
5
6
|
awspub/context.py,sha256=LDkp9Sz5AqRxQq70ICgFIJn5g2qrc5qiVawTyS_rXZE,4064
|
6
7
|
awspub/exceptions.py,sha256=SbGf9XyiGlj6estlraAwWAKLtuEfzwEuAbHXYiCiJD0,447
|
7
|
-
awspub/image.py,sha256=
|
8
|
+
awspub/image.py,sha256=1oJ5x4WljJt4C119qj7b0n9HRXAb3Mi_d_2g5_vRT8o,27451
|
8
9
|
awspub/image_marketplace.py,sha256=oiD7yNU5quG5CQG9Ql5Ut9hLWA1yewg6qVwTbyadGwc,5314
|
9
|
-
awspub/s3.py,sha256=
|
10
|
+
awspub/s3.py,sha256=ivR8DuAkYilph73EjFkTgUelkXxU7pZfosnsHHyoZkQ,11274
|
10
11
|
awspub/snapshot.py,sha256=V5e_07SnmCwEPjRmwZh43spWparhH8X4ugG16uQfGuo,10040
|
11
12
|
awspub/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
13
|
awspub/tests/fixtures/config-invalid-s3-extra.yaml,sha256=TdgqE-quxgueXS9L8ixsRuG6eTVfqalZ41G3JNCWn58,288
|
13
14
|
awspub/tests/fixtures/config-minimal.yaml,sha256=oHupXHYQXxmqgN2qFCAwvxzR7Bch-3yScrmMXeMIICE,176
|
14
15
|
awspub/tests/fixtures/config-valid-nonawspub.yaml,sha256=Md-YINQQRo3kveikUxk8Co9BYIZfDftmPT2LmIqoTL4,330
|
15
16
|
awspub/tests/fixtures/config1.vmdk,sha256=YlJHVAi5-e5kRSthHXBqB4gxqZsSPbadFE2HigSIoKg,65536
|
16
|
-
awspub/tests/fixtures/config1.yaml,sha256=
|
17
|
+
awspub/tests/fixtures/config1.yaml,sha256=QUxX7j7SNP40CeSIRFcXm54Ef2CmzgDeWfYYy_THlM0,3256
|
17
18
|
awspub/tests/fixtures/config2-mapping.yaml,sha256=lqJE0ej9DdGsE8O5dqG5PX7bOJrY4nMciXoOzMzV-so,31
|
18
19
|
awspub/tests/fixtures/config2.yaml,sha256=m2v-n1T-XPGDHyrJXArC_rYV-ZPMr9cgzHkLXiSRuDs,1250
|
19
20
|
awspub/tests/fixtures/config3-duplicate-keys.yaml,sha256=Cn0tTQawpEFocDNpWxDz1651uQa7aw88XjNyPcCG4iQ,324
|
20
21
|
awspub/tests/test_api.py,sha256=7MKm2aCtcvHJ0x_o2qinljfL9xFBWnasUnVpBxB37w8,2504
|
21
22
|
awspub/tests/test_cli.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
+
awspub/tests/test_common.py,sha256=kytMUU47uZYYe302XswdO15qX_i1vO2LS5n96--TcSU,478
|
22
24
|
awspub/tests/test_context.py,sha256=wMXQqj4vi2U3q5w1xPV-stB3mp3K6puUyXhsShJG4wA,3115
|
23
|
-
awspub/tests/test_image.py,sha256=
|
25
|
+
awspub/tests/test_image.py,sha256=tMcMx8rnx0q3oeRBA3JSeOHVxnnUGG_AAHVsZ7DWNYw,19083
|
24
26
|
awspub/tests/test_image_marketplace.py,sha256=JP7PrFjix1AyQg7eEaQ-wCROVoIOb873koseniOqGQQ,1456
|
25
27
|
awspub/tests/test_s3.py,sha256=UJL8CQDEvhA42MwPGeSvSbQFj8h86c1LrLFDvcMcRws,2857
|
26
28
|
awspub/tests/test_snapshot.py,sha256=8KPTqGVyzrpivWuq3HE7ZhgtLllcr3rA_3hZcxu2xjg,4123
|
27
|
-
awspub-0.0.
|
28
|
-
awspub-0.0.
|
29
|
-
awspub-0.0.
|
30
|
-
awspub-0.0.
|
31
|
-
awspub-0.0.
|
29
|
+
awspub-0.0.6.dist-info/LICENSE,sha256=9GbrzFQ3rWjVKj-IZnX1kGDsIGIdjc25KGRmAp03Jn0,35150
|
30
|
+
awspub-0.0.6.dist-info/METADATA,sha256=0Dk6LfbKHJXh--WbmzrPtLtK75hJNs_G6bpqzN6D2iM,1773
|
31
|
+
awspub-0.0.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
32
|
+
awspub-0.0.6.dist-info/entry_points.txt,sha256=hrQzy9P5yO58nj6W0UDPdQPUTqEkQLpMvuyDDRu7LRQ,42
|
33
|
+
awspub-0.0.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|