dbt-platform-helper 12.1.0__py3-none-any.whl → 12.2.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.
Potentially problematic release.
This version of dbt-platform-helper might be problematic. Click here for more details.
- dbt_platform_helper/COMMANDS.md +5 -4
- dbt_platform_helper/commands/application.py +1 -0
- dbt_platform_helper/commands/codebase.py +3 -2
- dbt_platform_helper/commands/conduit.py +34 -409
- dbt_platform_helper/commands/secrets.py +1 -1
- dbt_platform_helper/constants.py +12 -2
- dbt_platform_helper/domain/codebase.py +1 -1
- dbt_platform_helper/domain/conduit.py +172 -0
- dbt_platform_helper/exceptions.py +33 -0
- dbt_platform_helper/providers/__init__.py +0 -0
- dbt_platform_helper/providers/cloudformation.py +105 -0
- dbt_platform_helper/providers/copilot.py +144 -0
- dbt_platform_helper/providers/ecs.py +79 -0
- dbt_platform_helper/providers/secrets.py +85 -0
- dbt_platform_helper/templates/addons/svc/prometheus-policy.yml +2 -0
- dbt_platform_helper/templates/pipelines/environments/manifest.yml +0 -1
- dbt_platform_helper/utils/validation.py +22 -1
- {dbt_platform_helper-12.1.0.dist-info → dbt_platform_helper-12.2.1.dist-info}/METADATA +2 -1
- {dbt_platform_helper-12.1.0.dist-info → dbt_platform_helper-12.2.1.dist-info}/RECORD +22 -16
- {dbt_platform_helper-12.1.0.dist-info → dbt_platform_helper-12.2.1.dist-info}/WHEEL +1 -1
- {dbt_platform_helper-12.1.0.dist-info → dbt_platform_helper-12.2.1.dist-info}/LICENSE +0 -0
- {dbt_platform_helper-12.1.0.dist-info → dbt_platform_helper-12.2.1.dist-info}/entry_points.txt +0 -0
dbt_platform_helper/COMMANDS.md
CHANGED
|
@@ -256,7 +256,8 @@ platform-helper codebase deploy --app <application> --env <environment> --codeba
|
|
|
256
256
|
|
|
257
257
|
[↩ Parent](#platform-helper)
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
Opens a shell for a given addon_name create a conduit connection to
|
|
260
|
+
interact with postgres, opensearch or redis.
|
|
260
261
|
|
|
261
262
|
## Usage
|
|
262
263
|
|
|
@@ -272,11 +273,11 @@ platform-helper conduit <addon_name>
|
|
|
272
273
|
## Options
|
|
273
274
|
|
|
274
275
|
- `--app <text>`
|
|
275
|
-
-
|
|
276
|
+
- Application name
|
|
276
277
|
- `--env <text>`
|
|
277
|
-
-
|
|
278
|
+
- Environment name
|
|
278
279
|
- `--access <choice>` _Defaults to read._
|
|
279
|
-
- Allow write or admin access to database addons
|
|
280
|
+
- Allow read, write or admin access to the database addons.
|
|
280
281
|
- `--help <boolean>` _Defaults to False._
|
|
281
282
|
- Show this message and exit.
|
|
282
283
|
|
|
@@ -30,7 +30,7 @@ def prepare():
|
|
|
30
30
|
try:
|
|
31
31
|
Codebase().prepare()
|
|
32
32
|
except NotInCodeBaseRepositoryError:
|
|
33
|
-
# TODO
|
|
33
|
+
# TODO: Set exception message in the exceptions and just output the message in the command code
|
|
34
34
|
click.secho(
|
|
35
35
|
"You are in the deploy repository; make sure you are in the application codebase repository.",
|
|
36
36
|
fg="red",
|
|
@@ -109,6 +109,7 @@ def deploy(app, env, codebase, commit):
|
|
|
109
109
|
try:
|
|
110
110
|
Codebase().deploy(app, env, codebase, commit)
|
|
111
111
|
except ApplicationNotFoundError:
|
|
112
|
+
# TODO: Set exception message in the exceptions and just output the message in the command code
|
|
112
113
|
click.secho(
|
|
113
114
|
f"""The account "{os.environ.get("AWS_PROFILE")}" does not contain the application "{app}"; ensure you have set the environment variable "AWS_PROFILE" correctly.""",
|
|
114
115
|
fg="red",
|
|
@@ -120,9 +121,9 @@ def deploy(app, env, codebase, commit):
|
|
|
120
121
|
fg="red",
|
|
121
122
|
)
|
|
122
123
|
raise click.Abort
|
|
123
|
-
# TODO: don't hide json decode error
|
|
124
124
|
except (
|
|
125
125
|
CopilotCodebaseNotFoundError,
|
|
126
|
+
# TODO: Catch this error earlier and throw a more meaningful error, maybe it's CopilotCodebaseNotFoundError?
|
|
126
127
|
json.JSONDecodeError,
|
|
127
128
|
):
|
|
128
129
|
click.secho(
|
|
@@ -1,453 +1,78 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import random
|
|
3
|
-
import string
|
|
4
|
-
import subprocess
|
|
5
|
-
import time
|
|
6
|
-
|
|
7
1
|
import click
|
|
8
|
-
from botocore.exceptions import ClientError
|
|
9
|
-
from cfn_tools import dump_yaml
|
|
10
|
-
from cfn_tools import load_yaml
|
|
11
2
|
|
|
12
|
-
from dbt_platform_helper.
|
|
3
|
+
from dbt_platform_helper.constants import CONDUIT_ADDON_TYPES
|
|
4
|
+
from dbt_platform_helper.domain.conduit import Conduit
|
|
5
|
+
from dbt_platform_helper.exceptions import AddonNotFoundError
|
|
6
|
+
from dbt_platform_helper.exceptions import AddonTypeMissingFromConfigError
|
|
7
|
+
from dbt_platform_helper.exceptions import CreateTaskTimeoutError
|
|
8
|
+
from dbt_platform_helper.exceptions import InvalidAddonTypeError
|
|
9
|
+
from dbt_platform_helper.exceptions import NoClusterError
|
|
10
|
+
from dbt_platform_helper.exceptions import ParameterNotFoundError
|
|
11
|
+
from dbt_platform_helper.providers.secrets import SecretNotFoundError
|
|
13
12
|
from dbt_platform_helper.utils.application import load_application
|
|
14
|
-
from dbt_platform_helper.utils.aws import (
|
|
15
|
-
get_postgres_connection_data_updated_with_master_secret,
|
|
16
|
-
)
|
|
17
13
|
from dbt_platform_helper.utils.click import ClickDocOptCommand
|
|
18
|
-
from dbt_platform_helper.utils.messages import abort_with_error
|
|
19
14
|
from dbt_platform_helper.utils.versioning import (
|
|
20
15
|
check_platform_helper_version_needs_update,
|
|
21
16
|
)
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
class ConduitError(Exception):
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class InvalidAddonTypeConduitError(ConduitError):
|
|
29
|
-
def __init__(self, addon_type):
|
|
30
|
-
self.addon_type = addon_type
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class NoClusterConduitError(ConduitError):
|
|
34
|
-
pass
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class SecretNotFoundConduitError(ConduitError):
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class CreateTaskTimeoutConduitError(ConduitError):
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class ParameterNotFoundConduitError(ConduitError):
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class AddonNotFoundConduitError(ConduitError):
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
CONDUIT_DOCKER_IMAGE_LOCATION = "public.ecr.aws/uktrade/tunnel"
|
|
54
|
-
CONDUIT_ADDON_TYPES = [
|
|
55
|
-
"opensearch",
|
|
56
|
-
"postgres",
|
|
57
|
-
"redis",
|
|
58
|
-
]
|
|
59
18
|
CONDUIT_ACCESS_OPTIONS = ["read", "write", "admin"]
|
|
60
19
|
|
|
61
20
|
|
|
62
|
-
def normalise_secret_name(addon_name: str) -> str:
|
|
63
|
-
return addon_name.replace("-", "_").upper()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def get_addon_type(app: Application, env: str, addon_name: str) -> str:
|
|
67
|
-
session = app.environments[env].session
|
|
68
|
-
ssm_client = session.client("ssm")
|
|
69
|
-
addon_type = None
|
|
70
|
-
|
|
71
|
-
try:
|
|
72
|
-
addon_config = json.loads(
|
|
73
|
-
ssm_client.get_parameter(
|
|
74
|
-
Name=f"/copilot/applications/{app.name}/environments/{env}/addons"
|
|
75
|
-
)["Parameter"]["Value"]
|
|
76
|
-
)
|
|
77
|
-
except ssm_client.exceptions.ParameterNotFound:
|
|
78
|
-
raise ParameterNotFoundConduitError
|
|
79
|
-
|
|
80
|
-
if addon_name not in addon_config.keys():
|
|
81
|
-
raise AddonNotFoundConduitError
|
|
82
|
-
|
|
83
|
-
for name, config in addon_config.items():
|
|
84
|
-
if name == addon_name:
|
|
85
|
-
addon_type = config["type"]
|
|
86
|
-
|
|
87
|
-
if not addon_type or addon_type not in CONDUIT_ADDON_TYPES:
|
|
88
|
-
raise InvalidAddonTypeConduitError(addon_type)
|
|
89
|
-
|
|
90
|
-
if "postgres" in addon_type:
|
|
91
|
-
addon_type = "postgres"
|
|
92
|
-
|
|
93
|
-
return addon_type
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def get_parameter_name(
|
|
97
|
-
app: Application, env: str, addon_type: str, addon_name: str, access: str
|
|
98
|
-
) -> str:
|
|
99
|
-
if addon_type == "postgres":
|
|
100
|
-
return f"/copilot/{app.name}/{env}/conduits/{normalise_secret_name(addon_name)}_{access.upper()}"
|
|
101
|
-
elif addon_type == "redis" or addon_type == "opensearch":
|
|
102
|
-
return f"/copilot/{app.name}/{env}/conduits/{normalise_secret_name(addon_name)}_ENDPOINT"
|
|
103
|
-
else:
|
|
104
|
-
return f"/copilot/{app.name}/{env}/conduits/{normalise_secret_name(addon_name)}"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def get_or_create_task_name(
|
|
108
|
-
app: Application, env: str, addon_name: str, parameter_name: str
|
|
109
|
-
) -> str:
|
|
110
|
-
ssm = app.environments[env].session.client("ssm")
|
|
111
|
-
|
|
112
|
-
try:
|
|
113
|
-
return ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"]
|
|
114
|
-
except ssm.exceptions.ParameterNotFound:
|
|
115
|
-
random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
|
116
|
-
return f"conduit-{app.name}-{env}-{addon_name}-{random_id}"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def get_cluster_arn(app: Application, env: str) -> str:
|
|
120
|
-
ecs_client = app.environments[env].session.client("ecs")
|
|
121
|
-
|
|
122
|
-
for cluster_arn in ecs_client.list_clusters()["clusterArns"]:
|
|
123
|
-
tags_response = ecs_client.list_tags_for_resource(resourceArn=cluster_arn)
|
|
124
|
-
tags = tags_response["tags"]
|
|
125
|
-
|
|
126
|
-
app_key_found = False
|
|
127
|
-
env_key_found = False
|
|
128
|
-
cluster_key_found = False
|
|
129
|
-
|
|
130
|
-
for tag in tags:
|
|
131
|
-
if tag["key"] == "copilot-application" and tag["value"] == app.name:
|
|
132
|
-
app_key_found = True
|
|
133
|
-
if tag["key"] == "copilot-environment" and tag["value"] == env:
|
|
134
|
-
env_key_found = True
|
|
135
|
-
if tag["key"] == "aws:cloudformation:logical-id" and tag["value"] == "Cluster":
|
|
136
|
-
cluster_key_found = True
|
|
137
|
-
|
|
138
|
-
if app_key_found and env_key_found and cluster_key_found:
|
|
139
|
-
return cluster_arn
|
|
140
|
-
|
|
141
|
-
raise NoClusterConduitError
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def get_connection_secret_arn(app: Application, env: str, secret_name: str) -> str:
|
|
145
|
-
secrets_manager = app.environments[env].session.client("secretsmanager")
|
|
146
|
-
ssm = app.environments[env].session.client("ssm")
|
|
147
|
-
|
|
148
|
-
try:
|
|
149
|
-
return ssm.get_parameter(Name=secret_name, WithDecryption=False)["Parameter"]["ARN"]
|
|
150
|
-
except ssm.exceptions.ParameterNotFound:
|
|
151
|
-
pass
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
return secrets_manager.describe_secret(SecretId=secret_name)["ARN"]
|
|
155
|
-
except secrets_manager.exceptions.ResourceNotFoundException:
|
|
156
|
-
pass
|
|
157
|
-
|
|
158
|
-
raise SecretNotFoundConduitError(secret_name)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def create_postgres_admin_task(
|
|
162
|
-
app: Application, env: str, secret_name: str, task_name: str, addon_type: str, addon_name: str
|
|
163
|
-
):
|
|
164
|
-
session = app.environments[env].session
|
|
165
|
-
read_only_secret_name = secret_name + "_READ_ONLY_USER"
|
|
166
|
-
master_secret_name = (
|
|
167
|
-
f"/copilot/{app.name}/{env}/secrets/{normalise_secret_name(addon_name)}_RDS_MASTER_ARN"
|
|
168
|
-
)
|
|
169
|
-
master_secret_arn = session.client("ssm").get_parameter(
|
|
170
|
-
Name=master_secret_name, WithDecryption=True
|
|
171
|
-
)["Parameter"]["Value"]
|
|
172
|
-
connection_string = json.dumps(
|
|
173
|
-
get_postgres_connection_data_updated_with_master_secret(
|
|
174
|
-
session, read_only_secret_name, master_secret_arn
|
|
175
|
-
)
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
subprocess.call(
|
|
179
|
-
f"copilot task run --app {app.name} --env {env} "
|
|
180
|
-
f"--task-group-name {task_name} "
|
|
181
|
-
f"--image {CONDUIT_DOCKER_IMAGE_LOCATION}:{addon_type} "
|
|
182
|
-
f"--env-vars CONNECTION_SECRET='{connection_string}' "
|
|
183
|
-
"--platform-os linux "
|
|
184
|
-
"--platform-arch arm64",
|
|
185
|
-
shell=True,
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def create_addon_client_task(
|
|
190
|
-
app: Application,
|
|
191
|
-
env: str,
|
|
192
|
-
addon_type: str,
|
|
193
|
-
addon_name: str,
|
|
194
|
-
task_name: str,
|
|
195
|
-
access: str,
|
|
196
|
-
):
|
|
197
|
-
secret_name = f"/copilot/{app.name}/{env}/secrets/{normalise_secret_name(addon_name)}"
|
|
198
|
-
session = app.environments[env].session
|
|
199
|
-
|
|
200
|
-
if addon_type == "postgres":
|
|
201
|
-
if access == "read":
|
|
202
|
-
secret_name += "_READ_ONLY_USER"
|
|
203
|
-
elif access == "write":
|
|
204
|
-
secret_name += "_APPLICATION_USER"
|
|
205
|
-
elif access == "admin":
|
|
206
|
-
create_postgres_admin_task(app, env, secret_name, task_name, addon_type, addon_name)
|
|
207
|
-
return
|
|
208
|
-
elif addon_type == "redis" or addon_type == "opensearch":
|
|
209
|
-
secret_name += "_ENDPOINT"
|
|
210
|
-
|
|
211
|
-
role_name = f"{addon_name}-{app.name}-{env}-conduitEcsTask"
|
|
212
|
-
|
|
213
|
-
try:
|
|
214
|
-
session.client("iam").get_role(RoleName=role_name)
|
|
215
|
-
execution_role = f"--execution-role {role_name} "
|
|
216
|
-
except ClientError as ex:
|
|
217
|
-
execution_role = ""
|
|
218
|
-
# We cannot check for botocore.errorfactory.NoSuchEntityException as botocore generates that class on the fly as part of errorfactory.
|
|
219
|
-
# factory. Checking the error code is the recommended way of handling these exceptions.
|
|
220
|
-
if ex.response.get("Error", {}).get("Code", None) != "NoSuchEntity":
|
|
221
|
-
abort_with_error(
|
|
222
|
-
f"cannot obtain Role {role_name}: {ex.response.get('Error', {}).get('Message', '')}"
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
subprocess.call(
|
|
226
|
-
f"copilot task run --app {app.name} --env {env} "
|
|
227
|
-
f"--task-group-name {task_name} "
|
|
228
|
-
f"{execution_role}"
|
|
229
|
-
f"--image {CONDUIT_DOCKER_IMAGE_LOCATION}:{addon_type} "
|
|
230
|
-
f"--secrets CONNECTION_SECRET={get_connection_secret_arn(app, env, secret_name)} "
|
|
231
|
-
"--platform-os linux "
|
|
232
|
-
"--platform-arch arm64",
|
|
233
|
-
shell=True,
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def addon_client_is_running(app: Application, env: str, cluster_arn: str, task_name: str) -> bool:
|
|
238
|
-
ecs_client = app.environments[env].session.client("ecs")
|
|
239
|
-
|
|
240
|
-
tasks = ecs_client.list_tasks(
|
|
241
|
-
cluster=cluster_arn,
|
|
242
|
-
desiredStatus="RUNNING",
|
|
243
|
-
family=f"copilot-{task_name}",
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
if not tasks["taskArns"]:
|
|
247
|
-
return False
|
|
248
|
-
|
|
249
|
-
described_tasks = ecs_client.describe_tasks(cluster=cluster_arn, tasks=tasks["taskArns"])
|
|
250
|
-
|
|
251
|
-
# The ExecuteCommandAgent often takes longer to start running than the task and without the
|
|
252
|
-
# agent it's not possible to exec into a task.
|
|
253
|
-
for task in described_tasks["tasks"]:
|
|
254
|
-
for container in task["containers"]:
|
|
255
|
-
for agent in container["managedAgents"]:
|
|
256
|
-
if agent["name"] == "ExecuteCommandAgent" and agent["lastStatus"] == "RUNNING":
|
|
257
|
-
return True
|
|
258
|
-
|
|
259
|
-
return False
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def connect_to_addon_client_task(app: Application, env: str, cluster_arn: str, task_name: str):
|
|
263
|
-
tries = 0
|
|
264
|
-
running = False
|
|
265
|
-
|
|
266
|
-
while tries < 15 and not running:
|
|
267
|
-
tries += 1
|
|
268
|
-
|
|
269
|
-
if addon_client_is_running(app, env, cluster_arn, task_name):
|
|
270
|
-
running = True
|
|
271
|
-
subprocess.call(
|
|
272
|
-
"copilot task exec "
|
|
273
|
-
f"--app {app.name} --env {env} "
|
|
274
|
-
f"--name {task_name} "
|
|
275
|
-
f"--command bash",
|
|
276
|
-
shell=True,
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
time.sleep(1)
|
|
280
|
-
|
|
281
|
-
if not running:
|
|
282
|
-
raise CreateTaskTimeoutConduitError
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
def add_stack_delete_policy_to_task_role(app: Application, env: str, task_name: str):
|
|
286
|
-
session = app.environments[env].session
|
|
287
|
-
cloudformation_client = session.client("cloudformation")
|
|
288
|
-
iam_client = session.client("iam")
|
|
289
|
-
|
|
290
|
-
conduit_stack_name = f"task-{task_name}"
|
|
291
|
-
conduit_stack_resources = cloudformation_client.list_stack_resources(
|
|
292
|
-
StackName=conduit_stack_name
|
|
293
|
-
)["StackResourceSummaries"]
|
|
294
|
-
|
|
295
|
-
for resource in conduit_stack_resources:
|
|
296
|
-
if resource["LogicalResourceId"] == "DefaultTaskRole":
|
|
297
|
-
task_role_name = resource["PhysicalResourceId"]
|
|
298
|
-
iam_client.put_role_policy(
|
|
299
|
-
RoleName=task_role_name,
|
|
300
|
-
PolicyName="DeleteCloudFormationStack",
|
|
301
|
-
PolicyDocument=json.dumps(
|
|
302
|
-
{
|
|
303
|
-
"Version": "2012-10-17",
|
|
304
|
-
"Statement": [
|
|
305
|
-
{
|
|
306
|
-
"Action": ["cloudformation:DeleteStack"],
|
|
307
|
-
"Effect": "Allow",
|
|
308
|
-
"Resource": f"arn:aws:cloudformation:*:*:stack/{conduit_stack_name}/*",
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
},
|
|
312
|
-
),
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def update_conduit_stack_resources(
|
|
317
|
-
app: Application,
|
|
318
|
-
env: str,
|
|
319
|
-
addon_type: str,
|
|
320
|
-
addon_name: str,
|
|
321
|
-
task_name: str,
|
|
322
|
-
parameter_name: str,
|
|
323
|
-
access: str,
|
|
324
|
-
):
|
|
325
|
-
session = app.environments[env].session
|
|
326
|
-
cloudformation_client = session.client("cloudformation")
|
|
327
|
-
|
|
328
|
-
conduit_stack_name = f"task-{task_name}"
|
|
329
|
-
template = cloudformation_client.get_template(StackName=conduit_stack_name)
|
|
330
|
-
template_yml = load_yaml(template["TemplateBody"])
|
|
331
|
-
template_yml["Resources"]["LogGroup"]["DeletionPolicy"] = "Retain"
|
|
332
|
-
template_yml["Resources"]["TaskNameParameter"] = load_yaml(
|
|
333
|
-
f"""
|
|
334
|
-
Type: AWS::SSM::Parameter
|
|
335
|
-
Properties:
|
|
336
|
-
Name: {parameter_name}
|
|
337
|
-
Type: String
|
|
338
|
-
Value: {task_name}
|
|
339
|
-
"""
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
iam_client = session.client("iam")
|
|
343
|
-
log_filter_role_arn = iam_client.get_role(RoleName="CWLtoSubscriptionFilterRole")["Role"]["Arn"]
|
|
344
|
-
|
|
345
|
-
ssm_client = session.client("ssm")
|
|
346
|
-
destination_log_group_arns = json.loads(
|
|
347
|
-
ssm_client.get_parameter(Name="/copilot/tools/central_log_groups")["Parameter"]["Value"]
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
destination_arn = destination_log_group_arns["dev"]
|
|
351
|
-
if env.lower() in ("prod", "production"):
|
|
352
|
-
destination_arn = destination_log_group_arns["prod"]
|
|
353
|
-
|
|
354
|
-
template_yml["Resources"]["SubscriptionFilter"] = load_yaml(
|
|
355
|
-
f"""
|
|
356
|
-
Type: AWS::Logs::SubscriptionFilter
|
|
357
|
-
DeletionPolicy: Retain
|
|
358
|
-
Properties:
|
|
359
|
-
RoleArn: {log_filter_role_arn}
|
|
360
|
-
LogGroupName: /copilot/{task_name}
|
|
361
|
-
FilterName: /copilot/conduit/{app.name}/{env}/{addon_type}/{addon_name}/{task_name.rsplit("-", 1)[1]}/{access}
|
|
362
|
-
FilterPattern: ''
|
|
363
|
-
DestinationArn: {destination_arn}
|
|
364
|
-
"""
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
params = []
|
|
368
|
-
if "Parameters" in template_yml:
|
|
369
|
-
for param in template_yml["Parameters"]:
|
|
370
|
-
params.append({"ParameterKey": param, "UsePreviousValue": True})
|
|
371
|
-
|
|
372
|
-
cloudformation_client.update_stack(
|
|
373
|
-
StackName=conduit_stack_name,
|
|
374
|
-
TemplateBody=dump_yaml(template_yml),
|
|
375
|
-
Parameters=params,
|
|
376
|
-
Capabilities=["CAPABILITY_IAM"],
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def start_conduit(
|
|
381
|
-
application: Application,
|
|
382
|
-
env: str,
|
|
383
|
-
addon_type: str,
|
|
384
|
-
addon_name: str,
|
|
385
|
-
access: str = "read",
|
|
386
|
-
):
|
|
387
|
-
cluster_arn = get_cluster_arn(application, env)
|
|
388
|
-
parameter_name = get_parameter_name(application, env, addon_type, addon_name, access)
|
|
389
|
-
task_name = get_or_create_task_name(application, env, addon_name, parameter_name)
|
|
390
|
-
|
|
391
|
-
if not addon_client_is_running(application, env, cluster_arn, task_name):
|
|
392
|
-
create_addon_client_task(application, env, addon_type, addon_name, task_name, access)
|
|
393
|
-
add_stack_delete_policy_to_task_role(application, env, task_name)
|
|
394
|
-
update_conduit_stack_resources(
|
|
395
|
-
application, env, addon_type, addon_name, task_name, parameter_name, access
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
connect_to_addon_client_task(application, env, cluster_arn, task_name)
|
|
399
|
-
|
|
400
|
-
|
|
401
21
|
@click.command(cls=ClickDocOptCommand)
|
|
402
22
|
@click.argument("addon_name", type=str, required=True)
|
|
403
|
-
@click.option("--app", help="
|
|
404
|
-
@click.option("--env", help="
|
|
23
|
+
@click.option("--app", help="Application name", required=True)
|
|
24
|
+
@click.option("--env", help="Environment name", required=True)
|
|
405
25
|
@click.option(
|
|
406
26
|
"--access",
|
|
407
27
|
default="read",
|
|
408
28
|
type=click.Choice(CONDUIT_ACCESS_OPTIONS),
|
|
409
|
-
help="Allow write or admin access to database addons",
|
|
29
|
+
help="Allow read, write or admin access to the database addons.",
|
|
410
30
|
)
|
|
411
31
|
def conduit(addon_name: str, app: str, env: str, access: str):
|
|
412
|
-
"""
|
|
32
|
+
"""Opens a shell for a given addon_name create a conduit connection to
|
|
33
|
+
interact with postgres, opensearch or redis."""
|
|
413
34
|
check_platform_helper_version_needs_update()
|
|
414
35
|
application = load_application(app)
|
|
415
36
|
|
|
416
37
|
try:
|
|
417
|
-
|
|
418
|
-
except
|
|
38
|
+
Conduit(application).start(env, addon_name, access)
|
|
39
|
+
except NoClusterError:
|
|
40
|
+
# TODO: Set exception message in the exceptions and just output the message in the command code, should be able to catch all errors in one block
|
|
41
|
+
click.secho(f"""No ECS cluster found for "{app}" in "{env}" environment.""", fg="red")
|
|
42
|
+
exit(1)
|
|
43
|
+
except SecretNotFoundError as err:
|
|
419
44
|
click.secho(
|
|
420
|
-
f"""No
|
|
45
|
+
f"""No secret called "{err}" for "{app}" in "{env}" environment.""",
|
|
421
46
|
fg="red",
|
|
422
47
|
)
|
|
423
48
|
exit(1)
|
|
424
|
-
except
|
|
49
|
+
except CreateTaskTimeoutError:
|
|
425
50
|
click.secho(
|
|
426
|
-
f"""
|
|
51
|
+
f"""Client ({addon_name}) ECS task has failed to start for "{app}" in "{env}" environment.""",
|
|
427
52
|
fg="red",
|
|
428
53
|
)
|
|
429
54
|
exit(1)
|
|
430
|
-
except
|
|
55
|
+
except ParameterNotFoundError:
|
|
431
56
|
click.secho(
|
|
432
|
-
f"""
|
|
57
|
+
f"""No parameter called "/copilot/applications/{app}/environments/{env}/addons". Try deploying the "{app}" "{env}" environment.""",
|
|
433
58
|
fg="red",
|
|
434
59
|
)
|
|
435
60
|
exit(1)
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
61
|
+
except AddonNotFoundError:
|
|
62
|
+
click.secho(
|
|
63
|
+
f"""Addon "{addon_name}" does not exist.""",
|
|
64
|
+
fg="red",
|
|
65
|
+
)
|
|
441
66
|
exit(1)
|
|
442
|
-
except
|
|
67
|
+
except InvalidAddonTypeError as err:
|
|
443
68
|
click.secho(
|
|
444
|
-
f"""
|
|
69
|
+
f"""Addon type "{err.addon_type}" is not supported, we support: {", ".join(CONDUIT_ADDON_TYPES)}.""",
|
|
445
70
|
fg="red",
|
|
446
71
|
)
|
|
447
72
|
exit(1)
|
|
448
|
-
except
|
|
73
|
+
except AddonTypeMissingFromConfigError:
|
|
449
74
|
click.secho(
|
|
450
|
-
f"""
|
|
75
|
+
f"""The configuration for the addon {addon_name}, is missconfigured and missing the addon type.""",
|
|
451
76
|
fg="red",
|
|
452
77
|
)
|
|
453
78
|
exit(1)
|
|
@@ -102,7 +102,7 @@ def list(app, env):
|
|
|
102
102
|
params = dict(Path=path, Recursive=False, WithDecryption=True, MaxResults=10)
|
|
103
103
|
secrets = []
|
|
104
104
|
|
|
105
|
-
# TODO: refactor shared code with get_ssm_secret_names
|
|
105
|
+
# TODO: refactor shared code with get_ssm_secret_names - Check if this is still valid
|
|
106
106
|
while True:
|
|
107
107
|
response = client.get_parameters_by_path(**params)
|
|
108
108
|
|
dbt_platform_helper/constants.py
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
PLATFORM_CONFIG_FILE = "platform-config.yml"
|
|
2
2
|
PLATFORM_HELPER_VERSION_FILE = ".platform-helper-version"
|
|
3
|
-
CODEBASE_PIPELINES_KEY = "codebase_pipelines"
|
|
4
|
-
ENVIRONMENTS_KEY = "environments"
|
|
5
3
|
DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION = "5"
|
|
6
4
|
PLATFORM_HELPER_CACHE_FILE = ".platform-helper-config-cache.yml"
|
|
5
|
+
|
|
6
|
+
# Keys
|
|
7
|
+
CODEBASE_PIPELINES_KEY = "codebase_pipelines"
|
|
8
|
+
ENVIRONMENTS_KEY = "environments"
|
|
9
|
+
|
|
10
|
+
# Conduit
|
|
11
|
+
CONDUIT_ADDON_TYPES = [
|
|
12
|
+
"opensearch",
|
|
13
|
+
"postgres",
|
|
14
|
+
"redis",
|
|
15
|
+
]
|
|
16
|
+
CONDUIT_DOCKER_IMAGE_LOCATION = "public.ecr.aws/uktrade/tunnel"
|
|
@@ -195,7 +195,6 @@ class Codebase:
|
|
|
195
195
|
|
|
196
196
|
self.echo_fn("")
|
|
197
197
|
|
|
198
|
-
# TODO return empty list without exception
|
|
199
198
|
def __get_codebases(self, application, ssm_client):
|
|
200
199
|
parameters = ssm_client.get_parameters_by_path(
|
|
201
200
|
Path=f"/copilot/applications/{application.name}/codebases",
|
|
@@ -205,6 +204,7 @@ class Codebase:
|
|
|
205
204
|
codebases = [json.loads(p["Value"]) for p in parameters]
|
|
206
205
|
|
|
207
206
|
if not codebases:
|
|
207
|
+
# TODO Is this really an error? Or just no codebases so we could return an empty list?
|
|
208
208
|
raise NoCopilotCodebasesFoundError
|
|
209
209
|
return codebases
|
|
210
210
|
|