dbt-platform-helper 13.1.0__py3-none-any.whl → 15.16.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.
Files changed (95) hide show
  1. dbt_platform_helper/COMMANDS.md +107 -27
  2. dbt_platform_helper/commands/application.py +5 -6
  3. dbt_platform_helper/commands/codebase.py +31 -10
  4. dbt_platform_helper/commands/conduit.py +3 -5
  5. dbt_platform_helper/commands/config.py +20 -311
  6. dbt_platform_helper/commands/copilot.py +18 -391
  7. dbt_platform_helper/commands/database.py +17 -9
  8. dbt_platform_helper/commands/environment.py +20 -14
  9. dbt_platform_helper/commands/generate.py +0 -3
  10. dbt_platform_helper/commands/internal.py +140 -0
  11. dbt_platform_helper/commands/notify.py +58 -78
  12. dbt_platform_helper/commands/pipeline.py +23 -19
  13. dbt_platform_helper/commands/secrets.py +39 -93
  14. dbt_platform_helper/commands/version.py +7 -12
  15. dbt_platform_helper/constants.py +52 -7
  16. dbt_platform_helper/domain/codebase.py +89 -39
  17. dbt_platform_helper/domain/conduit.py +335 -76
  18. dbt_platform_helper/domain/config.py +381 -0
  19. dbt_platform_helper/domain/copilot.py +398 -0
  20. dbt_platform_helper/domain/copilot_environment.py +8 -8
  21. dbt_platform_helper/domain/database_copy.py +2 -2
  22. dbt_platform_helper/domain/maintenance_page.py +254 -430
  23. dbt_platform_helper/domain/notify.py +64 -0
  24. dbt_platform_helper/domain/pipelines.py +43 -35
  25. dbt_platform_helper/domain/plans.py +41 -0
  26. dbt_platform_helper/domain/secrets.py +279 -0
  27. dbt_platform_helper/domain/service.py +570 -0
  28. dbt_platform_helper/domain/terraform_environment.py +14 -13
  29. dbt_platform_helper/domain/update_alb_rules.py +412 -0
  30. dbt_platform_helper/domain/versioning.py +249 -0
  31. dbt_platform_helper/{providers → entities}/platform_config_schema.py +75 -82
  32. dbt_platform_helper/entities/semantic_version.py +83 -0
  33. dbt_platform_helper/entities/service.py +339 -0
  34. dbt_platform_helper/platform_exception.py +4 -0
  35. dbt_platform_helper/providers/autoscaling.py +24 -0
  36. dbt_platform_helper/providers/aws/__init__.py +0 -0
  37. dbt_platform_helper/providers/aws/exceptions.py +70 -0
  38. dbt_platform_helper/providers/aws/interfaces.py +13 -0
  39. dbt_platform_helper/providers/aws/opensearch.py +23 -0
  40. dbt_platform_helper/providers/aws/redis.py +21 -0
  41. dbt_platform_helper/providers/aws/sso_auth.py +75 -0
  42. dbt_platform_helper/providers/cache.py +40 -4
  43. dbt_platform_helper/providers/cloudformation.py +1 -1
  44. dbt_platform_helper/providers/config.py +137 -19
  45. dbt_platform_helper/providers/config_validator.py +112 -51
  46. dbt_platform_helper/providers/copilot.py +24 -16
  47. dbt_platform_helper/providers/ecr.py +89 -7
  48. dbt_platform_helper/providers/ecs.py +228 -36
  49. dbt_platform_helper/providers/environment_variable.py +24 -0
  50. dbt_platform_helper/providers/files.py +1 -1
  51. dbt_platform_helper/providers/io.py +36 -4
  52. dbt_platform_helper/providers/kms.py +22 -0
  53. dbt_platform_helper/providers/load_balancers.py +402 -42
  54. dbt_platform_helper/providers/logs.py +72 -0
  55. dbt_platform_helper/providers/parameter_store.py +134 -0
  56. dbt_platform_helper/providers/s3.py +21 -0
  57. dbt_platform_helper/providers/schema_migrations/__init__.py +0 -0
  58. dbt_platform_helper/providers/schema_migrations/schema_v0_to_v1_migration.py +43 -0
  59. dbt_platform_helper/providers/schema_migrator.py +77 -0
  60. dbt_platform_helper/providers/secrets.py +5 -5
  61. dbt_platform_helper/providers/slack_channel_notifier.py +62 -0
  62. dbt_platform_helper/providers/terraform_manifest.py +121 -19
  63. dbt_platform_helper/providers/version.py +106 -23
  64. dbt_platform_helper/providers/version_status.py +27 -0
  65. dbt_platform_helper/providers/vpc.py +36 -5
  66. dbt_platform_helper/providers/yaml_file.py +58 -2
  67. dbt_platform_helper/templates/environment-pipelines/main.tf +4 -3
  68. dbt_platform_helper/templates/svc/overrides/cfn.patches.yml +5 -0
  69. dbt_platform_helper/utilities/decorators.py +103 -0
  70. dbt_platform_helper/utils/application.py +119 -22
  71. dbt_platform_helper/utils/aws.py +39 -150
  72. dbt_platform_helper/utils/deep_merge.py +10 -0
  73. dbt_platform_helper/utils/git.py +1 -14
  74. dbt_platform_helper/utils/validation.py +1 -1
  75. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info}/METADATA +11 -20
  76. dbt_platform_helper-15.16.0.dist-info/RECORD +118 -0
  77. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info}/WHEEL +1 -1
  78. platform_helper.py +3 -1
  79. terraform/elasticache-redis/plans.yml +85 -0
  80. terraform/opensearch/plans.yml +71 -0
  81. terraform/postgres/plans.yml +128 -0
  82. dbt_platform_helper/addon-plans.yml +0 -224
  83. dbt_platform_helper/providers/aws.py +0 -37
  84. dbt_platform_helper/providers/opensearch.py +0 -36
  85. dbt_platform_helper/providers/redis.py +0 -34
  86. dbt_platform_helper/providers/semantic_version.py +0 -126
  87. dbt_platform_helper/templates/svc/manifest-backend.yml +0 -69
  88. dbt_platform_helper/templates/svc/manifest-public.yml +0 -109
  89. dbt_platform_helper/utils/cloudfoundry.py +0 -14
  90. dbt_platform_helper/utils/files.py +0 -53
  91. dbt_platform_helper/utils/manifests.py +0 -18
  92. dbt_platform_helper/utils/versioning.py +0 -238
  93. dbt_platform_helper-13.1.0.dist-info/RECORD +0 -96
  94. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info}/entry_points.txt +0 -0
  95. {dbt_platform_helper-13.1.0.dist-info → dbt_platform_helper-15.16.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,381 @@
1
+ import os
2
+ import re
3
+ import webbrowser
4
+ from pathlib import Path
5
+ from typing import Dict
6
+
7
+ from prettytable import PrettyTable
8
+
9
+ from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE
10
+ from dbt_platform_helper.constants import STANDARD_PLATFORM_SSO_ROLES
11
+ from dbt_platform_helper.domain.versioning import AWSVersioning
12
+ from dbt_platform_helper.domain.versioning import CopilotVersioning
13
+ from dbt_platform_helper.domain.versioning import PlatformHelperVersioning
14
+ from dbt_platform_helper.entities.semantic_version import (
15
+ IncompatibleMajorVersionException,
16
+ )
17
+ from dbt_platform_helper.entities.semantic_version import SemanticVersion
18
+ from dbt_platform_helper.platform_exception import PlatformException
19
+ from dbt_platform_helper.providers.aws.sso_auth import SSOAuthProvider
20
+ from dbt_platform_helper.providers.config import ConfigProvider
21
+ from dbt_platform_helper.providers.io import ClickIOProvider
22
+ from dbt_platform_helper.providers.schema_migrator import ALL_MIGRATIONS
23
+ from dbt_platform_helper.providers.schema_migrator import Migrator
24
+ from dbt_platform_helper.providers.validation import ValidationException
25
+ from dbt_platform_helper.providers.version_status import VersionStatus
26
+
27
+ yes = "\033[92m✔\033[0m"
28
+ no = "\033[91m✖\033[0m"
29
+ maybe = "\033[93m?\033[0m"
30
+
31
+ RECOMMENDATIONS = {
32
+ "dbt-platform-helper-upgrade": (
33
+ "Upgrade dbt-platform-helper to version {version} `pip install "
34
+ "--upgrade dbt-platform-helper=={version}`."
35
+ ),
36
+ "dbt-platform-helper-upgrade-note": (
37
+ "Post upgrade, run `platform-helper copilot make-addons` to " "update your addon templates."
38
+ ),
39
+ "generic-tool-upgrade": "Upgrade {tool} to version {version}.",
40
+ "install-copilot": "Install AWS Copilot https://aws.github.io/copilot-cli/",
41
+ "install-aws": "Install AWS CLI https://aws.amazon.com/cli/",
42
+ }
43
+
44
+ AWS_CONFIG = """
45
+ #
46
+ # uktrade
47
+ #
48
+
49
+ [sso-session uktrade]
50
+ sso_start_url = https://uktrade.awsapps.com/start#/
51
+ sso_region = eu-west-2
52
+ sso_registration_scopes = sso:account:access
53
+
54
+ [default]
55
+ sso_session = uktrade
56
+ region = eu-west-2
57
+ output = json
58
+
59
+ """
60
+
61
+
62
+ class NoDeploymentRepoConfigException(PlatformException):
63
+ def __init__(self):
64
+ super().__init__("Could not find a deployment repository, no checks to run.")
65
+
66
+
67
+ # TODO: DBTP-1993: move to generic location so it can be reused
68
+ class NoPlatformConfigException(PlatformException):
69
+ def __init__(self):
70
+ super().__init__(
71
+ f"`platform-config.yml` is missing. "
72
+ "Please check it exists and you are in the root directory of your deployment project."
73
+ )
74
+
75
+
76
+ class Config:
77
+ def __init__(
78
+ self,
79
+ io: ClickIOProvider = ClickIOProvider(),
80
+ platform_helper_versioning: PlatformHelperVersioning = PlatformHelperVersioning(),
81
+ aws_versioning: AWSVersioning = AWSVersioning(),
82
+ copilot_versioning: CopilotVersioning = CopilotVersioning(),
83
+ sso: SSOAuthProvider = None,
84
+ config_provider: ConfigProvider = ConfigProvider(),
85
+ migrator: Migrator = Migrator(ALL_MIGRATIONS, io_provider=ClickIOProvider()),
86
+ ):
87
+ self.oidc_app = None
88
+ self.io = io
89
+ self.platform_helper_versioning = platform_helper_versioning
90
+ self.aws_versioning = aws_versioning
91
+ self.copilot_versioning = copilot_versioning
92
+ self.sso = sso or SSOAuthProvider()
93
+ self.SSO_START_URL = "https://uktrade.awsapps.com/start"
94
+ self.config_provider = config_provider
95
+ self.migrator = migrator
96
+
97
+ def validate(self):
98
+ if not Path("copilot").exists():
99
+ raise NoDeploymentRepoConfigException()
100
+ if not Path(PLATFORM_CONFIG_FILE).exists():
101
+ raise NoPlatformConfigException()
102
+
103
+ self.io.debug("\nDetected a deployment repository\n")
104
+ platform_helper_version_status = self.platform_helper_versioning.get_version_status()
105
+ aws_version_status = self.aws_versioning.get_version_status()
106
+ copilot_version_status = self.copilot_versioning.get_version_status()
107
+
108
+ self._check_tool_versions(
109
+ platform_helper_version_status, aws_version_status, copilot_version_status
110
+ )
111
+
112
+ compatible = self._check_addon_versions(platform_helper_version_status)
113
+
114
+ exit(0 if compatible else 1)
115
+
116
+ def migrate(self):
117
+ platform_config = self.config_provider.load_unvalidated_config_file()
118
+ new_platform_config = self.migrator.migrate(platform_config)
119
+ self.config_provider.write_platform_config(new_platform_config)
120
+
121
+ def generate_aws(self, file_path):
122
+ self.oidc_app = self._create_oidc_application()
123
+ verification_url, device_code = self._get_device_code(self.oidc_app)
124
+
125
+ if self.io.confirm(
126
+ "You are about to be redirected to a verification page. You will need to complete sign-in before returning to the command line. Do you want to continue?",
127
+ ):
128
+ webbrowser.open(verification_url)
129
+
130
+ if self.io.confirm(
131
+ "Have you completed the sign-in process in your browser?",
132
+ ):
133
+ access_token = self.sso.create_access_token(
134
+ client_id=self.oidc_app[0],
135
+ client_secret=self.oidc_app[1],
136
+ device_code=device_code,
137
+ )
138
+
139
+ aws_config_path = os.path.expanduser(file_path)
140
+
141
+ if self.io.confirm(
142
+ f"This command is destructive and will overwrite file contents at {file_path}. Are you sure you want to continue?"
143
+ ):
144
+ self.io.info(
145
+ "Fetching credentials... this may take longer if you have access to many accounts."
146
+ )
147
+
148
+ with open(aws_config_path, "w") as config_file:
149
+ config_file.write(AWS_CONFIG)
150
+
151
+ for account in self._retrieve_aws_accounts(access_token):
152
+ config_file.write(f"[profile {account['accountName']}]\n")
153
+ config_file.write("sso_session = uktrade\n")
154
+ config_file.write(f"sso_account_id = {account['accountId']}\n")
155
+ config_file.write(
156
+ f"sso_role_name = {self._retrieve_role_for_aws_account(aws_sso_token=access_token, account_id=account['accountId'])}\n"
157
+ )
158
+ config_file.write("region = eu-west-2\n")
159
+ config_file.write("output = json\n")
160
+ config_file.write("\n")
161
+
162
+ def _create_oidc_application(self):
163
+ self.io.debug("Creating temporary AWS SSO OIDC application")
164
+ client_id, client_secret = self.sso.register(
165
+ client_name="platform-helper",
166
+ client_type="public",
167
+ )
168
+ return client_id, client_secret
169
+
170
+ def _get_device_code(self, oidc_application):
171
+ self.io.debug("Initiating device code flow")
172
+ url, device_code = self.sso.start_device_authorization(
173
+ client_id=oidc_application[0],
174
+ client_secret=oidc_application[1],
175
+ start_url=self.SSO_START_URL,
176
+ )
177
+
178
+ return url, device_code
179
+
180
+ def _retrieve_aws_accounts(self, aws_sso_token):
181
+ accounts_list = self.sso.list_accounts(
182
+ access_token=aws_sso_token,
183
+ max_results=100,
184
+ )
185
+ return accounts_list
186
+
187
+ def _retrieve_role_for_aws_account(self, aws_sso_token, account_id):
188
+ """
189
+ Selects the most appropriate IAM role for a given AWS account.
190
+
191
+ Roles listed in STANDARD_PLATFORM_SSO_ROLES are preferred if available.
192
+ If none are found, the first available role returned by
193
+ sso.list_account_roles is used.
194
+ """
195
+
196
+ role_list = self.sso.list_account_roles(
197
+ access_token=aws_sso_token,
198
+ account_id=account_id,
199
+ max_results=100,
200
+ )
201
+
202
+ roles_retrieved = [r["roleName"] for r in role_list]
203
+
204
+ for role in STANDARD_PLATFORM_SSO_ROLES:
205
+ if role in roles_retrieved:
206
+ return role
207
+ return role_list[0]["roleName"]
208
+
209
+ def _add_version_status_row(
210
+ self, table: PrettyTable, header: str, version_status: VersionStatus
211
+ ):
212
+ table.add_row(
213
+ [
214
+ header,
215
+ str(version_status.installed),
216
+ str(version_status.latest),
217
+ no if version_status.is_outdated() else yes,
218
+ ]
219
+ )
220
+
221
+ def _check_tool_versions(
222
+ self,
223
+ platform_helper_version_status: VersionStatus,
224
+ aws_version_status: VersionStatus,
225
+ copilot_version_status: VersionStatus,
226
+ ):
227
+ self.io.debug("Checking tooling versions...")
228
+
229
+ recommendations = {}
230
+
231
+ if copilot_version_status.installed is None:
232
+ recommendations["install-copilot"] = RECOMMENDATIONS["install-copilot"]
233
+
234
+ if aws_version_status.installed is None:
235
+ recommendations["install-aws"] = RECOMMENDATIONS["install-aws"]
236
+
237
+ tool_versions_table = PrettyTable()
238
+ tool_versions_table.field_names = [
239
+ "Tool",
240
+ "Local version",
241
+ "Released version",
242
+ "Running latest?",
243
+ ]
244
+ tool_versions_table.align["Tool"] = "l"
245
+
246
+ self._add_version_status_row(tool_versions_table, "aws", aws_version_status)
247
+ self._add_version_status_row(tool_versions_table, "copilot", copilot_version_status)
248
+ self._add_version_status_row(
249
+ tool_versions_table, "dbt-platform-helper", platform_helper_version_status
250
+ )
251
+
252
+ self.io.info(tool_versions_table)
253
+
254
+ if aws_version_status.is_outdated() and "install-aws" not in recommendations:
255
+ recommendations["aws-upgrade"] = RECOMMENDATIONS["generic-tool-upgrade"].format(
256
+ tool="AWS CLI",
257
+ version=str(aws_version_status.latest),
258
+ )
259
+
260
+ if copilot_version_status.is_outdated() and "install-copilot" not in recommendations:
261
+ recommendations["copilot-upgrade"] = RECOMMENDATIONS["generic-tool-upgrade"].format(
262
+ tool="AWS Copilot",
263
+ version=str(copilot_version_status.latest),
264
+ )
265
+
266
+ if platform_helper_version_status.is_outdated():
267
+ recommendations["dbt-platform-helper-upgrade"] = RECOMMENDATIONS[
268
+ "dbt-platform-helper-upgrade"
269
+ ].format(version=str(platform_helper_version_status.latest))
270
+ recommendations["dbt-platform-helper-upgrade-note"] = RECOMMENDATIONS[
271
+ "dbt-platform-helper-upgrade-note"
272
+ ]
273
+
274
+ self._render_recommendations(recommendations)
275
+
276
+ def _check_addon_versions(self, platform_helper_versions: VersionStatus) -> bool:
277
+
278
+ self.io.debug("Checking addons templates versions...")
279
+
280
+ compatible = True
281
+ recommendations = {}
282
+
283
+ local_version = platform_helper_versions.installed
284
+ latest_release = platform_helper_versions.latest
285
+
286
+ addons_templates_table = PrettyTable()
287
+ addons_templates_table.field_names = [
288
+ "Addons Template File",
289
+ "Generated with",
290
+ "Compatible with local?",
291
+ "Compatible with latest?",
292
+ ]
293
+ addons_templates_table.align["Addons Template File"] = "l"
294
+
295
+ addons_templates = list(Path("./copilot").glob("**/addons/*"))
296
+ # Sort by template file path
297
+ addons_templates.sort(key=lambda e: str(e))
298
+ # Bring environment addons to the top
299
+ addons_templates.sort(key=lambda e: "environments/" not in str(e))
300
+
301
+ for template_file in addons_templates:
302
+ generated_with_version = maybe
303
+ local_compatible_symbol = yes
304
+ latest_compatible_symbol = yes
305
+
306
+ generated_with_version = None
307
+
308
+ try:
309
+ generated_with_version = self.__get_template_generated_with_version(
310
+ str(template_file.resolve())
311
+ )
312
+ except ValidationException:
313
+ local_compatible_symbol = maybe
314
+ compatible = False
315
+ recommendations["dbt-platform-helper-upgrade"] = RECOMMENDATIONS[
316
+ "dbt-platform-helper-upgrade"
317
+ ].format(version=latest_release)
318
+ recommendations["dbt-platform-helper-upgrade-note"] = RECOMMENDATIONS[
319
+ "dbt-platform-helper-upgrade-note"
320
+ ]
321
+
322
+ try:
323
+ local_version.validate_compatibility_with(generated_with_version)
324
+ except IncompatibleMajorVersionException:
325
+ local_compatible_symbol = no
326
+ compatible = False
327
+ recommendations["dbt-platform-helper-upgrade"] = RECOMMENDATIONS[
328
+ "dbt-platform-helper-upgrade"
329
+ ].format(version=latest_release)
330
+ recommendations["dbt-platform-helper-upgrade-note"] = RECOMMENDATIONS[
331
+ "dbt-platform-helper-upgrade-note"
332
+ ]
333
+ except ValidationException:
334
+ local_compatible_symbol = maybe
335
+ compatible = False
336
+
337
+ try:
338
+ latest_release.validate_compatibility_with(generated_with_version)
339
+ except IncompatibleMajorVersionException:
340
+ latest_compatible_symbol = no
341
+ compatible = False
342
+ except ValidationException:
343
+ latest_compatible_symbol = maybe
344
+ compatible = False
345
+
346
+ addons_templates_table.add_row(
347
+ [
348
+ template_file.relative_to("."),
349
+ (maybe if latest_compatible_symbol is maybe else str(generated_with_version)),
350
+ local_compatible_symbol,
351
+ latest_compatible_symbol,
352
+ ]
353
+ )
354
+
355
+ self.io.info(addons_templates_table)
356
+ self._render_recommendations(recommendations)
357
+
358
+ return compatible
359
+
360
+ def _render_recommendations(self, recommendations: Dict[str, str]):
361
+ if recommendations:
362
+ self.io.info("\nRecommendations:\n", bold=True)
363
+
364
+ for name, recommendation in recommendations.items():
365
+ if name.endswith("-note"):
366
+ continue
367
+ self.io.info(f" - {recommendation}")
368
+ if recommendations.get(f"{name}-note", False):
369
+ self.io.info(f" {recommendations.get(f'{name}-note')}")
370
+
371
+ self.io.info("")
372
+
373
+ def __get_template_generated_with_version(self, template_file_path: str) -> SemanticVersion:
374
+ try:
375
+ template_contents = Path(template_file_path).read_text()
376
+ template_version = re.search(
377
+ r"# Generated by platform-helper ([v.\-0-9]+)", template_contents
378
+ ).group(1)
379
+ return SemanticVersion.from_string(template_version)
380
+ except (IndexError, AttributeError):
381
+ raise ValidationException(f"Template {template_file_path} has no version information")