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.

Files changed (28) hide show
  1. dbt_platform_helper/COMMANDS.md +38 -35
  2. dbt_platform_helper/commands/codebase.py +5 -8
  3. dbt_platform_helper/commands/conduit.py +2 -2
  4. dbt_platform_helper/commands/config.py +1 -1
  5. dbt_platform_helper/commands/environment.py +32 -18
  6. dbt_platform_helper/commands/pipeline.py +0 -3
  7. dbt_platform_helper/domain/codebase.py +13 -20
  8. dbt_platform_helper/domain/conduit.py +10 -12
  9. dbt_platform_helper/domain/config_validator.py +40 -7
  10. dbt_platform_helper/domain/copilot_environment.py +133 -129
  11. dbt_platform_helper/domain/database_copy.py +38 -37
  12. dbt_platform_helper/domain/maintenance_page.py +206 -193
  13. dbt_platform_helper/domain/pipelines.py +10 -11
  14. dbt_platform_helper/domain/terraform_environment.py +3 -3
  15. dbt_platform_helper/providers/cloudformation.py +12 -1
  16. dbt_platform_helper/providers/config.py +10 -12
  17. dbt_platform_helper/providers/io.py +31 -0
  18. dbt_platform_helper/providers/load_balancers.py +29 -3
  19. dbt_platform_helper/providers/platform_config_schema.py +10 -7
  20. dbt_platform_helper/providers/vpc.py +81 -32
  21. dbt_platform_helper/templates/COMMANDS.md.jinja +5 -3
  22. dbt_platform_helper/templates/pipelines/codebase/overrides/package-lock.json +819 -623
  23. dbt_platform_helper/utils/messages.py +2 -3
  24. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/METADATA +2 -2
  25. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/RECORD +28 -27
  26. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/LICENSE +0 -0
  27. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-12.6.0.dist-info}/WHEEL +0 -0
  28. {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 find_https_listener
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
- pass
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
- user_prompt_callback: Callable[[str], bool] = click.confirm,
36
- echo: Callable[[str], str] = click.secho,
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.user_prompt_callback = user_prompt_callback
39
- self.echo = echo
40
-
41
- def _get_deployed_load_balanced_web_services(self, app: Application, svc: str):
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.name, s) for s in list(svc)]
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, app, env, svc, template, vpc):
52
- try:
53
- services = self._get_deployed_load_balanced_web_services(load_application(app), svc)
54
- except LoadBalancedWebServiceNotFoundException:
55
- # TODO DBTP-1643 - this bit of logic does not depend on env, so env shouldn't really be in the exception
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 = find_https_listener(application_environment.session, app, env)
64
- current_maintenance_page = get_maintenance_page(
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.user_prompt_callback(
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 {app}.\nWould you like to replace it with a '{template}' "
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
- raise click.Abort
214
+ return
76
215
 
77
- if remove_current_maintenance_page or self.user_prompt_callback(
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 {app}.\nWould you like to continue?"
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
- app,
228
+ self.application.name,
90
229
  env,
91
230
  services,
92
231
  allowed_ips,
93
232
  template,
94
233
  )
95
- self.echo(
96
- f"Maintenance page '{template}' added for environment {env} in application {app}",
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
- self.echo(
104
- f"No load balancer found for environment {env} in the application {app}.", fg="red"
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
- self.echo(
110
- f"No HTTPS listener found for environment {env} in the application {app}.", fg="red"
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, app, env):
115
- application_environment = get_app_environment(app, env)
250
+ def deactivate(self, env: str):
251
+ application_environment = get_app_environment(self.application, env)
116
252
 
117
253
  try:
118
- https_listener = find_https_listener(application_environment.session, app, env)
119
- current_maintenance_page = get_maintenance_page(
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.echo("There is no current maintenance page to remove", fg="red")
124
- raise click.Abort
263
+ self.io.warn("There is no current maintenance page to remove")
264
+ return
125
265
 
126
- if not self.user_prompt_callback(
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
- raise click.Abort
270
+ return
131
271
 
132
- remove_maintenance_page(application_environment.session, https_listener)
133
- self.echo(
134
- f"Maintenance page removed from environment {env} in application {app}", fg="green"
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
- self.echo(
139
- f"No load balancer found for environment {env} in the application {app}.", fg="red"
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
- self.echo(
145
- f"No HTTPS listener found for environment {env} in the application {app}.", fg="red"
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(app_name: str, svc_name: str) -> 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 {app_name}. "
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(app_name: str, env_name: str) -> 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 {app_name}. "
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.echo("No pipelines defined: nothing to do.", err=True, fg="yellow")
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.abort("The current directory is not a git repository")
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.abort(f'There is no CodeStar Connection named "{app_name}" to use')
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.echo("Deleting copilot/pipelines directory.")
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.echo(message)
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.echo(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
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.echo(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
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
- echo_fn=click.echo,
59
+ echo=click.echo,
60
60
  ):
61
- self.echo = echo_fn
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"Error: cannot generate terraform for environment {environment_name}. It does not exist in your configuration"
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
- def __init__(self, cloudformation_client, iam_client, ssm_client):
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):