dbt-platform-helper 11.0.1__py3-none-any.whl → 11.2.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 +83 -20
- dbt_platform_helper/commands/database.py +97 -18
- dbt_platform_helper/commands/environment.py +6 -518
- dbt_platform_helper/commands/pipeline.py +103 -12
- dbt_platform_helper/domain/__init__.py +0 -0
- dbt_platform_helper/domain/database_copy.py +220 -0
- dbt_platform_helper/domain/maintenance_page.py +459 -0
- dbt_platform_helper/providers/load_balancers.py +51 -0
- dbt_platform_helper/templates/environment-pipelines/main.tf +52 -0
- dbt_platform_helper/utils/aws.py +47 -37
- dbt_platform_helper/utils/validation.py +29 -0
- {dbt_platform_helper-11.0.1.dist-info → dbt_platform_helper-11.2.0.dist-info}/METADATA +2 -1
- {dbt_platform_helper-11.0.1.dist-info → dbt_platform_helper-11.2.0.dist-info}/RECORD +16 -12
- dbt_platform_helper/commands/database_helpers.py +0 -145
- {dbt_platform_helper-11.0.1.dist-info → dbt_platform_helper-11.2.0.dist-info}/LICENSE +0 -0
- {dbt_platform_helper-11.0.1.dist-info → dbt_platform_helper-11.2.0.dist-info}/WHEEL +0 -0
- {dbt_platform_helper-11.0.1.dist-info → dbt_platform_helper-11.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import random
|
|
2
|
-
import re
|
|
3
|
-
import string
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import List
|
|
6
|
-
from typing import Union
|
|
7
|
-
|
|
8
1
|
import boto3
|
|
9
2
|
import click
|
|
10
3
|
from schema import SchemaError
|
|
11
4
|
|
|
12
5
|
from dbt_platform_helper.constants import DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION
|
|
13
6
|
from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE
|
|
14
|
-
from dbt_platform_helper.
|
|
15
|
-
from dbt_platform_helper.
|
|
16
|
-
from dbt_platform_helper.utils.application import load_application
|
|
7
|
+
from dbt_platform_helper.domain.maintenance_page import MaintenancePageProvider
|
|
8
|
+
from dbt_platform_helper.providers.load_balancers import find_https_listener
|
|
17
9
|
from dbt_platform_helper.utils.aws import get_aws_session_or_abort
|
|
18
10
|
from dbt_platform_helper.utils.click import ClickDocOptGroup
|
|
19
11
|
from dbt_platform_helper.utils.files import apply_environment_defaults
|
|
@@ -47,72 +39,8 @@ def environment():
|
|
|
47
39
|
@click.option("--vpc", type=str)
|
|
48
40
|
def offline(app, env, svc, template, vpc):
|
|
49
41
|
"""Take load-balanced web services offline with a maintenance page."""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if "*" in svc:
|
|
54
|
-
services = [
|
|
55
|
-
s for s in application.services.values() if s.kind == "Load Balanced Web Service"
|
|
56
|
-
]
|
|
57
|
-
else:
|
|
58
|
-
all_services = [get_app_service(app, s) for s in list(svc)]
|
|
59
|
-
services = [s for s in all_services if s.kind == "Load Balanced Web Service"]
|
|
60
|
-
|
|
61
|
-
if not services:
|
|
62
|
-
click.secho(f"No services deployed yet to {app} environment {env}", fg="red")
|
|
63
|
-
raise click.Abort
|
|
64
|
-
|
|
65
|
-
try:
|
|
66
|
-
https_listener = find_https_listener(application_environment.session, app, env)
|
|
67
|
-
current_maintenance_page = get_maintenance_page(
|
|
68
|
-
application_environment.session, https_listener
|
|
69
|
-
)
|
|
70
|
-
remove_current_maintenance_page = False
|
|
71
|
-
if current_maintenance_page:
|
|
72
|
-
remove_current_maintenance_page = click.confirm(
|
|
73
|
-
f"There is currently a '{current_maintenance_page}' maintenance page for the {env} "
|
|
74
|
-
f"environment in {app}.\nWould you like to replace it with a '{template}' "
|
|
75
|
-
f"maintenance page?"
|
|
76
|
-
)
|
|
77
|
-
if not remove_current_maintenance_page:
|
|
78
|
-
raise click.Abort
|
|
79
|
-
|
|
80
|
-
if remove_current_maintenance_page or click.confirm(
|
|
81
|
-
f"You are about to enable the '{template}' maintenance page for the {env} "
|
|
82
|
-
f"environment in {app}.\nWould you like to continue?"
|
|
83
|
-
):
|
|
84
|
-
if current_maintenance_page and remove_current_maintenance_page:
|
|
85
|
-
remove_maintenance_page(application_environment.session, https_listener)
|
|
86
|
-
|
|
87
|
-
allowed_ips = get_env_ips(vpc, application_environment)
|
|
88
|
-
|
|
89
|
-
add_maintenance_page(
|
|
90
|
-
application_environment.session,
|
|
91
|
-
https_listener,
|
|
92
|
-
app,
|
|
93
|
-
env,
|
|
94
|
-
services,
|
|
95
|
-
allowed_ips,
|
|
96
|
-
template,
|
|
97
|
-
)
|
|
98
|
-
click.secho(
|
|
99
|
-
f"Maintenance page '{template}' added for environment {env} in application {app}",
|
|
100
|
-
fg="green",
|
|
101
|
-
)
|
|
102
|
-
else:
|
|
103
|
-
raise click.Abort
|
|
104
|
-
|
|
105
|
-
except LoadBalancerNotFoundError:
|
|
106
|
-
click.secho(
|
|
107
|
-
f"No load balancer found for environment {env} in the application {app}.", fg="red"
|
|
108
|
-
)
|
|
109
|
-
raise click.Abort
|
|
110
|
-
|
|
111
|
-
except ListenerNotFoundError:
|
|
112
|
-
click.secho(
|
|
113
|
-
f"No HTTPS listener found for environment {env} in the application {app}.", fg="red"
|
|
114
|
-
)
|
|
115
|
-
raise click.Abort
|
|
42
|
+
maintenance_page = MaintenancePageProvider()
|
|
43
|
+
maintenance_page.activate(app, env, svc, template, vpc)
|
|
116
44
|
|
|
117
45
|
|
|
118
46
|
@environment.command()
|
|
@@ -120,85 +48,8 @@ def offline(app, env, svc, template, vpc):
|
|
|
120
48
|
@click.option("--env", type=str, required=True)
|
|
121
49
|
def online(app, env):
|
|
122
50
|
"""Remove a maintenance page from an environment."""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
try:
|
|
126
|
-
https_listener = find_https_listener(application_environment.session, app, env)
|
|
127
|
-
current_maintenance_page = get_maintenance_page(
|
|
128
|
-
application_environment.session, https_listener
|
|
129
|
-
)
|
|
130
|
-
if not current_maintenance_page:
|
|
131
|
-
click.secho("There is no current maintenance page to remove", fg="red")
|
|
132
|
-
raise click.Abort
|
|
133
|
-
|
|
134
|
-
if not click.confirm(
|
|
135
|
-
f"There is currently a '{current_maintenance_page}' maintenance page, "
|
|
136
|
-
f"would you like to remove it?"
|
|
137
|
-
):
|
|
138
|
-
raise click.Abort
|
|
139
|
-
|
|
140
|
-
remove_maintenance_page(application_environment.session, https_listener)
|
|
141
|
-
click.secho(
|
|
142
|
-
f"Maintenance page removed from environment {env} in application {app}", fg="green"
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
except LoadBalancerNotFoundError:
|
|
146
|
-
click.secho(
|
|
147
|
-
f"No load balancer found for environment {env} in the application {app}.", fg="red"
|
|
148
|
-
)
|
|
149
|
-
raise click.Abort
|
|
150
|
-
|
|
151
|
-
except ListenerNotFoundError:
|
|
152
|
-
click.secho(
|
|
153
|
-
f"No HTTPS listener found for environment {env} in the application {app}.", fg="red"
|
|
154
|
-
)
|
|
155
|
-
raise click.Abort
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def get_application(app_name: str):
|
|
159
|
-
return load_application(app_name)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def get_app_environment(app_name: str, env_name: str) -> Environment:
|
|
163
|
-
application = get_application(app_name)
|
|
164
|
-
application_environment = application.environments.get(env_name)
|
|
165
|
-
|
|
166
|
-
if not application_environment:
|
|
167
|
-
click.secho(
|
|
168
|
-
f"The environment {env_name} was not found in the application {app_name}. "
|
|
169
|
-
f"It either does not exist, or has not been deployed.",
|
|
170
|
-
fg="red",
|
|
171
|
-
)
|
|
172
|
-
raise click.Abort
|
|
173
|
-
|
|
174
|
-
return application_environment
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def get_app_service(app_name: str, svc_name: str) -> Service:
|
|
178
|
-
application = get_application(app_name)
|
|
179
|
-
application_service = application.services.get(svc_name)
|
|
180
|
-
|
|
181
|
-
if not application_service:
|
|
182
|
-
click.secho(
|
|
183
|
-
f"The service {svc_name} was not found in the application {app_name}. "
|
|
184
|
-
f"It either does not exist, or has not been deployed.",
|
|
185
|
-
fg="red",
|
|
186
|
-
)
|
|
187
|
-
raise click.Abort
|
|
188
|
-
|
|
189
|
-
return application_service
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def get_listener_rule_by_tag(elbv2_client, listener_arn, tag_key, tag_value):
|
|
193
|
-
response = elbv2_client.describe_rules(ListenerArn=listener_arn)
|
|
194
|
-
for rule in response["Rules"]:
|
|
195
|
-
rule_arn = rule["RuleArn"]
|
|
196
|
-
|
|
197
|
-
tags_response = elbv2_client.describe_tags(ResourceArns=[rule_arn])
|
|
198
|
-
for tag_description in tags_response["TagDescriptions"]:
|
|
199
|
-
for tag in tag_description["Tags"]:
|
|
200
|
-
if tag["Key"] == tag_key and tag["Value"] == tag_value:
|
|
201
|
-
return rule
|
|
51
|
+
maintenance_page = MaintenancePageProvider()
|
|
52
|
+
maintenance_page.deactivate(app, env)
|
|
202
53
|
|
|
203
54
|
|
|
204
55
|
def get_vpc_id(session, env_name, vpc_name=None):
|
|
@@ -250,20 +101,6 @@ def get_cert_arn(session, application, env_name):
|
|
|
250
101
|
return arn
|
|
251
102
|
|
|
252
103
|
|
|
253
|
-
def get_env_ips(vpc: str, application_environment: Environment) -> List[str]:
|
|
254
|
-
account_name = f"{application_environment.session.profile_name}-vpc"
|
|
255
|
-
vpc_name = vpc if vpc else account_name
|
|
256
|
-
ssm_client = application_environment.session.client("ssm")
|
|
257
|
-
|
|
258
|
-
try:
|
|
259
|
-
param_value = ssm_client.get_parameter(Name=f"/{vpc_name}/EGRESS_IPS")["Parameter"]["Value"]
|
|
260
|
-
except ssm_client.exceptions.ParameterNotFound:
|
|
261
|
-
click.secho(f"No parameter found with name: /{vpc_name}/EGRESS_IPS")
|
|
262
|
-
raise click.Abort
|
|
263
|
-
|
|
264
|
-
return [ip.strip() for ip in param_value.split(",")]
|
|
265
|
-
|
|
266
|
-
|
|
267
104
|
@environment.command()
|
|
268
105
|
@click.option("--vpc-name", hidden=True)
|
|
269
106
|
@click.option("--name", "-n", required=True)
|
|
@@ -362,44 +199,6 @@ def _determine_terraform_platform_modules_version(env_conf, cli_terraform_platfo
|
|
|
362
199
|
return [version for version in version_preference_order if version][0]
|
|
363
200
|
|
|
364
201
|
|
|
365
|
-
def find_load_balancer(session: boto3.Session, app: str, env: str) -> str:
|
|
366
|
-
lb_client = session.client("elbv2")
|
|
367
|
-
|
|
368
|
-
describe_response = lb_client.describe_load_balancers()
|
|
369
|
-
load_balancers = [lb["LoadBalancerArn"] for lb in describe_response["LoadBalancers"]]
|
|
370
|
-
|
|
371
|
-
load_balancers = lb_client.describe_tags(ResourceArns=load_balancers)["TagDescriptions"]
|
|
372
|
-
|
|
373
|
-
load_balancer_arn = None
|
|
374
|
-
for lb in load_balancers:
|
|
375
|
-
tags = {t["Key"]: t["Value"] for t in lb["Tags"]}
|
|
376
|
-
if tags.get("copilot-application") == app and tags.get("copilot-environment") == env:
|
|
377
|
-
load_balancer_arn = lb["ResourceArn"]
|
|
378
|
-
|
|
379
|
-
if not load_balancer_arn:
|
|
380
|
-
raise LoadBalancerNotFoundError()
|
|
381
|
-
|
|
382
|
-
return load_balancer_arn
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
def find_https_listener(session: boto3.Session, app: str, env: str) -> str:
|
|
386
|
-
load_balancer_arn = find_load_balancer(session, app, env)
|
|
387
|
-
lb_client = session.client("elbv2")
|
|
388
|
-
listeners = lb_client.describe_listeners(LoadBalancerArn=load_balancer_arn)["Listeners"]
|
|
389
|
-
|
|
390
|
-
listener_arn = None
|
|
391
|
-
|
|
392
|
-
try:
|
|
393
|
-
listener_arn = next(l["ListenerArn"] for l in listeners if l["Protocol"] == "HTTPS")
|
|
394
|
-
except StopIteration:
|
|
395
|
-
pass
|
|
396
|
-
|
|
397
|
-
if not listener_arn:
|
|
398
|
-
raise ListenerNotFoundError()
|
|
399
|
-
|
|
400
|
-
return listener_arn
|
|
401
|
-
|
|
402
|
-
|
|
403
202
|
def find_https_certificate(session: boto3.Session, app: str, env: str) -> str:
|
|
404
203
|
listener_arn = find_https_listener(session, app, env)
|
|
405
204
|
cert_client = session.client("elbv2")
|
|
@@ -407,8 +206,6 @@ def find_https_certificate(session: boto3.Session, app: str, env: str) -> str:
|
|
|
407
206
|
"Certificates"
|
|
408
207
|
]
|
|
409
208
|
|
|
410
|
-
certificate_arn = None
|
|
411
|
-
|
|
412
209
|
try:
|
|
413
210
|
certificate_arn = next(c["CertificateArn"] for c in certificates if c["IsDefault"])
|
|
414
211
|
except StopIteration:
|
|
@@ -417,314 +214,5 @@ def find_https_certificate(session: boto3.Session, app: str, env: str) -> str:
|
|
|
417
214
|
return certificate_arn
|
|
418
215
|
|
|
419
216
|
|
|
420
|
-
def find_target_group(app: str, env: str, svc: str, session: boto3.Session) -> str:
|
|
421
|
-
rg_tagging_client = session.client("resourcegroupstaggingapi")
|
|
422
|
-
response = rg_tagging_client.get_resources(
|
|
423
|
-
TagFilters=[
|
|
424
|
-
{
|
|
425
|
-
"Key": "copilot-application",
|
|
426
|
-
"Values": [
|
|
427
|
-
app,
|
|
428
|
-
],
|
|
429
|
-
"Key": "copilot-environment",
|
|
430
|
-
"Values": [
|
|
431
|
-
env,
|
|
432
|
-
],
|
|
433
|
-
"Key": "copilot-service",
|
|
434
|
-
"Values": [
|
|
435
|
-
svc,
|
|
436
|
-
],
|
|
437
|
-
},
|
|
438
|
-
],
|
|
439
|
-
ResourceTypeFilters=[
|
|
440
|
-
"elasticloadbalancing:targetgroup",
|
|
441
|
-
],
|
|
442
|
-
)
|
|
443
|
-
for resource in response["ResourceTagMappingList"]:
|
|
444
|
-
tags = {tag["Key"]: tag["Value"] for tag in resource["Tags"]}
|
|
445
|
-
|
|
446
|
-
if (
|
|
447
|
-
"copilot-service" in tags
|
|
448
|
-
and tags["copilot-service"] == svc
|
|
449
|
-
and "copilot-environment" in tags
|
|
450
|
-
and tags["copilot-environment"] == env
|
|
451
|
-
and "copilot-application" in tags
|
|
452
|
-
and tags["copilot-application"] == app
|
|
453
|
-
):
|
|
454
|
-
return resource["ResourceARN"]
|
|
455
|
-
|
|
456
|
-
click.secho(
|
|
457
|
-
f"No target group found for application: {app}, environment: {env}, service: {svc}",
|
|
458
|
-
fg="red",
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
return None
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
def get_maintenance_page(session: boto3.Session, listener_arn: str) -> Union[str, None]:
|
|
465
|
-
lb_client = session.client("elbv2")
|
|
466
|
-
|
|
467
|
-
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
468
|
-
tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
|
|
469
|
-
|
|
470
|
-
maintenance_page_type = None
|
|
471
|
-
for description in tag_descriptions:
|
|
472
|
-
tags = {t["Key"]: t["Value"] for t in description["Tags"]}
|
|
473
|
-
if tags.get("name") == "MaintenancePage":
|
|
474
|
-
maintenance_page_type = tags.get("type")
|
|
475
|
-
|
|
476
|
-
return maintenance_page_type
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
def delete_listener_rule(tag_descriptions: list, tag_name: str, lb_client: boto3.client):
|
|
480
|
-
current_rule_arn = None
|
|
481
|
-
|
|
482
|
-
for description in tag_descriptions:
|
|
483
|
-
tags = {t["Key"]: t["Value"] for t in description["Tags"]}
|
|
484
|
-
if tags.get("name") == tag_name:
|
|
485
|
-
current_rule_arn = description["ResourceArn"]
|
|
486
|
-
if current_rule_arn:
|
|
487
|
-
lb_client.delete_rule(RuleArn=current_rule_arn)
|
|
488
|
-
|
|
489
|
-
return current_rule_arn
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
def remove_maintenance_page(session: boto3.Session, listener_arn: str):
|
|
493
|
-
lb_client = session.client("elbv2")
|
|
494
|
-
|
|
495
|
-
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
496
|
-
tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
|
|
497
|
-
tag_descriptions = lb_client.describe_tags(ResourceArns=[r["RuleArn"] for r in rules])[
|
|
498
|
-
"TagDescriptions"
|
|
499
|
-
]
|
|
500
|
-
|
|
501
|
-
for name in ["MaintenancePage", "AllowedIps", "BypassIpFilter", "AllowedSourceIps"]:
|
|
502
|
-
deleted = delete_listener_rule(tag_descriptions, name, lb_client)
|
|
503
|
-
|
|
504
|
-
if name == "MaintenancePage" and not deleted:
|
|
505
|
-
raise ListenerRuleNotFoundError()
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
def get_rules_tag_descriptions(rules: list, lb_client):
|
|
509
|
-
tag_descriptions = []
|
|
510
|
-
chunk_size = 20
|
|
511
|
-
|
|
512
|
-
for i in range(0, len(rules), chunk_size):
|
|
513
|
-
chunk = rules[i : i + chunk_size]
|
|
514
|
-
resource_arns = [r["RuleArn"] for r in chunk]
|
|
515
|
-
response = lb_client.describe_tags(ResourceArns=resource_arns)
|
|
516
|
-
tag_descriptions.extend(response["TagDescriptions"])
|
|
517
|
-
|
|
518
|
-
return tag_descriptions
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
def get_host_conditions(lb_client: boto3.client, listener_arn: str, target_group_arn: str):
|
|
522
|
-
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
523
|
-
|
|
524
|
-
# Get current set of forwarding conditions for the target group
|
|
525
|
-
for rule in rules:
|
|
526
|
-
for action in rule["Actions"]:
|
|
527
|
-
if action["Type"] == "forward" and action["TargetGroupArn"] == target_group_arn:
|
|
528
|
-
conditions = rule["Conditions"]
|
|
529
|
-
|
|
530
|
-
# filter to host-header conditions
|
|
531
|
-
conditions = [
|
|
532
|
-
{i: condition[i] for i in condition if i != "Values"}
|
|
533
|
-
for condition in conditions
|
|
534
|
-
if condition["Field"] == "host-header"
|
|
535
|
-
]
|
|
536
|
-
|
|
537
|
-
# remove internal hosts
|
|
538
|
-
conditions[0]["HostHeaderConfig"]["Values"] = [
|
|
539
|
-
v for v in conditions[0]["HostHeaderConfig"]["Values"]
|
|
540
|
-
]
|
|
541
|
-
|
|
542
|
-
return conditions
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
def create_header_rule(
|
|
546
|
-
lb_client: boto3.client,
|
|
547
|
-
listener_arn: str,
|
|
548
|
-
target_group_arn: str,
|
|
549
|
-
header_name: str,
|
|
550
|
-
values: list,
|
|
551
|
-
rule_name: str,
|
|
552
|
-
priority: int,
|
|
553
|
-
):
|
|
554
|
-
conditions = get_host_conditions(lb_client, listener_arn, target_group_arn)
|
|
555
|
-
|
|
556
|
-
# add new condition to existing conditions
|
|
557
|
-
combined_conditions = [
|
|
558
|
-
{
|
|
559
|
-
"Field": "http-header",
|
|
560
|
-
"HttpHeaderConfig": {"HttpHeaderName": header_name, "Values": values},
|
|
561
|
-
}
|
|
562
|
-
] + conditions
|
|
563
|
-
|
|
564
|
-
lb_client.create_rule(
|
|
565
|
-
ListenerArn=listener_arn,
|
|
566
|
-
Priority=priority,
|
|
567
|
-
Conditions=combined_conditions,
|
|
568
|
-
Actions=[{"Type": "forward", "TargetGroupArn": target_group_arn}],
|
|
569
|
-
Tags=[
|
|
570
|
-
{"Key": "name", "Value": rule_name},
|
|
571
|
-
],
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
click.secho(
|
|
575
|
-
f"Creating listener rule {rule_name} for HTTPS Listener with arn {listener_arn}.\n\nIf request header {header_name} contains one of the values {values}, the request will be forwarded to target group with arn {target_group_arn}.",
|
|
576
|
-
fg="green",
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
def create_source_ip_rule(
|
|
581
|
-
lb_client: boto3.client,
|
|
582
|
-
listener_arn: str,
|
|
583
|
-
target_group_arn: str,
|
|
584
|
-
values: list,
|
|
585
|
-
rule_name: str,
|
|
586
|
-
priority: int,
|
|
587
|
-
):
|
|
588
|
-
conditions = get_host_conditions(lb_client, listener_arn, target_group_arn)
|
|
589
|
-
|
|
590
|
-
# add new condition to existing conditions
|
|
591
|
-
combined_conditions = [
|
|
592
|
-
{
|
|
593
|
-
"Field": "source-ip",
|
|
594
|
-
"SourceIpConfig": {"Values": [value + "/32" for value in values]},
|
|
595
|
-
}
|
|
596
|
-
] + conditions
|
|
597
|
-
|
|
598
|
-
lb_client.create_rule(
|
|
599
|
-
ListenerArn=listener_arn,
|
|
600
|
-
Priority=priority,
|
|
601
|
-
Conditions=combined_conditions,
|
|
602
|
-
Actions=[{"Type": "forward", "TargetGroupArn": target_group_arn}],
|
|
603
|
-
Tags=[
|
|
604
|
-
{"Key": "name", "Value": rule_name},
|
|
605
|
-
],
|
|
606
|
-
)
|
|
607
|
-
|
|
608
|
-
click.secho(
|
|
609
|
-
f"Creating listener rule {rule_name} for HTTPS Listener with arn {listener_arn}.\n\nIf request source ip matches one of the values {values}, the request will be forwarded to target group with arn {target_group_arn}.",
|
|
610
|
-
fg="green",
|
|
611
|
-
)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
def add_maintenance_page(
|
|
615
|
-
session: boto3.Session,
|
|
616
|
-
listener_arn: str,
|
|
617
|
-
app: str,
|
|
618
|
-
env: str,
|
|
619
|
-
services: List[Service],
|
|
620
|
-
allowed_ips: tuple,
|
|
621
|
-
template: str = "default",
|
|
622
|
-
):
|
|
623
|
-
lb_client = session.client("elbv2")
|
|
624
|
-
maintenance_page_content = get_maintenance_page_template(template)
|
|
625
|
-
bypass_value = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
|
626
|
-
|
|
627
|
-
service_number = 1
|
|
628
|
-
|
|
629
|
-
for svc in services:
|
|
630
|
-
target_group_arn = find_target_group(app, env, svc.name, session)
|
|
631
|
-
|
|
632
|
-
# not all of an application's services are guaranteed to have been deployed to an environment
|
|
633
|
-
if not target_group_arn:
|
|
634
|
-
continue
|
|
635
|
-
|
|
636
|
-
allowed_ips = list(allowed_ips)
|
|
637
|
-
max_allowed_ips = 100
|
|
638
|
-
for ip_index, ip in enumerate(allowed_ips):
|
|
639
|
-
forwarded_rule_priority = (service_number * max_allowed_ips) + ip_index
|
|
640
|
-
create_header_rule(
|
|
641
|
-
lb_client,
|
|
642
|
-
listener_arn,
|
|
643
|
-
target_group_arn,
|
|
644
|
-
"X-Forwarded-For",
|
|
645
|
-
[ip],
|
|
646
|
-
"AllowedIps",
|
|
647
|
-
forwarded_rule_priority,
|
|
648
|
-
)
|
|
649
|
-
create_source_ip_rule(
|
|
650
|
-
lb_client,
|
|
651
|
-
listener_arn,
|
|
652
|
-
target_group_arn,
|
|
653
|
-
[ip],
|
|
654
|
-
"AllowedSourceIps",
|
|
655
|
-
forwarded_rule_priority + 1,
|
|
656
|
-
)
|
|
657
|
-
|
|
658
|
-
bypass_rule_priority = service_number
|
|
659
|
-
create_header_rule(
|
|
660
|
-
lb_client,
|
|
661
|
-
listener_arn,
|
|
662
|
-
target_group_arn,
|
|
663
|
-
"Bypass-Key",
|
|
664
|
-
[bypass_value],
|
|
665
|
-
"BypassIpFilter",
|
|
666
|
-
bypass_rule_priority,
|
|
667
|
-
)
|
|
668
|
-
|
|
669
|
-
service_number += 1
|
|
670
|
-
|
|
671
|
-
click.secho(
|
|
672
|
-
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/activities/holding-and-maintenance-pages/",
|
|
673
|
-
fg="green",
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
fixed_rule_priority = (service_number + 5) * max_allowed_ips
|
|
677
|
-
lb_client.create_rule(
|
|
678
|
-
ListenerArn=listener_arn,
|
|
679
|
-
Priority=fixed_rule_priority, # big number because we create multiple higher priority "AllowedIps" rules for each allowed ip for each service above.
|
|
680
|
-
Conditions=[
|
|
681
|
-
{
|
|
682
|
-
"Field": "path-pattern",
|
|
683
|
-
"PathPatternConfig": {"Values": ["/*"]},
|
|
684
|
-
}
|
|
685
|
-
],
|
|
686
|
-
Actions=[
|
|
687
|
-
{
|
|
688
|
-
"Type": "fixed-response",
|
|
689
|
-
"FixedResponseConfig": {
|
|
690
|
-
"StatusCode": "503",
|
|
691
|
-
"ContentType": "text/html",
|
|
692
|
-
"MessageBody": maintenance_page_content,
|
|
693
|
-
},
|
|
694
|
-
}
|
|
695
|
-
],
|
|
696
|
-
Tags=[
|
|
697
|
-
{"Key": "name", "Value": "MaintenancePage"},
|
|
698
|
-
{"Key": "type", "Value": template},
|
|
699
|
-
],
|
|
700
|
-
)
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
def get_maintenance_page_template(template) -> str:
|
|
704
|
-
template_contents = (
|
|
705
|
-
Path(__file__)
|
|
706
|
-
.parent.parent.joinpath(
|
|
707
|
-
f"templates/svc/maintenance_pages/{template}.html",
|
|
708
|
-
)
|
|
709
|
-
.read_text()
|
|
710
|
-
.replace("\n", "")
|
|
711
|
-
)
|
|
712
|
-
|
|
713
|
-
# [^\S]\s+ - Remove any space that is not preceded by a non-space character.
|
|
714
|
-
return re.sub(r"[^\S]\s+", "", template_contents)
|
|
715
|
-
|
|
716
|
-
|
|
717
217
|
class CertificateNotFoundError(Exception):
|
|
718
218
|
pass
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
class LoadBalancerNotFoundError(Exception):
|
|
722
|
-
pass
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
class ListenerNotFoundError(Exception):
|
|
726
|
-
pass
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
class ListenerRuleNotFoundError(Exception):
|
|
730
|
-
pass
|