awspub 0.0.1__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 ADDED
@@ -0,0 +1,3 @@
1
+ from awspub.api import cleanup, create, list, publish, verify
2
+
3
+ __all__ = ["create", "list", "publish", "cleanup", "verify"]
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=argparse.BooleanOptionalAction, 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/configmodels.py ADDED
@@ -0,0 +1,157 @@
1
+ import pathlib
2
+ from typing import Dict, List, Literal, Optional
3
+
4
+ from pydantic import BaseModel, ConfigDict, Field
5
+
6
+
7
+ class ConfigS3Model(BaseModel):
8
+ """
9
+ S3 configuration.
10
+ This is required for uploading source files (usually .vmdk) to a bucket so
11
+ snapshots can be created out of the s3 file
12
+ """
13
+
14
+ model_config = ConfigDict(extra="forbid")
15
+
16
+ bucket_name: str = Field(description="The S3 bucket name")
17
+
18
+
19
+ class ConfigSourceModel(BaseModel):
20
+ """
21
+ Source configuration.
22
+ This defines the source (usually .vmdk) that is uploaded
23
+ to S3 and then used to create EC2 snapshots in different regions.
24
+ """
25
+
26
+ model_config = ConfigDict(extra="forbid")
27
+
28
+ path: pathlib.Path = Field(description="Path to a local .vmdk image")
29
+ architecture: Literal["x86_64", "arm64"] = Field(description="The architecture of the given .vmdk image")
30
+
31
+
32
+ class ConfigImageMarketplaceSecurityGroupModel(BaseModel):
33
+ """
34
+ Image/AMI Marketplace specific configuration for a security group
35
+ """
36
+
37
+ model_config = ConfigDict(extra="forbid")
38
+
39
+ from_port: int = Field(description="The source port")
40
+ ip_protocol: Literal["tcp", "udp"] = Field(description="The IP protocol (either 'tcp' or 'udp')")
41
+ ip_ranges: List[str] = Field(description="IP ranges to allow, in CIDR format (eg. '192.0.2.0/24')")
42
+ to_port: int = Field(description="The destination port")
43
+
44
+
45
+ class ConfigImageMarketplaceModel(BaseModel):
46
+ """
47
+ Image/AMI Marketplace specific configuration to request new Marketplace versions
48
+ See https://docs.aws.amazon.com/marketplace-catalog/latest/api-reference/ami-products.html
49
+ for further information
50
+ """
51
+
52
+ model_config = ConfigDict(extra="forbid")
53
+
54
+ entity_id: str = Field(description="The entity ID (product ID)")
55
+ # see https://docs.aws.amazon.com/marketplace/latest/userguide/ami-single-ami-products.html#single-ami-marketplace-ami-access # noqa:E501
56
+ access_role_arn: str = Field(
57
+ description="IAM role Amazon Resource Name (ARN) used by AWS Marketplace to access the provided AMI"
58
+ )
59
+ version_title: str = Field(description="The version title. Must be unique across the product")
60
+ release_notes: str = Field(description="The release notes")
61
+ user_name: str = Field(description="The login username to access the operating system")
62
+ scanning_port: int = Field(description="Port to access the operating system (default: 22)", default=22)
63
+ os_name: str = Field(description="Operating system name displayed to Marketplace buyers")
64
+ os_version: str = Field(description="Operating system version displayed to Marketplace buyers")
65
+ usage_instructions: str = Field(
66
+ description=" Instructions for using the AMI, or a link to more information about the AMI"
67
+ )
68
+ recommended_instance_type: str = Field(
69
+ description="The instance type that is recommended to run the service with the AMI and is the "
70
+ "default for 1-click installs of your service"
71
+ )
72
+ security_groups: Optional[List[ConfigImageMarketplaceSecurityGroupModel]]
73
+
74
+
75
+ class ConfigImageSSMParameterModel(BaseModel):
76
+ """
77
+ Image/AMI SSM specific configuration to push parameters of type `aws:ec2:image` to the parameter store
78
+ """
79
+
80
+ model_config = ConfigDict(extra="forbid")
81
+
82
+ name: str = Field(
83
+ description="The fully qualified name of the parameter that you want to add to the system. "
84
+ "A parameter name must be unique within an Amazon Web Services Region"
85
+ )
86
+ description: Optional[str] = Field(
87
+ description="Information about the parameter that you want to add to the system", default=None
88
+ )
89
+ allow_overwrite: Optional[bool] = Field(
90
+ description="allow to overwrite an existing parameter. Useful for keep a 'latest' parameter (default: false)",
91
+ default=False,
92
+ )
93
+
94
+
95
+ class ConfigImageModel(BaseModel):
96
+ """
97
+ Image/AMI configuration.
98
+ """
99
+
100
+ model_config = ConfigDict(extra="forbid")
101
+
102
+ description: Optional[str] = Field(description="Optional image description", default=None)
103
+ regions: Optional[List[str]] = Field(
104
+ description="Optional list of regions for this image. If not given, all available regions will be used",
105
+ default=None,
106
+ )
107
+ separate_snapshot: bool = Field(description="Use a separate snapshot for this image?", default=False)
108
+ billing_products: Optional[List[str]] = Field(description="Optional list of billing codes", default=None)
109
+ boot_mode: Literal["legacy-bios", "uefi", "uefi-preferred"] = Field(
110
+ description="The boot mode. For arm64, this needs to be 'uefi'"
111
+ )
112
+ root_device_name: Optional[str] = Field(description="The root device name", default="/dev/sda1")
113
+ root_device_volume_type: Optional[Literal["gp2", "gp3"]] = Field(
114
+ description="The root device volume type", default="gp3"
115
+ )
116
+ root_device_volume_size: Optional[int] = Field(description="The root device volume size (in GB)", default=8)
117
+ uefi_data: Optional[pathlib.Path] = Field(
118
+ description="Optional path to a non-volatile UEFI variable store (must be already base64 encoded)", default=None
119
+ )
120
+ tpm_support: Optional[Literal["v2.0"]] = Field(
121
+ description="Optional TPM support. If this is set, 'boot_mode' must be 'uefi'", default=None
122
+ )
123
+ imds_support: Optional[Literal["v2.0"]] = Field(description="Optional IMDS support", default=None)
124
+ share: Optional[List[str]] = Field(
125
+ description="Optional list of account IDs the image and snapshot will be shared with", default=None
126
+ )
127
+ temporary: Optional[bool] = Field(
128
+ description="Optional boolean field indicates that a image is only temporary", default=False
129
+ )
130
+ public: Optional[bool] = Field(
131
+ description="Optional boolean field indicates if the image should be public", default=False
132
+ )
133
+ marketplace: Optional[ConfigImageMarketplaceModel] = Field(
134
+ description="Optional structure containing Marketplace related configuration for the commercial "
135
+ "'aws' partition",
136
+ default=None,
137
+ )
138
+ ssm_parameter: Optional[List[ConfigImageSSMParameterModel]] = Field(
139
+ description="Optional list of SSM parameter paths of type `aws:ec2:image` which will "
140
+ "be pushed to the parameter store",
141
+ default=None,
142
+ )
143
+ groups: Optional[List[str]] = Field(description="Optional list of groups this image is part of", default=[])
144
+ tags: Optional[Dict[str, str]] = Field(description="Optional Tags to apply to this image only", default={})
145
+
146
+
147
+ class ConfigModel(BaseModel):
148
+ """
149
+ The base model for the whole configuration
150
+ """
151
+
152
+ model_config = ConfigDict(extra="forbid")
153
+
154
+ s3: ConfigS3Model
155
+ source: ConfigSourceModel
156
+ images: Dict[str, ConfigImageModel]
157
+ tags: Optional[Dict[str, str]] = Field(description="Optional Tags to apply to all resources", default={})
awspub/context.py ADDED
@@ -0,0 +1,108 @@
1
+ import hashlib
2
+ import logging
3
+ import pathlib
4
+ from string import Template
5
+ from typing import Dict
6
+
7
+ from ruamel.yaml import YAML
8
+
9
+ from awspub.configmodels import ConfigModel
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class Context:
15
+ """
16
+ Context holds the used configuration and some
17
+ automatically calculated values
18
+ """
19
+
20
+ def __init__(self, conf_path: pathlib.Path, conf_template_mapping_path: pathlib.Path):
21
+ self._conf_path = conf_path
22
+ self._conf = None
23
+ self._conf_template_mapping_path = conf_template_mapping_path
24
+ self._conf_template_mapping = {}
25
+ yaml = YAML(typ="safe")
26
+
27
+ # read the config mapping first
28
+ if self._conf_template_mapping_path:
29
+ with open(self._conf_template_mapping_path, "r") as ctm:
30
+ self._conf_template_mapping = yaml.load(ctm)
31
+ logger.debug(f"loaded config template mapping for substitution: {self._conf_template_mapping}")
32
+
33
+ # read the config itself
34
+ with open(self._conf_path, "r") as f:
35
+ template = Template(f.read())
36
+ # substitute the values in the config with values from the config template mapping
37
+ ft = template.substitute(**self._conf_template_mapping)
38
+ y = yaml.load(ft)["awspub"]
39
+ self._conf = ConfigModel(**y).model_dump()
40
+ logger.debug(f"config loaded and validated as: {self._conf}")
41
+
42
+ # handle relative paths in config files. those are relative to the config file dirname
43
+ if not self.conf["source"]["path"].is_absolute():
44
+ self.conf["source"]["path"] = pathlib.Path(self._conf_path).parent / self.conf["source"]["path"]
45
+
46
+ for image_name, props in self.conf["images"].items():
47
+ if props["uefi_data"] and not self.conf["images"][image_name]["uefi_data"].is_absolute():
48
+ self.conf["images"][image_name]["uefi_data"] = (
49
+ pathlib.Path(self._conf_path).parent / self.conf["images"][image_name]["uefi_data"]
50
+ )
51
+
52
+ # calculate the sha256 sum of the source file once
53
+ self._source_sha256_obj = self._sha256sum(self.conf["source"]["path"])
54
+ self._source_sha256 = self._source_sha256_obj.hexdigest()
55
+
56
+ @property
57
+ def conf(self):
58
+ return self._conf
59
+
60
+ @property
61
+ def source_sha256(self):
62
+ """
63
+ The sha256 sum hexdigest of the source->path value from the given
64
+ configuration. This value is used in different places (eg. to automatically
65
+ upload to S3 with this value as key)
66
+ """
67
+ return self._source_sha256
68
+
69
+ @property
70
+ def tags_dict(self) -> Dict[str, str]:
71
+ """
72
+ Common tags which will be used for all AWS resources
73
+ This includes tags defined in the configuration file
74
+ but doesn't include image group specific tags.
75
+ Usually the tags() method should be used.
76
+ """
77
+ tags = dict()
78
+ tags["awspub:source:filename"] = self.conf["source"]["path"].name
79
+ tags["awspub:source:architecture"] = self.conf["source"]["architecture"]
80
+ tags["awspub:source:sha256"] = self.source_sha256
81
+ tags.update(self.conf.get("tags", {}))
82
+ return tags
83
+
84
+ @property
85
+ def tags(self):
86
+ """
87
+ Helper to make tags directly usable by the AWS EC2 API
88
+ which requires a list of dicts with "Key" and "Value" defined.
89
+ """
90
+ tags = []
91
+ for name, value in self.tags_dict.items():
92
+ tags.append({"Key": name, "Value": value})
93
+ return tags
94
+
95
+ def _sha256sum(self, file_path: pathlib.Path):
96
+ """
97
+ Calculate a sha256 sum for a given file
98
+
99
+ :param file_path: the path to the local file to upload
100
+ :type file_path: pathlib.Path
101
+ :return: a haslib Hash object
102
+ :rtype: _hashlib.HASH
103
+ """
104
+ sha256_hash = hashlib.sha256()
105
+ with open(file_path.resolve(), "rb") as f:
106
+ for byte_block in iter(lambda: f.read(4096), b""):
107
+ sha256_hash.update(byte_block)
108
+ return sha256_hash
awspub/exceptions.py ADDED
@@ -0,0 +1,16 @@
1
+ class MultipleSnapshotsException(Exception):
2
+ pass
3
+
4
+
5
+ class MultipleImportSnapshotTasksException(Exception):
6
+ pass
7
+
8
+
9
+ class MultipleImagesException(Exception):
10
+ pass
11
+
12
+
13
+ class BucketDoesNotExistException(Exception):
14
+ def __init__(self, bucket_name: str, *args, **kwargs):
15
+ msg = f"The bucket named '{bucket_name}' does not exist. You will need to create the bucket before proceeding."
16
+ super().__init__(msg, *args, **kwargs)