dbt-platform-helper 12.5.1__py3-none-any.whl → 13.0.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.
Potentially problematic release.
This version of dbt-platform-helper might be problematic. Click here for more details.
- dbt_platform_helper/COMMANDS.md +45 -42
- dbt_platform_helper/commands/codebase.py +7 -10
- dbt_platform_helper/commands/conduit.py +2 -2
- dbt_platform_helper/commands/config.py +1 -1
- dbt_platform_helper/commands/environment.py +32 -18
- dbt_platform_helper/commands/notify.py +5 -3
- dbt_platform_helper/commands/pipeline.py +17 -11
- dbt_platform_helper/constants.py +3 -1
- dbt_platform_helper/domain/codebase.py +48 -36
- dbt_platform_helper/domain/conduit.py +10 -12
- dbt_platform_helper/domain/config_validator.py +42 -31
- dbt_platform_helper/domain/copilot_environment.py +133 -129
- dbt_platform_helper/domain/database_copy.py +38 -37
- dbt_platform_helper/domain/maintenance_page.py +243 -193
- dbt_platform_helper/domain/pipelines.py +60 -135
- dbt_platform_helper/domain/terraform_environment.py +7 -3
- dbt_platform_helper/providers/aws.py +5 -0
- dbt_platform_helper/providers/cloudformation.py +12 -1
- dbt_platform_helper/providers/config.py +12 -14
- dbt_platform_helper/providers/ecr.py +20 -0
- dbt_platform_helper/providers/files.py +1 -1
- dbt_platform_helper/providers/io.py +31 -0
- dbt_platform_helper/providers/load_balancers.py +29 -3
- dbt_platform_helper/providers/platform_config_schema.py +24 -22
- dbt_platform_helper/providers/terraform_manifest.py +120 -0
- dbt_platform_helper/providers/vpc.py +81 -32
- dbt_platform_helper/templates/COMMANDS.md.jinja +5 -3
- dbt_platform_helper/templates/environment-pipelines/main.tf +2 -2
- dbt_platform_helper/templates/environments/main.tf +3 -4
- dbt_platform_helper/utils/aws.py +16 -5
- dbt_platform_helper/utils/messages.py +2 -3
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/METADATA +2 -2
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/RECORD +36 -44
- dbt_platform_helper/templates/pipelines/codebase/manifest.yml +0 -56
- dbt_platform_helper/templates/pipelines/codebase/overrides/.gitignore +0 -12
- dbt_platform_helper/templates/pipelines/codebase/overrides/bin/override.ts +0 -8
- dbt_platform_helper/templates/pipelines/codebase/overrides/buildspec.deploy.yml +0 -29
- dbt_platform_helper/templates/pipelines/codebase/overrides/buildspec.image.yml +0 -48
- dbt_platform_helper/templates/pipelines/codebase/overrides/cdk.json +0 -20
- dbt_platform_helper/templates/pipelines/codebase/overrides/package-lock.json +0 -4232
- dbt_platform_helper/templates/pipelines/codebase/overrides/package.json +0 -27
- dbt_platform_helper/templates/pipelines/codebase/overrides/stack.ts +0 -521
- dbt_platform_helper/templates/pipelines/codebase/overrides/tsconfig.json +0 -30
- dbt_platform_helper/templates/pipelines/codebase/overrides/types.ts +0 -52
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/LICENSE +0 -0
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/WHEEL +0 -0
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -2,6 +2,7 @@ import itertools
|
|
|
2
2
|
import random
|
|
3
3
|
import re
|
|
4
4
|
import string
|
|
5
|
+
import traceback
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Callable
|
|
7
8
|
from typing import List
|
|
@@ -11,14 +12,16 @@ import boto3
|
|
|
11
12
|
import click
|
|
12
13
|
|
|
13
14
|
from dbt_platform_helper.platform_exception import PlatformException
|
|
15
|
+
from dbt_platform_helper.providers.io import ClickIOProvider
|
|
14
16
|
from dbt_platform_helper.providers.load_balancers import ListenerNotFoundException
|
|
15
17
|
from dbt_platform_helper.providers.load_balancers import ListenerRuleNotFoundException
|
|
16
18
|
from dbt_platform_helper.providers.load_balancers import LoadBalancerNotFoundException
|
|
17
|
-
from dbt_platform_helper.providers.load_balancers import
|
|
19
|
+
from dbt_platform_helper.providers.load_balancers import (
|
|
20
|
+
get_https_listener_for_application,
|
|
21
|
+
)
|
|
18
22
|
from dbt_platform_helper.utils.application import Application
|
|
19
23
|
from dbt_platform_helper.utils.application import Environment
|
|
20
24
|
from dbt_platform_helper.utils.application import Service
|
|
21
|
-
from dbt_platform_helper.utils.application import load_application
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
class MaintenancePageException(PlatformException):
|
|
@@ -26,134 +29,308 @@ class MaintenancePageException(PlatformException):
|
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class LoadBalancedWebServiceNotFoundException(MaintenancePageException):
|
|
29
|
-
|
|
32
|
+
def __init__(self, application_name: str):
|
|
33
|
+
super().__init__(f"No services deployed yet to {application_name} ")
|
|
30
34
|
|
|
31
35
|
|
|
32
|
-
class
|
|
36
|
+
class FailedToActivateMaintenancePageException(MaintenancePageException):
|
|
33
37
|
def __init__(
|
|
34
38
|
self,
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
application_name: str,
|
|
40
|
+
env: str,
|
|
41
|
+
original_exception: Exception,
|
|
42
|
+
rolled_back_rules: dict[str, bool] = {},
|
|
37
43
|
):
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
super().__init__(
|
|
45
|
+
f"Maintenance page failed to activate for the {application_name} application in environment {env}."
|
|
46
|
+
)
|
|
47
|
+
self.orginal_exception = original_exception
|
|
48
|
+
self.rolled_back_rules = rolled_back_rules
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
return (
|
|
52
|
+
f"{super().__str__()}\n"
|
|
53
|
+
f"Rolled-back rules: {self.rolled_back_rules }\n"
|
|
54
|
+
f"Original exception: {self.orginal_exception}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_maintenance_page_type(session: boto3.Session, listener_arn: str) -> Union[str, None]:
|
|
59
|
+
lb_client = session.client("elbv2")
|
|
60
|
+
|
|
61
|
+
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
62
|
+
tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
maintenance_page_type = None
|
|
65
|
+
for description in tag_descriptions:
|
|
66
|
+
tags = {t["Key"]: t["Value"] for t in description["Tags"]}
|
|
67
|
+
if tags.get("name") == "MaintenancePage":
|
|
68
|
+
maintenance_page_type = tags.get("type")
|
|
69
|
+
|
|
70
|
+
return maintenance_page_type
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_env_ips(vpc: str, application_environment: Environment) -> List[str]:
|
|
74
|
+
account_name = f"{application_environment.session.profile_name}-vpc"
|
|
75
|
+
vpc_name = vpc if vpc else account_name
|
|
76
|
+
ssm_client = application_environment.session.client("ssm")
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
param_value = ssm_client.get_parameter(Name=f"/{vpc_name}/EGRESS_IPS")["Parameter"]["Value"]
|
|
80
|
+
except ssm_client.exceptions.ParameterNotFound:
|
|
81
|
+
click.secho(f"No parameter found with name: /{vpc_name}/EGRESS_IPS")
|
|
82
|
+
raise click.Abort
|
|
83
|
+
|
|
84
|
+
return [ip.strip() for ip in param_value.split(",")]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def add_maintenance_page(
|
|
88
|
+
session: boto3.Session,
|
|
89
|
+
listener_arn: str,
|
|
90
|
+
app: str,
|
|
91
|
+
env: str,
|
|
92
|
+
services: List[Service],
|
|
93
|
+
allowed_ips: tuple,
|
|
94
|
+
template: str = "default",
|
|
95
|
+
):
|
|
96
|
+
lb_client = session.client("elbv2")
|
|
97
|
+
maintenance_page_content = get_maintenance_page_template(template)
|
|
98
|
+
bypass_value = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
|
99
|
+
|
|
100
|
+
rule_priority = itertools.count(start=1)
|
|
101
|
+
try:
|
|
102
|
+
for svc in services:
|
|
103
|
+
target_group_arn = find_target_group(app, env, svc.name, session)
|
|
104
|
+
|
|
105
|
+
# not all of an application's services are guaranteed to have been deployed to an environment
|
|
106
|
+
if not target_group_arn:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
for ip in allowed_ips:
|
|
110
|
+
create_header_rule(
|
|
111
|
+
lb_client,
|
|
112
|
+
listener_arn,
|
|
113
|
+
target_group_arn,
|
|
114
|
+
"X-Forwarded-For",
|
|
115
|
+
[ip],
|
|
116
|
+
"AllowedIps",
|
|
117
|
+
next(rule_priority),
|
|
118
|
+
)
|
|
119
|
+
create_source_ip_rule(
|
|
120
|
+
lb_client,
|
|
121
|
+
listener_arn,
|
|
122
|
+
target_group_arn,
|
|
123
|
+
[ip],
|
|
124
|
+
"AllowedSourceIps",
|
|
125
|
+
next(rule_priority),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
create_header_rule(
|
|
129
|
+
lb_client,
|
|
130
|
+
listener_arn,
|
|
131
|
+
target_group_arn,
|
|
132
|
+
"Bypass-Key",
|
|
133
|
+
[bypass_value],
|
|
134
|
+
"BypassIpFilter",
|
|
135
|
+
next(rule_priority),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
click.secho(
|
|
139
|
+
f"\nUse a browser plugin to add `Bypass-Key` header with value {bypass_value} to your requests. For more detail, visit https://platform.readme.trade.gov.uk/next-steps/put-a-service-under-maintenance/",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
lb_client.create_rule(
|
|
143
|
+
ListenerArn=listener_arn,
|
|
144
|
+
Priority=next(rule_priority),
|
|
145
|
+
Conditions=[
|
|
146
|
+
{
|
|
147
|
+
"Field": "path-pattern",
|
|
148
|
+
"PathPatternConfig": {"Values": ["/*"]},
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
Actions=[
|
|
152
|
+
{
|
|
153
|
+
"Type": "fixed-response",
|
|
154
|
+
"FixedResponseConfig": {
|
|
155
|
+
"StatusCode": "503",
|
|
156
|
+
"ContentType": "text/html",
|
|
157
|
+
"MessageBody": maintenance_page_content,
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
Tags=[
|
|
162
|
+
{"Key": "name", "Value": "MaintenancePage"},
|
|
163
|
+
{"Key": "type", "Value": template},
|
|
164
|
+
],
|
|
165
|
+
)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
deleted_rules = clean_up_maintenance_page_rules(session, listener_arn)
|
|
168
|
+
raise FailedToActivateMaintenancePageException(
|
|
169
|
+
app, env, f"{e}:\n {traceback.format_exc()}", deleted_rules
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def clean_up_maintenance_page_rules(
|
|
174
|
+
session: boto3.Session, listener_arn: str, fail_when_not_deleted: bool = False
|
|
175
|
+
) -> dict[str, bool]:
|
|
176
|
+
lb_client = session.client("elbv2")
|
|
177
|
+
|
|
178
|
+
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
179
|
+
tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
|
|
180
|
+
|
|
181
|
+
deletes = {}
|
|
182
|
+
for name in ["MaintenancePage", "AllowedIps", "BypassIpFilter", "AllowedSourceIps"]:
|
|
183
|
+
deleted = delete_listener_rule(tag_descriptions, name, lb_client)
|
|
184
|
+
deletes[name] = bool(deleted)
|
|
185
|
+
if fail_when_not_deleted and name == "MaintenancePage" and not deleted:
|
|
186
|
+
raise ListenerRuleNotFoundException()
|
|
187
|
+
|
|
188
|
+
return deletes
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def remove_maintenance_page(session: boto3.Session, listener_arn: str):
|
|
192
|
+
clean_up_maintenance_page_rules(session, listener_arn, True)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class MaintenancePage:
|
|
196
|
+
def __init__(
|
|
197
|
+
self,
|
|
198
|
+
application: Application,
|
|
199
|
+
io: ClickIOProvider = ClickIOProvider(),
|
|
200
|
+
get_https_listener_for_application: Callable[
|
|
201
|
+
[boto3.Session, str, str], str
|
|
202
|
+
] = get_https_listener_for_application,
|
|
203
|
+
# TODO refactor get_maintenance_page_type, add_maintenance_page, remove_maintenance_page into MaintenancePage class with LoadBalancerProvider as the dependency
|
|
204
|
+
get_maintenance_page_type: Callable[
|
|
205
|
+
[boto3.Session, str], Union[str, None]
|
|
206
|
+
] = get_maintenance_page_type,
|
|
207
|
+
get_env_ips: Callable[[str, Environment], List[str]] = get_env_ips,
|
|
208
|
+
add_maintenance_page: Callable[
|
|
209
|
+
[boto3.Session, str, str, str, List[Service], tuple, str], None
|
|
210
|
+
] = add_maintenance_page,
|
|
211
|
+
remove_maintenance_page: Callable[[boto3.Session, str], None] = remove_maintenance_page,
|
|
212
|
+
):
|
|
213
|
+
self.application = application
|
|
214
|
+
self.get_https_listener_for_application = get_https_listener_for_application
|
|
215
|
+
self.io = io
|
|
216
|
+
self.get_maintenance_page_type = get_maintenance_page_type
|
|
217
|
+
self.get_env_ips = get_env_ips
|
|
218
|
+
self.add_maintenance_page = add_maintenance_page
|
|
219
|
+
self.remove_maintenance_page = remove_maintenance_page
|
|
220
|
+
|
|
221
|
+
def _get_deployed_load_balanced_web_services(self, app: Application, svc: List[str]):
|
|
42
222
|
if "*" in svc:
|
|
43
223
|
services = [s for s in app.services.values() if s.kind == "Load Balanced Web Service"]
|
|
44
224
|
else:
|
|
45
|
-
all_services = [get_app_service(app
|
|
225
|
+
all_services = [get_app_service(app, s) for s in list(svc)]
|
|
46
226
|
services = [s for s in all_services if s.kind == "Load Balanced Web Service"]
|
|
47
227
|
if not services:
|
|
48
|
-
raise LoadBalancedWebServiceNotFoundException
|
|
228
|
+
raise LoadBalancedWebServiceNotFoundException(app.name)
|
|
49
229
|
return services
|
|
50
230
|
|
|
51
|
-
def activate(self,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# message
|
|
57
|
-
# Exception should be propagated to command and caught there.
|
|
58
|
-
self.echo(f"No services deployed yet to {app} environment {env}", fg="red")
|
|
59
|
-
raise click.Abort
|
|
60
|
-
|
|
61
|
-
application_environment = get_app_environment(app, env)
|
|
231
|
+
def activate(self, env: str, services: List[str], template: str, vpc: Union[str, None]):
|
|
232
|
+
|
|
233
|
+
services = self._get_deployed_load_balanced_web_services(self.application, services)
|
|
234
|
+
application_environment = get_app_environment(self.application, env)
|
|
235
|
+
|
|
62
236
|
try:
|
|
63
|
-
https_listener =
|
|
64
|
-
|
|
237
|
+
https_listener = self.get_https_listener_for_application(
|
|
238
|
+
application_environment.session, self.application.name, env
|
|
239
|
+
)
|
|
240
|
+
current_maintenance_page = self.get_maintenance_page_type(
|
|
65
241
|
application_environment.session, https_listener
|
|
66
242
|
)
|
|
67
243
|
remove_current_maintenance_page = False
|
|
68
244
|
if current_maintenance_page:
|
|
69
|
-
remove_current_maintenance_page = self.
|
|
245
|
+
remove_current_maintenance_page = self.io.confirm(
|
|
70
246
|
f"There is currently a '{current_maintenance_page}' maintenance page for the {env} "
|
|
71
|
-
f"environment in {
|
|
247
|
+
f"environment in {self.application.name}.\nWould you like to replace it with a '{template}' "
|
|
72
248
|
f"maintenance page?"
|
|
73
249
|
)
|
|
74
250
|
if not remove_current_maintenance_page:
|
|
75
|
-
|
|
251
|
+
return
|
|
76
252
|
|
|
77
|
-
if remove_current_maintenance_page or self.
|
|
253
|
+
if remove_current_maintenance_page or self.io.confirm(
|
|
78
254
|
f"You are about to enable the '{template}' maintenance page for the {env} "
|
|
79
|
-
f"environment in {
|
|
255
|
+
f"environment in {self.application.name}.\nWould you like to continue?"
|
|
80
256
|
):
|
|
81
257
|
if current_maintenance_page and remove_current_maintenance_page:
|
|
82
|
-
remove_maintenance_page(application_environment.session, https_listener)
|
|
258
|
+
self.remove_maintenance_page(application_environment.session, https_listener)
|
|
83
259
|
|
|
84
|
-
allowed_ips = get_env_ips(vpc, application_environment)
|
|
260
|
+
allowed_ips = self.get_env_ips(vpc, application_environment)
|
|
85
261
|
|
|
86
|
-
add_maintenance_page(
|
|
262
|
+
self.add_maintenance_page(
|
|
87
263
|
application_environment.session,
|
|
88
264
|
https_listener,
|
|
89
|
-
|
|
265
|
+
self.application.name,
|
|
90
266
|
env,
|
|
91
267
|
services,
|
|
92
268
|
allowed_ips,
|
|
93
269
|
template,
|
|
94
270
|
)
|
|
95
|
-
self.
|
|
96
|
-
f"Maintenance page '{template}' added for environment {env} in application {
|
|
97
|
-
fg="green",
|
|
271
|
+
self.io.info(
|
|
272
|
+
f"Maintenance page '{template}' added for environment {env} in application {self.application.name}",
|
|
98
273
|
)
|
|
99
|
-
else:
|
|
100
|
-
raise click.Abort
|
|
101
274
|
|
|
102
275
|
except LoadBalancerNotFoundException:
|
|
103
|
-
|
|
104
|
-
|
|
276
|
+
# TODO push exception to command layer
|
|
277
|
+
self.io.abort_with_error(
|
|
278
|
+
f"No load balancer found for environment {env} in the application {self.application.name}.",
|
|
105
279
|
)
|
|
106
|
-
raise click.Abort
|
|
107
280
|
|
|
108
281
|
except ListenerNotFoundException:
|
|
109
|
-
|
|
110
|
-
|
|
282
|
+
# TODO push exception to command layer
|
|
283
|
+
self.io.abort_with_error(
|
|
284
|
+
f"No HTTPS listener found for environment {env} in the application {self.application.name}.",
|
|
111
285
|
)
|
|
112
|
-
raise click.Abort
|
|
113
286
|
|
|
114
|
-
def deactivate(self,
|
|
115
|
-
application_environment = get_app_environment(
|
|
287
|
+
def deactivate(self, env: str):
|
|
288
|
+
application_environment = get_app_environment(self.application, env)
|
|
116
289
|
|
|
117
290
|
try:
|
|
118
|
-
https_listener =
|
|
119
|
-
|
|
291
|
+
https_listener = self.get_https_listener_for_application(
|
|
292
|
+
application_environment.session, self.application.name, env
|
|
293
|
+
)
|
|
294
|
+
current_maintenance_page = self.get_maintenance_page_type(
|
|
120
295
|
application_environment.session, https_listener
|
|
121
296
|
)
|
|
297
|
+
|
|
298
|
+
# TODO discuss, reduce number of return statements but more nested if statements
|
|
122
299
|
if not current_maintenance_page:
|
|
123
|
-
self.
|
|
124
|
-
|
|
300
|
+
self.io.warn("There is no current maintenance page to remove")
|
|
301
|
+
return
|
|
125
302
|
|
|
126
|
-
if not self.
|
|
303
|
+
if not self.io.confirm(
|
|
127
304
|
f"There is currently a '{current_maintenance_page}' maintenance page, "
|
|
128
305
|
f"would you like to remove it?"
|
|
129
306
|
):
|
|
130
|
-
|
|
307
|
+
return
|
|
131
308
|
|
|
132
|
-
remove_maintenance_page(application_environment.session, https_listener)
|
|
133
|
-
self.
|
|
134
|
-
f"Maintenance page removed from environment {env} in application {
|
|
309
|
+
self.remove_maintenance_page(application_environment.session, https_listener)
|
|
310
|
+
self.io.info(
|
|
311
|
+
f"Maintenance page removed from environment {env} in application {self.application.name}",
|
|
135
312
|
)
|
|
136
313
|
|
|
137
314
|
except LoadBalancerNotFoundException:
|
|
138
|
-
|
|
139
|
-
|
|
315
|
+
# TODO push exception to command layer
|
|
316
|
+
self.io.abort_with_error(
|
|
317
|
+
f"No load balancer found for environment {env} in the application {self.application.name}.",
|
|
140
318
|
)
|
|
141
|
-
raise click.Abort
|
|
142
319
|
|
|
143
320
|
except ListenerNotFoundException:
|
|
144
|
-
|
|
145
|
-
|
|
321
|
+
# TODO push exception to command layer
|
|
322
|
+
self.io.abort_with_error(
|
|
323
|
+
f"No HTTPS listener found for environment {env} in the application {self.application.name}.",
|
|
146
324
|
)
|
|
147
|
-
raise click.Abort
|
|
148
325
|
|
|
149
326
|
|
|
150
|
-
def get_app_service(
|
|
151
|
-
application = load_application(app_name)
|
|
327
|
+
def get_app_service(application: Application, svc_name: str) -> Service:
|
|
152
328
|
application_service = application.services.get(svc_name)
|
|
153
329
|
|
|
154
330
|
if not application_service:
|
|
331
|
+
# TODO raise exception instead of abort
|
|
155
332
|
click.secho(
|
|
156
|
-
f"The service {svc_name} was not found in the application {
|
|
333
|
+
f"The service {svc_name} was not found in the application {application.name}. "
|
|
157
334
|
f"It either does not exist, or has not been deployed.",
|
|
158
335
|
fg="red",
|
|
159
336
|
)
|
|
@@ -162,13 +339,12 @@ def get_app_service(app_name: str, svc_name: str) -> Service:
|
|
|
162
339
|
return application_service
|
|
163
340
|
|
|
164
341
|
|
|
165
|
-
def get_app_environment(
|
|
166
|
-
application = load_application(app_name)
|
|
342
|
+
def get_app_environment(application: Application, env_name: str) -> Environment:
|
|
167
343
|
application_environment = application.environments.get(env_name)
|
|
168
344
|
|
|
169
345
|
if not application_environment:
|
|
170
346
|
click.secho(
|
|
171
|
-
f"The environment {env_name} was not found in the application {
|
|
347
|
+
f"The environment {env_name} was not found in the application {application.name}. "
|
|
172
348
|
f"It either does not exist, or has not been deployed.",
|
|
173
349
|
fg="red",
|
|
174
350
|
)
|
|
@@ -177,36 +353,6 @@ def get_app_environment(app_name: str, env_name: str) -> Environment:
|
|
|
177
353
|
return application_environment
|
|
178
354
|
|
|
179
355
|
|
|
180
|
-
def get_maintenance_page(session: boto3.Session, listener_arn: str) -> Union[str, None]:
|
|
181
|
-
lb_client = session.client("elbv2")
|
|
182
|
-
|
|
183
|
-
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
184
|
-
tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
|
|
185
|
-
|
|
186
|
-
maintenance_page_type = None
|
|
187
|
-
for description in tag_descriptions:
|
|
188
|
-
tags = {t["Key"]: t["Value"] for t in description["Tags"]}
|
|
189
|
-
if tags.get("name") == "MaintenancePage":
|
|
190
|
-
maintenance_page_type = tags.get("type")
|
|
191
|
-
|
|
192
|
-
return maintenance_page_type
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def remove_maintenance_page(session: boto3.Session, listener_arn: str):
|
|
196
|
-
lb_client = session.client("elbv2")
|
|
197
|
-
|
|
198
|
-
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
199
|
-
tag_descriptions = lb_client.describe_tags(ResourceArns=[r["RuleArn"] for r in rules])[
|
|
200
|
-
"TagDescriptions"
|
|
201
|
-
]
|
|
202
|
-
|
|
203
|
-
for name in ["MaintenancePage", "AllowedIps", "BypassIpFilter", "AllowedSourceIps"]:
|
|
204
|
-
deleted = delete_listener_rule(tag_descriptions, name, lb_client)
|
|
205
|
-
|
|
206
|
-
if name == "MaintenancePage" and not deleted:
|
|
207
|
-
raise ListenerRuleNotFoundException()
|
|
208
|
-
|
|
209
|
-
|
|
210
356
|
def get_rules_tag_descriptions(rules: list, lb_client):
|
|
211
357
|
tag_descriptions = []
|
|
212
358
|
chunk_size = 20
|
|
@@ -233,88 +379,6 @@ def delete_listener_rule(tag_descriptions: list, tag_name: str, lb_client: boto3
|
|
|
233
379
|
return current_rule_arn
|
|
234
380
|
|
|
235
381
|
|
|
236
|
-
def add_maintenance_page(
|
|
237
|
-
session: boto3.Session,
|
|
238
|
-
listener_arn: str,
|
|
239
|
-
app: str,
|
|
240
|
-
env: str,
|
|
241
|
-
services: List[Service],
|
|
242
|
-
allowed_ips: tuple,
|
|
243
|
-
template: str = "default",
|
|
244
|
-
):
|
|
245
|
-
lb_client = session.client("elbv2")
|
|
246
|
-
maintenance_page_content = get_maintenance_page_template(template)
|
|
247
|
-
bypass_value = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
|
248
|
-
|
|
249
|
-
rule_priority = itertools.count(start=1)
|
|
250
|
-
|
|
251
|
-
for svc in services:
|
|
252
|
-
target_group_arn = find_target_group(app, env, svc.name, session)
|
|
253
|
-
|
|
254
|
-
# not all of an application's services are guaranteed to have been deployed to an environment
|
|
255
|
-
if not target_group_arn:
|
|
256
|
-
continue
|
|
257
|
-
|
|
258
|
-
for ip in allowed_ips:
|
|
259
|
-
create_header_rule(
|
|
260
|
-
lb_client,
|
|
261
|
-
listener_arn,
|
|
262
|
-
target_group_arn,
|
|
263
|
-
"X-Forwarded-For",
|
|
264
|
-
[ip],
|
|
265
|
-
"AllowedIps",
|
|
266
|
-
next(rule_priority),
|
|
267
|
-
)
|
|
268
|
-
create_source_ip_rule(
|
|
269
|
-
lb_client,
|
|
270
|
-
listener_arn,
|
|
271
|
-
target_group_arn,
|
|
272
|
-
[ip],
|
|
273
|
-
"AllowedSourceIps",
|
|
274
|
-
next(rule_priority),
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
create_header_rule(
|
|
278
|
-
lb_client,
|
|
279
|
-
listener_arn,
|
|
280
|
-
target_group_arn,
|
|
281
|
-
"Bypass-Key",
|
|
282
|
-
[bypass_value],
|
|
283
|
-
"BypassIpFilter",
|
|
284
|
-
next(rule_priority),
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
click.secho(
|
|
288
|
-
f"\nUse a browser plugin to add `Bypass-Key` header with value {bypass_value} to your requests. For more detail, visit https://platform.readme.trade.gov.uk/next-steps/put-a-service-under-maintenance/",
|
|
289
|
-
fg="green",
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
lb_client.create_rule(
|
|
293
|
-
ListenerArn=listener_arn,
|
|
294
|
-
Priority=next(rule_priority),
|
|
295
|
-
Conditions=[
|
|
296
|
-
{
|
|
297
|
-
"Field": "path-pattern",
|
|
298
|
-
"PathPatternConfig": {"Values": ["/*"]},
|
|
299
|
-
}
|
|
300
|
-
],
|
|
301
|
-
Actions=[
|
|
302
|
-
{
|
|
303
|
-
"Type": "fixed-response",
|
|
304
|
-
"FixedResponseConfig": {
|
|
305
|
-
"StatusCode": "503",
|
|
306
|
-
"ContentType": "text/html",
|
|
307
|
-
"MessageBody": maintenance_page_content,
|
|
308
|
-
},
|
|
309
|
-
}
|
|
310
|
-
],
|
|
311
|
-
Tags=[
|
|
312
|
-
{"Key": "name", "Value": "MaintenancePage"},
|
|
313
|
-
{"Key": "type", "Value": template},
|
|
314
|
-
],
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
|
|
318
382
|
def get_maintenance_page_template(template) -> str:
|
|
319
383
|
template_contents = (
|
|
320
384
|
Path(__file__)
|
|
@@ -472,17 +536,3 @@ def get_host_conditions(lb_client: boto3.client, listener_arn: str, target_group
|
|
|
472
536
|
]
|
|
473
537
|
|
|
474
538
|
return conditions
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
def get_env_ips(vpc: str, application_environment: Environment) -> List[str]:
|
|
478
|
-
account_name = f"{application_environment.session.profile_name}-vpc"
|
|
479
|
-
vpc_name = vpc if vpc else account_name
|
|
480
|
-
ssm_client = application_environment.session.client("ssm")
|
|
481
|
-
|
|
482
|
-
try:
|
|
483
|
-
param_value = ssm_client.get_parameter(Name=f"/{vpc_name}/EGRESS_IPS")["Parameter"]["Value"]
|
|
484
|
-
except ssm_client.exceptions.ParameterNotFound:
|
|
485
|
-
click.secho(f"No parameter found with name: /{vpc_name}/EGRESS_IPS")
|
|
486
|
-
raise click.Abort
|
|
487
|
-
|
|
488
|
-
return [ip.strip() for ip in param_value.split(",")]
|