dbt-platform-helper 12.5.1__py3-none-any.whl → 12.6.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 +38 -35
- dbt_platform_helper/commands/codebase.py +5 -8
- 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/pipeline.py +0 -3
- dbt_platform_helper/domain/codebase.py +13 -20
- dbt_platform_helper/domain/conduit.py +10 -12
- dbt_platform_helper/domain/config_validator.py +40 -7
- 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 +206 -193
- dbt_platform_helper/domain/pipelines.py +10 -11
- dbt_platform_helper/domain/terraform_environment.py +3 -3
- dbt_platform_helper/providers/cloudformation.py +12 -1
- dbt_platform_helper/providers/config.py +10 -12
- 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 +10 -7
- dbt_platform_helper/providers/vpc.py +81 -32
- dbt_platform_helper/templates/COMMANDS.md.jinja +5 -3
- dbt_platform_helper/templates/pipelines/codebase/overrides/package-lock.json +819 -623
- dbt_platform_helper/utils/messages.py +2 -3
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/METADATA +2 -2
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/RECORD +28 -27
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/LICENSE +0 -0
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/WHEEL +0 -0
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -11,14 +11,16 @@ import boto3
|
|
|
11
11
|
import click
|
|
12
12
|
|
|
13
13
|
from dbt_platform_helper.platform_exception import PlatformException
|
|
14
|
+
from dbt_platform_helper.providers.io import ClickIOProvider
|
|
14
15
|
from dbt_platform_helper.providers.load_balancers import ListenerNotFoundException
|
|
15
16
|
from dbt_platform_helper.providers.load_balancers import ListenerRuleNotFoundException
|
|
16
17
|
from dbt_platform_helper.providers.load_balancers import LoadBalancerNotFoundException
|
|
17
|
-
from dbt_platform_helper.providers.load_balancers import
|
|
18
|
+
from dbt_platform_helper.providers.load_balancers import (
|
|
19
|
+
get_https_listener_for_application,
|
|
20
|
+
)
|
|
18
21
|
from dbt_platform_helper.utils.application import Application
|
|
19
22
|
from dbt_platform_helper.utils.application import Environment
|
|
20
23
|
from dbt_platform_helper.utils.application import Service
|
|
21
|
-
from dbt_platform_helper.utils.application import load_application
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class MaintenancePageException(PlatformException):
|
|
@@ -26,134 +28,272 @@ class MaintenancePageException(PlatformException):
|
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
class LoadBalancedWebServiceNotFoundException(MaintenancePageException):
|
|
29
|
-
|
|
31
|
+
def __init__(self, application_name):
|
|
32
|
+
super().__init__(f"No services deployed yet to {application_name} ")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_maintenance_page_type(session: boto3.Session, listener_arn: str) -> Union[str, None]:
|
|
36
|
+
lb_client = session.client("elbv2")
|
|
37
|
+
|
|
38
|
+
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
39
|
+
tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
|
|
40
|
+
|
|
41
|
+
maintenance_page_type = None
|
|
42
|
+
for description in tag_descriptions:
|
|
43
|
+
tags = {t["Key"]: t["Value"] for t in description["Tags"]}
|
|
44
|
+
if tags.get("name") == "MaintenancePage":
|
|
45
|
+
maintenance_page_type = tags.get("type")
|
|
46
|
+
|
|
47
|
+
return maintenance_page_type
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_env_ips(vpc: str, application_environment: Environment) -> List[str]:
|
|
51
|
+
account_name = f"{application_environment.session.profile_name}-vpc"
|
|
52
|
+
vpc_name = vpc if vpc else account_name
|
|
53
|
+
ssm_client = application_environment.session.client("ssm")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
param_value = ssm_client.get_parameter(Name=f"/{vpc_name}/EGRESS_IPS")["Parameter"]["Value"]
|
|
57
|
+
except ssm_client.exceptions.ParameterNotFound:
|
|
58
|
+
click.secho(f"No parameter found with name: /{vpc_name}/EGRESS_IPS")
|
|
59
|
+
raise click.Abort
|
|
60
|
+
|
|
61
|
+
return [ip.strip() for ip in param_value.split(",")]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def add_maintenance_page(
|
|
65
|
+
session: boto3.Session,
|
|
66
|
+
listener_arn: str,
|
|
67
|
+
app: str,
|
|
68
|
+
env: str,
|
|
69
|
+
services: List[Service],
|
|
70
|
+
allowed_ips: tuple,
|
|
71
|
+
template: str = "default",
|
|
72
|
+
):
|
|
73
|
+
lb_client = session.client("elbv2")
|
|
74
|
+
maintenance_page_content = get_maintenance_page_template(template)
|
|
75
|
+
bypass_value = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
|
76
|
+
|
|
77
|
+
rule_priority = itertools.count(start=1)
|
|
78
|
+
|
|
79
|
+
for svc in services:
|
|
80
|
+
target_group_arn = find_target_group(app, env, svc.name, session)
|
|
81
|
+
|
|
82
|
+
# not all of an application's services are guaranteed to have been deployed to an environment
|
|
83
|
+
if not target_group_arn:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
for ip in allowed_ips:
|
|
87
|
+
create_header_rule(
|
|
88
|
+
lb_client,
|
|
89
|
+
listener_arn,
|
|
90
|
+
target_group_arn,
|
|
91
|
+
"X-Forwarded-For",
|
|
92
|
+
[ip],
|
|
93
|
+
"AllowedIps",
|
|
94
|
+
next(rule_priority),
|
|
95
|
+
)
|
|
96
|
+
create_source_ip_rule(
|
|
97
|
+
lb_client,
|
|
98
|
+
listener_arn,
|
|
99
|
+
target_group_arn,
|
|
100
|
+
[ip],
|
|
101
|
+
"AllowedSourceIps",
|
|
102
|
+
next(rule_priority),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
create_header_rule(
|
|
106
|
+
lb_client,
|
|
107
|
+
listener_arn,
|
|
108
|
+
target_group_arn,
|
|
109
|
+
"Bypass-Key",
|
|
110
|
+
[bypass_value],
|
|
111
|
+
"BypassIpFilter",
|
|
112
|
+
next(rule_priority),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
click.secho(
|
|
116
|
+
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/",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
lb_client.create_rule(
|
|
120
|
+
ListenerArn=listener_arn,
|
|
121
|
+
Priority=next(rule_priority),
|
|
122
|
+
Conditions=[
|
|
123
|
+
{
|
|
124
|
+
"Field": "path-pattern",
|
|
125
|
+
"PathPatternConfig": {"Values": ["/*"]},
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
Actions=[
|
|
129
|
+
{
|
|
130
|
+
"Type": "fixed-response",
|
|
131
|
+
"FixedResponseConfig": {
|
|
132
|
+
"StatusCode": "503",
|
|
133
|
+
"ContentType": "text/html",
|
|
134
|
+
"MessageBody": maintenance_page_content,
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
Tags=[
|
|
139
|
+
{"Key": "name", "Value": "MaintenancePage"},
|
|
140
|
+
{"Key": "type", "Value": template},
|
|
141
|
+
],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def remove_maintenance_page(session: boto3.Session, listener_arn: str):
|
|
146
|
+
lb_client = session.client("elbv2")
|
|
147
|
+
|
|
148
|
+
rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
|
|
149
|
+
tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
|
|
150
|
+
|
|
151
|
+
for name in ["MaintenancePage", "AllowedIps", "BypassIpFilter", "AllowedSourceIps"]:
|
|
152
|
+
deleted = delete_listener_rule(tag_descriptions, name, lb_client)
|
|
153
|
+
|
|
154
|
+
if name == "MaintenancePage" and not deleted:
|
|
155
|
+
raise ListenerRuleNotFoundException()
|
|
30
156
|
|
|
31
157
|
|
|
32
158
|
class MaintenancePage:
|
|
33
159
|
def __init__(
|
|
34
160
|
self,
|
|
35
|
-
|
|
36
|
-
|
|
161
|
+
application: Application,
|
|
162
|
+
io: ClickIOProvider = ClickIOProvider(),
|
|
163
|
+
get_https_listener_for_application: Callable[
|
|
164
|
+
[boto3.Session, str, str], str
|
|
165
|
+
] = get_https_listener_for_application,
|
|
166
|
+
# TODO refactor get_maintenance_page_type, add_maintenance_page, remove_maintenance_page into MaintenancePage class with LoadBalancerProvider as the dependency
|
|
167
|
+
get_maintenance_page_type: Callable[
|
|
168
|
+
[boto3.Session, str], Union[str, None]
|
|
169
|
+
] = get_maintenance_page_type,
|
|
170
|
+
get_env_ips: Callable[[str, Environment], List[str]] = get_env_ips,
|
|
171
|
+
add_maintenance_page: Callable[
|
|
172
|
+
[boto3.Session, str, str, str, List[Service], tuple, str], None
|
|
173
|
+
] = add_maintenance_page,
|
|
174
|
+
remove_maintenance_page: Callable[[boto3.Session, str], None] = remove_maintenance_page,
|
|
37
175
|
):
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
|
|
41
|
-
|
|
176
|
+
self.application = application
|
|
177
|
+
self.get_https_listener_for_application = get_https_listener_for_application
|
|
178
|
+
self.io = io
|
|
179
|
+
self.get_maintenance_page_type = get_maintenance_page_type
|
|
180
|
+
self.get_env_ips = get_env_ips
|
|
181
|
+
self.add_maintenance_page = add_maintenance_page
|
|
182
|
+
self.remove_maintenance_page = remove_maintenance_page
|
|
183
|
+
|
|
184
|
+
def _get_deployed_load_balanced_web_services(self, app: Application, svc: List[str]):
|
|
42
185
|
if "*" in svc:
|
|
43
186
|
services = [s for s in app.services.values() if s.kind == "Load Balanced Web Service"]
|
|
44
187
|
else:
|
|
45
|
-
all_services = [get_app_service(app
|
|
188
|
+
all_services = [get_app_service(app, s) for s in list(svc)]
|
|
46
189
|
services = [s for s in all_services if s.kind == "Load Balanced Web Service"]
|
|
47
190
|
if not services:
|
|
48
|
-
raise LoadBalancedWebServiceNotFoundException
|
|
191
|
+
raise LoadBalancedWebServiceNotFoundException(app.name)
|
|
49
192
|
return services
|
|
50
193
|
|
|
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)
|
|
194
|
+
def activate(self, env: str, services: List[str], template: str, vpc: Union[str, None]):
|
|
195
|
+
|
|
196
|
+
services = self._get_deployed_load_balanced_web_services(self.application, services)
|
|
197
|
+
application_environment = get_app_environment(self.application, env)
|
|
198
|
+
|
|
62
199
|
try:
|
|
63
|
-
https_listener =
|
|
64
|
-
|
|
200
|
+
https_listener = self.get_https_listener_for_application(
|
|
201
|
+
application_environment.session, self.application.name, env
|
|
202
|
+
)
|
|
203
|
+
current_maintenance_page = self.get_maintenance_page_type(
|
|
65
204
|
application_environment.session, https_listener
|
|
66
205
|
)
|
|
67
206
|
remove_current_maintenance_page = False
|
|
68
207
|
if current_maintenance_page:
|
|
69
|
-
remove_current_maintenance_page = self.
|
|
208
|
+
remove_current_maintenance_page = self.io.confirm(
|
|
70
209
|
f"There is currently a '{current_maintenance_page}' maintenance page for the {env} "
|
|
71
|
-
f"environment in {
|
|
210
|
+
f"environment in {self.application.name}.\nWould you like to replace it with a '{template}' "
|
|
72
211
|
f"maintenance page?"
|
|
73
212
|
)
|
|
74
213
|
if not remove_current_maintenance_page:
|
|
75
|
-
|
|
214
|
+
return
|
|
76
215
|
|
|
77
|
-
if remove_current_maintenance_page or self.
|
|
216
|
+
if remove_current_maintenance_page or self.io.confirm(
|
|
78
217
|
f"You are about to enable the '{template}' maintenance page for the {env} "
|
|
79
|
-
f"environment in {
|
|
218
|
+
f"environment in {self.application.name}.\nWould you like to continue?"
|
|
80
219
|
):
|
|
81
220
|
if current_maintenance_page and remove_current_maintenance_page:
|
|
82
|
-
remove_maintenance_page(application_environment.session, https_listener)
|
|
221
|
+
self.remove_maintenance_page(application_environment.session, https_listener)
|
|
83
222
|
|
|
84
|
-
allowed_ips = get_env_ips(vpc, application_environment)
|
|
223
|
+
allowed_ips = self.get_env_ips(vpc, application_environment)
|
|
85
224
|
|
|
86
|
-
add_maintenance_page(
|
|
225
|
+
self.add_maintenance_page(
|
|
87
226
|
application_environment.session,
|
|
88
227
|
https_listener,
|
|
89
|
-
|
|
228
|
+
self.application.name,
|
|
90
229
|
env,
|
|
91
230
|
services,
|
|
92
231
|
allowed_ips,
|
|
93
232
|
template,
|
|
94
233
|
)
|
|
95
|
-
self.
|
|
96
|
-
f"Maintenance page '{template}' added for environment {env} in application {
|
|
97
|
-
fg="green",
|
|
234
|
+
self.io.info(
|
|
235
|
+
f"Maintenance page '{template}' added for environment {env} in application {self.application.name}",
|
|
98
236
|
)
|
|
99
|
-
else:
|
|
100
|
-
raise click.Abort
|
|
101
237
|
|
|
102
238
|
except LoadBalancerNotFoundException:
|
|
103
|
-
|
|
104
|
-
|
|
239
|
+
# TODO push exception to command layer
|
|
240
|
+
self.io.abort_with_error(
|
|
241
|
+
f"No load balancer found for environment {env} in the application {self.application.name}.",
|
|
105
242
|
)
|
|
106
|
-
raise click.Abort
|
|
107
243
|
|
|
108
244
|
except ListenerNotFoundException:
|
|
109
|
-
|
|
110
|
-
|
|
245
|
+
# TODO push exception to command layer
|
|
246
|
+
self.io.abort_with_error(
|
|
247
|
+
f"No HTTPS listener found for environment {env} in the application {self.application.name}.",
|
|
111
248
|
)
|
|
112
|
-
raise click.Abort
|
|
113
249
|
|
|
114
|
-
def deactivate(self,
|
|
115
|
-
application_environment = get_app_environment(
|
|
250
|
+
def deactivate(self, env: str):
|
|
251
|
+
application_environment = get_app_environment(self.application, env)
|
|
116
252
|
|
|
117
253
|
try:
|
|
118
|
-
https_listener =
|
|
119
|
-
|
|
254
|
+
https_listener = self.get_https_listener_for_application(
|
|
255
|
+
application_environment.session, self.application.name, env
|
|
256
|
+
)
|
|
257
|
+
current_maintenance_page = self.get_maintenance_page_type(
|
|
120
258
|
application_environment.session, https_listener
|
|
121
259
|
)
|
|
260
|
+
|
|
261
|
+
# TODO discuss, reduce number of return statements but more nested if statements
|
|
122
262
|
if not current_maintenance_page:
|
|
123
|
-
self.
|
|
124
|
-
|
|
263
|
+
self.io.warn("There is no current maintenance page to remove")
|
|
264
|
+
return
|
|
125
265
|
|
|
126
|
-
if not self.
|
|
266
|
+
if not self.io.confirm(
|
|
127
267
|
f"There is currently a '{current_maintenance_page}' maintenance page, "
|
|
128
268
|
f"would you like to remove it?"
|
|
129
269
|
):
|
|
130
|
-
|
|
270
|
+
return
|
|
131
271
|
|
|
132
|
-
remove_maintenance_page(application_environment.session, https_listener)
|
|
133
|
-
self.
|
|
134
|
-
f"Maintenance page removed from environment {env} in application {
|
|
272
|
+
self.remove_maintenance_page(application_environment.session, https_listener)
|
|
273
|
+
self.io.info(
|
|
274
|
+
f"Maintenance page removed from environment {env} in application {self.application.name}",
|
|
135
275
|
)
|
|
136
276
|
|
|
137
277
|
except LoadBalancerNotFoundException:
|
|
138
|
-
|
|
139
|
-
|
|
278
|
+
# TODO push exception to command layer
|
|
279
|
+
self.io.abort_with_error(
|
|
280
|
+
f"No load balancer found for environment {env} in the application {self.application.name}.",
|
|
140
281
|
)
|
|
141
|
-
raise click.Abort
|
|
142
282
|
|
|
143
283
|
except ListenerNotFoundException:
|
|
144
|
-
|
|
145
|
-
|
|
284
|
+
# TODO push exception to command layer
|
|
285
|
+
self.io.abort_with_error(
|
|
286
|
+
f"No HTTPS listener found for environment {env} in the application {self.application.name}.",
|
|
146
287
|
)
|
|
147
|
-
raise click.Abort
|
|
148
288
|
|
|
149
289
|
|
|
150
|
-
def get_app_service(
|
|
151
|
-
application = load_application(app_name)
|
|
290
|
+
def get_app_service(application: Application, svc_name: str) -> Service:
|
|
152
291
|
application_service = application.services.get(svc_name)
|
|
153
292
|
|
|
154
293
|
if not application_service:
|
|
294
|
+
# TODO raise exception instead of abort
|
|
155
295
|
click.secho(
|
|
156
|
-
f"The service {svc_name} was not found in the application {
|
|
296
|
+
f"The service {svc_name} was not found in the application {application.name}. "
|
|
157
297
|
f"It either does not exist, or has not been deployed.",
|
|
158
298
|
fg="red",
|
|
159
299
|
)
|
|
@@ -162,13 +302,12 @@ def get_app_service(app_name: str, svc_name: str) -> Service:
|
|
|
162
302
|
return application_service
|
|
163
303
|
|
|
164
304
|
|
|
165
|
-
def get_app_environment(
|
|
166
|
-
application = load_application(app_name)
|
|
305
|
+
def get_app_environment(application: Application, env_name: str) -> Environment:
|
|
167
306
|
application_environment = application.environments.get(env_name)
|
|
168
307
|
|
|
169
308
|
if not application_environment:
|
|
170
309
|
click.secho(
|
|
171
|
-
f"The environment {env_name} was not found in the application {
|
|
310
|
+
f"The environment {env_name} was not found in the application {application.name}. "
|
|
172
311
|
f"It either does not exist, or has not been deployed.",
|
|
173
312
|
fg="red",
|
|
174
313
|
)
|
|
@@ -177,36 +316,6 @@ def get_app_environment(app_name: str, env_name: str) -> Environment:
|
|
|
177
316
|
return application_environment
|
|
178
317
|
|
|
179
318
|
|
|
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
319
|
def get_rules_tag_descriptions(rules: list, lb_client):
|
|
211
320
|
tag_descriptions = []
|
|
212
321
|
chunk_size = 20
|
|
@@ -233,88 +342,6 @@ def delete_listener_rule(tag_descriptions: list, tag_name: str, lb_client: boto3
|
|
|
233
342
|
return current_rule_arn
|
|
234
343
|
|
|
235
344
|
|
|
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
345
|
def get_maintenance_page_template(template) -> str:
|
|
319
346
|
template_contents = (
|
|
320
347
|
Path(__file__)
|
|
@@ -472,17 +499,3 @@ def get_host_conditions(lb_client: boto3.client, listener_arn: str, target_group
|
|
|
472
499
|
]
|
|
473
500
|
|
|
474
501
|
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(",")]
|
|
@@ -8,6 +8,7 @@ from dbt_platform_helper.constants import ENVIRONMENT_PIPELINES_KEY
|
|
|
8
8
|
from dbt_platform_helper.constants import ENVIRONMENTS_KEY
|
|
9
9
|
from dbt_platform_helper.providers.config import ConfigProvider
|
|
10
10
|
from dbt_platform_helper.providers.files import FileProvider
|
|
11
|
+
from dbt_platform_helper.providers.io import ClickIOProvider
|
|
11
12
|
from dbt_platform_helper.utils.application import get_application_name
|
|
12
13
|
from dbt_platform_helper.utils.aws import get_account_details
|
|
13
14
|
from dbt_platform_helper.utils.aws import get_public_repository_arn
|
|
@@ -22,16 +23,14 @@ class Pipelines:
|
|
|
22
23
|
def __init__(
|
|
23
24
|
self,
|
|
24
25
|
config_provider: ConfigProvider,
|
|
25
|
-
echo: Callable[[str], str],
|
|
26
|
-
abort: Callable[[str], None],
|
|
27
26
|
get_git_remote: Callable[[], str],
|
|
28
27
|
get_codestar_arn: Callable[[str], str],
|
|
28
|
+
io: ClickIOProvider = ClickIOProvider(),
|
|
29
29
|
):
|
|
30
30
|
self.config_provider = config_provider
|
|
31
|
-
self.echo = echo
|
|
32
|
-
self.abort = abort
|
|
33
31
|
self.get_git_remote = get_git_remote
|
|
34
32
|
self.get_codestar_arn = get_codestar_arn
|
|
33
|
+
self.io = io
|
|
35
34
|
|
|
36
35
|
def generate(self, terraform_platform_modules_version, deploy_branch):
|
|
37
36
|
pipeline_config = self.config_provider.load_and_validate_platform_config()
|
|
@@ -40,7 +39,7 @@ class Pipelines:
|
|
|
40
39
|
has_environment_pipelines = ENVIRONMENT_PIPELINES_KEY in pipeline_config
|
|
41
40
|
|
|
42
41
|
if not (has_codebase_pipelines or has_environment_pipelines):
|
|
43
|
-
self.
|
|
42
|
+
self.io.warn("No pipelines defined: nothing to do.")
|
|
44
43
|
return
|
|
45
44
|
|
|
46
45
|
platform_config_terraform_modules_default_version = pipeline_config.get(
|
|
@@ -52,11 +51,11 @@ class Pipelines:
|
|
|
52
51
|
|
|
53
52
|
git_repo = self.get_git_remote()
|
|
54
53
|
if not git_repo:
|
|
55
|
-
self.
|
|
54
|
+
self.io.abort_with_error("The current directory is not a git repository")
|
|
56
55
|
|
|
57
56
|
codestar_connection_arn = self.get_codestar_arn(app_name)
|
|
58
57
|
if codestar_connection_arn is None:
|
|
59
|
-
self.
|
|
58
|
+
self.io.abort_with_error(f'There is no CodeStar Connection named "{app_name}" to use')
|
|
60
59
|
|
|
61
60
|
base_path = Path(".")
|
|
62
61
|
copilot_pipelines_dir = base_path / f"copilot/pipelines"
|
|
@@ -93,7 +92,7 @@ class Pipelines:
|
|
|
93
92
|
|
|
94
93
|
def _clean_pipeline_config(self, pipelines_dir):
|
|
95
94
|
if pipelines_dir.exists():
|
|
96
|
-
self.
|
|
95
|
+
self.io.info("Deleting copilot/pipelines directory.")
|
|
97
96
|
rmtree(pipelines_dir)
|
|
98
97
|
|
|
99
98
|
def _generate_codebase_pipeline(
|
|
@@ -152,7 +151,7 @@ class Pipelines:
|
|
|
152
151
|
message = FileProvider.mkfile(
|
|
153
152
|
base_path, pipelines_dir / file_name, contents, overwrite=True
|
|
154
153
|
)
|
|
155
|
-
self.
|
|
154
|
+
self.io.info(message)
|
|
156
155
|
|
|
157
156
|
def _generate_terraform_environment_pipeline_manifest(
|
|
158
157
|
self,
|
|
@@ -181,7 +180,7 @@ class Pipelines:
|
|
|
181
180
|
dir_path = f"terraform/environment-pipelines/{aws_account}"
|
|
182
181
|
makedirs(dir_path, exist_ok=True)
|
|
183
182
|
|
|
184
|
-
self.
|
|
183
|
+
self.io.info(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
|
|
185
184
|
|
|
186
185
|
def generate_terraform_codebase_pipeline_manifest(
|
|
187
186
|
self,
|
|
@@ -210,4 +209,4 @@ class Pipelines:
|
|
|
210
209
|
dir_path = f"terraform/environment-pipelines/{aws_account}"
|
|
211
210
|
makedirs(dir_path, exist_ok=True)
|
|
212
211
|
|
|
213
|
-
self.
|
|
212
|
+
self.io.info(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
|
|
@@ -56,9 +56,9 @@ class TerraformEnvironment:
|
|
|
56
56
|
self,
|
|
57
57
|
config_provider,
|
|
58
58
|
manifest_generator: PlatformTerraformManifestGenerator = None,
|
|
59
|
-
|
|
59
|
+
echo=click.echo,
|
|
60
60
|
):
|
|
61
|
-
self.echo =
|
|
61
|
+
self.echo = echo
|
|
62
62
|
self.config_provider = config_provider
|
|
63
63
|
self.manifest_generator = manifest_generator or PlatformTerraformManifestGenerator(
|
|
64
64
|
FileProvider()
|
|
@@ -69,7 +69,7 @@ class TerraformEnvironment:
|
|
|
69
69
|
|
|
70
70
|
if environment_name not in config.get("environments").keys():
|
|
71
71
|
raise EnvironmentNotFoundException(
|
|
72
|
-
f"
|
|
72
|
+
f"cannot generate terraform for environment {environment_name}. It does not exist in your configuration"
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
manifest = self.manifest_generator.generate_manifest(
|
|
@@ -8,7 +8,8 @@ from dbt_platform_helper.platform_exception import PlatformException
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class CloudFormation:
|
|
11
|
-
|
|
11
|
+
# TODO add handling for optional client parameters to handle case of calling boto API with None
|
|
12
|
+
def __init__(self, cloudformation_client, iam_client=None, ssm_client=None):
|
|
12
13
|
self.cloudformation_client = cloudformation_client
|
|
13
14
|
self.iam_client = iam_client
|
|
14
15
|
self.ssm_client = ssm_client
|
|
@@ -125,6 +126,16 @@ class CloudFormation:
|
|
|
125
126
|
stack_name, f"Error while waiting for stack status: {str(err)}"
|
|
126
127
|
)
|
|
127
128
|
|
|
129
|
+
def get_cloudformation_exports_for_environment(self, environment_name):
|
|
130
|
+
exports = []
|
|
131
|
+
|
|
132
|
+
for page in self.cloudformation_client.get_paginator("list_exports").paginate():
|
|
133
|
+
for export in page["Exports"]:
|
|
134
|
+
if f"-{environment_name}-" in export["Name"]:
|
|
135
|
+
exports.append(export)
|
|
136
|
+
|
|
137
|
+
return exports
|
|
138
|
+
|
|
128
139
|
|
|
129
140
|
class CloudFormationException(PlatformException):
|
|
130
141
|
def __init__(self, stack_name: str, current_status: str):
|