awspub 0.0.10__py3-none-any.whl → 0.0.13__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 CHANGED
@@ -15,13 +15,20 @@ def _split_partition(val: str) -> Tuple[str, str]:
15
15
  :return: the partition and the resource
16
16
  :rtype: Tuple[str, str]
17
17
  """
18
- if ":" in val:
19
- partition, resource = val.split(":")
20
- else:
21
- # if no partition is given, assume default commercial partition "aws"
22
- partition = "aws"
23
- resource = val
24
- return partition, resource
18
+
19
+ # ARNs encode partition https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
20
+ if val.startswith("arn:"):
21
+ arn, partition, resource = val.split(":", maxsplit=2)
22
+ # Return extracted partition, but keep full ARN intact
23
+ return partition, val
24
+
25
+ # Partition prefix
26
+ if ":" in val and val.startswith("aws"):
27
+ partition, resource = val.split(":", maxsplit=1)
28
+ return partition, resource
29
+
30
+ # if no partition is given, assume default commercial partition "aws"
31
+ return "aws", val
25
32
 
26
33
 
27
34
  def _get_regions(region_to_query: str, regions_allowlist: List[str]) -> List[str]:
awspub/configmodels.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import pathlib
2
+ import re
2
3
  from enum import Enum
3
4
  from typing import Dict, List, Literal, Optional
4
5
 
@@ -161,8 +162,8 @@ class ConfigImageModel(BaseModel):
161
162
  )
162
163
  imds_support: Optional[Literal["v2.0"]] = Field(description="Optional IMDS support", default=None)
163
164
  share: Optional[List[str]] = Field(
164
- description="Optional list of account IDs the image and snapshot will be shared with. The account"
165
- "ID can be prefixed with the partition and separated by ':'. Eg 'aws-cn:123456789123'",
165
+ description="Optional list of account IDs, organization ARN, OU ARN the image and snapshot will be shared with."
166
+ " The account ID can be prefixed with the partition and separated by ':'. Eg 'aws-cn:123456789123'",
166
167
  default=None,
167
168
  )
168
169
  temporary: Optional[bool] = Field(
@@ -193,11 +194,25 @@ class ConfigImageModel(BaseModel):
193
194
  """
194
195
  Make sure the account IDs are valid and if given the partition is correct
195
196
  """
197
+ patterns = [
198
+ # https://docs.aws.amazon.com/organizations/latest/APIReference/API_Account.html
199
+ r"\d{12}",
200
+ # Adjusted for partitions
201
+ # https://docs.aws.amazon.com/organizations/latest/APIReference/API_Organization.html
202
+ r"arn:aws(?:-cn)?(?:-us-gov)?:organizations::\d{12}:organization\/o-[a-z0-9]{10,32}",
203
+ # https://docs.aws.amazon.com/organizations/latest/APIReference/API_OrganizationalUnit.html
204
+ r"arn:aws(?:-cn)?(?:-us-gov)?:organizations::\d{12}:ou\/o-[a-z0-9]{10,32}\/ou-[0-9a-z]{4,32}-[0-9a-z]{8,32}", # noqa:E501
205
+ ]
196
206
  if v is not None:
197
207
  for val in v:
198
- partition, account_id = _split_partition(val)
199
- if len(account_id) != 12:
200
- raise ValueError("Account ID must be 12 characters long")
208
+ partition, account_id_or_arn = _split_partition(val)
209
+ valid = False
210
+ for pattern in patterns:
211
+ if re.fullmatch(pattern, account_id_or_arn):
212
+ valid = True
213
+ break
214
+ if not valid:
215
+ raise ValueError("Account ID must be 12 digits long or an ARN for Organization or OU")
201
216
  if partition not in ["aws", "aws-cn", "aws-us-gov"]:
202
217
  raise ValueError("Partition must be one of 'aws', 'aws-cn', 'aws-us-gov'")
203
218
  return v
awspub/image.py CHANGED
@@ -2,7 +2,7 @@ import hashlib
2
2
  import logging
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
- from typing import Any, Dict, List, Optional
5
+ from typing import Any, Dict, List, Optional, Tuple
6
6
 
7
7
  import boto3
8
8
  import botocore.exceptions
@@ -145,23 +145,30 @@ class Image:
145
145
  tags.append({"Key": name, "Value": value})
146
146
  return tags
147
147
 
148
- def _share_list_filtered(self, share_conf: List[str]) -> List[Dict[str, str]]:
148
+ def _share_list_filtered(self, share_conf: List[str]) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
149
149
  """
150
150
  Get a filtered list of share configurations based on the current partition
151
151
  :param share_conf: the share configuration
152
152
  :type share_conf: List[str]
153
153
  :return: a List of share configurations that is usable by modify_image_attribute()
154
- :rtype: List[Dict[str, str]]
154
+ :rtype: Tuple[List[Dict[str, str]], List[Dict[str, str]]]
155
155
  """
156
156
  # the current partition
157
157
  partition_current = boto3.client("ec2").meta.partition
158
158
 
159
159
  share_list: List[Dict[str, str]] = []
160
+ volume_list: List[Dict[str, str]] = []
160
161
  for share in share_conf:
161
- partition, account_id = _split_partition(share)
162
+ partition, account_id_or_arn = _split_partition(share)
162
163
  if partition == partition_current:
163
- share_list.append({"UserId": account_id})
164
- return share_list
164
+ if ":organization/o-" in account_id_or_arn:
165
+ share_list.append({"OrganizationArn": account_id_or_arn})
166
+ elif ":ou/o-" in account_id_or_arn:
167
+ share_list.append({"OrganizationalUnitArn": account_id_or_arn})
168
+ else:
169
+ share_list.append({"UserId": account_id_or_arn})
170
+ volume_list.append({"UserId": account_id_or_arn})
171
+ return share_list, volume_list
165
172
 
166
173
  def _share(self, share_conf: List[str], images: Dict[str, _ImageInfo]):
167
174
  """
@@ -172,7 +179,7 @@ class Image:
172
179
  :param images: a Dict with region names as keys and _ImageInfo objects as values
173
180
  :type images: Dict[str, _ImageInfo]
174
181
  """
175
- share_list = self._share_list_filtered(share_conf)
182
+ share_list, volume_list = self._share_list_filtered(share_conf)
176
183
 
177
184
  if not share_list:
178
185
  logger.info("no valid accounts found for sharing in this partition, skipping")
@@ -188,11 +195,11 @@ class Image:
188
195
  )
189
196
 
190
197
  # modify snapshot permissions
191
- if image_info.snapshot_id:
198
+ if image_info.snapshot_id and volume_list:
192
199
  ec2client.modify_snapshot_attribute(
193
200
  Attribute="createVolumePermission",
194
201
  SnapshotId=image_info.snapshot_id,
195
- CreateVolumePermission={"Add": share_list}, # type: ignore
202
+ CreateVolumePermission={"Add": volume_list}, # type: ignore
196
203
  )
197
204
 
198
205
  logger.info(f"shared images & snapshots with '{share_conf}'")
@@ -83,6 +83,8 @@ awspub:
83
83
  - "221020170000"
84
84
  - "aws:290620200000"
85
85
  - "aws-cn:334455667788"
86
+ - "arn:aws:organizations::123456789012:organization/o-123example"
87
+ - "arn:aws-cn:organizations::334455667788:ou/o-123example/ou-1234-5example"
86
88
  marketplace:
87
89
  entity_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
88
90
  access_role_arn: "arn:aws:iam::xxxxxxxxxxxx:role/AWSMarketplaceAccess"
@@ -12,6 +12,30 @@ from awspub.common import _get_regions, _split_partition
12
12
  ("aws:123456789123", ("aws", "123456789123")),
13
13
  ("aws-cn:123456789123", ("aws-cn", "123456789123")),
14
14
  ("aws-us-gov:123456789123", ("aws-us-gov", "123456789123")),
15
+ (
16
+ "arn:aws:organizations::123456789012:organization/o-123example",
17
+ ("aws", "arn:aws:organizations::123456789012:organization/o-123example"),
18
+ ),
19
+ (
20
+ "arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5example",
21
+ ("aws", "arn:aws:organizations::123456789012:ou/o-123example/ou-1234-5example"),
22
+ ),
23
+ (
24
+ "arn:aws-cn:organizations::123456789012:organization/o-123example",
25
+ ("aws-cn", "arn:aws-cn:organizations::123456789012:organization/o-123example"),
26
+ ),
27
+ (
28
+ "arn:aws-cn:organizations::123456789012:ou/o-123example/ou-1234-5example",
29
+ ("aws-cn", "arn:aws-cn:organizations::123456789012:ou/o-123example/ou-1234-5example"),
30
+ ),
31
+ (
32
+ "arn:aws-us-gov:organizations::123456789012:organization/o-123example",
33
+ ("aws-us-gov", "arn:aws-us-gov:organizations::123456789012:organization/o-123example"),
34
+ ),
35
+ (
36
+ "arn:aws-us-gov:organizations::123456789012:ou/o-123example/ou-1234-5example",
37
+ ("aws-us-gov", "arn:aws-us-gov:organizations::123456789012:ou/o-123example/ou-1234-5example"),
38
+ ),
15
39
  ],
16
40
  )
17
41
  def test_common__split_partition(input, expected_output):
@@ -478,14 +478,38 @@ def test_image__verify(image_found, config, config_image_name, expected_problems
478
478
 
479
479
 
480
480
  @pytest.mark.parametrize(
481
- "partition,imagename,share_list_expected",
481
+ "partition,imagename,share_list_expected,volume_list_expected",
482
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", []),
483
+ (
484
+ "aws",
485
+ "test-image-8",
486
+ [
487
+ {"UserId": "123456789123"},
488
+ {"UserId": "221020170000"},
489
+ {"UserId": "290620200000"},
490
+ {"OrganizationArn": "arn:aws:organizations::123456789012:organization/o-123example"},
491
+ ],
492
+ [
493
+ {"UserId": "123456789123"},
494
+ {"UserId": "221020170000"},
495
+ {"UserId": "290620200000"},
496
+ ],
497
+ ),
498
+ (
499
+ "aws-cn",
500
+ "test-image-8",
501
+ [
502
+ {"UserId": "334455667788"},
503
+ {"OrganizationalUnitArn": "arn:aws-cn:organizations::334455667788:ou/o-123example/ou-1234-5example"},
504
+ ],
505
+ [
506
+ {"UserId": "334455667788"},
507
+ ],
508
+ ),
509
+ ("aws-us-gov", "test-image-8", [], []),
486
510
  ],
487
511
  )
488
- def test_image__share_list_filtered(partition, imagename, share_list_expected):
512
+ def test_image__share_list_filtered(partition, imagename, share_list_expected, volume_list_expected):
489
513
  """
490
514
  Test _share_list_filtered() for a given image
491
515
  """
@@ -494,7 +518,7 @@ def test_image__share_list_filtered(partition, imagename, share_list_expected):
494
518
  instance.meta.partition = partition
495
519
  ctx = context.Context(curdir / "fixtures/config1.yaml", None)
496
520
  img = image.Image(ctx, imagename)
497
- assert img._share_list_filtered(img.conf["share"]) == share_list_expected
521
+ assert img._share_list_filtered(img.conf["share"]) == (share_list_expected, volume_list_expected)
498
522
 
499
523
 
500
524
  @patch("awspub.s3.S3.bucket_region", return_value="region1")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: awspub
3
- Version: 0.0.10
3
+ Version: 0.0.13
4
4
  Summary: Publish images to AWS EC2
5
5
  License: GPL-3.0-or-later
6
6
  Keywords: AWS,EC2,publication
@@ -1,11 +1,11 @@
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/common.py,sha256=RFNPfPAw_C0mtdEyBoWWMP6n6CyESdakXhzAvDoyAfQ,2340
5
- awspub/configmodels.py,sha256=_6I_-1YzfJiSY81QWK-T3afiQUMx810lQQ3EjoEVuNg,9396
4
+ awspub/common.py,sha256=FbCngRJPdBc8EnZI9B9p7zQgxzHJkgvByV7i7W-6Srs,2650
5
+ awspub/configmodels.py,sha256=OaU-Mn7HCnwkObO7VIPBJVQJjknJX_-Yh8_glqxb7no,10280
6
6
  awspub/context.py,sha256=LDkp9Sz5AqRxQq70ICgFIJn5g2qrc5qiVawTyS_rXZE,4064
7
7
  awspub/exceptions.py,sha256=edWb03Gv35nDv3eoQGwI7rvsX5FNZT1otkBV8ly3W1A,613
8
- awspub/image.py,sha256=RuJ8gOAHhnqGUBEBMT7WYSruI688r_1pOTf0nzzB3tU,27965
8
+ awspub/image.py,sha256=hQPsYHSSGPuK3xyU0INCM1lfMbkcFtmEYvyDD6huwkI,28504
9
9
  awspub/image_marketplace.py,sha256=oiD7yNU5quG5CQG9Ql5Ut9hLWA1yewg6qVwTbyadGwc,5314
10
10
  awspub/s3.py,sha256=ivR8DuAkYilph73EjFkTgUelkXxU7pZfosnsHHyoZkQ,11274
11
11
  awspub/snapshot.py,sha256=V5e_07SnmCwEPjRmwZh43spWparhH8X4ugG16uQfGuo,10040
@@ -15,21 +15,21 @@ awspub/tests/fixtures/config-invalid-s3-extra.yaml,sha256=TdgqE-quxgueXS9L8ixsRu
15
15
  awspub/tests/fixtures/config-minimal.yaml,sha256=oHupXHYQXxmqgN2qFCAwvxzR7Bch-3yScrmMXeMIICE,176
16
16
  awspub/tests/fixtures/config-valid-nonawspub.yaml,sha256=Md-YINQQRo3kveikUxk8Co9BYIZfDftmPT2LmIqoTL4,330
17
17
  awspub/tests/fixtures/config1.vmdk,sha256=YlJHVAi5-e5kRSthHXBqB4gxqZsSPbadFE2HigSIoKg,65536
18
- awspub/tests/fixtures/config1.yaml,sha256=knkwwBiImVa8vIxurFbKP6hAB4-84_L2bXcxwdsraig,4609
18
+ awspub/tests/fixtures/config1.yaml,sha256=i32F3LibNAlaus-DgSvlKbYwLEZPXW2msOS_LT9VI6Y,4767
19
19
  awspub/tests/fixtures/config2-mapping.yaml,sha256=lqJE0ej9DdGsE8O5dqG5PX7bOJrY4nMciXoOzMzV-so,31
20
20
  awspub/tests/fixtures/config2.yaml,sha256=m2v-n1T-XPGDHyrJXArC_rYV-ZPMr9cgzHkLXiSRuDs,1250
21
21
  awspub/tests/fixtures/config3-duplicate-keys.yaml,sha256=Cn0tTQawpEFocDNpWxDz1651uQa7aw88XjNyPcCG4iQ,324
22
22
  awspub/tests/test_api.py,sha256=eC8iqpGFgFygSbqlyWLiLjSW7TwZeEtngJ-g7CPQT7I,2603
23
23
  awspub/tests/test_cli.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- awspub/tests/test_common.py,sha256=K61eBmBt8NPqyME8co_dt6dr_yvlw3UZ54klcw7m2RA,1217
24
+ awspub/tests/test_common.py,sha256=zK-JCdJIV7ZQGeef_J-T407Bs062O15gtY3Isgotr_M,2423
25
25
  awspub/tests/test_context.py,sha256=wMXQqj4vi2U3q5w1xPV-stB3mp3K6puUyXhsShJG4wA,3115
26
- awspub/tests/test_image.py,sha256=CNcy8_ySSlPmTl1CwBJncPm4p9TAopUNyjbaNEqemaE,22170
26
+ awspub/tests/test_image.py,sha256=I89Og4inBW9XrX6waYGXDrHBJTKBptTx3os9RP2vnoo,22883
27
27
  awspub/tests/test_image_marketplace.py,sha256=JP7PrFjix1AyQg7eEaQ-wCROVoIOb873koseniOqGQQ,1456
28
28
  awspub/tests/test_s3.py,sha256=UJL8CQDEvhA42MwPGeSvSbQFj8h86c1LrLFDvcMcRws,2857
29
29
  awspub/tests/test_snapshot.py,sha256=8KPTqGVyzrpivWuq3HE7ZhgtLllcr3rA_3hZcxu2xjg,4123
30
30
  awspub/tests/test_sns.py,sha256=XdZh0ETwRHSp_77UFkot-07BlS8pKVMEJIs2HS9EdaQ,6622
31
- awspub-0.0.10.dist-info/LICENSE,sha256=9GbrzFQ3rWjVKj-IZnX1kGDsIGIdjc25KGRmAp03Jn0,35150
32
- awspub-0.0.10.dist-info/METADATA,sha256=PL0ruNu1TmqFxfyNyK8YrVEZKN5v6_-HkeW4MaeBo_4,1418
33
- awspub-0.0.10.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
34
- awspub-0.0.10.dist-info/entry_points.txt,sha256=hrQzy9P5yO58nj6W0UDPdQPUTqEkQLpMvuyDDRu7LRQ,42
35
- awspub-0.0.10.dist-info/RECORD,,
31
+ awspub-0.0.13.dist-info/LICENSE,sha256=9GbrzFQ3rWjVKj-IZnX1kGDsIGIdjc25KGRmAp03Jn0,35150
32
+ awspub-0.0.13.dist-info/METADATA,sha256=mAcD3dq3k0nCko-lHlYg9mwYalkyQ8n-nyMPuI4K-Wk,1418
33
+ awspub-0.0.13.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
34
+ awspub-0.0.13.dist-info/entry_points.txt,sha256=hrQzy9P5yO58nj6W0UDPdQPUTqEkQLpMvuyDDRu7LRQ,42
35
+ awspub-0.0.13.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.0.1
2
+ Generator: poetry-core 2.1.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any