dbt-platform-helper 13.1.0__py3-none-any.whl → 15.16.0__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.
- dbt_platform_helper/COMMANDS.md +107 -27
- dbt_platform_helper/commands/application.py +5 -6
- dbt_platform_helper/commands/codebase.py +31 -10
- dbt_platform_helper/commands/conduit.py +3 -5
- dbt_platform_helper/commands/config.py +20 -311
- dbt_platform_helper/commands/copilot.py +18 -391
- dbt_platform_helper/commands/database.py +17 -9
- dbt_platform_helper/commands/environment.py +20 -14
- dbt_platform_helper/commands/generate.py +0 -3
- dbt_platform_helper/commands/internal.py +140 -0
- dbt_platform_helper/commands/notify.py +58 -78
- dbt_platform_helper/commands/pipeline.py +23 -19
- dbt_platform_helper/commands/secrets.py +39 -93
- dbt_platform_helper/commands/version.py +7 -12
- dbt_platform_helper/constants.py +52 -7
- dbt_platform_helper/domain/codebase.py +89 -39
- dbt_platform_helper/domain/conduit.py +335 -76
- dbt_platform_helper/domain/config.py +381 -0
- dbt_platform_helper/domain/copilot.py +398 -0
- dbt_platform_helper/domain/copilot_environment.py +8 -8
- dbt_platform_helper/domain/database_copy.py +2 -2
- dbt_platform_helper/domain/maintenance_page.py +254 -430
- dbt_platform_helper/domain/notify.py +64 -0
- dbt_platform_helper/domain/pipelines.py +43 -35
- dbt_platform_helper/domain/plans.py +41 -0
- dbt_platform_helper/domain/secrets.py +279 -0
- dbt_platform_helper/domain/service.py +570 -0
- dbt_platform_helper/domain/terraform_environment.py +14 -13
- dbt_platform_helper/domain/update_alb_rules.py +412 -0
- dbt_platform_helper/domain/versioning.py +249 -0
- dbt_platform_helper/{providers → entities}/platform_config_schema.py +75 -82
- dbt_platform_helper/entities/semantic_version.py +83 -0
- dbt_platform_helper/entities/service.py +339 -0
- dbt_platform_helper/platform_exception.py +4 -0
- dbt_platform_helper/providers/autoscaling.py +24 -0
- dbt_platform_helper/providers/aws/__init__.py +0 -0
- dbt_platform_helper/providers/aws/exceptions.py +70 -0
- dbt_platform_helper/providers/aws/interfaces.py +13 -0
- dbt_platform_helper/providers/aws/opensearch.py +23 -0
- dbt_platform_helper/providers/aws/redis.py +21 -0
- dbt_platform_helper/providers/aws/sso_auth.py +75 -0
- dbt_platform_helper/providers/cache.py +40 -4
- dbt_platform_helper/providers/cloudformation.py +1 -1
- dbt_platform_helper/providers/config.py +137 -19
- dbt_platform_helper/providers/config_validator.py +112 -51
- dbt_platform_helper/providers/copilot.py +24 -16
- dbt_platform_helper/providers/ecr.py +89 -7
- dbt_platform_helper/providers/ecs.py +228 -36
- dbt_platform_helper/providers/environment_variable.py +24 -0
- dbt_platform_helper/providers/files.py +1 -1
- dbt_platform_helper/providers/io.py +36 -4
- dbt_platform_helper/providers/kms.py +22 -0
- dbt_platform_helper/providers/load_balancers.py +402 -42
- dbt_platform_helper/providers/logs.py +72 -0
- dbt_platform_helper/providers/parameter_store.py +134 -0
- dbt_platform_helper/providers/s3.py +21 -0
- dbt_platform_helper/providers/schema_migrations/__init__.py +0 -0
- dbt_platform_helper/providers/schema_migrations/schema_v0_to_v1_migration.py +43 -0
- dbt_platform_helper/providers/schema_migrator.py +77 -0
- dbt_platform_helper/providers/secrets.py +5 -5
- dbt_platform_helper/providers/slack_channel_notifier.py +62 -0
- dbt_platform_helper/providers/terraform_manifest.py +121 -19
- dbt_platform_helper/providers/version.py +106 -23
- dbt_platform_helper/providers/version_status.py +27 -0
- dbt_platform_helper/providers/vpc.py +36 -5
- dbt_platform_helper/providers/yaml_file.py +58 -2
- dbt_platform_helper/templates/environment-pipelines/main.tf +4 -3
- dbt_platform_helper/templates/svc/overrides/cfn.patches.yml +5 -0
- dbt_platform_helper/utilities/decorators.py +103 -0
- dbt_platform_helper/utils/application.py +119 -22
- dbt_platform_helper/utils/aws.py +39 -150
- dbt_platform_helper/utils/deep_merge.py +10 -0
- dbt_platform_helper/utils/git.py +1 -14
- dbt_platform_helper/utils/validation.py +1 -1
- {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info}/METADATA +11 -20
- dbt_platform_helper-15.16.0.dist-info/RECORD +118 -0
- {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info}/WHEEL +1 -1
- platform_helper.py +3 -1
- terraform/elasticache-redis/plans.yml +85 -0
- terraform/opensearch/plans.yml +71 -0
- terraform/postgres/plans.yml +128 -0
- dbt_platform_helper/addon-plans.yml +0 -224
- dbt_platform_helper/providers/aws.py +0 -37
- dbt_platform_helper/providers/opensearch.py +0 -36
- dbt_platform_helper/providers/redis.py +0 -34
- dbt_platform_helper/providers/semantic_version.py +0 -126
- dbt_platform_helper/templates/svc/manifest-backend.yml +0 -69
- dbt_platform_helper/templates/svc/manifest-public.yml +0 -109
- dbt_platform_helper/utils/cloudfoundry.py +0 -14
- dbt_platform_helper/utils/files.py +0 -53
- dbt_platform_helper/utils/manifests.py +0 -18
- dbt_platform_helper/utils/versioning.py +0 -238
- dbt_platform_helper-13.1.0.dist-info/RECORD +0 -96
- {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info}/entry_points.txt +0 -0
- {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import random
|
|
2
2
|
import string
|
|
3
|
-
import
|
|
4
|
-
from typing import
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Any
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from botocore.exceptions import ClientError
|
|
5
8
|
|
|
6
9
|
from dbt_platform_helper.platform_exception import PlatformException
|
|
10
|
+
from dbt_platform_helper.platform_exception import ValidationException
|
|
11
|
+
from dbt_platform_helper.providers.vpc import Vpc
|
|
12
|
+
from dbt_platform_helper.utilities.decorators import retry
|
|
13
|
+
from dbt_platform_helper.utilities.decorators import wait_until
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ECSException(PlatformException):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ECSAgentNotRunningException(ECSException):
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__("""ECS exec agent never reached "RUNNING" status""")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NoClusterException(ECSException):
|
|
26
|
+
def __init__(self, application_name: str, environment: str):
|
|
27
|
+
super().__init__(
|
|
28
|
+
f"""No ECS cluster found for "{application_name}" in "{environment}" environment."""
|
|
29
|
+
)
|
|
7
30
|
|
|
8
31
|
|
|
9
32
|
class ECS:
|
|
@@ -13,7 +36,49 @@ class ECS:
|
|
|
13
36
|
self.application_name = application_name
|
|
14
37
|
self.env = env
|
|
15
38
|
|
|
16
|
-
def
|
|
39
|
+
def start_ecs_task(
|
|
40
|
+
self,
|
|
41
|
+
cluster_name: str,
|
|
42
|
+
container_name: str,
|
|
43
|
+
task_def_arn: str,
|
|
44
|
+
vpc_config: Vpc,
|
|
45
|
+
env_vars: list[dict] = None,
|
|
46
|
+
):
|
|
47
|
+
container_override = {"name": container_name}
|
|
48
|
+
if env_vars:
|
|
49
|
+
container_override["environment"] = env_vars
|
|
50
|
+
|
|
51
|
+
response = self.ecs_client.run_task(
|
|
52
|
+
taskDefinition=task_def_arn,
|
|
53
|
+
cluster=cluster_name,
|
|
54
|
+
capacityProviderStrategy=[
|
|
55
|
+
{"capacityProvider": "FARGATE", "weight": 1, "base": 0},
|
|
56
|
+
],
|
|
57
|
+
enableExecuteCommand=True,
|
|
58
|
+
networkConfiguration={
|
|
59
|
+
"awsvpcConfiguration": {
|
|
60
|
+
"subnets": vpc_config.public_subnets,
|
|
61
|
+
"securityGroups": vpc_config.security_groups,
|
|
62
|
+
"assignPublicIp": "ENABLED",
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
overrides={"containerOverrides": [container_override]},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return response.get("tasks", [{}])[0].get("taskArn")
|
|
69
|
+
|
|
70
|
+
def get_cluster_arn_by_name(self, cluster_name: str) -> str:
|
|
71
|
+
clusters = self.ecs_client.describe_clusters(
|
|
72
|
+
clusters=[
|
|
73
|
+
cluster_name,
|
|
74
|
+
],
|
|
75
|
+
)["clusters"]
|
|
76
|
+
if len(clusters) == 1 and "clusterArn" in clusters[0]:
|
|
77
|
+
return clusters[0]["clusterArn"]
|
|
78
|
+
|
|
79
|
+
raise NoClusterException(self.application_name, self.env)
|
|
80
|
+
|
|
81
|
+
def get_cluster_arn_by_copilot_tag(self) -> str:
|
|
17
82
|
"""Returns the ARN of the ECS cluster for the given application and
|
|
18
83
|
environment."""
|
|
19
84
|
for cluster_arn in self.ecs_client.list_clusters()["clusterArns"]:
|
|
@@ -45,58 +110,185 @@ class ECS:
|
|
|
45
110
|
random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
|
46
111
|
return f"conduit-{self.application_name}-{self.env}-{addon_name}-{random_id}"
|
|
47
112
|
|
|
48
|
-
def get_ecs_task_arns(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
113
|
+
def get_ecs_task_arns(
|
|
114
|
+
self,
|
|
115
|
+
cluster: str,
|
|
116
|
+
max_results: int = 100,
|
|
117
|
+
desired_status: str = "RUNNING",
|
|
118
|
+
service_name: Optional[str] = None,
|
|
119
|
+
started_by: Optional[str] = None,
|
|
120
|
+
task_def_family: Optional[str] = None,
|
|
121
|
+
) -> list[str]:
|
|
122
|
+
"""Returns the ECS task ARNs based on the parameters provided."""
|
|
123
|
+
|
|
124
|
+
params = {
|
|
125
|
+
"cluster": cluster,
|
|
126
|
+
"maxResults": max_results,
|
|
127
|
+
"desiredStatus": desired_status,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if service_name:
|
|
131
|
+
params["serviceName"] = service_name
|
|
132
|
+
if started_by:
|
|
133
|
+
params["startedBy"] = started_by
|
|
134
|
+
if task_def_family:
|
|
135
|
+
params["family"] = task_def_family
|
|
136
|
+
|
|
137
|
+
tasks = self.ecs_client.list_tasks(**params)
|
|
55
138
|
|
|
56
139
|
if not tasks["taskArns"]:
|
|
57
140
|
return []
|
|
58
141
|
|
|
59
142
|
return tasks["taskArns"]
|
|
60
143
|
|
|
61
|
-
|
|
144
|
+
@retry()
|
|
145
|
+
def exec_task(self, cluster_arn: str, task_arn: str, subprocess_call=subprocess.call):
|
|
146
|
+
result = subprocess_call(
|
|
147
|
+
f"aws ecs execute-command --cluster {cluster_arn} "
|
|
148
|
+
f"--task {task_arn} "
|
|
149
|
+
f"--interactive --command bash ",
|
|
150
|
+
shell=True,
|
|
151
|
+
)
|
|
152
|
+
if result != 0:
|
|
153
|
+
raise PlatformException("Failed to exec into ECS task.")
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
@wait_until(
|
|
157
|
+
max_attempts=25,
|
|
158
|
+
exceptions_to_catch=(ECSException,),
|
|
159
|
+
message_on_false="ECS Agent Not running",
|
|
160
|
+
)
|
|
161
|
+
def ecs_exec_is_available(self, cluster_arn: str, task_arns: list[str]) -> bool:
|
|
62
162
|
"""
|
|
63
163
|
Checks if the ExecuteCommandAgent is running on the specified ECS task.
|
|
64
164
|
|
|
65
165
|
Waits for up to 25 attempts, then raises ECSAgentNotRunning if still not
|
|
66
166
|
running.
|
|
67
167
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
168
|
+
if not task_arns:
|
|
169
|
+
raise ValidationException("No task ARNs provided")
|
|
170
|
+
task_details = self.ecs_client.describe_tasks(cluster=cluster_arn, tasks=task_arns)
|
|
70
171
|
|
|
71
|
-
|
|
72
|
-
|
|
172
|
+
if not task_details["tasks"]:
|
|
173
|
+
raise ECSException("No ECS tasks returned.")
|
|
174
|
+
container_details = task_details["tasks"][0]["containers"][0]
|
|
175
|
+
if container_details.get("managedAgents", None):
|
|
176
|
+
managed_agents = container_details["managedAgents"]
|
|
177
|
+
else:
|
|
178
|
+
raise ECSException("No managed agent on ecs task.")
|
|
73
179
|
|
|
74
|
-
|
|
180
|
+
execute_command_agent = [
|
|
181
|
+
agent for agent in managed_agents if agent["name"] == "ExecuteCommandAgent"
|
|
182
|
+
]
|
|
183
|
+
if not execute_command_agent:
|
|
184
|
+
raise ECSException("No ExecuteCommandAgent on ecs task.")
|
|
185
|
+
return execute_command_agent[0]["lastStatus"] == "RUNNING"
|
|
75
186
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
187
|
+
@wait_until(
|
|
188
|
+
max_attempts=20,
|
|
189
|
+
message_on_false="ECS task did not register in time",
|
|
190
|
+
)
|
|
191
|
+
def wait_for_task_to_register(self, cluster_arn: str, task_family: str) -> list[str]:
|
|
192
|
+
task_arns = self.get_ecs_task_arns(cluster=cluster_arn, task_def_family=task_family)
|
|
193
|
+
if task_arns:
|
|
194
|
+
return task_arns
|
|
195
|
+
return False
|
|
84
196
|
|
|
85
|
-
|
|
86
|
-
|
|
197
|
+
def get_service_deployment_state(
|
|
198
|
+
self, cluster_name: str, service_name: str, start_time: float
|
|
199
|
+
) -> tuple[Optional[str], Optional[str]]:
|
|
200
|
+
"""
|
|
201
|
+
Returns status & statusReason for the deployment of an ECS service.
|
|
87
202
|
|
|
203
|
+
statusReason can be:
|
|
204
|
+
PENDING | SUCCESSFUL | STOPPED | STOP_REQUESTED |
|
|
205
|
+
IN_PROGRESS | ROLLBACK_REQUESTED | ROLLBACK_IN_PROGRESS |
|
|
206
|
+
ROLLBACK_SUCCESSFUL | ROLLBACK_FAILED
|
|
207
|
+
"""
|
|
208
|
+
resp = self.ecs_client.list_service_deployments(
|
|
209
|
+
cluster=cluster_name, service=service_name, createdAt={"after": start_time}
|
|
210
|
+
)
|
|
211
|
+
deployments = resp.get("serviceDeployments", [])
|
|
88
212
|
|
|
89
|
-
|
|
90
|
-
|
|
213
|
+
if not deployments:
|
|
214
|
+
return None, f"No deployments found for '{service_name}'"
|
|
91
215
|
|
|
216
|
+
return deployments[0].get("status"), deployments[0].get("statusReason")
|
|
92
217
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
218
|
+
def get_container_names_from_ecs_tasks(
|
|
219
|
+
self, cluster_name: str, task_ids: list[str]
|
|
220
|
+
) -> list[str]:
|
|
221
|
+
"""Retrieve container names from each ECS task provided."""
|
|
96
222
|
|
|
223
|
+
response = self.ecs_client.describe_tasks(cluster=cluster_name, tasks=task_ids)
|
|
97
224
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
225
|
+
names = []
|
|
226
|
+
for task in response.get("tasks", []):
|
|
227
|
+
for container in task.get("containers", []):
|
|
228
|
+
if container["name"] not in names:
|
|
229
|
+
names.append(container["name"])
|
|
230
|
+
return names
|
|
231
|
+
|
|
232
|
+
def register_task_definition(
|
|
233
|
+
self,
|
|
234
|
+
service: str,
|
|
235
|
+
task_definition: dict,
|
|
236
|
+
image_tag: Optional[str] = None,
|
|
237
|
+
) -> str:
|
|
238
|
+
"""Register a new task definition revision using provided model and
|
|
239
|
+
containerDefinitions."""
|
|
240
|
+
|
|
241
|
+
for container in task_definition["containerDefinitions"]:
|
|
242
|
+
if container["name"] == service:
|
|
243
|
+
container["image"] = f"{container['image']}:{image_tag}"
|
|
244
|
+
break
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
task_definition_response = self.ecs_client.register_task_definition(**task_definition)
|
|
248
|
+
return task_definition_response["taskDefinition"]["taskDefinitionArn"]
|
|
249
|
+
except ClientError as err:
|
|
250
|
+
raise PlatformException(f"Error registering task definition: {err}")
|
|
251
|
+
|
|
252
|
+
def update_service(
|
|
253
|
+
self,
|
|
254
|
+
service: str,
|
|
255
|
+
task_def_arn: str,
|
|
256
|
+
environment: str,
|
|
257
|
+
application: str,
|
|
258
|
+
desired_count: int,
|
|
259
|
+
) -> dict[str, Any]:
|
|
260
|
+
"""Update an ECS service and return the response."""
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
service_response = self.ecs_client.update_service(
|
|
264
|
+
cluster=f"{application}-{environment}-cluster",
|
|
265
|
+
service=f"{application}-{environment}-{service}",
|
|
266
|
+
taskDefinition=task_def_arn,
|
|
267
|
+
desiredCount=desired_count,
|
|
268
|
+
)
|
|
269
|
+
return service_response["service"]
|
|
270
|
+
except ClientError as err:
|
|
271
|
+
raise PlatformException(f"Error updating ECS service: {err}")
|
|
272
|
+
|
|
273
|
+
def describe_service(self, service: str, environment: str, application: str) -> dict[str, Any]:
|
|
274
|
+
"""Return information about an ECS service."""
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
service_response = self.ecs_client.describe_services(
|
|
278
|
+
cluster=f"{application}-{environment}-cluster",
|
|
279
|
+
services=[
|
|
280
|
+
f"{application}-{environment}-{service}",
|
|
281
|
+
],
|
|
282
|
+
)
|
|
283
|
+
return service_response["services"][0]
|
|
284
|
+
except ClientError as err:
|
|
285
|
+
raise PlatformException(f"Error retrieving ECS service: {err}")
|
|
286
|
+
|
|
287
|
+
def describe_tasks(self, cluster_name: str, task_ids: list[str]) -> list[dict[str, Any]]:
|
|
288
|
+
"""Return information about ECS tasks."""
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
response = self.ecs_client.describe_tasks(cluster=cluster_name, tasks=task_ids)
|
|
292
|
+
return response["tasks"]
|
|
293
|
+
except ClientError as err:
|
|
294
|
+
raise PlatformException(f"Error retrieving ECS tasks: {err}")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from dbt_platform_helper.platform_exception import PlatformException
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EnvironmentVariableProvider:
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def get(env_var: str, default=None) -> Optional[str]:
|
|
11
|
+
"""Returns the stripped value or None if not set or empty."""
|
|
12
|
+
value = os.environ.get(env_var)
|
|
13
|
+
if value and value.strip():
|
|
14
|
+
return value.strip()
|
|
15
|
+
return default
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_required(env_var: str) -> str:
|
|
19
|
+
"""Returns the stripped value or raises a PlatformException if not set
|
|
20
|
+
or empty."""
|
|
21
|
+
value = os.environ.get(env_var)
|
|
22
|
+
if not value or not value.strip():
|
|
23
|
+
raise PlatformException(f"Environment variable '{env_var}' is not set or is empty")
|
|
24
|
+
return value.strip()
|
|
@@ -17,7 +17,7 @@ class FileProvider:
|
|
|
17
17
|
file_path.write_text(contents)
|
|
18
18
|
|
|
19
19
|
action = "overwritten" if file_exists and overwrite else "created"
|
|
20
|
-
return f"File {
|
|
20
|
+
return f"File {file_path} {action}"
|
|
21
21
|
|
|
22
22
|
@staticmethod
|
|
23
23
|
def delete_file(base_path: str, file_name: str):
|
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
3
|
from dbt_platform_helper.platform_exception import PlatformException
|
|
4
|
+
from dbt_platform_helper.providers.environment_variable import (
|
|
5
|
+
EnvironmentVariableProvider,
|
|
6
|
+
)
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class ClickIOProvider:
|
|
10
|
+
def __init__(self, env_var_provider=EnvironmentVariableProvider()):
|
|
11
|
+
self.env_var_provider = env_var_provider
|
|
12
|
+
self.debug_flag = self.env_var_provider.get("DEBUG")
|
|
13
|
+
|
|
7
14
|
def warn(self, message: str):
|
|
8
15
|
click.secho(message, fg="magenta")
|
|
9
16
|
|
|
17
|
+
def debug(self, message: str):
|
|
18
|
+
if self.debug_flag and self.debug_flag.strip().upper() == "TRUE":
|
|
19
|
+
click.secho(message, fg="green")
|
|
20
|
+
|
|
10
21
|
def error(self, message: str):
|
|
11
22
|
click.secho(f"Error: {message}", fg="red")
|
|
12
23
|
|
|
13
|
-
def info(self, message: str):
|
|
14
|
-
click.secho(message)
|
|
24
|
+
def info(self, message: str, **kwargs):
|
|
25
|
+
click.secho(message, **kwargs)
|
|
15
26
|
|
|
16
|
-
def input(
|
|
17
|
-
|
|
27
|
+
def input(
|
|
28
|
+
self, message: str, hide_input=False, confirmation_prompt=False, input_type=str
|
|
29
|
+
) -> str:
|
|
30
|
+
return click.prompt(
|
|
31
|
+
message, hide_input=hide_input, confirmation_prompt=confirmation_prompt, type=input_type
|
|
32
|
+
)
|
|
18
33
|
|
|
19
34
|
def confirm(self, message: str) -> bool:
|
|
20
35
|
try:
|
|
@@ -26,6 +41,23 @@ class ClickIOProvider:
|
|
|
26
41
|
click.secho(f"Error: {message}", err=True, fg="red")
|
|
27
42
|
exit(1)
|
|
28
43
|
|
|
44
|
+
def deploy_error(self, message: str):
|
|
45
|
+
click.secho(message, fg="red")
|
|
46
|
+
|
|
47
|
+
# TODO: DBTP-1979: messages will be a ValidationMessages class rather than a free-rein dictionary
|
|
48
|
+
def process_messages(self, messages: dict):
|
|
49
|
+
if not messages:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
if messages.get("errors"):
|
|
53
|
+
self.error("\n".join(messages["errors"]))
|
|
54
|
+
|
|
55
|
+
if messages.get("warnings"):
|
|
56
|
+
self.warn("\n".join(messages["warnings"]))
|
|
57
|
+
|
|
58
|
+
if messages.get("info"):
|
|
59
|
+
self.info("\n".join(messages["info"]))
|
|
60
|
+
|
|
29
61
|
|
|
30
62
|
class ClickIOProviderException(PlatformException):
|
|
31
63
|
pass
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KMSProvider:
|
|
5
|
+
"""A provider class for interacting with the AWS KMS (Key Management
|
|
6
|
+
Service)."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, kms_client: boto3.client):
|
|
9
|
+
self.kms_client = kms_client
|
|
10
|
+
|
|
11
|
+
def describe_key(self, alias_name: str) -> dict:
|
|
12
|
+
"""
|
|
13
|
+
Retrieves metadata about a KMS key using its alias.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
alias_name (str): The alias name of the KMS key.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
dict: A dictionary containing metadata about the specified KMS key.
|
|
20
|
+
"""
|
|
21
|
+
# The kms client can take an alias name as the KeyId
|
|
22
|
+
return self.kms_client.describe_key(KeyId=alias_name)
|