dbt-platform-helper 13.1.2__py3-none-any.whl → 13.3.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 (57) hide show
  1. dbt_platform_helper/commands/application.py +2 -4
  2. dbt_platform_helper/commands/codebase.py +11 -7
  3. dbt_platform_helper/commands/conduit.py +1 -3
  4. dbt_platform_helper/commands/config.py +12 -314
  5. dbt_platform_helper/commands/copilot.py +14 -8
  6. dbt_platform_helper/commands/database.py +17 -9
  7. dbt_platform_helper/commands/environment.py +5 -6
  8. dbt_platform_helper/commands/generate.py +2 -3
  9. dbt_platform_helper/commands/notify.py +1 -3
  10. dbt_platform_helper/commands/pipeline.py +1 -3
  11. dbt_platform_helper/commands/secrets.py +1 -3
  12. dbt_platform_helper/commands/version.py +2 -2
  13. dbt_platform_helper/domain/codebase.py +17 -9
  14. dbt_platform_helper/domain/config.py +345 -0
  15. dbt_platform_helper/domain/copilot.py +158 -157
  16. dbt_platform_helper/domain/maintenance_page.py +42 -15
  17. dbt_platform_helper/domain/pipelines.py +1 -1
  18. dbt_platform_helper/domain/terraform_environment.py +1 -1
  19. dbt_platform_helper/domain/versioning.py +161 -30
  20. dbt_platform_helper/providers/aws/__init__.py +0 -0
  21. dbt_platform_helper/providers/{aws.py → aws/exceptions.py} +10 -0
  22. dbt_platform_helper/providers/aws/interfaces.py +13 -0
  23. dbt_platform_helper/providers/aws/opensearch.py +23 -0
  24. dbt_platform_helper/providers/aws/redis.py +21 -0
  25. dbt_platform_helper/providers/aws/sso_auth.py +61 -0
  26. dbt_platform_helper/providers/cache.py +40 -4
  27. dbt_platform_helper/providers/config.py +2 -1
  28. dbt_platform_helper/providers/config_validator.py +28 -25
  29. dbt_platform_helper/providers/copilot.py +1 -1
  30. dbt_platform_helper/providers/io.py +5 -2
  31. dbt_platform_helper/providers/kms.py +22 -0
  32. dbt_platform_helper/providers/load_balancers.py +26 -15
  33. dbt_platform_helper/providers/parameter_store.py +47 -0
  34. dbt_platform_helper/providers/platform_config_schema.py +17 -0
  35. dbt_platform_helper/providers/semantic_version.py +18 -88
  36. dbt_platform_helper/providers/terraform_manifest.py +1 -0
  37. dbt_platform_helper/providers/version.py +102 -26
  38. dbt_platform_helper/providers/version_status.py +80 -0
  39. dbt_platform_helper/providers/yaml_file.py +0 -1
  40. dbt_platform_helper/utils/aws.py +24 -142
  41. dbt_platform_helper/utils/git.py +3 -1
  42. dbt_platform_helper/utils/tool_versioning.py +12 -0
  43. {dbt_platform_helper-13.1.2.dist-info → dbt_platform_helper-13.3.0.dist-info}/METADATA +2 -2
  44. {dbt_platform_helper-13.1.2.dist-info → dbt_platform_helper-13.3.0.dist-info}/RECORD +48 -47
  45. {dbt_platform_helper-13.1.2.dist-info → dbt_platform_helper-13.3.0.dist-info}/WHEEL +1 -1
  46. platform_helper.py +1 -1
  47. dbt_platform_helper/providers/opensearch.py +0 -36
  48. dbt_platform_helper/providers/platform_helper_versioning.py +0 -107
  49. dbt_platform_helper/providers/redis.py +0 -34
  50. dbt_platform_helper/templates/svc/manifest-backend.yml +0 -69
  51. dbt_platform_helper/templates/svc/manifest-public.yml +0 -109
  52. dbt_platform_helper/utils/cloudfoundry.py +0 -14
  53. dbt_platform_helper/utils/files.py +0 -59
  54. dbt_platform_helper/utils/manifests.py +0 -18
  55. dbt_platform_helper/utils/versioning.py +0 -91
  56. {dbt_platform_helper-13.1.2.dist-info → dbt_platform_helper-13.3.0.dist-info}/LICENSE +0 -0
  57. {dbt_platform_helper-13.1.2.dist-info → dbt_platform_helper-13.3.0.dist-info}/entry_points.txt +0 -0
@@ -5,18 +5,19 @@ import json
5
5
  from pathlib import Path
6
6
  from pathlib import PosixPath
7
7
 
8
- import click
9
- import yaml
10
- from schema import SchemaError
8
+ import botocore
9
+ import botocore.errorfactory
11
10
 
12
11
  from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE
13
12
  from dbt_platform_helper.domain.copilot_environment import CopilotTemplating
14
13
  from dbt_platform_helper.providers.config import ConfigProvider
15
14
  from dbt_platform_helper.providers.files import FileProvider
15
+ from dbt_platform_helper.providers.io import ClickIOProvider
16
+ from dbt_platform_helper.providers.kms import KMSProvider
17
+ from dbt_platform_helper.providers.parameter_store import ParameterStore
18
+ from dbt_platform_helper.providers.yaml_file import YamlFileProvider
16
19
  from dbt_platform_helper.utils.application import get_application_name
17
20
  from dbt_platform_helper.utils.application import load_application
18
- from dbt_platform_helper.utils.aws import get_aws_session_or_abort
19
- from dbt_platform_helper.utils.files import generate_override_files
20
21
  from dbt_platform_helper.utils.template import ADDON_TEMPLATE_MAP
21
22
  from dbt_platform_helper.utils.template import camel_case
22
23
  from dbt_platform_helper.utils.template import setup_templates
@@ -27,9 +28,6 @@ class Copilot:
27
28
 
28
29
  PACKAGE_DIR = Path(__file__).resolve().parent.parent
29
30
 
30
- # TODO Remove and test
31
- WAF_ACL_ARN_KEY = "waf-acl-arn"
32
-
33
31
  SERVICE_TYPES = [
34
32
  "Load Balanced Web Service",
35
33
  "Backend Service",
@@ -41,34 +39,115 @@ class Copilot:
41
39
  def __init__(
42
40
  self,
43
41
  config_provider: ConfigProvider,
42
+ parameter_provider: ParameterStore,
44
43
  file_provider: FileProvider,
45
44
  copilot_templating: CopilotTemplating,
45
+ kms_provider: KMSProvider,
46
+ session,
47
+ io: ClickIOProvider = ClickIOProvider(),
48
+ yaml_file_provider: YamlFileProvider = YamlFileProvider,
46
49
  ):
47
50
  self.config_provider = config_provider
51
+ self.parameter_provider = parameter_provider
48
52
  self.file_provider = file_provider
49
53
  self.copilot_templating = copilot_templating
54
+ self.kms_provider = kms_provider
55
+ self.io = io
56
+ self.yaml_file_provider = yaml_file_provider
57
+ self.session = session
58
+
59
+ def make_addons(self):
60
+ config = self.config_provider.load_and_validate_platform_config()
61
+
62
+ templates = setup_templates()
63
+ extensions = self._get_extensions()
64
+ application_name = get_application_name()
65
+
66
+ self.io.info("\n>>> Generating Terraform compatible addons CloudFormation\n")
67
+
68
+ output_dir = Path(".").absolute()
69
+ env_path = Path(f"copilot/environments/")
70
+ env_addons_path = env_path / "addons"
71
+ env_overrides_path = env_path / "overrides"
72
+
73
+ self._cleanup_old_files(extensions, output_dir, env_addons_path, env_overrides_path)
74
+ self._generate_env_overrides(output_dir)
75
+
76
+ svc_names = self._list_copilot_local_services()
77
+ base_path = Path(".")
78
+ for svc_name in svc_names:
79
+ self._generate_svc_overrides(base_path, templates, svc_name)
50
80
 
51
- def list_copilot_local_environments(self):
81
+ services = []
82
+ for ext_name, ext_data in extensions.items():
83
+ extension = {**ext_data}
84
+ addon_type = extension.pop("type")
85
+ environments = extension.pop("environments")
86
+ environment_addon_config = {
87
+ "addon_type": addon_type,
88
+ "environments": environments,
89
+ "name": extension.get("name", None) or ext_name,
90
+ "prefix": camel_case(ext_name),
91
+ "secret_name": ext_name.upper().replace("-", "_"),
92
+ **extension,
93
+ }
94
+
95
+ services.append(environment_addon_config)
96
+
97
+ service_addon_config = {
98
+ "application_name": application_name,
99
+ "name": extension.get("name", None) or ext_name,
100
+ "prefix": camel_case(ext_name),
101
+ "environments": environments,
102
+ **extension,
103
+ }
104
+
105
+ log_destination_arns = self._get_log_destination_arn()
106
+
107
+ if addon_type in ["s3", "s3-policy"]:
108
+ if extensions[ext_name].get("serve_static_content"):
109
+ continue
110
+
111
+ s3_kms_arns = self._get_s3_kms_alias_arns(application_name, environments)
112
+ for environment_name in environments:
113
+ environments[environment_name]["kms_key_arn"] = s3_kms_arns.get(
114
+ environment_name, "kms-key-not-found"
115
+ )
116
+
117
+ self._generate_service_addons(
118
+ extension,
119
+ ext_name,
120
+ addon_type,
121
+ output_dir,
122
+ service_addon_config,
123
+ templates,
124
+ log_destination_arns,
125
+ )
126
+
127
+ environments = self.config_provider.apply_environment_defaults(config)["environments"]
128
+
129
+ self.copilot_templating.generate_cross_account_s3_policies(environments, extensions)
130
+
131
+ self.io.info(templates.get_template("addon-instructions.txt").render(services=services))
132
+
133
+ def _list_copilot_local_environments(self):
52
134
  return [
53
135
  path.parent.parts[-1] for path in Path("./copilot/environments/").glob("*/manifest.yml")
54
136
  ]
55
137
 
56
- def is_service(self, path: PosixPath) -> bool:
57
- with open(path) as manifest_file:
58
- data = yaml.safe_load(manifest_file)
59
- if not data or not data.get("type"):
60
- click.echo(
61
- click.style(f"No type defined in manifest file {str(path)}; exiting", fg="red")
62
- )
63
- exit(1)
138
+ def _is_service(self, path: PosixPath) -> bool:
139
+
140
+ manifest_file = self.yaml_file_provider.load(path)
141
+ if not manifest_file or not manifest_file.get("type"):
142
+ self.io.abort_with_error(f"No type defined in manifest file {str(path)}; exiting")
64
143
 
65
- return data.get("type") in self.SERVICE_TYPES
144
+ return manifest_file.get("type") in self.SERVICE_TYPES
66
145
 
67
- def list_copilot_local_services(self):
146
+ def _list_copilot_local_services(self):
68
147
  return [
69
148
  path.parent.parts[-1]
70
149
  for path in Path("./copilot/").glob("*/manifest.yml")
71
- if self.is_service(path)
150
+ if self._is_service(path)
72
151
  ]
73
152
 
74
153
  def _validate_and_normalise_extensions_config(self, config_file, key_in_config_file=None):
@@ -90,12 +169,10 @@ class Copilot:
90
169
  def _normalise_keys(source: dict):
91
170
  return {k.replace("-", "_"): v for k, v in source.items()}
92
171
 
93
- with open(self.PACKAGE_DIR / "addon-plans.yml", "r") as fd:
94
- addon_plans = yaml.safe_load(fd)
172
+ addon_plans = self.yaml_file_provider.load(self.PACKAGE_DIR / "addon-plans.yml")
95
173
 
96
174
  # load and validate config
97
- with open(config_file, "r") as fd:
98
- config = yaml.safe_load(fd)
175
+ config = self.yaml_file_provider.load(config_file)
99
176
 
100
177
  if config and key_in_config_file:
101
178
  config = config[key_in_config_file]
@@ -107,23 +184,19 @@ class Copilot:
107
184
  errors = validate_addons(config)
108
185
 
109
186
  if errors:
110
- click.echo(click.style(f"Errors found in {config_file}:", fg="red"))
187
+ self.io.error(f"Errors found in {config_file}:")
111
188
  for addon, error in errors.items():
112
- click.echo(click.style(f"Addon '{addon}': {error}", fg="red"))
113
- exit(1)
189
+ self.io.error(f"Addon '{addon}': {error}")
190
+ self.io.abort_with_error("Invalid platform-config.yml provided, see above warnings")
114
191
 
115
- env_names = self.list_copilot_local_environments()
116
- svc_names = self.list_copilot_local_services()
192
+ env_names = self._list_copilot_local_environments()
193
+ svc_names = self._list_copilot_local_services()
117
194
 
118
195
  if not env_names:
119
- click.echo(
120
- click.style(f"No environments found in ./copilot/environments; exiting", fg="red")
121
- )
122
- exit(1)
196
+ self.io.abort_with_error("No environments found in ./copilot/environments; exiting")
123
197
 
124
198
  if not svc_names:
125
- click.echo(click.style(f"No services found in ./copilot/; exiting", fg="red"))
126
- exit(1)
199
+ self.io.abort_with_error("No services found in ./copilot/; exiting")
127
200
 
128
201
  normalised_config = {}
129
202
  config_has_errors = False
@@ -136,11 +209,8 @@ class Copilot:
136
209
  normalised_config[addon_name]["services"] = svc_names
137
210
 
138
211
  if not set(normalised_config[addon_name]["services"]).issubset(set(svc_names)):
139
- click.echo(
140
- click.style(
141
- f"Services listed in {addon_name}.services do not exist in ./copilot/",
142
- fg="red",
143
- ),
212
+ self.io.error(
213
+ f"Services listed in {addon_name}.services do not exist in ./copilot/"
144
214
  )
145
215
  config_has_errors = True
146
216
 
@@ -151,18 +221,10 @@ class Copilot:
151
221
 
152
222
  missing_envs = set(environments.keys()) - set(env_names)
153
223
  if missing_envs:
154
- click.echo(
155
- click.style(
156
- f"Environment keys listed in {addon_name} do not match those defined in ./copilot/environments.",
157
- fg="red",
158
- )
159
- ),
160
- click.echo(
161
- click.style(
162
- f" Missing environments: {', '.join(sorted(missing_envs))}",
163
- fg="white",
164
- ),
224
+ self.io.error(
225
+ f"Environment keys listed in {addon_name} do not match those defined in ./copilot/environments"
165
226
  )
227
+ self.io.error(f" Missing environments: {', '.join(sorted(missing_envs))}")
166
228
  config_has_errors = True
167
229
 
168
230
  if config_has_errors:
@@ -183,41 +245,41 @@ class Copilot:
183
245
  normalised_config[addon_name]["environments"] = normalised_environments
184
246
 
185
247
  if config_has_errors:
186
- exit(1)
248
+ self.io.abort_with_error("Configuration has errors. Exiting.")
187
249
 
188
250
  return normalised_config
189
251
 
190
- def get_log_destination_arn(self):
252
+ def _get_log_destination_arn(self):
191
253
  """Get destination arns stored in param store in projects aws
192
254
  account."""
193
- session = get_aws_session_or_abort()
194
- client = session.client("ssm", region_name="eu-west-2")
195
- response = client.get_parameters(Names=["/copilot/tools/central_log_groups"])
196
-
197
- if not response["Parameters"]:
198
- click.echo(
199
- click.style(
200
- "No aws central log group defined in Parameter Store at location /copilot/tools/central_log_groups; exiting",
201
- fg="red",
202
- )
255
+
256
+ try:
257
+ destination_arns = self.parameter_provider.get_ssm_parameter_by_name(
258
+ "/copilot/tools/central_log_groups"
259
+ )
260
+ except botocore.errorfactory.ParameterNotFound:
261
+ self.io.abort_with_error(
262
+ "No aws central log group defined in Parameter Store at location /copilot/tools/central_log_groups; exiting"
203
263
  )
204
- exit(1)
205
264
 
206
- destination_arns = json.loads(response["Parameters"][0]["Value"])
207
- return destination_arns
265
+ return json.loads(destination_arns["Value"])
208
266
 
209
267
  def _generate_svc_overrides(self, base_path, templates, name):
210
- click.echo(f"\n>>> Generating service overrides for {name}\n")
268
+ self.io.info(f"\n>>> Generating service overrides for {name}\n")
211
269
  overrides_path = base_path.joinpath(f"copilot/{name}/overrides")
212
270
  overrides_path.mkdir(parents=True, exist_ok=True)
213
271
  overrides_file = overrides_path.joinpath("cfn.patches.yml")
214
272
  overrides_file.write_text(templates.get_template("svc/overrides/cfn.patches.yml").render())
215
273
 
216
- def _get_s3_kms_alias_arns(self, session, application_name, config):
217
- application = load_application(application_name, session)
274
+ def _get_s3_kms_alias_arns(self, application_name, config):
275
+ application = load_application(application_name, self.session)
218
276
  arns = {}
219
277
 
220
278
  for environment_name in application.environments:
279
+ kms_provider = self.kms_provider(
280
+ application.environments[environment_name].session.client("kms")
281
+ )
282
+
221
283
  if environment_name not in config:
222
284
  continue
223
285
 
@@ -225,99 +287,20 @@ class Copilot:
225
287
  continue
226
288
 
227
289
  bucket_name = config[environment_name]["bucket_name"]
228
- kms_client = application.environments[environment_name].session.client("kms")
229
290
  alias_name = f"alias/{application_name}-{environment_name}-{bucket_name}-key"
230
291
 
231
292
  try:
232
- response = kms_client.describe_key(KeyId=alias_name)
233
- except kms_client.exceptions.NotFoundException:
234
- pass
293
+ response = kms_provider.describe_key(alias_name)
294
+
295
+ # Boto3 classifies all AWS service errors and exceptions as ClientError exceptions
296
+ except botocore.exceptions.ClientError as error:
297
+ if error.response["Error"]["Code"] == "NotFoundException":
298
+ pass
235
299
  else:
236
300
  arns[environment_name] = response["KeyMetadata"]["Arn"]
237
301
 
238
302
  return arns
239
303
 
240
- def make_addons(self):
241
- self.config_provider.config_file_check()
242
- try:
243
- config = self.config_provider.load_and_validate_platform_config()
244
- except SchemaError as ex:
245
- click.secho(f"Invalid `{PLATFORM_CONFIG_FILE}` file: {str(ex)}", fg="red")
246
- raise click.Abort
247
-
248
- templates = setup_templates()
249
- extensions = self._get_extensions()
250
- session = get_aws_session_or_abort()
251
-
252
- application_name = get_application_name()
253
-
254
- click.echo("\n>>> Generating Terraform compatible addons CloudFormation\n")
255
-
256
- output_dir = Path(".").absolute()
257
- env_path = Path(f"copilot/environments/")
258
- env_addons_path = env_path / "addons"
259
- env_overrides_path = env_path / "overrides"
260
-
261
- self._cleanup_old_files(extensions, output_dir, env_addons_path, env_overrides_path)
262
- self._generate_env_overrides(output_dir)
263
-
264
- svc_names = self.list_copilot_local_services()
265
- base_path = Path(".")
266
- for svc_name in svc_names:
267
- self._generate_svc_overrides(base_path, templates, svc_name)
268
-
269
- services = []
270
- for ext_name, ext_data in extensions.items():
271
- extension = {**ext_data}
272
- addon_type = extension.pop("type")
273
- environments = extension.pop("environments")
274
- environment_addon_config = {
275
- "addon_type": addon_type,
276
- "environments": environments,
277
- "name": extension.get("name", None) or ext_name,
278
- "prefix": camel_case(ext_name),
279
- "secret_name": ext_name.upper().replace("-", "_"),
280
- **extension,
281
- }
282
-
283
- services.append(environment_addon_config)
284
-
285
- service_addon_config = {
286
- "application_name": application_name,
287
- "name": extension.get("name", None) or ext_name,
288
- "prefix": camel_case(ext_name),
289
- "environments": environments,
290
- **extension,
291
- }
292
-
293
- log_destination_arns = self.get_log_destination_arn()
294
-
295
- if addon_type in ["s3", "s3-policy"]:
296
- if extensions[ext_name].get("serve_static_content"):
297
- continue
298
-
299
- s3_kms_arns = self._get_s3_kms_alias_arns(session, application_name, environments)
300
- for environment_name in environments:
301
- environments[environment_name]["kms_key_arn"] = s3_kms_arns.get(
302
- environment_name, "kms-key-not-found"
303
- )
304
-
305
- self._generate_service_addons(
306
- extension,
307
- ext_name,
308
- addon_type,
309
- output_dir,
310
- service_addon_config,
311
- templates,
312
- log_destination_arns,
313
- )
314
-
315
- environments = self.config_provider.apply_environment_defaults(config)["environments"]
316
-
317
- self.copilot_templating.generate_cross_account_s3_policies(environments, extensions)
318
-
319
- click.echo(templates.get_template("addon-instructions.txt").render(services=services))
320
-
321
304
  def _get_extensions(self):
322
305
  config = self._validate_and_normalise_extensions_config(
323
306
  self.PACKAGE_DIR / "default-extensions.yml"
@@ -328,13 +311,31 @@ class Copilot:
328
311
  config.update(project_config)
329
312
  return config
330
313
 
314
+ def _generate_override_files(self, base_path, file_path, output_dir):
315
+ def generate_files_for_dir(pattern):
316
+ for file in file_path.glob(pattern):
317
+ if file.is_file():
318
+ contents = file.read_text()
319
+ file_name = str(file).removeprefix(f"{file_path}/")
320
+ self.io.info(
321
+ self.file_provider.mkfile(
322
+ base_path,
323
+ output_dir / file_name,
324
+ contents,
325
+ overwrite=True,
326
+ )
327
+ )
328
+
329
+ generate_files_for_dir("*")
330
+ generate_files_for_dir("bin/*")
331
+
331
332
  def _generate_env_overrides(self, output_dir):
332
333
  path = "templates/env/terraform-overrides"
333
- click.echo("\n>>> Generating Environment overrides\n")
334
+ self.io.info("\n>>> Generating Environment overrides\n")
334
335
  overrides_path = output_dir.joinpath(f"copilot/environments/overrides")
335
336
  overrides_path.mkdir(parents=True, exist_ok=True)
336
337
  template_overrides_path = Path(__file__).parent.parent.joinpath(path)
337
- generate_override_files(Path("."), template_overrides_path, overrides_path)
338
+ self._generate_override_files(Path("."), template_overrides_path, overrides_path)
338
339
 
339
340
  def _generate_service_addons(
340
341
  self,
@@ -361,7 +362,7 @@ class Copilot:
361
362
  )
362
363
 
363
364
  (output_dir / service_path).mkdir(parents=True, exist_ok=True)
364
- click.echo(
365
+ self.io.info(
365
366
  self.file_provider.mkfile(
366
367
  output_dir, service_path / f"{addon_name}.yml", contents, overwrite=True
367
368
  )
@@ -38,20 +38,14 @@ class FailedToActivateMaintenancePageException(MaintenancePageException):
38
38
  application_name: str,
39
39
  env: str,
40
40
  original_exception: Exception,
41
- rolled_back_rules: dict[str, bool] = {},
42
41
  ):
43
42
  super().__init__(
44
43
  f"Maintenance page failed to activate for the {application_name} application in environment {env}."
45
44
  )
46
45
  self.orginal_exception = original_exception
47
- self.rolled_back_rules = rolled_back_rules
48
46
 
49
47
  def __str__(self):
50
- return (
51
- f"{super().__str__()}\n"
52
- f"Rolled-back rules: {self.rolled_back_rules }\n"
53
- f"Original exception: {self.orginal_exception}"
54
- )
48
+ return f"{super().__str__()}\n" f"Original exception: {self.orginal_exception}"
55
49
 
56
50
 
57
51
  # TODO should this be in its own provider, inside the VPC one, what logic is this sepcific too?
@@ -187,6 +181,15 @@ class MaintenancePage:
187
181
  listener_arn, target_group_arn
188
182
  )
189
183
 
184
+ self.io.debug(
185
+ f"""
186
+ #----------------------------------------------------------#
187
+ # Creating listener rules for service {svc.name.ljust(21, " ")}#
188
+ #----------------------------------------------------------#
189
+
190
+ """,
191
+ )
192
+
190
193
  for ip in allowed_ips:
191
194
  self.load_balancer.create_header_rule(
192
195
  listener_arn,
@@ -196,6 +199,7 @@ class MaintenancePage:
196
199
  "AllowedIps",
197
200
  next(rule_priority),
198
201
  service_conditions,
202
+ [{"Key": "service", "Value": svc.name}],
199
203
  )
200
204
  self.load_balancer.create_source_ip_rule(
201
205
  listener_arn,
@@ -204,6 +208,7 @@ class MaintenancePage:
204
208
  "AllowedSourceIps",
205
209
  next(rule_priority),
206
210
  service_conditions,
211
+ [{"Key": "service", "Value": svc.name}],
207
212
  )
208
213
 
209
214
  self.load_balancer.create_header_rule(
@@ -214,6 +219,7 @@ class MaintenancePage:
214
219
  "BypassIpFilter",
215
220
  next(rule_priority),
216
221
  service_conditions,
222
+ [{"Key": "service", "Value": svc.name}],
217
223
  )
218
224
 
219
225
  # add to accumilating list of conditions for maintenace page rule
@@ -262,27 +268,48 @@ class MaintenancePage:
262
268
  ],
263
269
  )
264
270
  except Exception as e:
265
- deleted_rules = self.__clean_up_maintenance_page_rules(listener_arn)
271
+ self.__clean_up_maintenance_page_rules(listener_arn)
266
272
  raise FailedToActivateMaintenancePageException(
267
- app, env, f"{e}:\n {traceback.format_exc()}", deleted_rules
273
+ app, env, f"{e}:\n {traceback.format_exc()}"
268
274
  )
269
275
 
270
276
  def __clean_up_maintenance_page_rules(
271
277
  self, listener_arn: str, fail_when_not_deleted: bool = False
272
- ) -> dict[str, bool]:
278
+ ) -> None:
273
279
 
274
280
  tag_descriptions = self.load_balancer.get_rules_tag_descriptions_by_listener_arn(
275
281
  listener_arn
276
282
  )
277
283
 
278
- deletes = {}
284
+ # keep track of rules deleted
285
+ deleted_rules = {"MaintenancePage": 0}
279
286
  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:
287
+ deleted_list = self.load_balancer.delete_listener_rule_by_tags(tag_descriptions, name)
288
+
289
+ # track the rules deleted grouped by service
290
+ for deleted_rule in deleted_list:
291
+ tags = {t["Key"]: t["Value"] for t in deleted_rule["Tags"]}
292
+ if "service" in tags:
293
+ if tags["service"] not in deleted_rules:
294
+ deleted_rules[tags["service"]] = {
295
+ "AllowedIps": 0,
296
+ "BypassIpFilter": 0,
297
+ "AllowedSourceIps": 0,
298
+ }
299
+ deleted_rules[tags["service"]][name] += 1
300
+ elif tags.get("name") == "MaintenancePage":
301
+ deleted_rules["MaintenancePage"] += 1
302
+
303
+ if (
304
+ fail_when_not_deleted
305
+ and name == "MaintenancePage"
306
+ and deleted_rules["MaintenancePage"] == 0
307
+ ):
283
308
  raise ListenerRuleNotFoundException()
284
309
 
285
- return deletes
310
+ self.io.warn(
311
+ f"Rules deleted by type and grouped by service: {deleted_rules}",
312
+ )
286
313
 
287
314
  def __remove_maintenance_page(self, listener_arn: str) -> dict[str, bool]:
288
315
  self.__clean_up_maintenance_page_rules(listener_arn, True)
@@ -14,7 +14,7 @@ from dbt_platform_helper.providers.io import ClickIOProvider
14
14
  from dbt_platform_helper.providers.terraform_manifest import TerraformManifestProvider
15
15
  from dbt_platform_helper.utils.application import get_application_name
16
16
  from dbt_platform_helper.utils.template import setup_templates
17
- from dbt_platform_helper.utils.versioning import (
17
+ from dbt_platform_helper.utils.tool_versioning import (
18
18
  get_required_terraform_platform_modules_version,
19
19
  )
20
20
 
@@ -1,7 +1,7 @@
1
1
  from dbt_platform_helper.platform_exception import PlatformException
2
2
  from dbt_platform_helper.providers.io import ClickIOProvider
3
3
  from dbt_platform_helper.providers.terraform_manifest import TerraformManifestProvider
4
- from dbt_platform_helper.utils.versioning import (
4
+ from dbt_platform_helper.utils.tool_versioning import (
5
5
  get_required_terraform_platform_modules_version,
6
6
  )
7
7