dbt-platform-helper 11.3.0__py3-none-any.whl → 12.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 +3 -252
- dbt_platform_helper/addons-template-map.yml +7 -33
- dbt_platform_helper/commands/application.py +8 -7
- dbt_platform_helper/commands/conduit.py +1 -4
- dbt_platform_helper/commands/copilot.py +14 -110
- dbt_platform_helper/commands/environment.py +0 -5
- dbt_platform_helper/commands/pipeline.py +1 -13
- dbt_platform_helper/domain/database_copy.py +2 -2
- dbt_platform_helper/domain/maintenance_page.py +0 -3
- dbt_platform_helper/templates/addon-instructions.txt +1 -1
- dbt_platform_helper/templates/addons/svc/s3-policy.yml +0 -8
- dbt_platform_helper/utils/aws.py +3 -1
- dbt_platform_helper/utils/platform_config.py +2 -7
- dbt_platform_helper/utils/validation.py +3 -78
- {dbt_platform_helper-11.3.0.dist-info → dbt_platform_helper-12.0.0.dist-info}/METADATA +1 -1
- {dbt_platform_helper-11.3.0.dist-info → dbt_platform_helper-12.0.0.dist-info}/RECORD +20 -33
- platform_helper.py +0 -8
- dbt_platform_helper/commands/check_cloudformation.py +0 -87
- dbt_platform_helper/commands/dns.py +0 -952
- dbt_platform_helper/custom_resources/__init__.py +0 -0
- dbt_platform_helper/custom_resources/s3_object.py +0 -85
- dbt_platform_helper/templates/addons/env/addons.parameters.yml +0 -19
- dbt_platform_helper/templates/addons/env/aurora-postgres.yml +0 -604
- dbt_platform_helper/templates/addons/env/monitoring.yml +0 -121
- dbt_platform_helper/templates/addons/env/opensearch.yml +0 -257
- dbt_platform_helper/templates/addons/env/rds-postgres.yml +0 -603
- dbt_platform_helper/templates/addons/env/redis-cluster.yml +0 -171
- dbt_platform_helper/templates/addons/env/s3.yml +0 -219
- dbt_platform_helper/templates/addons/env/vpc.yml +0 -120
- dbt_platform_helper/utils/cloudformation.py +0 -34
- {dbt_platform_helper-11.3.0.dist-info → dbt_platform_helper-12.0.0.dist-info}/LICENSE +0 -0
- {dbt_platform_helper-11.3.0.dist-info → dbt_platform_helper-12.0.0.dist-info}/WHEEL +0 -0
- {dbt_platform_helper-11.3.0.dist-info → dbt_platform_helper-12.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,952 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import re
|
|
5
|
-
import time
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
import click
|
|
9
|
-
import yaml
|
|
10
|
-
|
|
11
|
-
from dbt_platform_helper.utils.aws import check_response
|
|
12
|
-
from dbt_platform_helper.utils.aws import get_aws_session_or_abort
|
|
13
|
-
from dbt_platform_helper.utils.aws import get_load_balancer_configuration
|
|
14
|
-
from dbt_platform_helper.utils.aws import get_load_balancer_domain_and_configuration
|
|
15
|
-
from dbt_platform_helper.utils.click import ClickDocOptGroup
|
|
16
|
-
from dbt_platform_helper.utils.messages import abort_with_error
|
|
17
|
-
from dbt_platform_helper.utils.versioning import (
|
|
18
|
-
check_platform_helper_version_needs_update,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
# To do
|
|
22
|
-
# -----
|
|
23
|
-
# Check user has logged into the aws accounts before scanning the accounts
|
|
24
|
-
# When adding records from parent to subdomain, if ok, it then should remove them from parent domain
|
|
25
|
-
# (run a test before removing)
|
|
26
|
-
|
|
27
|
-
# Base domain depth
|
|
28
|
-
AVAILABLE_DOMAINS = {
|
|
29
|
-
"great.gov.uk": "live",
|
|
30
|
-
"trade.gov.uk": "live",
|
|
31
|
-
"prod.uktrade.digital": "live",
|
|
32
|
-
"uktrade.digital": "dev",
|
|
33
|
-
}
|
|
34
|
-
AWS_CERT_REGION = "eu-west-2"
|
|
35
|
-
COPILOT_DIR = "copilot"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _wait_for_certificate_validation(acm_client, certificate_arn, sleep_time=10, timeout=600):
|
|
39
|
-
click.secho(f"Waiting up to {timeout} seconds for certificate to be validated...", fg="yellow")
|
|
40
|
-
status = acm_client.describe_certificate(CertificateArn=certificate_arn)["Certificate"][
|
|
41
|
-
"Status"
|
|
42
|
-
]
|
|
43
|
-
elapsed_time = 0
|
|
44
|
-
while status == "PENDING_VALIDATION":
|
|
45
|
-
if elapsed_time >= timeout:
|
|
46
|
-
raise Exception(
|
|
47
|
-
f"Timeout ({timeout}s) reached for certificate validation, might be worth checking things in the AWS Console"
|
|
48
|
-
)
|
|
49
|
-
click.echo(
|
|
50
|
-
click.style(f"{certificate_arn}", fg="white", bold=True)
|
|
51
|
-
+ click.style(
|
|
52
|
-
f": Waiting {sleep_time}s for validation, {elapsed_time}s elapsed, {timeout - elapsed_time}s until we give up...",
|
|
53
|
-
fg="yellow",
|
|
54
|
-
),
|
|
55
|
-
)
|
|
56
|
-
time.sleep(sleep_time)
|
|
57
|
-
status = acm_client.describe_certificate(CertificateArn=certificate_arn)["Certificate"][
|
|
58
|
-
"Status"
|
|
59
|
-
]
|
|
60
|
-
elapsed_time += sleep_time
|
|
61
|
-
|
|
62
|
-
if status == "ISSUED":
|
|
63
|
-
click.secho("Certificate validated...", fg="green")
|
|
64
|
-
else:
|
|
65
|
-
raise Exception(f"""Certificate validation failed with the status "{status}".""")
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def create_cert(client, domain_client, domain_name, zone_id):
|
|
69
|
-
certificate_arn = _get_existing_cert_if_one_exists_and_cleanup_bad_certs(client, domain_name)
|
|
70
|
-
|
|
71
|
-
if certificate_arn:
|
|
72
|
-
return certificate_arn
|
|
73
|
-
|
|
74
|
-
if not click.confirm(
|
|
75
|
-
click.style("Creating Certificate for ", fg="yellow")
|
|
76
|
-
+ click.style(f"{domain_name}\n", fg="white", bold=True)
|
|
77
|
-
+ click.style("Do you want to continue?", fg="yellow"),
|
|
78
|
-
):
|
|
79
|
-
exit()
|
|
80
|
-
|
|
81
|
-
certificate_arn = _request_cert(client, domain_name)
|
|
82
|
-
|
|
83
|
-
# Create DNS validation records
|
|
84
|
-
cert_record = _get_certificate_record(certificate_arn, client)
|
|
85
|
-
|
|
86
|
-
if not click.confirm(
|
|
87
|
-
click.style("Updating DNS record for certificate ", fg="yellow")
|
|
88
|
-
+ click.style(f"{domain_name}", fg="white", bold=True)
|
|
89
|
-
+ click.style(" with value ", fg="yellow")
|
|
90
|
-
+ click.style(f"""{cert_record["Value"]}\n""", fg="white", bold=True)
|
|
91
|
-
+ click.style("Do you want to continue?", fg="yellow"),
|
|
92
|
-
):
|
|
93
|
-
exit()
|
|
94
|
-
|
|
95
|
-
_create_resource_records(cert_record, domain_client, zone_id)
|
|
96
|
-
|
|
97
|
-
# Wait for certificate to get to validation state before continuing.
|
|
98
|
-
# Will upped the timeout from 600 to 1200 because he repeatedly saw it timeout at 600
|
|
99
|
-
# and wants to give it a chance to take longer in case that's all that's wrong.
|
|
100
|
-
_wait_for_certificate_validation(client, certificate_arn=certificate_arn, timeout=1200)
|
|
101
|
-
|
|
102
|
-
return certificate_arn
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _get_existing_cert_if_one_exists_and_cleanup_bad_certs(client, domain):
|
|
106
|
-
for cert in _certs_for_domain(client, domain):
|
|
107
|
-
if cert["Status"] == "ISSUED":
|
|
108
|
-
click.secho("Certificate already exists, do not need to create.", fg="green")
|
|
109
|
-
return cert["CertificateArn"]
|
|
110
|
-
else:
|
|
111
|
-
click.secho(
|
|
112
|
-
f"Certificate already exists but appears to be invalid (in status '{cert['Status']}'), deleting the old cert.",
|
|
113
|
-
fg="yellow",
|
|
114
|
-
)
|
|
115
|
-
client.delete_certificate(CertificateArn=cert["CertificateArn"])
|
|
116
|
-
return None
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def _create_resource_records(cert_record, domain_client, zone_id):
|
|
120
|
-
resource_records = domain_client.list_resource_record_sets(HostedZoneId=zone_id)[
|
|
121
|
-
"ResourceRecordSets"
|
|
122
|
-
]
|
|
123
|
-
|
|
124
|
-
for record in resource_records:
|
|
125
|
-
if record["Type"] == cert_record["Type"] and record["Name"] == cert_record["Name"]:
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
domain_client.change_resource_record_sets(
|
|
129
|
-
HostedZoneId=zone_id,
|
|
130
|
-
ChangeBatch={
|
|
131
|
-
"Changes": [
|
|
132
|
-
{
|
|
133
|
-
"Action": "CREATE",
|
|
134
|
-
"ResourceRecordSet": {
|
|
135
|
-
"Name": cert_record["Name"],
|
|
136
|
-
"Type": cert_record["Type"],
|
|
137
|
-
"TTL": 300,
|
|
138
|
-
"ResourceRecords": [
|
|
139
|
-
{"Value": cert_record["Value"]},
|
|
140
|
-
],
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def _certs_for_domain(client, domain):
|
|
149
|
-
# Check if cert is present.
|
|
150
|
-
cert_list = client.list_certificates(
|
|
151
|
-
CertificateStatuses=[
|
|
152
|
-
"PENDING_VALIDATION",
|
|
153
|
-
"ISSUED",
|
|
154
|
-
"INACTIVE",
|
|
155
|
-
"EXPIRED",
|
|
156
|
-
"VALIDATION_TIMED_OUT",
|
|
157
|
-
"REVOKED",
|
|
158
|
-
"FAILED",
|
|
159
|
-
],
|
|
160
|
-
MaxItems=500,
|
|
161
|
-
)["CertificateSummaryList"]
|
|
162
|
-
certs_for_domain = [cert for cert in cert_list if cert["DomainName"] == domain]
|
|
163
|
-
return certs_for_domain
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _get_certificate_record(arn, client):
|
|
167
|
-
# Need a pause for it to populate the DNS resource records
|
|
168
|
-
cert_description = client.describe_certificate(CertificateArn=arn)
|
|
169
|
-
while (
|
|
170
|
-
cert_description["Certificate"].get("DomainValidationOptions") is None
|
|
171
|
-
or cert_description["Certificate"]["DomainValidationOptions"][0].get("ResourceRecord")
|
|
172
|
-
is None
|
|
173
|
-
):
|
|
174
|
-
click.secho("Waiting for DNS records...", fg="yellow")
|
|
175
|
-
time.sleep(2)
|
|
176
|
-
cert_description = client.describe_certificate(CertificateArn=arn)
|
|
177
|
-
return cert_description["Certificate"]["DomainValidationOptions"][0]["ResourceRecord"]
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def _request_cert(client, domain):
|
|
181
|
-
cert_response = client.request_certificate(DomainName=domain, ValidationMethod="DNS")
|
|
182
|
-
arn = cert_response["CertificateArn"]
|
|
183
|
-
return arn
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def add_records(client, records, subdomain_id, action):
|
|
187
|
-
if records["Type"] == "A":
|
|
188
|
-
response = client.change_resource_record_sets(
|
|
189
|
-
HostedZoneId=subdomain_id,
|
|
190
|
-
ChangeBatch={
|
|
191
|
-
"Comment": "Record created for copilot",
|
|
192
|
-
"Changes": [
|
|
193
|
-
{
|
|
194
|
-
"Action": action,
|
|
195
|
-
"ResourceRecordSet": {
|
|
196
|
-
"Name": records["Name"],
|
|
197
|
-
"Type": records["Type"],
|
|
198
|
-
"AliasTarget": {
|
|
199
|
-
"HostedZoneId": records["AliasTarget"]["HostedZoneId"],
|
|
200
|
-
"DNSName": records["AliasTarget"]["DNSName"],
|
|
201
|
-
"EvaluateTargetHealth": records["AliasTarget"][
|
|
202
|
-
"EvaluateTargetHealth"
|
|
203
|
-
],
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
},
|
|
209
|
-
)
|
|
210
|
-
else:
|
|
211
|
-
response = client.change_resource_record_sets(
|
|
212
|
-
HostedZoneId=subdomain_id,
|
|
213
|
-
ChangeBatch={
|
|
214
|
-
"Comment": "Record created for copilot",
|
|
215
|
-
"Changes": [
|
|
216
|
-
{
|
|
217
|
-
"Action": action,
|
|
218
|
-
"ResourceRecordSet": {
|
|
219
|
-
"Name": records["Name"],
|
|
220
|
-
"Type": records["Type"],
|
|
221
|
-
"TTL": records["TTL"],
|
|
222
|
-
"ResourceRecords": [
|
|
223
|
-
{"Value": records["ResourceRecords"][0]["Value"]},
|
|
224
|
-
],
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
],
|
|
228
|
-
},
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
check_response(response)
|
|
232
|
-
click.echo(
|
|
233
|
-
click.style(f"{records['Name']}, Type: {records['Type']}", fg="white", bold=True)
|
|
234
|
-
+ click.style("Added.", fg="magenta"),
|
|
235
|
-
)
|
|
236
|
-
return response["ChangeInfo"]["Status"]
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def copy_records_from_parent_to_subdomain(client, parent_zone_id, subdomain, zone_id):
|
|
240
|
-
response = client.list_resource_record_sets(HostedZoneId=parent_zone_id)
|
|
241
|
-
|
|
242
|
-
for records in response["ResourceRecordSets"]:
|
|
243
|
-
if subdomain in records["Name"]:
|
|
244
|
-
click.secho(records["Name"] + " found", fg="green")
|
|
245
|
-
add_records(client, records, zone_id, "UPSERT")
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def create_hosted_zones(client, base_domain, subdomain):
|
|
249
|
-
domains_to_create = get_required_subdomains(base_domain, subdomain)
|
|
250
|
-
zone_ids = {}
|
|
251
|
-
|
|
252
|
-
for domain_name in domains_to_create:
|
|
253
|
-
domain_zone = f"{domain_name}."
|
|
254
|
-
parent_domain = domain_name.split(".", maxsplit=1)[1]
|
|
255
|
-
parent_zone = f"{parent_domain}."
|
|
256
|
-
|
|
257
|
-
domain_zone_id, parent_zone_id = _get_paginated_zones(client, parent_zone, domain_zone)
|
|
258
|
-
|
|
259
|
-
if parent_zone_id is None:
|
|
260
|
-
click.secho(
|
|
261
|
-
f"The hosted zone: {parent_zone} does not exist in your AWS domain account",
|
|
262
|
-
fg="red",
|
|
263
|
-
)
|
|
264
|
-
exit()
|
|
265
|
-
|
|
266
|
-
if domain_zone_id is not None:
|
|
267
|
-
click.secho(f"Hosted zone '{domain_zone}' already exists", fg="yellow")
|
|
268
|
-
zone_ids[domain_name] = domain_zone_id
|
|
269
|
-
else:
|
|
270
|
-
subdomain_zone_id = _create_hosted_zone(
|
|
271
|
-
client, domain_name, parent_zone, parent_zone_id
|
|
272
|
-
)
|
|
273
|
-
zone_ids[domain_name] = subdomain_zone_id
|
|
274
|
-
|
|
275
|
-
return zone_ids
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def _get_hosted_zones_paginator(client):
|
|
279
|
-
return client.get_paginator("list_hosted_zones").paginate()
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def _get_paginated_zones(client, parent_zone, domain_zone):
|
|
283
|
-
domain_zone_id = parent_zone_id = None
|
|
284
|
-
|
|
285
|
-
for page in _get_hosted_zones_paginator(client):
|
|
286
|
-
# each page is a hosted zones response type object
|
|
287
|
-
hosted_zones = {hz["Name"]: hz for hz in page["HostedZones"]}
|
|
288
|
-
|
|
289
|
-
if parent_zone in hosted_zones and parent_zone_id is None:
|
|
290
|
-
parent_zone_id = hosted_zones[parent_zone]["Id"]
|
|
291
|
-
|
|
292
|
-
if domain_zone in hosted_zones and domain_zone_id is None:
|
|
293
|
-
domain_zone_id = hosted_zones[domain_zone]["Id"]
|
|
294
|
-
|
|
295
|
-
if all([parent_zone_id, domain_zone_id]):
|
|
296
|
-
# both were found, no need to further pagination
|
|
297
|
-
break
|
|
298
|
-
|
|
299
|
-
return domain_zone_id, parent_zone_id
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
def _create_hosted_zone(client, domain_name, parent_zone, parent_zone_id):
|
|
303
|
-
if not click.confirm(
|
|
304
|
-
click.style("About to create domain: ", fg="cyan")
|
|
305
|
-
+ click.style(f"{domain_name}\n", fg="white", bold=True)
|
|
306
|
-
+ click.style("Do you want to continue?", fg="cyan"),
|
|
307
|
-
):
|
|
308
|
-
exit()
|
|
309
|
-
response = client.create_hosted_zone(
|
|
310
|
-
Name=domain_name,
|
|
311
|
-
# Timestamp is on the end because CallerReference must be unique for every call
|
|
312
|
-
CallerReference=f"{domain_name}_from_code_{int(time.time())}",
|
|
313
|
-
)
|
|
314
|
-
ns_records = response["DelegationSet"]
|
|
315
|
-
subdomain_zone_id = response["HostedZone"]["Id"]
|
|
316
|
-
copy_records_from_parent_to_subdomain(client, parent_zone_id, domain_name, subdomain_zone_id)
|
|
317
|
-
if not click.confirm(
|
|
318
|
-
click.style(f"Updating parent {parent_zone} domain with records: ", fg="cyan")
|
|
319
|
-
+ click.style(f"{ns_records['NameServers']}\n", fg="white", bold=True)
|
|
320
|
-
+ click.style("Do you want to continue?", fg="cyan"),
|
|
321
|
-
):
|
|
322
|
-
exit()
|
|
323
|
-
_add_subdomain_ns_records_to_parent(client, domain_name, ns_records, parent_zone_id)
|
|
324
|
-
|
|
325
|
-
return subdomain_zone_id
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
def _add_subdomain_ns_records_to_parent(client, domain_name, ns_records, parent_zone_id):
|
|
329
|
-
# Add nameserver records of subdomain to parent
|
|
330
|
-
nameservers = ns_records["NameServers"]
|
|
331
|
-
# append . to make fully qualified domain name
|
|
332
|
-
nameservers = [f"{nameserver}." for nameserver in nameservers]
|
|
333
|
-
nameserver_resource_records = [{"Value": nameserver} for nameserver in nameservers]
|
|
334
|
-
client.change_resource_record_sets(
|
|
335
|
-
HostedZoneId=parent_zone_id,
|
|
336
|
-
ChangeBatch={
|
|
337
|
-
"Changes": [
|
|
338
|
-
{
|
|
339
|
-
"Action": "UPSERT",
|
|
340
|
-
"ResourceRecordSet": {
|
|
341
|
-
"Name": domain_name,
|
|
342
|
-
"Type": "NS",
|
|
343
|
-
"TTL": 300,
|
|
344
|
-
"ResourceRecords": nameserver_resource_records,
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
],
|
|
348
|
-
},
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
class InvalidDomainException(Exception):
|
|
353
|
-
pass
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
def _get_subdomains_from_base(base: list[str], subdomain: list[str]) -> list[list[str]]:
|
|
357
|
-
"""
|
|
358
|
-
Recurse over domain to get a list of subdomains.
|
|
359
|
-
|
|
360
|
-
These are ordered from smallest to largest, so the domains can be created in
|
|
361
|
-
the correct order.
|
|
362
|
-
"""
|
|
363
|
-
if base == subdomain:
|
|
364
|
-
return []
|
|
365
|
-
return _get_subdomains_from_base(base, subdomain[1:]) + [subdomain]
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
def get_required_subdomains(base_domain: str, subdomain: str) -> list[str]:
|
|
369
|
-
"""
|
|
370
|
-
We only want to get subdomains up to 4 levels deep.
|
|
371
|
-
|
|
372
|
-
Prod base domains should be 3 levels deep, so we should create up to one
|
|
373
|
-
additional subdomain. Dev base domain is always "uktrade.digital" and should
|
|
374
|
-
have the app and env subdomains created, so 4 levels in either case.
|
|
375
|
-
"""
|
|
376
|
-
if base_domain not in AVAILABLE_DOMAINS:
|
|
377
|
-
raise InvalidDomainException(f"{base_domain} is not a supported base domain")
|
|
378
|
-
_validate_subdomain(base_domain, subdomain)
|
|
379
|
-
domains_as_lists = _get_subdomains_from_base(base_domain.split("."), subdomain.split("."))
|
|
380
|
-
|
|
381
|
-
max_domain_levels = 4
|
|
382
|
-
return [
|
|
383
|
-
".".join(domain_parts)
|
|
384
|
-
for domain_parts in domains_as_lists
|
|
385
|
-
if len(domain_parts) <= max_domain_levels
|
|
386
|
-
]
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
def _validate_subdomain(base_domain, subdomain):
|
|
390
|
-
errors = []
|
|
391
|
-
if not subdomain.endswith("." + base_domain):
|
|
392
|
-
errors.append(f"{subdomain} is not a subdomain of {base_domain}")
|
|
393
|
-
|
|
394
|
-
errors.extend(_validate_basic_domain_syntax(subdomain))
|
|
395
|
-
|
|
396
|
-
if errors:
|
|
397
|
-
raise InvalidDomainException("\n".join(errors))
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
def _validate_basic_domain_syntax(domain):
|
|
401
|
-
errors = []
|
|
402
|
-
if ".." in domain or not re.match(r"^[0-9a-zA-Z-.]+$", domain):
|
|
403
|
-
errors.append(f"Subdomain {domain} is not a valid domain")
|
|
404
|
-
|
|
405
|
-
return errors
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def validate_subdomains(subdomains: list[str]) -> None:
|
|
409
|
-
bad_domains = []
|
|
410
|
-
for subdomain in subdomains:
|
|
411
|
-
is_valid = False
|
|
412
|
-
for key in AVAILABLE_DOMAINS:
|
|
413
|
-
if subdomain.endswith(key):
|
|
414
|
-
is_valid = True
|
|
415
|
-
break
|
|
416
|
-
if not is_valid:
|
|
417
|
-
bad_domains.append(subdomain)
|
|
418
|
-
if bad_domains:
|
|
419
|
-
csv_domains = ", ".join(bad_domains)
|
|
420
|
-
raise InvalidDomainException(
|
|
421
|
-
f"The following subdomains do not have one of the allowed base domains: {csv_domains}"
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
def get_base_domain(subdomains: list[str]) -> str:
|
|
426
|
-
matching_domains = set()
|
|
427
|
-
domains_by_length = sorted(AVAILABLE_DOMAINS.keys(), key=lambda d: len(d), reverse=True)
|
|
428
|
-
for subdomain in subdomains:
|
|
429
|
-
for domain_name in domains_by_length:
|
|
430
|
-
if subdomain.endswith(f".{domain_name}"):
|
|
431
|
-
matching_domains.add(domain_name)
|
|
432
|
-
break
|
|
433
|
-
|
|
434
|
-
if len(matching_domains) > 1:
|
|
435
|
-
ordered_subdomains = ", ".join(sorted(matching_domains))
|
|
436
|
-
raise InvalidDomainException(f"Multiple base domains were found: {ordered_subdomains}")
|
|
437
|
-
|
|
438
|
-
if not matching_domains:
|
|
439
|
-
ordered_subdomains = ", ".join(sorted(subdomains))
|
|
440
|
-
raise InvalidDomainException(
|
|
441
|
-
f"No base domains were found for subdomains: {ordered_subdomains}"
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
return matching_domains.pop()
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
def get_certificate_zone_id(hosted_zones):
|
|
448
|
-
hosted_zone_names = sorted([zone for zone in hosted_zones], key=lambda z: len(z))
|
|
449
|
-
longest_hosted_zone = hosted_zone_names[-1]
|
|
450
|
-
|
|
451
|
-
return hosted_zones[longest_hosted_zone]
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
def create_required_zones_and_certs(domain_client, project_client, subdomain, base_domain):
|
|
455
|
-
hosted_zones = create_hosted_zones(domain_client, base_domain, subdomain)
|
|
456
|
-
|
|
457
|
-
cert_zone_id = get_certificate_zone_id(hosted_zones)
|
|
458
|
-
|
|
459
|
-
return create_cert(project_client, domain_client, subdomain, cert_zone_id)
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
@click.group(chain=True, cls=ClickDocOptGroup)
|
|
463
|
-
def domain():
|
|
464
|
-
check_platform_helper_version_needs_update()
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
@domain.command()
|
|
468
|
-
@click.option(
|
|
469
|
-
"--project-profile", help="AWS account profile name for certificates account", required=True
|
|
470
|
-
)
|
|
471
|
-
@click.option("--env", help="AWS Copilot environment name", required=True)
|
|
472
|
-
def configure(project_profile, env):
|
|
473
|
-
"""Creates subdomains if they do not exist and then creates certificates for
|
|
474
|
-
them."""
|
|
475
|
-
|
|
476
|
-
# If you need to reset to debug this command, you will need to delete any of the following
|
|
477
|
-
# which have been created:
|
|
478
|
-
# the certificate in your application's AWS account,
|
|
479
|
-
# the hosted zone for the application environment in the dev AWS account,
|
|
480
|
-
# and the applications records on the hosted zone for the environment in the dev AWS account.
|
|
481
|
-
|
|
482
|
-
manifests = _get_manifests_or_abort()
|
|
483
|
-
|
|
484
|
-
subdomains = _get_subdomains_from_env_manifests(env, manifests)
|
|
485
|
-
validate_subdomains(subdomains)
|
|
486
|
-
base_domain = get_base_domain(subdomains)
|
|
487
|
-
domain_profile = AVAILABLE_DOMAINS[base_domain]
|
|
488
|
-
|
|
489
|
-
domain_session = get_aws_session_or_abort(domain_profile)
|
|
490
|
-
project_session = get_aws_session_or_abort(project_profile)
|
|
491
|
-
domain_client = domain_session.client("route53")
|
|
492
|
-
project_client = project_session.client("acm", region_name=AWS_CERT_REGION)
|
|
493
|
-
|
|
494
|
-
cert_list = {}
|
|
495
|
-
|
|
496
|
-
for subdomain in subdomains:
|
|
497
|
-
cert_arn = create_required_zones_and_certs(
|
|
498
|
-
domain_client,
|
|
499
|
-
project_client,
|
|
500
|
-
subdomain,
|
|
501
|
-
base_domain,
|
|
502
|
-
)
|
|
503
|
-
cert_list.update({subdomain: cert_arn})
|
|
504
|
-
|
|
505
|
-
if cert_list:
|
|
506
|
-
click.secho("\nHere are your Certificate ARNs:", fg="cyan")
|
|
507
|
-
for domain, cert in cert_list.items():
|
|
508
|
-
click.secho(f"Domain: {domain} => Cert ARN: {cert}", fg="white", bold=True)
|
|
509
|
-
else:
|
|
510
|
-
click.secho("No domains found, please check the manifest file", fg="red")
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
def _get_domain_aliases(conf, environment):
|
|
514
|
-
aliases = []
|
|
515
|
-
for env, domain in conf["environments"].items():
|
|
516
|
-
if env == environment:
|
|
517
|
-
# ensure configure doesn't blow up when http or alias
|
|
518
|
-
# are missing
|
|
519
|
-
if domain.get("http") and domain.get("http").get("alias"):
|
|
520
|
-
aliases.append(domain["http"]["alias"])
|
|
521
|
-
return aliases
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
def _get_subdomains_from_env_manifests(environment, manifests):
|
|
525
|
-
subdomains = []
|
|
526
|
-
for manifest in manifests:
|
|
527
|
-
# Need to check that the manifest file is correctly configured.
|
|
528
|
-
with open(manifest, "r") as fd:
|
|
529
|
-
conf = yaml.safe_load(fd)
|
|
530
|
-
if "environments" in conf:
|
|
531
|
-
click.echo(
|
|
532
|
-
click.style("Checking file: ", fg="cyan") + click.style(manifest, fg="white"),
|
|
533
|
-
)
|
|
534
|
-
aliases = _get_domain_aliases(conf, environment)
|
|
535
|
-
if not len(aliases):
|
|
536
|
-
click.echo(
|
|
537
|
-
click.style(
|
|
538
|
-
f"No http.alias present for {environment} environment in {manifest}, skipping...",
|
|
539
|
-
fg="cyan",
|
|
540
|
-
),
|
|
541
|
-
)
|
|
542
|
-
else:
|
|
543
|
-
click.secho("Domains listed in manifest file", fg="cyan", underline=True)
|
|
544
|
-
click.secho(
|
|
545
|
-
" " + "\n ".join(aliases),
|
|
546
|
-
fg="yellow",
|
|
547
|
-
bold=True,
|
|
548
|
-
)
|
|
549
|
-
subdomains.extend(aliases)
|
|
550
|
-
return subdomains
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
def _get_manifests_or_abort():
|
|
554
|
-
if not os.path.exists(COPILOT_DIR):
|
|
555
|
-
abort_with_error("Please check path, copilot directory appears to be missing.")
|
|
556
|
-
|
|
557
|
-
manifests = []
|
|
558
|
-
for root, dirs, files in os.walk(COPILOT_DIR):
|
|
559
|
-
for file in files:
|
|
560
|
-
if file == "manifest.yml" or file == "manifest.yaml":
|
|
561
|
-
manifests.append(os.path.join(root, file))
|
|
562
|
-
|
|
563
|
-
if not manifests:
|
|
564
|
-
abort_with_error("Please check path, no manifest files were found")
|
|
565
|
-
|
|
566
|
-
return manifests
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
@domain.command()
|
|
570
|
-
@click.option("--app", help="Application Name", required=True)
|
|
571
|
-
@click.option("--env", help="Environment", required=True)
|
|
572
|
-
@click.option("--svc", help="Service Name", required=True)
|
|
573
|
-
@click.option(
|
|
574
|
-
"--domain-profile",
|
|
575
|
-
help="AWS account profile name for Route53 domains account",
|
|
576
|
-
required=True,
|
|
577
|
-
type=click.Choice(["dev", "live"]),
|
|
578
|
-
)
|
|
579
|
-
@click.option(
|
|
580
|
-
"--project-profile", help="AWS account profile name for application account", required=True
|
|
581
|
-
)
|
|
582
|
-
def assign(app, domain_profile, project_profile, svc, env):
|
|
583
|
-
"""Assigns the load balancer for a service to its domain name."""
|
|
584
|
-
domain_session = get_aws_session_or_abort(domain_profile)
|
|
585
|
-
project_session = get_aws_session_or_abort(project_profile)
|
|
586
|
-
|
|
587
|
-
if not Path("./copilot").exists() or not Path("./copilot").is_dir():
|
|
588
|
-
click.secho(
|
|
589
|
-
"Cannot find copilot directory. Run this command in the root of the deployment repository.",
|
|
590
|
-
bg="red",
|
|
591
|
-
)
|
|
592
|
-
exit(1)
|
|
593
|
-
# Find the Load Balancer name.
|
|
594
|
-
domain_name, load_balancer_configuration = get_load_balancer_domain_and_configuration(
|
|
595
|
-
project_session, app, env, svc
|
|
596
|
-
)
|
|
597
|
-
elb_name = load_balancer_configuration["DNSName"]
|
|
598
|
-
|
|
599
|
-
click.echo(
|
|
600
|
-
click.style("The Domain: ", fg="yellow")
|
|
601
|
-
+ click.style(f"{domain_name}\n", fg="white", bold=True)
|
|
602
|
-
+ click.style("has been assigned the Load Balancer: ", fg="yellow")
|
|
603
|
-
+ click.style(f"{elb_name}\n", fg="white", bold=True)
|
|
604
|
-
+ click.style("Checking to see if this is in Route53", fg="yellow"),
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
domain_client = domain_session.client("route53")
|
|
608
|
-
response = domain_client.list_hosted_zones_by_name()
|
|
609
|
-
check_response(response)
|
|
610
|
-
|
|
611
|
-
# Scan Route53 Zone for matching domains and update records if needed.
|
|
612
|
-
hosted_zones = {}
|
|
613
|
-
for hz in response["HostedZones"]:
|
|
614
|
-
hosted_zones[hz["Name"]] = hz
|
|
615
|
-
|
|
616
|
-
parts = domain_name.split(".")
|
|
617
|
-
for _ in range(len(parts) - 1):
|
|
618
|
-
subdomain = ".".join(parts) + "."
|
|
619
|
-
click.echo(
|
|
620
|
-
click.style("Searching for ", fg="yellow")
|
|
621
|
-
+ click.style(f"{subdomain}..", fg="white", bold=True)
|
|
622
|
-
)
|
|
623
|
-
|
|
624
|
-
if subdomain in hosted_zones:
|
|
625
|
-
click.echo(
|
|
626
|
-
click.style("Found hosted zone ", fg="yellow")
|
|
627
|
-
+ click.style(hosted_zones[subdomain]["Name"], fg="white", bold=True),
|
|
628
|
-
)
|
|
629
|
-
hosted_zone_id = hosted_zones[subdomain]["Id"]
|
|
630
|
-
|
|
631
|
-
# Does record existing
|
|
632
|
-
response = domain_client.list_resource_record_sets(
|
|
633
|
-
HostedZoneId=hosted_zone_id,
|
|
634
|
-
)
|
|
635
|
-
check_response(response)
|
|
636
|
-
|
|
637
|
-
for record in response["ResourceRecordSets"]:
|
|
638
|
-
if domain_name == record["Name"][:-1]:
|
|
639
|
-
click.echo(
|
|
640
|
-
click.style("Record: ", fg="yellow")
|
|
641
|
-
+ click.style(f"{record['Name']} found", fg="white", bold=True),
|
|
642
|
-
)
|
|
643
|
-
click.echo(
|
|
644
|
-
click.style("is pointing to LB ", fg="yellow")
|
|
645
|
-
+ click.style(
|
|
646
|
-
f"{record['ResourceRecords'][0]['Value']}", fg="white", bold=True
|
|
647
|
-
),
|
|
648
|
-
)
|
|
649
|
-
if record["ResourceRecords"][0]["Value"] != elb_name:
|
|
650
|
-
if click.confirm(
|
|
651
|
-
click.style("This doesnt match with the current LB ", fg="yellow")
|
|
652
|
-
+ click.style(f"{elb_name}", fg="white", bold=True)
|
|
653
|
-
+ click.style("Do you wish to update the record?", fg="yellow"),
|
|
654
|
-
):
|
|
655
|
-
record = {
|
|
656
|
-
"Name": domain_name,
|
|
657
|
-
"Type": "CNAME",
|
|
658
|
-
"TTL": 300,
|
|
659
|
-
"ResourceRecords": [{"Value": elb_name}],
|
|
660
|
-
}
|
|
661
|
-
add_records(domain_client, record, hosted_zone_id, "UPSERT")
|
|
662
|
-
else:
|
|
663
|
-
click.secho("No need to add as it already exists", fg="green")
|
|
664
|
-
exit()
|
|
665
|
-
|
|
666
|
-
record = {
|
|
667
|
-
"Name": domain_name,
|
|
668
|
-
"Type": "CNAME",
|
|
669
|
-
"TTL": 300,
|
|
670
|
-
"ResourceRecords": [{"Value": elb_name}],
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if not click.confirm(
|
|
674
|
-
click.style("Creating Route53 record: ", fg="yellow")
|
|
675
|
-
+ click.style(
|
|
676
|
-
f"{record['Name']} -> {record['ResourceRecords'][0]['Value']}\n",
|
|
677
|
-
fg="white",
|
|
678
|
-
bold=True,
|
|
679
|
-
)
|
|
680
|
-
+ click.style("In Domain: ", fg="yellow")
|
|
681
|
-
+ click.style(f"{subdomain}", fg="white", bold=True)
|
|
682
|
-
+ click.style("\tZone ID: ", fg="yellow")
|
|
683
|
-
+ click.style(f"{hosted_zone_id}\n", fg="white", bold=True)
|
|
684
|
-
+ click.style("Do you want to continue?", fg="yellow"),
|
|
685
|
-
):
|
|
686
|
-
exit()
|
|
687
|
-
add_records(domain_client, record, hosted_zone_id, "CREATE")
|
|
688
|
-
exit()
|
|
689
|
-
|
|
690
|
-
parts.pop(0)
|
|
691
|
-
|
|
692
|
-
else:
|
|
693
|
-
click.echo(
|
|
694
|
-
click.style("No hosted zone found for ", fg="yellow")
|
|
695
|
-
+ click.style(f"{domain_name}", fg="white", bold=True),
|
|
696
|
-
)
|
|
697
|
-
return
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
def update_rule(delete, listener, conditions, rule_arn, elb_client, acm_client, cdn_domain):
|
|
701
|
-
# Update Rule
|
|
702
|
-
response = elb_client.modify_rule(RuleArn=rule_arn, Conditions=conditions)
|
|
703
|
-
check_response(response)
|
|
704
|
-
|
|
705
|
-
# Create certificate if not already present.
|
|
706
|
-
base_domain = get_base_domain([cdn_domain])
|
|
707
|
-
domain_profile = AVAILABLE_DOMAINS[base_domain]
|
|
708
|
-
domain_session = get_aws_session_or_abort(domain_profile)
|
|
709
|
-
r53_client = domain_session.client("route53")
|
|
710
|
-
cert_arn = create_required_zones_and_certs(
|
|
711
|
-
r53_client,
|
|
712
|
-
acm_client,
|
|
713
|
-
cdn_domain,
|
|
714
|
-
base_domain,
|
|
715
|
-
)
|
|
716
|
-
|
|
717
|
-
# Attach/Remove certificate from LB.
|
|
718
|
-
if delete:
|
|
719
|
-
response = elb_client.remove_listener_certificates(
|
|
720
|
-
ListenerArn=listener["ListenerArn"],
|
|
721
|
-
Certificates=[
|
|
722
|
-
{
|
|
723
|
-
"CertificateArn": cert_arn,
|
|
724
|
-
},
|
|
725
|
-
],
|
|
726
|
-
)
|
|
727
|
-
else:
|
|
728
|
-
response = elb_client.add_listener_certificates(
|
|
729
|
-
ListenerArn=listener["ListenerArn"],
|
|
730
|
-
Certificates=[
|
|
731
|
-
{
|
|
732
|
-
"CertificateArn": cert_arn,
|
|
733
|
-
},
|
|
734
|
-
],
|
|
735
|
-
)
|
|
736
|
-
check_response(response)
|
|
737
|
-
|
|
738
|
-
# List newly configured domains.
|
|
739
|
-
for cond in conditions:
|
|
740
|
-
if cond["Field"] == "host-header":
|
|
741
|
-
click.echo(
|
|
742
|
-
click.style("Domains now configured: ", fg="green")
|
|
743
|
-
+ click.style(
|
|
744
|
-
f"{cond['HostHeaderConfig']['Values']}",
|
|
745
|
-
fg="white",
|
|
746
|
-
bold=True,
|
|
747
|
-
),
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
def find_domain_rules(action, delete, project_profile, env, app, svc):
|
|
752
|
-
project_session = get_aws_session_or_abort(project_profile)
|
|
753
|
-
elb_client = project_session.client("elbv2")
|
|
754
|
-
acm_client = project_session.client("acm")
|
|
755
|
-
|
|
756
|
-
loadbalancerarn = get_load_balancer_configuration(project_session, app, env, svc)[
|
|
757
|
-
"LoadBalancers"
|
|
758
|
-
][0]["LoadBalancerArn"]
|
|
759
|
-
|
|
760
|
-
response = elb_client.describe_listeners(
|
|
761
|
-
LoadBalancerArn=loadbalancerarn,
|
|
762
|
-
)
|
|
763
|
-
check_response(response)
|
|
764
|
-
|
|
765
|
-
# Certificates and domains only need to be configured on the HTTPS listener.
|
|
766
|
-
for listener in response["Listeners"]:
|
|
767
|
-
if listener["Protocol"] == "HTTPS":
|
|
768
|
-
response = elb_client.describe_rules(
|
|
769
|
-
ListenerArn=listener["ListenerArn"],
|
|
770
|
-
)
|
|
771
|
-
# If there are multiple services there will be more than two rules
|
|
772
|
-
# (1 default, 1 custom domain).
|
|
773
|
-
if len(response["Rules"]) > 2:
|
|
774
|
-
click.echo(
|
|
775
|
-
click.style(
|
|
776
|
-
f"Note: Multiple Domains are configured on this Load Balancer.\n",
|
|
777
|
-
fg="blue",
|
|
778
|
-
bold=True,
|
|
779
|
-
),
|
|
780
|
-
)
|
|
781
|
-
|
|
782
|
-
# Get the current rule so we can update host header with new domain.
|
|
783
|
-
for rule in response["Rules"]:
|
|
784
|
-
save = True
|
|
785
|
-
rule_arn = rule["RuleArn"]
|
|
786
|
-
conditions = rule["Conditions"]
|
|
787
|
-
|
|
788
|
-
if conditions:
|
|
789
|
-
for cond in conditions:
|
|
790
|
-
# Only check if the condition is using the host header
|
|
791
|
-
if cond["Field"] == "host-header":
|
|
792
|
-
values_string = ";".join(cond["Values"])
|
|
793
|
-
click.echo(
|
|
794
|
-
click.style("Domains currently configured: ", fg="yellow")
|
|
795
|
-
+ click.style(f"{values_string}", fg="white", bold=True),
|
|
796
|
-
)
|
|
797
|
-
cdn_domain = input(
|
|
798
|
-
"Enter in domain you wish to remove (or press Enter to skip): "
|
|
799
|
-
if delete
|
|
800
|
-
else "Enter in domain you wish to add (or press Enter to skip): "
|
|
801
|
-
)
|
|
802
|
-
|
|
803
|
-
if not cdn_domain:
|
|
804
|
-
save = False
|
|
805
|
-
break
|
|
806
|
-
|
|
807
|
-
save = action(cond, cdn_domain)
|
|
808
|
-
|
|
809
|
-
if not save:
|
|
810
|
-
break
|
|
811
|
-
|
|
812
|
-
cond.pop("Values", None)
|
|
813
|
-
|
|
814
|
-
if cond["Field"] == "path-pattern":
|
|
815
|
-
cond.pop("PathPatternConfig", None)
|
|
816
|
-
|
|
817
|
-
if save:
|
|
818
|
-
update_rule(
|
|
819
|
-
delete,
|
|
820
|
-
listener,
|
|
821
|
-
conditions,
|
|
822
|
-
rule_arn,
|
|
823
|
-
elb_client,
|
|
824
|
-
acm_client,
|
|
825
|
-
cdn_domain,
|
|
826
|
-
)
|
|
827
|
-
break
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
@click.group(chain=True, cls=ClickDocOptGroup)
|
|
831
|
-
def cdn():
|
|
832
|
-
check_platform_helper_version_needs_update()
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
@cdn.command(name="assign")
|
|
836
|
-
@click.option(
|
|
837
|
-
"--project-profile", help="AWS account profile name for certificates account", required=True
|
|
838
|
-
)
|
|
839
|
-
@click.option("--env", help="AWS Copilot environment name", required=True)
|
|
840
|
-
@click.option("--app", help="Application Name", required=True)
|
|
841
|
-
@click.option("--svc", help="Service Name", required=True)
|
|
842
|
-
def cdn_assign(project_profile, env, app, svc):
|
|
843
|
-
"""Assigns a CDN domain name to application loadbalancer."""
|
|
844
|
-
|
|
845
|
-
def assign_domain(cond, cdn_domain):
|
|
846
|
-
if cdn_domain in cond["Values"]:
|
|
847
|
-
click.echo(
|
|
848
|
-
click.style(f"{cdn_domain} already exists, exiting", fg="red"),
|
|
849
|
-
)
|
|
850
|
-
save = False
|
|
851
|
-
return save
|
|
852
|
-
|
|
853
|
-
cond["HostHeaderConfig"]["Values"].append(cdn_domain)
|
|
854
|
-
save = True
|
|
855
|
-
return save
|
|
856
|
-
|
|
857
|
-
find_domain_rules(assign_domain, False, project_profile, env, app, svc)
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
@cdn.command(name="delete")
|
|
861
|
-
@click.option(
|
|
862
|
-
"--project-profile", help="AWS account profile name for certificates account", required=True
|
|
863
|
-
)
|
|
864
|
-
@click.option("--env", help="AWS Copilot environment name", required=True)
|
|
865
|
-
@click.option("--app", help="Application Name", required=True)
|
|
866
|
-
@click.option("--svc", help="Service Name", required=True)
|
|
867
|
-
# Feature to be added at later date if needed, there shouldn't be a need to remove all domains
|
|
868
|
-
# @click.option("--force", help="Force remove", is_flag=True)
|
|
869
|
-
def cdn_delete(project_profile, env, app, svc, force=False):
|
|
870
|
-
"""Assigns a CDN domain name to application loadbalancer."""
|
|
871
|
-
|
|
872
|
-
def delete_domain(cond, cdn_domain):
|
|
873
|
-
if click.confirm(
|
|
874
|
-
click.style("Are you sure you wish to delete the domain ", fg="yellow")
|
|
875
|
-
+ click.style(f"{cdn_domain}?", fg="white", bold=True),
|
|
876
|
-
):
|
|
877
|
-
# Exit if specified domain doesn't exist.
|
|
878
|
-
if cdn_domain not in cond["Values"]:
|
|
879
|
-
click.echo(
|
|
880
|
-
click.style(f"{cdn_domain} doesn't exists, exiting", fg="red"),
|
|
881
|
-
)
|
|
882
|
-
save = False
|
|
883
|
-
return save
|
|
884
|
-
|
|
885
|
-
# At present at least 1 domain needs to be on the listener.
|
|
886
|
-
if len(cond["Values"]) == 1 and not force:
|
|
887
|
-
click.echo(
|
|
888
|
-
click.style(
|
|
889
|
-
f"{cdn_domain} is the only domain configured on the "
|
|
890
|
-
+ "LoadBalancer, exiting",
|
|
891
|
-
fg="red",
|
|
892
|
-
),
|
|
893
|
-
)
|
|
894
|
-
save = False
|
|
895
|
-
return save
|
|
896
|
-
|
|
897
|
-
click.echo(
|
|
898
|
-
click.style(f"deleting {cdn_domain}", fg="green"),
|
|
899
|
-
)
|
|
900
|
-
cond["HostHeaderConfig"]["Values"].remove(cdn_domain)
|
|
901
|
-
save = True
|
|
902
|
-
return save
|
|
903
|
-
# Exit if not sure.
|
|
904
|
-
else:
|
|
905
|
-
save = False
|
|
906
|
-
return save
|
|
907
|
-
|
|
908
|
-
find_domain_rules(delete_domain, True, project_profile, env, app, svc)
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
@cdn.command(name="list")
|
|
912
|
-
@click.option(
|
|
913
|
-
"--project-profile", help="AWS account profile name for certificates account", required=True
|
|
914
|
-
)
|
|
915
|
-
@click.option("--env", help="AWS Copilot environment name", required=True)
|
|
916
|
-
@click.option("--app", help="Application Name", required=True)
|
|
917
|
-
@click.option("--svc", help="Service Name", required=True)
|
|
918
|
-
def cdn_list(project_profile, env, app, svc):
|
|
919
|
-
"""List CDN domain name attached to application loadbalancer."""
|
|
920
|
-
project_session = get_aws_session_or_abort(project_profile)
|
|
921
|
-
|
|
922
|
-
elb_client = project_session.client("elbv2")
|
|
923
|
-
|
|
924
|
-
loadbalancerarn = get_load_balancer_configuration(project_session, app, env, svc)[
|
|
925
|
-
"LoadBalancers"
|
|
926
|
-
][0]["LoadBalancerArn"]
|
|
927
|
-
|
|
928
|
-
response = elb_client.describe_listeners(
|
|
929
|
-
LoadBalancerArn=loadbalancerarn,
|
|
930
|
-
)
|
|
931
|
-
check_response(response)
|
|
932
|
-
|
|
933
|
-
for listener in response["Listeners"]:
|
|
934
|
-
if listener["Protocol"] == "HTTPS":
|
|
935
|
-
response = elb_client.describe_rules(
|
|
936
|
-
ListenerArn=listener["ListenerArn"],
|
|
937
|
-
)
|
|
938
|
-
|
|
939
|
-
# Get the current rule so we can list the configured domains.
|
|
940
|
-
for rule in response["Rules"]:
|
|
941
|
-
conditions = rule["Conditions"]
|
|
942
|
-
if conditions:
|
|
943
|
-
for cond in conditions:
|
|
944
|
-
if cond["Field"] == "host-header":
|
|
945
|
-
click.echo(
|
|
946
|
-
click.style("Domains currently configured: ", fg="yellow")
|
|
947
|
-
+ click.style(f"{cond['Values']}", fg="white", bold=True),
|
|
948
|
-
)
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if __name__ == "__main__":
|
|
952
|
-
domain()
|