dbt-platform-helper 13.1.0__py3-none-any.whl → 13.1.2__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.

Files changed (30) hide show
  1. dbt_platform_helper/commands/application.py +4 -4
  2. dbt_platform_helper/commands/codebase.py +4 -4
  3. dbt_platform_helper/commands/conduit.py +4 -4
  4. dbt_platform_helper/commands/config.py +7 -5
  5. dbt_platform_helper/commands/copilot.py +12 -391
  6. dbt_platform_helper/commands/environment.py +4 -4
  7. dbt_platform_helper/commands/generate.py +1 -1
  8. dbt_platform_helper/commands/notify.py +4 -4
  9. dbt_platform_helper/commands/pipeline.py +4 -4
  10. dbt_platform_helper/commands/secrets.py +4 -4
  11. dbt_platform_helper/commands/version.py +1 -1
  12. dbt_platform_helper/domain/codebase.py +4 -9
  13. dbt_platform_helper/domain/copilot.py +394 -0
  14. dbt_platform_helper/domain/copilot_environment.py +6 -6
  15. dbt_platform_helper/domain/maintenance_page.py +193 -424
  16. dbt_platform_helper/domain/versioning.py +67 -0
  17. dbt_platform_helper/providers/io.py +14 -0
  18. dbt_platform_helper/providers/load_balancers.py +258 -43
  19. dbt_platform_helper/providers/platform_config_schema.py +3 -1
  20. dbt_platform_helper/providers/platform_helper_versioning.py +107 -0
  21. dbt_platform_helper/providers/semantic_version.py +27 -7
  22. dbt_platform_helper/providers/version.py +24 -0
  23. dbt_platform_helper/utils/application.py +14 -0
  24. dbt_platform_helper/utils/files.py +6 -0
  25. dbt_platform_helper/utils/versioning.py +11 -158
  26. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-13.1.2.dist-info}/METADATA +3 -4
  27. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-13.1.2.dist-info}/RECORD +30 -27
  28. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-13.1.2.dist-info}/LICENSE +0 -0
  29. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-13.1.2.dist-info}/WHEEL +0 -0
  30. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-13.1.2.dist-info}/entry_points.txt +0 -0
@@ -8,18 +8,17 @@ from typing import Callable
8
8
  from typing import List
9
9
  from typing import Union
10
10
 
11
- import boto3
12
11
  import click
13
12
 
14
13
  from dbt_platform_helper.platform_exception import PlatformException
15
14
  from dbt_platform_helper.providers.io import ClickIOProvider
16
- from dbt_platform_helper.providers.load_balancers import ListenerNotFoundException
17
15
  from dbt_platform_helper.providers.load_balancers import ListenerRuleNotFoundException
18
- from dbt_platform_helper.providers.load_balancers import LoadBalancerNotFoundException
19
- from dbt_platform_helper.providers.load_balancers import (
20
- get_https_listener_for_application,
21
- )
16
+ from dbt_platform_helper.providers.load_balancers import LoadBalancerProvider
22
17
  from dbt_platform_helper.utils.application import Application
18
+ from dbt_platform_helper.utils.application import (
19
+ ApplicationEnvironmentNotFoundException,
20
+ )
21
+ from dbt_platform_helper.utils.application import ApplicationServiceNotFoundException
23
22
  from dbt_platform_helper.utils.application import Environment
24
23
  from dbt_platform_helper.utils.application import Service
25
24
 
@@ -55,21 +54,7 @@ class FailedToActivateMaintenancePageException(MaintenancePageException):
55
54
  )
56
55
 
57
56
 
58
- def get_maintenance_page_type(session: boto3.Session, listener_arn: str) -> Union[str, None]:
59
- lb_client = session.client("elbv2")
60
-
61
- rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
62
- tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
63
-
64
- maintenance_page_type = None
65
- for description in tag_descriptions:
66
- tags = {t["Key"]: t["Value"] for t in description["Tags"]}
67
- if tags.get("name") == "MaintenancePage":
68
- maintenance_page_type = tags.get("type")
69
-
70
- return maintenance_page_type
71
-
72
-
57
+ # TODO should this be in its own provider, inside the VPC one, what logic is this sepcific too?
73
58
  def get_env_ips(vpc: str, application_environment: Environment) -> List[str]:
74
59
  account_name = f"{application_environment.session.profile_name}-vpc"
75
60
  vpc_name = vpc if vpc else account_name
@@ -84,164 +69,19 @@ def get_env_ips(vpc: str, application_environment: Environment) -> List[str]:
84
69
  return [ip.strip() for ip in param_value.split(",")]
85
70
 
86
71
 
87
- def add_maintenance_page(
88
- session: boto3.Session,
89
- listener_arn: str,
90
- app: str,
91
- env: str,
92
- services: List[Service],
93
- allowed_ips: List[str],
94
- template: str = "default",
95
- ):
96
- lb_client = session.client("elbv2")
97
- maintenance_page_content = get_maintenance_page_template(template)
98
- bypass_value = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
99
-
100
- rule_priority = itertools.count(start=1)
101
- maintenance_page_host_header_conditions = []
102
- try:
103
- for svc in services:
104
- target_group_arn = find_target_group(app, env, svc.name, session)
105
-
106
- # not all of an application's services are guaranteed to have been deployed to an environment
107
- if not target_group_arn:
108
- continue
109
-
110
- service_conditions = get_host_header_conditions(
111
- lb_client, listener_arn, target_group_arn
112
- )
113
-
114
- for ip in allowed_ips:
115
- create_header_rule(
116
- lb_client,
117
- listener_arn,
118
- target_group_arn,
119
- "X-Forwarded-For",
120
- [ip],
121
- "AllowedIps",
122
- next(rule_priority),
123
- service_conditions,
124
- )
125
- create_source_ip_rule(
126
- lb_client,
127
- listener_arn,
128
- target_group_arn,
129
- [ip],
130
- "AllowedSourceIps",
131
- next(rule_priority),
132
- service_conditions,
133
- )
134
-
135
- create_header_rule(
136
- lb_client,
137
- listener_arn,
138
- target_group_arn,
139
- "Bypass-Key",
140
- [bypass_value],
141
- "BypassIpFilter",
142
- next(rule_priority),
143
- service_conditions,
144
- )
145
-
146
- # add to accumilating list of conditions for maintenace page rule
147
- maintenance_page_host_header_conditions.extend(service_conditions)
148
-
149
- click.secho(
150
- 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/",
151
- )
152
-
153
- lb_client.create_rule(
154
- ListenerArn=listener_arn,
155
- Priority=next(rule_priority),
156
- Conditions=[
157
- {
158
- "Field": "path-pattern",
159
- "PathPatternConfig": {"Values": ["/*"]},
160
- },
161
- {
162
- "Field": "host-header",
163
- "HostHeaderConfig": {
164
- "Values": sorted(
165
- list(
166
- {
167
- value
168
- for condition in maintenance_page_host_header_conditions
169
- for value in condition["HostHeaderConfig"]["Values"]
170
- }
171
- )
172
- )
173
- },
174
- },
175
- ],
176
- Actions=[
177
- {
178
- "Type": "fixed-response",
179
- "FixedResponseConfig": {
180
- "StatusCode": "503",
181
- "ContentType": "text/html",
182
- "MessageBody": maintenance_page_content,
183
- },
184
- }
185
- ],
186
- Tags=[
187
- {"Key": "name", "Value": "MaintenancePage"},
188
- {"Key": "type", "Value": template},
189
- ],
190
- )
191
- except Exception as e:
192
- deleted_rules = clean_up_maintenance_page_rules(session, listener_arn)
193
- raise FailedToActivateMaintenancePageException(
194
- app, env, f"{e}:\n {traceback.format_exc()}", deleted_rules
195
- )
196
-
197
-
198
- def clean_up_maintenance_page_rules(
199
- session: boto3.Session, listener_arn: str, fail_when_not_deleted: bool = False
200
- ) -> dict[str, bool]:
201
- lb_client = session.client("elbv2")
202
-
203
- rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
204
- tag_descriptions = get_rules_tag_descriptions(rules, lb_client)
205
-
206
- deletes = {}
207
- for name in ["MaintenancePage", "AllowedIps", "BypassIpFilter", "AllowedSourceIps"]:
208
- deleted = delete_listener_rule(tag_descriptions, name, lb_client)
209
- deletes[name] = bool(deleted)
210
- if fail_when_not_deleted and name == "MaintenancePage" and not deleted:
211
- raise ListenerRuleNotFoundException()
212
-
213
- return deletes
214
-
215
-
216
- def remove_maintenance_page(session: boto3.Session, listener_arn: str):
217
- clean_up_maintenance_page_rules(session, listener_arn, True)
218
-
219
-
220
72
  class MaintenancePage:
221
73
  def __init__(
222
74
  self,
223
75
  application: Application,
224
76
  io: ClickIOProvider = ClickIOProvider(),
225
- get_https_listener_for_application: Callable[
226
- [boto3.Session, str, str], str
227
- ] = get_https_listener_for_application,
228
- # TODO refactor get_maintenance_page_type, add_maintenance_page, remove_maintenance_page into MaintenancePage class with LoadBalancerProvider as the dependency
229
- get_maintenance_page_type: Callable[
230
- [boto3.Session, str], Union[str, None]
231
- ] = get_maintenance_page_type,
77
+ load_balancer_provider: LoadBalancerProvider = LoadBalancerProvider,
232
78
  get_env_ips: Callable[[str, Environment], List[str]] = get_env_ips,
233
- add_maintenance_page: Callable[
234
- [boto3.Session, str, str, str, List[Service], tuple, str], None
235
- ] = add_maintenance_page,
236
- remove_maintenance_page: Callable[[boto3.Session, str], None] = remove_maintenance_page,
237
79
  ):
238
80
  self.application = application
239
- self.get_https_listener_for_application = get_https_listener_for_application
240
81
  self.io = io
241
- self.get_maintenance_page_type = get_maintenance_page_type
82
+ self.load_balancer_provider = load_balancer_provider # TODO requires session from environment in application object which is only known during method execution
83
+ self.load_balancer: LoadBalancerProvider = None
242
84
  self.get_env_ips = get_env_ips
243
- self.add_maintenance_page = add_maintenance_page
244
- self.remove_maintenance_page = remove_maintenance_page
245
85
 
246
86
  def _get_deployed_load_balanced_web_services(self, app: Application, svc: List[str]):
247
87
  if "*" in svc:
@@ -253,155 +93,229 @@ class MaintenancePage:
253
93
  raise LoadBalancedWebServiceNotFoundException(app.name)
254
94
  return services
255
95
 
96
+ # TODO: inject load balancer provider in activate method to avoid passing load balancer provider in init?
256
97
  def activate(self, env: str, services: List[str], template: str, vpc: Union[str, None]):
257
98
 
258
99
  services = self._get_deployed_load_balanced_web_services(self.application, services)
259
100
  application_environment = get_app_environment(self.application, env)
101
+ self.load_balancer = self.load_balancer_provider(application_environment.session)
260
102
 
261
- try:
262
- https_listener = self.get_https_listener_for_application(
263
- application_environment.session, self.application.name, env
264
- )
265
- current_maintenance_page = self.get_maintenance_page_type(
266
- application_environment.session, https_listener
103
+ https_listener = self.load_balancer.get_https_listener_for_application(
104
+ self.application.name, env
105
+ )
106
+ current_maintenance_page = self.__get_maintenance_page_type(https_listener)
107
+ remove_current_maintenance_page = False
108
+ if current_maintenance_page:
109
+ remove_current_maintenance_page = self.io.confirm(
110
+ f"There is currently a '{current_maintenance_page}' maintenance page for the {env} "
111
+ f"environment in {self.application.name}.\nWould you like to replace it with a '{template}' "
112
+ f"maintenance page?"
267
113
  )
268
- remove_current_maintenance_page = False
269
- if current_maintenance_page:
270
- remove_current_maintenance_page = self.io.confirm(
271
- f"There is currently a '{current_maintenance_page}' maintenance page for the {env} "
272
- f"environment in {self.application.name}.\nWould you like to replace it with a '{template}' "
273
- f"maintenance page?"
274
- )
275
- if not remove_current_maintenance_page:
276
- return
277
-
278
- if remove_current_maintenance_page or self.io.confirm(
279
- f"You are about to enable the '{template}' maintenance page for the {env} "
280
- f"environment in {self.application.name}.\nWould you like to continue?"
281
- ):
282
- if current_maintenance_page and remove_current_maintenance_page:
283
- self.remove_maintenance_page(application_environment.session, https_listener)
284
-
285
- allowed_ips = self.get_env_ips(vpc, application_environment)
286
-
287
- self.add_maintenance_page(
288
- application_environment.session,
289
- https_listener,
290
- self.application.name,
291
- env,
292
- services,
293
- allowed_ips,
294
- template,
295
- )
296
- self.io.info(
297
- f"Maintenance page '{template}' added for environment {env} in application {self.application.name}",
298
- )
114
+ if not remove_current_maintenance_page:
115
+ return
299
116
 
300
- except LoadBalancerNotFoundException:
301
- # TODO push exception to command layer
302
- self.io.abort_with_error(
303
- f"No load balancer found for environment {env} in the application {self.application.name}.",
117
+ if remove_current_maintenance_page or self.io.confirm(
118
+ f"You are about to enable the '{template}' maintenance page for the {env} "
119
+ f"environment in {self.application.name}.\nWould you like to continue?"
120
+ ):
121
+ if current_maintenance_page and remove_current_maintenance_page:
122
+ self.__remove_maintenance_page(https_listener)
123
+
124
+ allowed_ips = self.get_env_ips(vpc, application_environment)
125
+
126
+ self.add_maintenance_page(
127
+ https_listener,
128
+ self.application.name,
129
+ env,
130
+ services,
131
+ allowed_ips,
132
+ template,
304
133
  )
305
-
306
- except ListenerNotFoundException:
307
- # TODO push exception to command layer
308
- self.io.abort_with_error(
309
- f"No HTTPS listener found for environment {env} in the application {self.application.name}.",
134
+ self.io.info(
135
+ f"Maintenance page '{template}' added for environment {env} in application {self.application.name}",
310
136
  )
311
137
 
312
138
  def deactivate(self, env: str):
313
139
  application_environment = get_app_environment(self.application, env)
314
140
 
141
+ self.load_balancer = self.load_balancer_provider(application_environment.session)
142
+
143
+ https_listener = self.load_balancer.get_https_listener_for_application(
144
+ self.application.name, env
145
+ )
146
+ current_maintenance_page = self.__get_maintenance_page_type(https_listener)
147
+
148
+ if not current_maintenance_page:
149
+ self.io.warn("There is no current maintenance page to remove")
150
+ return
151
+
152
+ if not self.io.confirm(
153
+ f"There is currently a '{current_maintenance_page}' maintenance page, "
154
+ f"would you like to remove it?"
155
+ ):
156
+ return
157
+
158
+ self.__remove_maintenance_page(https_listener)
159
+ self.io.info(
160
+ f"Maintenance page removed from environment {env} in application {self.application.name}",
161
+ )
162
+
163
+ def add_maintenance_page(
164
+ self,
165
+ listener_arn: str,
166
+ app: str,
167
+ env: str,
168
+ services: List[Service],
169
+ allowed_ips: List[str],
170
+ template: str = "default",
171
+ ):
172
+
173
+ maintenance_page_content = get_maintenance_page_template(template)
174
+ bypass_value = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
175
+
176
+ rule_priority = itertools.count(start=1)
177
+ maintenance_page_host_header_conditions = []
315
178
  try:
316
- https_listener = self.get_https_listener_for_application(
317
- application_environment.session, self.application.name, env
318
- )
319
- current_maintenance_page = self.get_maintenance_page_type(
320
- application_environment.session, https_listener
321
- )
179
+ for svc in services:
180
+ target_group_arn = self.load_balancer.find_target_group(app, env, svc.name)
322
181
 
323
- # TODO discuss, reduce number of return statements but more nested if statements
324
- if not current_maintenance_page:
325
- self.io.warn("There is no current maintenance page to remove")
326
- return
182
+ # not all of an application's services are guaranteed to have been deployed to an environment
183
+ if not target_group_arn:
184
+ continue
327
185
 
328
- if not self.io.confirm(
329
- f"There is currently a '{current_maintenance_page}' maintenance page, "
330
- f"would you like to remove it?"
331
- ):
332
- return
186
+ service_conditions = self.load_balancer.get_host_header_conditions(
187
+ listener_arn, target_group_arn
188
+ )
189
+
190
+ for ip in allowed_ips:
191
+ self.load_balancer.create_header_rule(
192
+ listener_arn,
193
+ target_group_arn,
194
+ "X-Forwarded-For",
195
+ [ip],
196
+ "AllowedIps",
197
+ next(rule_priority),
198
+ service_conditions,
199
+ )
200
+ self.load_balancer.create_source_ip_rule(
201
+ listener_arn,
202
+ target_group_arn,
203
+ [ip],
204
+ "AllowedSourceIps",
205
+ next(rule_priority),
206
+ service_conditions,
207
+ )
208
+
209
+ self.load_balancer.create_header_rule(
210
+ listener_arn,
211
+ target_group_arn,
212
+ "Bypass-Key",
213
+ [bypass_value],
214
+ "BypassIpFilter",
215
+ next(rule_priority),
216
+ service_conditions,
217
+ )
218
+
219
+ # add to accumilating list of conditions for maintenace page rule
220
+ maintenance_page_host_header_conditions.extend(service_conditions)
333
221
 
334
- self.remove_maintenance_page(application_environment.session, https_listener)
335
222
  self.io.info(
336
- f"Maintenance page removed from environment {env} in application {self.application.name}",
223
+ 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/",
337
224
  )
338
225
 
339
- except LoadBalancerNotFoundException:
340
- # TODO push exception to command layer
341
- self.io.abort_with_error(
342
- f"No load balancer found for environment {env} in the application {self.application.name}.",
226
+ self.load_balancer.create_rule(
227
+ listener_arn=listener_arn,
228
+ priority=next(rule_priority),
229
+ conditions=[
230
+ {
231
+ "Field": "path-pattern",
232
+ "PathPatternConfig": {"Values": ["/*"]},
233
+ },
234
+ {
235
+ "Field": "host-header",
236
+ "HostHeaderConfig": {
237
+ "Values": sorted(
238
+ list(
239
+ {
240
+ value
241
+ for condition in maintenance_page_host_header_conditions
242
+ for value in condition["HostHeaderConfig"]["Values"]
243
+ }
244
+ )
245
+ )
246
+ },
247
+ },
248
+ ],
249
+ actions=[
250
+ {
251
+ "Type": "fixed-response",
252
+ "FixedResponseConfig": {
253
+ "StatusCode": "503",
254
+ "ContentType": "text/html",
255
+ "MessageBody": maintenance_page_content,
256
+ },
257
+ }
258
+ ],
259
+ tags=[
260
+ {"Key": "name", "Value": "MaintenancePage"},
261
+ {"Key": "type", "Value": template},
262
+ ],
343
263
  )
344
-
345
- except ListenerNotFoundException:
346
- # TODO push exception to command layer
347
- self.io.abort_with_error(
348
- f"No HTTPS listener found for environment {env} in the application {self.application.name}.",
264
+ except Exception as e:
265
+ deleted_rules = self.__clean_up_maintenance_page_rules(listener_arn)
266
+ raise FailedToActivateMaintenancePageException(
267
+ app, env, f"{e}:\n {traceback.format_exc()}", deleted_rules
349
268
  )
350
269
 
270
+ def __clean_up_maintenance_page_rules(
271
+ self, listener_arn: str, fail_when_not_deleted: bool = False
272
+ ) -> dict[str, bool]:
351
273
 
352
- def get_app_service(application: Application, svc_name: str) -> Service:
353
- application_service = application.services.get(svc_name)
354
-
355
- if not application_service:
356
- # TODO raise exception instead of abort
357
- click.secho(
358
- f"The service {svc_name} was not found in the application {application.name}. "
359
- f"It either does not exist, or has not been deployed.",
360
- fg="red",
274
+ tag_descriptions = self.load_balancer.get_rules_tag_descriptions_by_listener_arn(
275
+ listener_arn
361
276
  )
362
- raise click.Abort
363
277
 
364
- return application_service
278
+ deletes = {}
279
+ for name in ["MaintenancePage", "AllowedIps", "BypassIpFilter", "AllowedSourceIps"]:
280
+ deleted = self.load_balancer.delete_listener_rule_by_tags(tag_descriptions, name)
281
+ deletes[name] = bool(deleted)
282
+ if fail_when_not_deleted and name == "MaintenancePage" and not deleted:
283
+ raise ListenerRuleNotFoundException()
365
284
 
285
+ return deletes
366
286
 
367
- def get_app_environment(application: Application, env_name: str) -> Environment:
368
- application_environment = application.environments.get(env_name)
287
+ def __remove_maintenance_page(self, listener_arn: str) -> dict[str, bool]:
288
+ self.__clean_up_maintenance_page_rules(listener_arn, True)
369
289
 
370
- if not application_environment:
371
- click.secho(
372
- f"The environment {env_name} was not found in the application {application.name}. "
373
- f"It either does not exist, or has not been deployed.",
374
- fg="red",
290
+ def __get_maintenance_page_type(self, listener_arn: str) -> Union[str, None]:
291
+ tag_descriptions = self.load_balancer.get_rules_tag_descriptions_by_listener_arn(
292
+ listener_arn
375
293
  )
376
- raise click.Abort
294
+ maintenance_page_type = None
295
+ for description in tag_descriptions:
296
+ tags = {t["Key"]: t["Value"] for t in description["Tags"]}
297
+ if tags.get("name") == "MaintenancePage":
298
+ maintenance_page_type = tags.get("type")
377
299
 
378
- return application_environment
300
+ return maintenance_page_type
379
301
 
380
302
 
381
- def get_rules_tag_descriptions(rules: list, lb_client):
382
- tag_descriptions = []
383
- chunk_size = 20
303
+ def get_app_service(application: Application, svc_name: str) -> Service:
304
+ application_service = application.services.get(svc_name)
384
305
 
385
- for i in range(0, len(rules), chunk_size):
386
- chunk = rules[i : i + chunk_size]
387
- resource_arns = [r["RuleArn"] for r in chunk]
388
- response = lb_client.describe_tags(ResourceArns=resource_arns)
389
- tag_descriptions.extend(response["TagDescriptions"])
306
+ if not application_service:
307
+ raise ApplicationServiceNotFoundException(application.name, svc_name)
390
308
 
391
- return tag_descriptions
309
+ return application_service
392
310
 
393
311
 
394
- def delete_listener_rule(tag_descriptions: list, tag_name: str, lb_client: boto3.client):
395
- current_rule_arn = None
312
+ def get_app_environment(application: Application, env_name: str) -> Environment:
313
+ application_environment = application.environments.get(env_name)
396
314
 
397
- for description in tag_descriptions:
398
- tags = {t["Key"]: t["Value"] for t in description["Tags"]}
399
- if tags.get("name") == tag_name:
400
- current_rule_arn = description["ResourceArn"]
401
- if current_rule_arn:
402
- lb_client.delete_rule(RuleArn=current_rule_arn)
315
+ if not application_environment:
316
+ raise ApplicationEnvironmentNotFoundException(application.name, env_name)
403
317
 
404
- return current_rule_arn
318
+ return application_environment
405
319
 
406
320
 
407
321
  def get_maintenance_page_template(template) -> str:
@@ -416,148 +330,3 @@ def get_maintenance_page_template(template) -> str:
416
330
 
417
331
  # [^\S]\s+ - Remove any space that is not preceded by a non-space character.
418
332
  return re.sub(r"[^\S]\s+", "", template_contents)
419
-
420
-
421
- def find_target_group(app: str, env: str, svc: str, session: boto3.Session) -> str:
422
- rg_tagging_client = session.client("resourcegroupstaggingapi")
423
- response = rg_tagging_client.get_resources(
424
- TagFilters=[
425
- {
426
- "Key": "copilot-application",
427
- "Values": [
428
- app,
429
- ],
430
- "Key": "copilot-environment",
431
- "Values": [
432
- env,
433
- ],
434
- "Key": "copilot-service",
435
- "Values": [
436
- svc,
437
- ],
438
- },
439
- ],
440
- ResourceTypeFilters=[
441
- "elasticloadbalancing:targetgroup",
442
- ],
443
- )
444
- for resource in response["ResourceTagMappingList"]:
445
- tags = {tag["Key"]: tag["Value"] for tag in resource["Tags"]}
446
-
447
- if (
448
- "copilot-service" in tags
449
- and tags["copilot-service"] == svc
450
- and "copilot-environment" in tags
451
- and tags["copilot-environment"] == env
452
- and "copilot-application" in tags
453
- and tags["copilot-application"] == app
454
- ):
455
- return resource["ResourceARN"]
456
-
457
- click.secho(
458
- f"No target group found for application: {app}, environment: {env}, service: {svc}",
459
- fg="red",
460
- )
461
-
462
- return None
463
-
464
-
465
- def create_header_rule(
466
- lb_client: boto3.client,
467
- listener_arn: str,
468
- target_group_arn: str,
469
- header_name: str,
470
- values: list,
471
- rule_name: str,
472
- priority: int,
473
- conditions: list,
474
- ):
475
- # add new condition to existing conditions
476
- combined_conditions = [
477
- {
478
- "Field": "http-header",
479
- "HttpHeaderConfig": {"HttpHeaderName": header_name, "Values": values},
480
- }
481
- ] + conditions
482
-
483
- lb_client.create_rule(
484
- ListenerArn=listener_arn,
485
- Priority=priority,
486
- Conditions=combined_conditions,
487
- Actions=[{"Type": "forward", "TargetGroupArn": target_group_arn}],
488
- Tags=[
489
- {"Key": "name", "Value": rule_name},
490
- ],
491
- )
492
-
493
- click.secho(
494
- 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}.",
495
- fg="green",
496
- )
497
-
498
-
499
- def normalise_to_cidr(ip: str):
500
- if "/" in ip:
501
- return ip
502
- SINGLE_IPV4_CIDR_PREFIX_LENGTH = "32"
503
- return f"{ip}/{SINGLE_IPV4_CIDR_PREFIX_LENGTH}"
504
-
505
-
506
- def create_source_ip_rule(
507
- lb_client: boto3.client,
508
- listener_arn: str,
509
- target_group_arn: str,
510
- values: list,
511
- rule_name: str,
512
- priority: int,
513
- conditions: list,
514
- ):
515
- # add new condition to existing conditions
516
-
517
- combined_conditions = [
518
- {
519
- "Field": "source-ip",
520
- "SourceIpConfig": {"Values": [normalise_to_cidr(value) for value in values]},
521
- }
522
- ] + conditions
523
-
524
- lb_client.create_rule(
525
- ListenerArn=listener_arn,
526
- Priority=priority,
527
- Conditions=combined_conditions,
528
- Actions=[{"Type": "forward", "TargetGroupArn": target_group_arn}],
529
- Tags=[
530
- {"Key": "name", "Value": rule_name},
531
- ],
532
- )
533
-
534
- click.secho(
535
- 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}.",
536
- fg="green",
537
- )
538
-
539
-
540
- def get_host_header_conditions(
541
- lb_client: boto3.client, listener_arn: str, target_group_arn: str
542
- ) -> list:
543
- rules = lb_client.describe_rules(ListenerArn=listener_arn)["Rules"]
544
-
545
- # Get current set of forwarding conditions for the target group
546
- for rule in rules:
547
- for action in rule["Actions"]:
548
- if action["Type"] == "forward" and action["TargetGroupArn"] == target_group_arn:
549
- conditions = rule["Conditions"]
550
-
551
- # filter to host-header conditions
552
- conditions = [
553
- {i: condition[i] for i in condition if i != "Values"}
554
- for condition in conditions
555
- if condition["Field"] == "host-header"
556
- ]
557
-
558
- # remove internal hosts
559
- conditions[0]["HostHeaderConfig"]["Values"] = [
560
- v for v in conditions[0]["HostHeaderConfig"]["Values"]
561
- ]
562
-
563
- return conditions