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
@@ -11,6 +11,7 @@ from boto3 import Session
11
11
  from dbt_platform_helper.platform_exception import PlatformException
12
12
  from dbt_platform_helper.providers.files import FileProvider
13
13
  from dbt_platform_helper.providers.io import ClickIOProvider
14
+ from dbt_platform_helper.providers.parameter_store import ParameterStore
14
15
  from dbt_platform_helper.utils.application import Application
15
16
  from dbt_platform_helper.utils.application import (
16
17
  ApplicationEnvironmentNotFoundException,
@@ -20,6 +21,8 @@ from dbt_platform_helper.utils.aws import check_image_exists
20
21
  from dbt_platform_helper.utils.aws import get_aws_session_or_abort
21
22
  from dbt_platform_helper.utils.aws import get_build_url_from_arn
22
23
  from dbt_platform_helper.utils.aws import get_build_url_from_pipeline_execution_id
24
+ from dbt_platform_helper.utils.aws import get_image_build_project
25
+ from dbt_platform_helper.utils.aws import get_manual_release_pipeline
23
26
  from dbt_platform_helper.utils.aws import list_latest_images
24
27
  from dbt_platform_helper.utils.aws import start_build_extraction
25
28
  from dbt_platform_helper.utils.aws import start_pipeline_and_return_execution_id
@@ -30,10 +33,13 @@ from dbt_platform_helper.utils.template import setup_templates
30
33
  class Codebase:
31
34
  def __init__(
32
35
  self,
36
+ parameter_provider: ParameterStore,
33
37
  io: ClickIOProvider = ClickIOProvider(),
34
38
  load_application: Callable[[str], Application] = load_application,
35
39
  get_aws_session_or_abort: Callable[[str], Session] = get_aws_session_or_abort,
36
40
  check_image_exists: Callable[[str], str] = check_image_exists,
41
+ get_image_build_project: Callable[[str], str] = get_image_build_project,
42
+ get_manual_release_pipeline: Callable[[str], str] = get_manual_release_pipeline,
37
43
  get_build_url_from_arn: Callable[[str], str] = get_build_url_from_arn,
38
44
  get_build_url_from_pipeline_execution_id: Callable[
39
45
  [str], str
@@ -46,10 +52,13 @@ class Codebase:
46
52
  check_if_commit_exists: Callable[[str], str] = check_if_commit_exists,
47
53
  run_subprocess: Callable[[str], str] = subprocess.run,
48
54
  ):
55
+ self.parameter_provider = parameter_provider
49
56
  self.io = io
50
57
  self.load_application = load_application
51
58
  self.get_aws_session_or_abort = get_aws_session_or_abort
52
59
  self.check_image_exists = check_image_exists
60
+ self.get_image_build_project = get_image_build_project
61
+ self.get_manual_release_pipeline = get_manual_release_pipeline
53
62
  self.get_build_url_from_arn = get_build_url_from_arn
54
63
  self.get_build_url_from_pipeline_execution_id = get_build_url_from_pipeline_execution_id
55
64
  self.list_latest_images = list_latest_images
@@ -126,12 +135,13 @@ class Codebase:
126
135
  self.check_if_commit_exists(commit)
127
136
 
128
137
  codebuild_client = session.client("codebuild")
138
+ project_name = self.get_image_build_project(codebuild_client, app, codebase)
129
139
  build_url = self.__start_build_with_confirmation(
130
140
  codebuild_client,
131
141
  self.get_build_url_from_arn,
132
142
  f'You are about to build "{app}" for "{codebase}" with commit "{commit}". Do you want to continue?',
133
143
  {
134
- "projectName": f"{app}-{codebase}-codebase-pipeline-image-build",
144
+ "projectName": project_name,
135
145
  "artifactsOverride": {"type": "NO_ARTIFACTS"},
136
146
  "sourceVersion": commit,
137
147
  },
@@ -154,9 +164,10 @@ class Codebase:
154
164
 
155
165
  self.check_image_exists(session, application, codebase, commit)
156
166
 
157
- pipeline_name = f"{app}-{codebase}-manual-release-pipeline"
158
167
  codepipeline_client = session.client("codepipeline")
159
168
 
169
+ pipeline_name = self.get_manual_release_pipeline(codepipeline_client, app, codebase)
170
+
160
171
  build_url = self.__start_pipeline_execution_with_confirmation(
161
172
  codepipeline_client,
162
173
  self.get_build_url_from_pipeline_execution_id,
@@ -182,9 +193,8 @@ class Codebase:
182
193
  """List available codebases for the application."""
183
194
  session = self.get_aws_session_or_abort()
184
195
  application = self.load_application(app, session)
185
- ssm_client = session.client("ssm")
186
196
  ecr_client = session.client("ecr")
187
- codebases = self.__get_codebases(application, ssm_client)
197
+ codebases = self.__get_codebases(application, session.client("ssm"))
188
198
 
189
199
  self.io.info("The following codebases are available:")
190
200
 
@@ -201,11 +211,9 @@ class Codebase:
201
211
  self.io.info("")
202
212
 
203
213
  def __get_codebases(self, application, ssm_client):
204
- parameters = ssm_client.get_parameters_by_path(
205
- Path=f"/copilot/applications/{application.name}/codebases",
206
- Recursive=True,
207
- )["Parameters"]
208
-
214
+ parameters = self.parameter_provider.get_ssm_parameters_by_path(
215
+ f"/copilot/applications/{application.name}/codebases"
216
+ )
209
217
  codebases = [json.loads(p["Value"]) for p in parameters]
210
218
 
211
219
  if not codebases:
@@ -0,0 +1,345 @@
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.domain.versioning import AWSVersioning
11
+ from dbt_platform_helper.domain.versioning import CopilotVersioning
12
+ from dbt_platform_helper.domain.versioning import PlatformHelperVersioning
13
+ from dbt_platform_helper.platform_exception import PlatformException
14
+ from dbt_platform_helper.providers.aws.sso_auth import SSOAuthProvider
15
+ from dbt_platform_helper.providers.io import ClickIOProvider
16
+ from dbt_platform_helper.providers.semantic_version import (
17
+ IncompatibleMajorVersionException,
18
+ )
19
+ from dbt_platform_helper.providers.semantic_version import SemanticVersion
20
+ from dbt_platform_helper.providers.validation import ValidationException
21
+ from dbt_platform_helper.providers.version_status import PlatformHelperVersionStatus
22
+ from dbt_platform_helper.providers.version_status import VersionStatus
23
+
24
+ yes = "\033[92m✔\033[0m"
25
+ no = "\033[91m✖\033[0m"
26
+ maybe = "\033[93m?\033[0m"
27
+
28
+ RECOMMENDATIONS = {
29
+ "dbt-platform-helper-upgrade": (
30
+ "Upgrade dbt-platform-helper to version {version} `pip install "
31
+ "--upgrade dbt-platform-helper=={version}`."
32
+ ),
33
+ "dbt-platform-helper-upgrade-note": (
34
+ "Post upgrade, run `platform-helper copilot make-addons` to " "update your addon templates."
35
+ ),
36
+ "generic-tool-upgrade": "Upgrade {tool} to version {version}.",
37
+ "install-copilot": "Install AWS Copilot https://aws.github.io/copilot-cli/",
38
+ "install-aws": "Install AWS CLI https://aws.amazon.com/cli/",
39
+ }
40
+
41
+ AWS_CONFIG = """
42
+ #
43
+ # uktrade
44
+ #
45
+
46
+ [sso-session uktrade]
47
+ sso_start_url = https://uktrade.awsapps.com/start#/
48
+ sso_region = eu-west-2
49
+ sso_registration_scopes = sso:account:access
50
+
51
+ [default]
52
+ sso_session = uktrade
53
+ region = eu-west-2
54
+ output = json
55
+
56
+ """
57
+
58
+
59
+ class NoDeploymentRepoConfigException(PlatformException):
60
+ def __init__(self):
61
+ super().__init__("Could not find a deployment repository, no checks to run.")
62
+
63
+
64
+ # TODO move to generic location so it can be reused
65
+ class NoPlatformConfigException(PlatformException):
66
+ def __init__(self):
67
+ super().__init__(
68
+ f"`platform-config.yml` is missing. "
69
+ "Please check it exists and you are in the root directory of your deployment project."
70
+ )
71
+
72
+
73
+ class Config:
74
+
75
+ def __init__(
76
+ self,
77
+ io: ClickIOProvider = ClickIOProvider(),
78
+ platform_helper_versioning: PlatformHelperVersioning = PlatformHelperVersioning(),
79
+ aws_versioning: AWSVersioning = AWSVersioning(),
80
+ copilot_versioning: CopilotVersioning = CopilotVersioning(),
81
+ sso: SSOAuthProvider = None,
82
+ ):
83
+ self.oidc_app = None
84
+ self.io = io
85
+ self.platform_helper_versioning = platform_helper_versioning
86
+ self.aws_versioning = aws_versioning
87
+ self.copilot_versioning = copilot_versioning
88
+ self.sso = sso or SSOAuthProvider()
89
+ self.SSO_START_URL = "https://uktrade.awsapps.com/start"
90
+
91
+ def validate(self):
92
+ if not Path("copilot").exists():
93
+ raise NoDeploymentRepoConfigException()
94
+ if not Path(PLATFORM_CONFIG_FILE).exists():
95
+ raise NoPlatformConfigException()
96
+
97
+ self.io.debug("\nDetected a deployment repository\n")
98
+ platform_helper_version_status = self.platform_helper_versioning._get_version_status(
99
+ include_project_versions=True
100
+ )
101
+ self.io.process_messages(platform_helper_version_status.validate())
102
+ aws_version_status = self.aws_versioning.get_version_status()
103
+ copilot_version_status = self.copilot_versioning.get_version_status()
104
+
105
+ self._check_tool_versions(
106
+ platform_helper_version_status, aws_version_status, copilot_version_status
107
+ )
108
+
109
+ compatible = self._check_addon_versions(platform_helper_version_status)
110
+
111
+ exit(0 if compatible else 1)
112
+
113
+ def generate_aws(self, file_path):
114
+ self.oidc_app = self._create_oidc_application()
115
+ verification_url, device_code = self._get_device_code(self.oidc_app)
116
+
117
+ if self.io.confirm(
118
+ "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?",
119
+ ):
120
+ webbrowser.open(verification_url)
121
+
122
+ if self.io.confirm(
123
+ "Have you completed the sign-in process in your browser?",
124
+ ):
125
+ access_token = self.sso.create_access_token(
126
+ client_id=self.oidc_app[0],
127
+ client_secret=self.oidc_app[1],
128
+ device_code=device_code,
129
+ )
130
+
131
+ aws_config_path = os.path.expanduser(file_path)
132
+
133
+ if self.io.confirm(
134
+ f"This command is destructive and will overwrite file contents at {file_path}. Are you sure you want to continue?"
135
+ ):
136
+ with open(aws_config_path, "w") as config_file:
137
+ config_file.write(AWS_CONFIG)
138
+
139
+ for account in self._retrieve_aws_accounts(access_token):
140
+ config_file.write(f"[profile {account['accountName']}]\n")
141
+ config_file.write("sso_session = uktrade\n")
142
+ config_file.write(f"sso_account_id = {account['accountId']}\n")
143
+ config_file.write("sso_role_name = AdministratorAccess\n")
144
+ config_file.write("region = eu-west-2\n")
145
+ config_file.write("output = json\n")
146
+ config_file.write("\n")
147
+
148
+ def _create_oidc_application(self):
149
+ self.io.debug("Creating temporary AWS SSO OIDC application")
150
+ client_id, client_secret = self.sso.register(
151
+ client_name="platform-helper",
152
+ client_type="public",
153
+ )
154
+ return client_id, client_secret
155
+
156
+ def _get_device_code(self, oidc_application):
157
+ self.io.debug("Initiating device code flow")
158
+ url, device_code = self.sso.start_device_authorization(
159
+ client_id=oidc_application[0],
160
+ client_secret=oidc_application[1],
161
+ start_url=self.SSO_START_URL,
162
+ )
163
+
164
+ return url, device_code
165
+
166
+ def _retrieve_aws_accounts(self, aws_sso_token):
167
+ accounts_list = self.sso.list_accounts(
168
+ access_token=aws_sso_token,
169
+ max_results=100,
170
+ )
171
+ return accounts_list
172
+
173
+ def _add_version_status_row(
174
+ self, table: PrettyTable, header: str, version_status: VersionStatus
175
+ ):
176
+ table.add_row(
177
+ [
178
+ header,
179
+ str(version_status.installed),
180
+ str(version_status.latest),
181
+ no if version_status.is_outdated() else yes,
182
+ ]
183
+ )
184
+
185
+ def _check_tool_versions(
186
+ self,
187
+ platform_helper_version_status: PlatformHelperVersionStatus,
188
+ aws_version_status: VersionStatus,
189
+ copilot_version_status: VersionStatus,
190
+ ):
191
+ self.io.debug("Checking tooling versions...")
192
+
193
+ recommendations = {}
194
+
195
+ if copilot_version_status.installed is None:
196
+ recommendations["install-copilot"] = RECOMMENDATIONS["install-copilot"]
197
+
198
+ if aws_version_status.installed is None:
199
+ recommendations["install-aws"] = RECOMMENDATIONS["install-aws"]
200
+
201
+ tool_versions_table = PrettyTable()
202
+ tool_versions_table.field_names = [
203
+ "Tool",
204
+ "Local version",
205
+ "Released version",
206
+ "Running latest?",
207
+ ]
208
+ tool_versions_table.align["Tool"] = "l"
209
+
210
+ self._add_version_status_row(tool_versions_table, "aws", aws_version_status)
211
+ self._add_version_status_row(tool_versions_table, "copilot", copilot_version_status)
212
+ self._add_version_status_row(
213
+ tool_versions_table, "dbt-platform-helper", platform_helper_version_status
214
+ )
215
+
216
+ self.io.info(tool_versions_table)
217
+
218
+ if aws_version_status.is_outdated() and "install-aws" not in recommendations:
219
+ recommendations["aws-upgrade"] = RECOMMENDATIONS["generic-tool-upgrade"].format(
220
+ tool="AWS CLI",
221
+ version=str(aws_version_status.latest),
222
+ )
223
+
224
+ if copilot_version_status.is_outdated() and "install-copilot" not in recommendations:
225
+ recommendations["copilot-upgrade"] = RECOMMENDATIONS["generic-tool-upgrade"].format(
226
+ tool="AWS Copilot",
227
+ version=str(copilot_version_status.latest),
228
+ )
229
+
230
+ if platform_helper_version_status.is_outdated():
231
+ recommendations["dbt-platform-helper-upgrade"] = RECOMMENDATIONS[
232
+ "dbt-platform-helper-upgrade"
233
+ ].format(version=str(platform_helper_version_status.latest))
234
+ recommendations["dbt-platform-helper-upgrade-note"] = RECOMMENDATIONS[
235
+ "dbt-platform-helper-upgrade-note"
236
+ ]
237
+
238
+ self._render_recommendations(recommendations)
239
+
240
+ def _check_addon_versions(self, platform_helper_versions: PlatformHelperVersionStatus) -> bool:
241
+
242
+ self.io.debug("Checking addons templates versions...")
243
+
244
+ compatible = True
245
+ recommendations = {}
246
+
247
+ local_version = platform_helper_versions.installed
248
+ latest_release = platform_helper_versions.latest
249
+
250
+ addons_templates_table = PrettyTable()
251
+ addons_templates_table.field_names = [
252
+ "Addons Template File",
253
+ "Generated with",
254
+ "Compatible with local?",
255
+ "Compatible with latest?",
256
+ ]
257
+ addons_templates_table.align["Addons Template File"] = "l"
258
+
259
+ addons_templates = list(Path("./copilot").glob("**/addons/*"))
260
+ # Sort by template file path
261
+ addons_templates.sort(key=lambda e: str(e))
262
+ # Bring environment addons to the top
263
+ addons_templates.sort(key=lambda e: "environments/" not in str(e))
264
+
265
+ for template_file in addons_templates:
266
+ generated_with_version = maybe
267
+ local_compatible_symbol = yes
268
+ latest_compatible_symbol = yes
269
+
270
+ generated_with_version = None
271
+
272
+ try:
273
+ generated_with_version = self.__get_template_generated_with_version(
274
+ str(template_file.resolve())
275
+ )
276
+ except ValidationException:
277
+ local_compatible_symbol = maybe
278
+ compatible = False
279
+ recommendations["dbt-platform-helper-upgrade"] = RECOMMENDATIONS[
280
+ "dbt-platform-helper-upgrade"
281
+ ].format(version=latest_release)
282
+ recommendations["dbt-platform-helper-upgrade-note"] = RECOMMENDATIONS[
283
+ "dbt-platform-helper-upgrade-note"
284
+ ]
285
+
286
+ try:
287
+ local_version.validate_compatibility_with(generated_with_version)
288
+ except IncompatibleMajorVersionException:
289
+ local_compatible_symbol = no
290
+ compatible = False
291
+ recommendations["dbt-platform-helper-upgrade"] = RECOMMENDATIONS[
292
+ "dbt-platform-helper-upgrade"
293
+ ].format(version=latest_release)
294
+ recommendations["dbt-platform-helper-upgrade-note"] = RECOMMENDATIONS[
295
+ "dbt-platform-helper-upgrade-note"
296
+ ]
297
+ except ValidationException:
298
+ local_compatible_symbol = maybe
299
+ compatible = False
300
+
301
+ try:
302
+ latest_release.validate_compatibility_with(generated_with_version)
303
+ except IncompatibleMajorVersionException:
304
+ latest_compatible_symbol = no
305
+ compatible = False
306
+ except ValidationException:
307
+ latest_compatible_symbol = maybe
308
+ compatible = False
309
+
310
+ addons_templates_table.add_row(
311
+ [
312
+ template_file.relative_to("."),
313
+ (maybe if latest_compatible_symbol is maybe else str(generated_with_version)),
314
+ local_compatible_symbol,
315
+ latest_compatible_symbol,
316
+ ]
317
+ )
318
+
319
+ self.io.info(addons_templates_table)
320
+ self._render_recommendations(recommendations)
321
+
322
+ return compatible
323
+
324
+ def _render_recommendations(self, recommendations: Dict[str, str]):
325
+ if recommendations:
326
+ self.io.info("\nRecommendations:\n", bold=True)
327
+
328
+ for name, recommendation in recommendations.items():
329
+ if name.endswith("-note"):
330
+ continue
331
+ self.io.info(f" - {recommendation}")
332
+ if recommendations.get(f"{name}-note", False):
333
+ self.io.info(f" {recommendations.get(f'{name}-note')}")
334
+
335
+ self.io.info("")
336
+
337
+ def __get_template_generated_with_version(self, template_file_path: str) -> SemanticVersion:
338
+ try:
339
+ template_contents = Path(template_file_path).read_text()
340
+ template_version = re.search(
341
+ r"# Generated by platform-helper ([v.\-0-9]+)", template_contents
342
+ ).group(1)
343
+ return SemanticVersion.from_string(template_version)
344
+ except (IndexError, AttributeError):
345
+ raise ValidationException(f"Template {template_file_path} has no version information")