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
awspub/__init__.py
ADDED
awspub/api.py
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
import logging
|
2
|
+
import pathlib
|
3
|
+
from typing import Dict, Iterator, List, Optional, Tuple
|
4
|
+
|
5
|
+
from awspub.context import Context
|
6
|
+
from awspub.image import Image, _ImageInfo
|
7
|
+
from awspub.s3 import S3
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
|
12
|
+
def _images_grouped(
|
13
|
+
images: List[Tuple[str, Image, Dict[str, _ImageInfo]]], group: Optional[str]
|
14
|
+
) -> Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, Dict[str, str]]]]:
|
15
|
+
"""
|
16
|
+
Group the given images by name and by group
|
17
|
+
|
18
|
+
:param images: the images
|
19
|
+
:type images: List[Tuple[str, Image, Dict[str, _ImageInfo]]]
|
20
|
+
:param group: a optional group name
|
21
|
+
:type group: Optional[str]
|
22
|
+
:return: the images grouped by name and by group
|
23
|
+
:rtype: Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, Dict[str, str]]]
|
24
|
+
"""
|
25
|
+
images_by_name: Dict[str, Dict[str, str]] = dict()
|
26
|
+
images_by_group: Dict[str, Dict[str, Dict[str, str]]] = dict()
|
27
|
+
for image_name, image, image_result in images:
|
28
|
+
images_region_id: Dict[str, str] = {key: val.image_id for (key, val) in image_result.items()}
|
29
|
+
images_by_name[image_name] = images_region_id
|
30
|
+
for image_group in image.conf.get("groups", []):
|
31
|
+
if group and image_group != group:
|
32
|
+
continue
|
33
|
+
if not images_by_group.get(image_group):
|
34
|
+
images_by_group[image_group] = {}
|
35
|
+
images_by_group[image_group][image_name] = images_region_id
|
36
|
+
return images_by_name, images_by_group
|
37
|
+
|
38
|
+
|
39
|
+
def _images_filtered(context: Context, group: Optional[str]) -> Iterator[Tuple[str, Image]]:
|
40
|
+
"""
|
41
|
+
Filter the images from ctx based on the given args
|
42
|
+
|
43
|
+
:param context: the context
|
44
|
+
:type context: a awspub.context.Context instance
|
45
|
+
:param group: a optional group name
|
46
|
+
:type group: Optional[str]
|
47
|
+
"""
|
48
|
+
for image_name in context.conf["images"].keys():
|
49
|
+
image = Image(context, image_name)
|
50
|
+
if group:
|
51
|
+
# limit the images to process to the group given on the command line
|
52
|
+
if group not in image.conf.get("groups", []):
|
53
|
+
logger.info(f"skipping image {image_name} because not part of group {group}")
|
54
|
+
continue
|
55
|
+
|
56
|
+
logger.info(f"processing image {image_name} (group: {group})")
|
57
|
+
yield image_name, image
|
58
|
+
|
59
|
+
|
60
|
+
def create(
|
61
|
+
config: pathlib.Path, config_mapping: pathlib.Path, group: Optional[str]
|
62
|
+
) -> Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, Dict[str, str]]]]:
|
63
|
+
"""
|
64
|
+
Create images in the partition of the used account based on
|
65
|
+
the given configuration file and the config mapping
|
66
|
+
|
67
|
+
:param config: the configuration file path
|
68
|
+
:type config: pathlib.Path
|
69
|
+
:param config_mapping: the config template mapping file path
|
70
|
+
:type config_mapping: pathlib.Path
|
71
|
+
:param group: only handles images from given group
|
72
|
+
:type group: Optional[str]
|
73
|
+
:return: the images grouped by name and by group
|
74
|
+
:rtype: Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, Dict[str, str]]]
|
75
|
+
"""
|
76
|
+
|
77
|
+
ctx = Context(config, config_mapping)
|
78
|
+
s3 = S3(ctx)
|
79
|
+
s3.upload_file(ctx.conf["source"]["path"])
|
80
|
+
images: List[Tuple[str, Image, Dict[str, _ImageInfo]]] = []
|
81
|
+
for image_name, image in _images_filtered(ctx, group):
|
82
|
+
image_result: Dict[str, _ImageInfo] = image.create()
|
83
|
+
images.append((image_name, image, image_result))
|
84
|
+
images_by_name, images_by_group = _images_grouped(images, group)
|
85
|
+
return images_by_name, images_by_group
|
86
|
+
|
87
|
+
|
88
|
+
def list(
|
89
|
+
config: pathlib.Path, config_mapping: pathlib.Path, group: Optional[str]
|
90
|
+
) -> Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, Dict[str, str]]]]:
|
91
|
+
"""
|
92
|
+
List images in the partition of the used account based on
|
93
|
+
the given configuration file and the config mapping
|
94
|
+
|
95
|
+
:param config: the configuration file path
|
96
|
+
:type config: pathlib.Path
|
97
|
+
:param config_mapping: the config template mapping file path
|
98
|
+
:type config_mapping: pathlib.Path
|
99
|
+
:param group: only handles images from given group
|
100
|
+
:type group: Optional[str]
|
101
|
+
:return: the images grouped by name and by group
|
102
|
+
:rtype: Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, Dict[str, str]]]
|
103
|
+
"""
|
104
|
+
ctx = Context(config, config_mapping)
|
105
|
+
images: List[Tuple[str, Image, Dict[str, _ImageInfo]]] = []
|
106
|
+
for image_name, image in _images_filtered(ctx, group):
|
107
|
+
image_result: Dict[str, _ImageInfo] = image.list()
|
108
|
+
images.append((image_name, image, image_result))
|
109
|
+
|
110
|
+
images_by_name, images_by_group = _images_grouped(images, group)
|
111
|
+
return images_by_name, images_by_group
|
112
|
+
|
113
|
+
|
114
|
+
def publish(config: pathlib.Path, config_mapping: pathlib.Path, group: Optional[str]):
|
115
|
+
"""
|
116
|
+
Make available images in the partition of the used account based on
|
117
|
+
the given configuration file public
|
118
|
+
|
119
|
+
:param config: the configuration file path
|
120
|
+
:type config: pathlib.Path
|
121
|
+
:param config_mapping: the config template mapping file path
|
122
|
+
:type config_mapping: pathlib.Path
|
123
|
+
:param group: only handles images from given group
|
124
|
+
:type group: Optional[str]
|
125
|
+
"""
|
126
|
+
ctx = Context(config, config_mapping)
|
127
|
+
for image_name, image in _images_filtered(ctx, group):
|
128
|
+
image.publish()
|
129
|
+
|
130
|
+
|
131
|
+
def cleanup(config: pathlib.Path, config_mapping: pathlib.Path, group: Optional[str]):
|
132
|
+
"""
|
133
|
+
Cleanup available images in the partition of the used account based on
|
134
|
+
the given configuration file
|
135
|
+
|
136
|
+
:param config: the configuration file path
|
137
|
+
:type config: pathlib.Path
|
138
|
+
:param config_mapping: the config template mapping file path
|
139
|
+
:type config_mapping: pathlib.Path
|
140
|
+
:param group: only handles images from given group
|
141
|
+
:type group: Optional[str]
|
142
|
+
"""
|
143
|
+
ctx = Context(config, config_mapping)
|
144
|
+
for image_name, image in _images_filtered(ctx, group):
|
145
|
+
image.cleanup()
|
146
|
+
|
147
|
+
|
148
|
+
def verify(config: pathlib.Path, config_mapping: pathlib.Path, group: Optional[str]) -> Dict[str, Dict]:
|
149
|
+
"""
|
150
|
+
Verify available images in the partition of the used account based on
|
151
|
+
the given configuration file.
|
152
|
+
This is EXPERIMENTAL and doesn't work reliable yet!
|
153
|
+
|
154
|
+
:param config: the configuration file path
|
155
|
+
:type config: pathlib.Path
|
156
|
+
:param config_mapping: the config template mapping file path
|
157
|
+
:type config_mapping: pathlib.Path
|
158
|
+
:param group: only handles images from given group
|
159
|
+
:type group: Optional[str]
|
160
|
+
"""
|
161
|
+
problems: Dict[str, Dict] = dict()
|
162
|
+
ctx = Context(config, config_mapping)
|
163
|
+
for image_name, image in _images_filtered(ctx, group):
|
164
|
+
problems[image_name] = image.verify()
|
165
|
+
return problems
|
awspub/cli/__init__.py
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/python3
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import json
|
5
|
+
import logging
|
6
|
+
import pathlib
|
7
|
+
import sys
|
8
|
+
|
9
|
+
import awspub
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
def _create(args) -> None:
|
15
|
+
"""
|
16
|
+
Create images based on the given configuration and write json
|
17
|
+
data to the given output
|
18
|
+
"""
|
19
|
+
images_by_name, images_by_group = awspub.create(args.config, args.config_mapping, args.group)
|
20
|
+
images_json = json.dumps({"images": images_by_name, "images-by-group": images_by_group}, indent=4)
|
21
|
+
args.output.write(images_json)
|
22
|
+
|
23
|
+
|
24
|
+
def _list(args) -> None:
|
25
|
+
"""
|
26
|
+
List images based on the given configuration and write json
|
27
|
+
data to the given output
|
28
|
+
"""
|
29
|
+
images_by_name, images_by_group = awspub.list(args.config, args.config_mapping, args.group)
|
30
|
+
images_json = json.dumps({"images": images_by_name, "images-by-group": images_by_group}, indent=4)
|
31
|
+
args.output.write(images_json)
|
32
|
+
|
33
|
+
|
34
|
+
def _verify(args) -> None:
|
35
|
+
"""
|
36
|
+
Verify available images against configuration
|
37
|
+
"""
|
38
|
+
problems = awspub.verify(args.config, args.config_mapping, args.group)
|
39
|
+
args.output.write((json.dumps({"problems": problems}, indent=4)))
|
40
|
+
|
41
|
+
|
42
|
+
def _cleanup(args) -> None:
|
43
|
+
"""
|
44
|
+
Cleanup available images
|
45
|
+
"""
|
46
|
+
awspub.cleanup(args.config, args.config_mapping, args.group)
|
47
|
+
|
48
|
+
|
49
|
+
def _publish(args) -> None:
|
50
|
+
"""
|
51
|
+
Make available images public
|
52
|
+
"""
|
53
|
+
awspub.publish(args.config, args.config_mapping, args.group)
|
54
|
+
|
55
|
+
|
56
|
+
def _parser():
|
57
|
+
parser = argparse.ArgumentParser(description="AWS EC2 publication tool")
|
58
|
+
parser.add_argument("--log-level", choices=["info", "debug"], default="info")
|
59
|
+
parser.add_argument("--log-file", type=pathlib.Path, help="write log to given file instead of stdout")
|
60
|
+
parser.add_argument("--log-console", action="store_true", help="write log to stdout")
|
61
|
+
p_sub = parser.add_subparsers(help="sub-command help")
|
62
|
+
|
63
|
+
# create
|
64
|
+
p_create = p_sub.add_parser("create", help="Create images")
|
65
|
+
p_create.add_argument(
|
66
|
+
"--output", type=argparse.FileType("w+"), help="output file path. defaults to stdout", default=sys.stdout
|
67
|
+
)
|
68
|
+
p_create.add_argument("--config-mapping", type=pathlib.Path, help="the image config template mapping file path")
|
69
|
+
p_create.add_argument("--group", type=str, help="only handles images from given group")
|
70
|
+
p_create.add_argument("config", type=pathlib.Path, help="the image configuration file path")
|
71
|
+
p_create.set_defaults(func=_create)
|
72
|
+
|
73
|
+
# list
|
74
|
+
p_list = p_sub.add_parser("list", help="List images (but don't modify anything)")
|
75
|
+
p_list.add_argument(
|
76
|
+
"--output", type=argparse.FileType("w+"), help="output file path. defaults to stdout", default=sys.stdout
|
77
|
+
)
|
78
|
+
p_list.add_argument("--config-mapping", type=pathlib.Path, help="the image config template mapping file path")
|
79
|
+
p_list.add_argument("--group", type=str, help="only handles images from given group")
|
80
|
+
p_list.add_argument("config", type=pathlib.Path, help="the image configuration file path")
|
81
|
+
p_list.set_defaults(func=_list)
|
82
|
+
|
83
|
+
# verify
|
84
|
+
p_verify = p_sub.add_parser("verify", help="Verify images")
|
85
|
+
p_verify.add_argument(
|
86
|
+
"--output", type=argparse.FileType("w+"), help="output file path. defaults to stdout", default=sys.stdout
|
87
|
+
)
|
88
|
+
p_verify.add_argument("--config-mapping", type=pathlib.Path, help="the image config template mapping file path")
|
89
|
+
p_verify.add_argument("--group", type=str, help="only handles images from given group")
|
90
|
+
p_verify.add_argument("config", type=pathlib.Path, help="the image configuration file path")
|
91
|
+
|
92
|
+
p_verify.set_defaults(func=_verify)
|
93
|
+
|
94
|
+
# cleanup
|
95
|
+
p_cleanup = p_sub.add_parser("cleanup", help="Cleanup images")
|
96
|
+
p_cleanup.add_argument(
|
97
|
+
"--output", type=argparse.FileType("w+"), help="output file path. defaults to stdout", default=sys.stdout
|
98
|
+
)
|
99
|
+
p_cleanup.add_argument("--config-mapping", type=pathlib.Path, help="the image config template mapping file path")
|
100
|
+
p_cleanup.add_argument("--group", type=str, help="only handles images from given group")
|
101
|
+
p_cleanup.add_argument("config", type=pathlib.Path, help="the image configuration file path")
|
102
|
+
|
103
|
+
p_cleanup.set_defaults(func=_cleanup)
|
104
|
+
|
105
|
+
# publish
|
106
|
+
p_publish = p_sub.add_parser("publish", help="Publish images")
|
107
|
+
p_publish.add_argument(
|
108
|
+
"--output", type=argparse.FileType("w+"), help="output file path. defaults to stdout", default=sys.stdout
|
109
|
+
)
|
110
|
+
p_publish.add_argument("--config-mapping", type=pathlib.Path, help="the image config template mapping file path")
|
111
|
+
p_publish.add_argument("--group", type=str, help="only handles images from given group")
|
112
|
+
p_publish.add_argument("config", type=pathlib.Path, help="the image configuration file path")
|
113
|
+
|
114
|
+
p_publish.set_defaults(func=_publish)
|
115
|
+
|
116
|
+
return parser
|
117
|
+
|
118
|
+
|
119
|
+
def main():
|
120
|
+
parser = _parser()
|
121
|
+
args = parser.parse_args()
|
122
|
+
log_formatter = logging.Formatter("%(asctime)s:%(name)s:%(levelname)s:%(message)s")
|
123
|
+
# log level
|
124
|
+
loglevel = logging.INFO
|
125
|
+
if args.log_level == "debug":
|
126
|
+
loglevel = logging.DEBUG
|
127
|
+
root_logger = logging.getLogger()
|
128
|
+
root_logger.setLevel(loglevel)
|
129
|
+
# log file
|
130
|
+
if args.log_file:
|
131
|
+
file_handler = logging.FileHandler(filename=args.log_file)
|
132
|
+
file_handler.setFormatter(log_formatter)
|
133
|
+
root_logger.addHandler(file_handler)
|
134
|
+
# log console
|
135
|
+
if args.log_console:
|
136
|
+
console_handler = logging.StreamHandler()
|
137
|
+
console_handler.setFormatter(log_formatter)
|
138
|
+
root_logger.addHandler(console_handler)
|
139
|
+
if "func" not in args:
|
140
|
+
sys.exit(parser.print_help())
|
141
|
+
args.func(args)
|
142
|
+
sys.exit(0)
|
143
|
+
|
144
|
+
|
145
|
+
if __name__ == "__main__":
|
146
|
+
main()
|
awspub/common.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import List, Tuple
|
3
|
+
|
4
|
+
import boto3
|
5
|
+
from mypy_boto3_ec2.client import EC2Client
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
def _split_partition(val: str) -> Tuple[str, str]:
|
11
|
+
"""
|
12
|
+
Split a string into partition and resource, separated by a colon. If no partition is given, assume "aws"
|
13
|
+
:param val: the string to split
|
14
|
+
:type val: str
|
15
|
+
:return: the partition and the resource
|
16
|
+
:rtype: Tuple[str, str]
|
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
|
25
|
+
|
26
|
+
|
27
|
+
def _get_regions(region_to_query: str, regions_allowlist: List[str]) -> List[str]:
|
28
|
+
"""
|
29
|
+
Get a list of region names querying the `region_to_query` for all regions and
|
30
|
+
then filtering by `regions_allowlist`.
|
31
|
+
If no `regions_allowlist` is given, all queried regions are returned for the
|
32
|
+
current partition.
|
33
|
+
If `regions_allowlist` is given, all regions from that list are returned if
|
34
|
+
the listed region exist in the current partition.
|
35
|
+
Eg. `us-east-1` listed in `regions_allowlist` won't be returned if the current
|
36
|
+
partition is `aws-cn`.
|
37
|
+
:param region_to_query: region name of current partition
|
38
|
+
:type region_to_query: str
|
39
|
+
:praram regions_allowlist: list of regions in config file
|
40
|
+
:type regions_allowlist: List[str]
|
41
|
+
:return: list of regions names
|
42
|
+
:rtype: List[str]
|
43
|
+
"""
|
44
|
+
|
45
|
+
# get all available regions
|
46
|
+
ec2client: EC2Client = boto3.client("ec2", region_name=region_to_query)
|
47
|
+
resp = ec2client.describe_regions()
|
48
|
+
ec2_regions_all = [r["RegionName"] for r in resp["Regions"]]
|
49
|
+
|
50
|
+
if regions_allowlist:
|
51
|
+
# filter out regions that are not available in the current partition
|
52
|
+
regions_allowlist_set = set(regions_allowlist)
|
53
|
+
ec2_regions_all_set = set(ec2_regions_all)
|
54
|
+
regions = list(regions_allowlist_set.intersection(ec2_regions_all_set))
|
55
|
+
diff = regions_allowlist_set.difference(ec2_regions_all_set)
|
56
|
+
if diff:
|
57
|
+
logger.warning(
|
58
|
+
f"regions {diff} listed in regions allowlist are not available in the current partition."
|
59
|
+
" Ignoring those."
|
60
|
+
)
|
61
|
+
else:
|
62
|
+
regions = ec2_regions_all
|
63
|
+
|
64
|
+
return regions
|
awspub/configmodels.py
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
import pathlib
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Dict, List, Literal, Optional
|
4
|
+
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
6
|
+
|
7
|
+
from awspub.common import _split_partition
|
8
|
+
|
9
|
+
|
10
|
+
class ConfigS3Model(BaseModel):
|
11
|
+
"""
|
12
|
+
S3 configuration.
|
13
|
+
This is required for uploading source files (usually .vmdk) to a bucket so
|
14
|
+
snapshots can be created out of the s3 file
|
15
|
+
"""
|
16
|
+
|
17
|
+
model_config = ConfigDict(extra="forbid")
|
18
|
+
|
19
|
+
bucket_name: str = Field(description="The S3 bucket name")
|
20
|
+
|
21
|
+
|
22
|
+
class ConfigSourceModel(BaseModel):
|
23
|
+
"""
|
24
|
+
Source configuration.
|
25
|
+
This defines the source (usually .vmdk) that is uploaded
|
26
|
+
to S3 and then used to create EC2 snapshots in different regions.
|
27
|
+
"""
|
28
|
+
|
29
|
+
model_config = ConfigDict(extra="forbid")
|
30
|
+
|
31
|
+
path: pathlib.Path = Field(description="Path to a local .vmdk image")
|
32
|
+
architecture: Literal["x86_64", "arm64"] = Field(description="The architecture of the given .vmdk image")
|
33
|
+
|
34
|
+
|
35
|
+
class ConfigImageMarketplaceSecurityGroupModel(BaseModel):
|
36
|
+
"""
|
37
|
+
Image/AMI Marketplace specific configuration for a security group
|
38
|
+
"""
|
39
|
+
|
40
|
+
model_config = ConfigDict(extra="forbid")
|
41
|
+
|
42
|
+
from_port: int = Field(description="The source port")
|
43
|
+
ip_protocol: Literal["tcp", "udp"] = Field(description="The IP protocol (either 'tcp' or 'udp')")
|
44
|
+
ip_ranges: List[str] = Field(description="IP ranges to allow, in CIDR format (eg. '192.0.2.0/24')")
|
45
|
+
to_port: int = Field(description="The destination port")
|
46
|
+
|
47
|
+
|
48
|
+
class ConfigImageMarketplaceModel(BaseModel):
|
49
|
+
"""
|
50
|
+
Image/AMI Marketplace specific configuration to request new Marketplace versions
|
51
|
+
See https://docs.aws.amazon.com/marketplace-catalog/latest/api-reference/ami-products.html
|
52
|
+
for further information
|
53
|
+
"""
|
54
|
+
|
55
|
+
model_config = ConfigDict(extra="forbid")
|
56
|
+
|
57
|
+
entity_id: str = Field(description="The entity ID (product ID)")
|
58
|
+
# see https://docs.aws.amazon.com/marketplace/latest/userguide/ami-single-ami-products.html#single-ami-marketplace-ami-access # noqa:E501
|
59
|
+
access_role_arn: str = Field(
|
60
|
+
description="IAM role Amazon Resource Name (ARN) used by AWS Marketplace to access the provided AMI"
|
61
|
+
)
|
62
|
+
version_title: str = Field(description="The version title. Must be unique across the product")
|
63
|
+
release_notes: str = Field(description="The release notes")
|
64
|
+
user_name: str = Field(description="The login username to access the operating system")
|
65
|
+
scanning_port: int = Field(description="Port to access the operating system (default: 22)", default=22)
|
66
|
+
os_name: str = Field(description="Operating system name displayed to Marketplace buyers")
|
67
|
+
os_version: str = Field(description="Operating system version displayed to Marketplace buyers")
|
68
|
+
usage_instructions: str = Field(
|
69
|
+
description=" Instructions for using the AMI, or a link to more information about the AMI"
|
70
|
+
)
|
71
|
+
recommended_instance_type: str = Field(
|
72
|
+
description="The instance type that is recommended to run the service with the AMI and is the "
|
73
|
+
"default for 1-click installs of your service"
|
74
|
+
)
|
75
|
+
security_groups: Optional[List[ConfigImageMarketplaceSecurityGroupModel]]
|
76
|
+
|
77
|
+
|
78
|
+
class ConfigImageSSMParameterModel(BaseModel):
|
79
|
+
"""
|
80
|
+
Image/AMI SSM specific configuration to push parameters of type `aws:ec2:image` to the parameter store
|
81
|
+
"""
|
82
|
+
|
83
|
+
model_config = ConfigDict(extra="forbid")
|
84
|
+
|
85
|
+
name: str = Field(
|
86
|
+
description="The fully qualified name of the parameter that you want to add to the system. "
|
87
|
+
"A parameter name must be unique within an Amazon Web Services Region"
|
88
|
+
)
|
89
|
+
description: Optional[str] = Field(
|
90
|
+
description="Information about the parameter that you want to add to the system", default=None
|
91
|
+
)
|
92
|
+
allow_overwrite: Optional[bool] = Field(
|
93
|
+
description="allow to overwrite an existing parameter. Useful for keep a 'latest' parameter (default: false)",
|
94
|
+
default=False,
|
95
|
+
)
|
96
|
+
|
97
|
+
|
98
|
+
class SNSNotificationProtocol(str, Enum):
|
99
|
+
DEFAULT = "default"
|
100
|
+
EMAIL = "email"
|
101
|
+
|
102
|
+
|
103
|
+
class ConfigImageSNSNotificationModel(BaseModel):
|
104
|
+
"""
|
105
|
+
Image/AMI SNS Notification specific configuration to notify subscribers about new images availability
|
106
|
+
"""
|
107
|
+
|
108
|
+
model_config = ConfigDict(extra="forbid")
|
109
|
+
|
110
|
+
subject: str = Field(description="The subject of SNS Notification", min_length=1, max_length=99)
|
111
|
+
message: Dict[SNSNotificationProtocol, str] = Field(
|
112
|
+
description="The body of the message to be sent to subscribers.",
|
113
|
+
default={SNSNotificationProtocol.DEFAULT: ""},
|
114
|
+
)
|
115
|
+
regions: Optional[List[str]] = Field(
|
116
|
+
description="Optional list of regions for sending notification. If not given, regions where the image "
|
117
|
+
"registered will be used from the currently used parition. If a region doesn't exist in the currently "
|
118
|
+
"used partition, it will be ignored.",
|
119
|
+
default=None,
|
120
|
+
)
|
121
|
+
|
122
|
+
@field_validator("message")
|
123
|
+
def check_message(cls, value):
|
124
|
+
# Check message protocols have default key
|
125
|
+
# Message should contain at least a top-level JSON key of “default”
|
126
|
+
# with a value that is a string
|
127
|
+
if SNSNotificationProtocol.DEFAULT not in value:
|
128
|
+
raise ValueError(f"{SNSNotificationProtocol.DEFAULT.value} key is required to send SNS notification")
|
129
|
+
return value
|
130
|
+
|
131
|
+
|
132
|
+
class ConfigImageModel(BaseModel):
|
133
|
+
"""
|
134
|
+
Image/AMI configuration.
|
135
|
+
"""
|
136
|
+
|
137
|
+
model_config = ConfigDict(extra="forbid")
|
138
|
+
|
139
|
+
description: Optional[str] = Field(description="Optional image description", default=None)
|
140
|
+
regions: Optional[List[str]] = Field(
|
141
|
+
description="Optional list of regions for this image. If not given, all available regions will"
|
142
|
+
"be used from the currently used partition. If a region doesn't exist in the currently used partition,"
|
143
|
+
" it will be ignored.",
|
144
|
+
default=None,
|
145
|
+
)
|
146
|
+
separate_snapshot: bool = Field(description="Use a separate snapshot for this image?", default=False)
|
147
|
+
billing_products: Optional[List[str]] = Field(description="Optional list of billing codes", default=None)
|
148
|
+
boot_mode: Literal["legacy-bios", "uefi", "uefi-preferred"] = Field(
|
149
|
+
description="The boot mode. For arm64, this needs to be 'uefi'"
|
150
|
+
)
|
151
|
+
root_device_name: Optional[str] = Field(description="The root device name", default="/dev/sda1")
|
152
|
+
root_device_volume_type: Optional[Literal["gp2", "gp3"]] = Field(
|
153
|
+
description="The root device volume type", default="gp3"
|
154
|
+
)
|
155
|
+
root_device_volume_size: Optional[int] = Field(description="The root device volume size (in GB)", default=8)
|
156
|
+
uefi_data: Optional[pathlib.Path] = Field(
|
157
|
+
description="Optional path to a non-volatile UEFI variable store (must be already base64 encoded)", default=None
|
158
|
+
)
|
159
|
+
tpm_support: Optional[Literal["v2.0"]] = Field(
|
160
|
+
description="Optional TPM support. If this is set, 'boot_mode' must be 'uefi'", default=None
|
161
|
+
)
|
162
|
+
imds_support: Optional[Literal["v2.0"]] = Field(description="Optional IMDS support", default=None)
|
163
|
+
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'",
|
166
|
+
default=None,
|
167
|
+
)
|
168
|
+
temporary: Optional[bool] = Field(
|
169
|
+
description="Optional boolean field indicates that a image is only temporary", default=False
|
170
|
+
)
|
171
|
+
public: Optional[bool] = Field(
|
172
|
+
description="Optional boolean field indicates if the image should be public", default=False
|
173
|
+
)
|
174
|
+
marketplace: Optional[ConfigImageMarketplaceModel] = Field(
|
175
|
+
description="Optional structure containing Marketplace related configuration for the commercial "
|
176
|
+
"'aws' partition",
|
177
|
+
default=None,
|
178
|
+
)
|
179
|
+
ssm_parameter: Optional[List[ConfigImageSSMParameterModel]] = Field(
|
180
|
+
description="Optional list of SSM parameter paths of type `aws:ec2:image` which will "
|
181
|
+
"be pushed to the parameter store",
|
182
|
+
default=None,
|
183
|
+
)
|
184
|
+
groups: Optional[List[str]] = Field(description="Optional list of groups this image is part of", default=[])
|
185
|
+
tags: Optional[Dict[str, str]] = Field(description="Optional Tags to apply to this image only", default={})
|
186
|
+
sns: Optional[List[Dict[str, ConfigImageSNSNotificationModel]]] = Field(
|
187
|
+
description="Optional list of SNS Notification related configuration", default=None
|
188
|
+
)
|
189
|
+
|
190
|
+
@field_validator("share")
|
191
|
+
@classmethod
|
192
|
+
def check_share(cls, v: Optional[List[str]]) -> Optional[List[str]]:
|
193
|
+
"""
|
194
|
+
Make sure the account IDs are valid and if given the partition is correct
|
195
|
+
"""
|
196
|
+
if v is not None:
|
197
|
+
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")
|
201
|
+
if partition not in ["aws", "aws-cn", "aws-us-gov"]:
|
202
|
+
raise ValueError("Partition must be one of 'aws', 'aws-cn', 'aws-us-gov'")
|
203
|
+
return v
|
204
|
+
|
205
|
+
|
206
|
+
class ConfigModel(BaseModel):
|
207
|
+
"""
|
208
|
+
The base model for the whole configuration
|
209
|
+
"""
|
210
|
+
|
211
|
+
model_config = ConfigDict(extra="forbid")
|
212
|
+
|
213
|
+
s3: ConfigS3Model
|
214
|
+
source: ConfigSourceModel
|
215
|
+
images: Dict[str, ConfigImageModel]
|
216
|
+
tags: Optional[Dict[str, str]] = Field(description="Optional Tags to apply to all resources", default={})
|