dbt-platform-helper 12.5.1__py3-none-any.whl → 13.0.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 (47) hide show
  1. dbt_platform_helper/COMMANDS.md +45 -42
  2. dbt_platform_helper/commands/codebase.py +7 -10
  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/notify.py +5 -3
  7. dbt_platform_helper/commands/pipeline.py +17 -11
  8. dbt_platform_helper/constants.py +3 -1
  9. dbt_platform_helper/domain/codebase.py +48 -36
  10. dbt_platform_helper/domain/conduit.py +10 -12
  11. dbt_platform_helper/domain/config_validator.py +42 -31
  12. dbt_platform_helper/domain/copilot_environment.py +133 -129
  13. dbt_platform_helper/domain/database_copy.py +38 -37
  14. dbt_platform_helper/domain/maintenance_page.py +243 -193
  15. dbt_platform_helper/domain/pipelines.py +60 -135
  16. dbt_platform_helper/domain/terraform_environment.py +7 -3
  17. dbt_platform_helper/providers/aws.py +5 -0
  18. dbt_platform_helper/providers/cloudformation.py +12 -1
  19. dbt_platform_helper/providers/config.py +12 -14
  20. dbt_platform_helper/providers/ecr.py +20 -0
  21. dbt_platform_helper/providers/files.py +1 -1
  22. dbt_platform_helper/providers/io.py +31 -0
  23. dbt_platform_helper/providers/load_balancers.py +29 -3
  24. dbt_platform_helper/providers/platform_config_schema.py +24 -22
  25. dbt_platform_helper/providers/terraform_manifest.py +120 -0
  26. dbt_platform_helper/providers/vpc.py +81 -32
  27. dbt_platform_helper/templates/COMMANDS.md.jinja +5 -3
  28. dbt_platform_helper/templates/environment-pipelines/main.tf +2 -2
  29. dbt_platform_helper/templates/environments/main.tf +3 -4
  30. dbt_platform_helper/utils/aws.py +16 -5
  31. dbt_platform_helper/utils/messages.py +2 -3
  32. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/METADATA +2 -2
  33. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/RECORD +36 -44
  34. dbt_platform_helper/templates/pipelines/codebase/manifest.yml +0 -56
  35. dbt_platform_helper/templates/pipelines/codebase/overrides/.gitignore +0 -12
  36. dbt_platform_helper/templates/pipelines/codebase/overrides/bin/override.ts +0 -8
  37. dbt_platform_helper/templates/pipelines/codebase/overrides/buildspec.deploy.yml +0 -29
  38. dbt_platform_helper/templates/pipelines/codebase/overrides/buildspec.image.yml +0 -48
  39. dbt_platform_helper/templates/pipelines/codebase/overrides/cdk.json +0 -20
  40. dbt_platform_helper/templates/pipelines/codebase/overrides/package-lock.json +0 -4232
  41. dbt_platform_helper/templates/pipelines/codebase/overrides/package.json +0 -27
  42. dbt_platform_helper/templates/pipelines/codebase/overrides/stack.ts +0 -521
  43. dbt_platform_helper/templates/pipelines/codebase/overrides/tsconfig.json +0 -30
  44. dbt_platform_helper/templates/pipelines/codebase/overrides/types.ts +0 -52
  45. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/LICENSE +0 -0
  46. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/WHEEL +0 -0
  47. {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/entry_points.txt +0 -0
@@ -5,13 +5,14 @@ from shutil import rmtree
5
5
 
6
6
  from dbt_platform_helper.constants import CODEBASE_PIPELINES_KEY
7
7
  from dbt_platform_helper.constants import ENVIRONMENT_PIPELINES_KEY
8
- from dbt_platform_helper.constants import ENVIRONMENTS_KEY
8
+ from dbt_platform_helper.constants import SUPPORTED_AWS_PROVIDER_VERSION
9
+ from dbt_platform_helper.constants import SUPPORTED_TERRAFORM_VERSION
9
10
  from dbt_platform_helper.providers.config import ConfigProvider
11
+ from dbt_platform_helper.providers.ecr import ECRProvider
10
12
  from dbt_platform_helper.providers.files import FileProvider
13
+ from dbt_platform_helper.providers.io import ClickIOProvider
14
+ from dbt_platform_helper.providers.terraform_manifest import TerraformManifestProvider
11
15
  from dbt_platform_helper.utils.application import get_application_name
12
- from dbt_platform_helper.utils.aws import get_account_details
13
- from dbt_platform_helper.utils.aws import get_public_repository_arn
14
- from dbt_platform_helper.utils.files import generate_override_files_from_template
15
16
  from dbt_platform_helper.utils.template import setup_templates
16
17
  from dbt_platform_helper.utils.versioning import (
17
18
  get_required_terraform_platform_modules_version,
@@ -22,192 +23,116 @@ class Pipelines:
22
23
  def __init__(
23
24
  self,
24
25
  config_provider: ConfigProvider,
25
- echo: Callable[[str], str],
26
- abort: Callable[[str], None],
26
+ terraform_manifest_provider: TerraformManifestProvider,
27
+ ecr_provider: ECRProvider,
27
28
  get_git_remote: Callable[[], str],
28
29
  get_codestar_arn: Callable[[str], str],
30
+ io: ClickIOProvider = ClickIOProvider(),
31
+ file_provider: FileProvider = FileProvider(),
29
32
  ):
30
33
  self.config_provider = config_provider
31
- self.echo = echo
32
- self.abort = abort
33
34
  self.get_git_remote = get_git_remote
34
35
  self.get_codestar_arn = get_codestar_arn
36
+ self.terraform_manifest_provider = terraform_manifest_provider
37
+ self.ecr_provider = ecr_provider
38
+ self.io = io
39
+ self.file_provider = file_provider
35
40
 
36
- def generate(self, terraform_platform_modules_version, deploy_branch):
37
- pipeline_config = self.config_provider.load_and_validate_platform_config()
41
+ def generate(self, cli_terraform_platform_modules_version: str, deploy_branch: str):
42
+ platform_config = self.config_provider.load_and_validate_platform_config()
38
43
 
39
- has_codebase_pipelines = CODEBASE_PIPELINES_KEY in pipeline_config
40
- has_environment_pipelines = ENVIRONMENT_PIPELINES_KEY in pipeline_config
44
+ has_codebase_pipelines = CODEBASE_PIPELINES_KEY in platform_config
45
+ has_environment_pipelines = ENVIRONMENT_PIPELINES_KEY in platform_config
41
46
 
42
47
  if not (has_codebase_pipelines or has_environment_pipelines):
43
- self.echo("No pipelines defined: nothing to do.", err=True, fg="yellow")
48
+ self.io.warn("No pipelines defined: nothing to do.")
44
49
  return
45
50
 
46
- platform_config_terraform_modules_default_version = pipeline_config.get(
51
+ platform_config_terraform_modules_default_version = platform_config.get(
47
52
  "default_versions", {}
48
53
  ).get("terraform-platform-modules", "")
49
54
 
50
- templates = setup_templates()
51
55
  app_name = get_application_name()
52
56
 
53
57
  git_repo = self.get_git_remote()
54
58
  if not git_repo:
55
- self.abort("The current directory is not a git repository")
59
+ self.io.abort_with_error("The current directory is not a git repository")
56
60
 
57
61
  codestar_connection_arn = self.get_codestar_arn(app_name)
58
62
  if codestar_connection_arn is None:
59
- self.abort(f'There is no CodeStar Connection named "{app_name}" to use')
63
+ self.io.abort_with_error(f'There is no CodeStar Connection named "{app_name}" to use')
60
64
 
61
65
  base_path = Path(".")
62
66
  copilot_pipelines_dir = base_path / f"copilot/pipelines"
63
67
 
64
68
  self._clean_pipeline_config(copilot_pipelines_dir)
65
69
 
70
+ terraform_platform_modules_version = get_required_terraform_platform_modules_version(
71
+ cli_terraform_platform_modules_version,
72
+ platform_config_terraform_modules_default_version,
73
+ )
74
+
66
75
  if has_environment_pipelines:
67
- environment_pipelines = pipeline_config[ENVIRONMENT_PIPELINES_KEY]
76
+ environment_pipelines = platform_config[ENVIRONMENT_PIPELINES_KEY]
77
+ accounts = {
78
+ config.get("account")
79
+ for config in environment_pipelines.values()
80
+ if "account" in config
81
+ }
68
82
 
69
- for config in environment_pipelines.values():
70
- aws_account = config.get("account")
83
+ for account in accounts:
71
84
  self._generate_terraform_environment_pipeline_manifest(
72
- pipeline_config["application"],
73
- aws_account,
85
+ platform_config["application"],
86
+ account,
74
87
  terraform_platform_modules_version,
75
- platform_config_terraform_modules_default_version,
76
88
  deploy_branch,
77
89
  )
78
90
 
79
91
  if has_codebase_pipelines:
80
- account_id, _ = get_account_details()
81
-
82
- for codebase in pipeline_config[CODEBASE_PIPELINES_KEY]:
83
- self._generate_codebase_pipeline(
84
- account_id,
85
- app_name,
86
- codestar_connection_arn,
87
- git_repo,
88
- codebase,
89
- base_path,
90
- copilot_pipelines_dir,
91
- templates,
92
- )
92
+ codebase_pipelines = platform_config[CODEBASE_PIPELINES_KEY]
93
+ ecrs_to_be_managed = {
94
+ codebase: f"{platform_config['application']}/{codebase}"
95
+ for codebase in codebase_pipelines.keys()
96
+ }
97
+ ecrs_already_provisioned = set(self.ecr_provider.get_ecr_repo_names())
98
+ ecrs_that_need_importing = {
99
+ codebase: repo
100
+ for codebase, repo in ecrs_to_be_managed.items()
101
+ if repo in ecrs_already_provisioned
102
+ }
93
103
 
94
- def _clean_pipeline_config(self, pipelines_dir):
104
+ self.terraform_manifest_provider.generate_codebase_pipeline_config(
105
+ platform_config, terraform_platform_modules_version, ecrs_that_need_importing
106
+ )
107
+
108
+ def _clean_pipeline_config(self, pipelines_dir: Path):
95
109
  if pipelines_dir.exists():
96
- self.echo("Deleting copilot/pipelines directory.")
110
+ self.io.info("Deleting copilot/pipelines directory.")
97
111
  rmtree(pipelines_dir)
98
112
 
99
- def _generate_codebase_pipeline(
100
- self,
101
- account_id,
102
- app_name,
103
- codestar_connection_arn,
104
- git_repo,
105
- codebase,
106
- base_path,
107
- pipelines_dir,
108
- templates,
109
- ):
110
- makedirs(pipelines_dir / codebase["name"] / "overrides", exist_ok=True)
111
- environments = []
112
- for pipelines in codebase["pipelines"]:
113
- environments += pipelines[ENVIRONMENTS_KEY]
114
-
115
- additional_ecr = codebase.get("additional_ecr_repository", None)
116
- add_public_perms = additional_ecr and additional_ecr.startswith("public.ecr.aws")
117
- additional_ecr_arn = get_public_repository_arn(additional_ecr) if add_public_perms else None
118
-
119
- template_data = {
120
- "account_id": account_id,
121
- "app_name": app_name,
122
- "deploy_repo": git_repo,
123
- "codebase": codebase,
124
- ENVIRONMENTS_KEY: environments,
125
- "codestar_connection_arn": codestar_connection_arn,
126
- "codestar_connection_id": codestar_connection_arn.split("/")[-1],
127
- "additional_ecr_arn": additional_ecr_arn,
128
- }
129
-
130
- self._create_file_from_template(
131
- base_path,
132
- f"{codebase['name']}/manifest.yml",
133
- pipelines_dir,
134
- template_data,
135
- templates,
136
- "codebase/manifest.yml",
137
- )
138
-
139
- overrides_path = Path(__file__).parent.parent.joinpath(
140
- "templates/pipelines/codebase/overrides"
141
- )
142
- generate_override_files_from_template(
143
- base_path, overrides_path, pipelines_dir / codebase["name"] / "overrides", template_data
144
- )
145
-
146
- def _create_file_from_template(
147
- self, base_path, file_name, pipelines_dir, template_data, templates, template_name=None
148
- ):
149
- contents = templates.get_template(
150
- f"pipelines/{file_name if template_name is None else template_name}"
151
- ).render(template_data)
152
- message = FileProvider.mkfile(
153
- base_path, pipelines_dir / file_name, contents, overwrite=True
154
- )
155
- self.echo(message)
156
-
157
113
  def _generate_terraform_environment_pipeline_manifest(
158
114
  self,
159
- application,
160
- aws_account,
161
- cli_terraform_platform_modules_version,
162
- platform_config_terraform_modules_default_version,
163
- deploy_branch,
115
+ application: str,
116
+ aws_account: str,
117
+ terraform_platform_modules_version: str,
118
+ deploy_branch: str,
164
119
  ):
165
120
  env_pipeline_template = setup_templates().get_template("environment-pipelines/main.tf")
166
121
 
167
- terraform_platform_modules_version = get_required_terraform_platform_modules_version(
168
- cli_terraform_platform_modules_version,
169
- platform_config_terraform_modules_default_version,
170
- )
171
-
172
122
  contents = env_pipeline_template.render(
173
123
  {
174
124
  "application": application,
175
125
  "aws_account": aws_account,
176
126
  "terraform_platform_modules_version": terraform_platform_modules_version,
177
127
  "deploy_branch": deploy_branch,
128
+ "terraform_version": SUPPORTED_TERRAFORM_VERSION,
129
+ "aws_provider_version": SUPPORTED_AWS_PROVIDER_VERSION,
178
130
  }
179
131
  )
180
132
 
181
133
  dir_path = f"terraform/environment-pipelines/{aws_account}"
182
134
  makedirs(dir_path, exist_ok=True)
183
135
 
184
- self.echo(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
185
-
186
- def generate_terraform_codebase_pipeline_manifest(
187
- self,
188
- application,
189
- aws_account,
190
- cli_terraform_platform_modules_version,
191
- platform_config_terraform_modules_default_version,
192
- deploy_branch,
193
- ):
194
- env_pipeline_template = setup_templates().get_template("codebase-pipelines/main.tf")
195
-
196
- terraform_platform_modules_version = get_required_terraform_platform_modules_version(
197
- cli_terraform_platform_modules_version,
198
- platform_config_terraform_modules_default_version,
199
- )
200
-
201
- contents = env_pipeline_template.render(
202
- {
203
- "application": application,
204
- "aws_account": aws_account,
205
- "terraform_platform_modules_version": terraform_platform_modules_version,
206
- "deploy_branch": deploy_branch,
207
- }
136
+ self.io.info(
137
+ self.file_provider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True)
208
138
  )
209
-
210
- dir_path = f"terraform/environment-pipelines/{aws_account}"
211
- makedirs(dir_path, exist_ok=True)
212
-
213
- self.echo(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
@@ -1,6 +1,8 @@
1
1
  import click
2
2
 
3
3
  from dbt_platform_helper.constants import DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION
4
+ from dbt_platform_helper.constants import SUPPORTED_AWS_PROVIDER_VERSION
5
+ from dbt_platform_helper.constants import SUPPORTED_TERRAFORM_VERSION
4
6
  from dbt_platform_helper.platform_exception import PlatformException
5
7
  from dbt_platform_helper.providers.files import FileProvider
6
8
  from dbt_platform_helper.utils.template import setup_templates
@@ -31,6 +33,8 @@ class PlatformTerraformManifestGenerator:
31
33
  "environment": environment_name,
32
34
  "config": environment_config,
33
35
  "terraform_platform_modules_version": terraform_platform_modules_version,
36
+ "terraform_version": SUPPORTED_TERRAFORM_VERSION,
37
+ "aws_provider_version": SUPPORTED_AWS_PROVIDER_VERSION,
34
38
  }
35
39
  )
36
40
 
@@ -56,9 +60,9 @@ class TerraformEnvironment:
56
60
  self,
57
61
  config_provider,
58
62
  manifest_generator: PlatformTerraformManifestGenerator = None,
59
- echo_fn=click.echo,
63
+ echo=click.echo,
60
64
  ):
61
- self.echo = echo_fn
65
+ self.echo = echo
62
66
  self.config_provider = config_provider
63
67
  self.manifest_generator = manifest_generator or PlatformTerraformManifestGenerator(
64
68
  FileProvider()
@@ -69,7 +73,7 @@ class TerraformEnvironment:
69
73
 
70
74
  if environment_name not in config.get("environments").keys():
71
75
  raise EnvironmentNotFoundException(
72
- f"Error: cannot generate terraform for environment {environment_name}. It does not exist in your configuration"
76
+ f"cannot generate terraform for environment {environment_name}. It does not exist in your configuration"
73
77
  )
74
78
 
75
79
  manifest = self.manifest_generator.generate_manifest(
@@ -19,6 +19,11 @@ class ImageNotFoundException(AWSException):
19
19
  )
20
20
 
21
21
 
22
+ class RepositoryNotFoundException(AWSException):
23
+ def __init__(self, repository: str):
24
+ super().__init__(f"""The ECR repository "{repository}" could not be found.""")
25
+
26
+
22
27
  class LogGroupNotFoundException(AWSException):
23
28
  def __init__(self, log_group_name: str):
24
29
  super().__init__(f"""No log group called "{log_group_name}".""")
@@ -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):
@@ -1,35 +1,34 @@
1
1
  from copy import deepcopy
2
2
  from pathlib import Path
3
3
 
4
- import click
5
4
  from schema import SchemaError
6
5
 
7
6
  from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE
8
7
  from dbt_platform_helper.domain.config_validator import ConfigValidator
8
+ from dbt_platform_helper.providers.io import ClickIOProvider
9
9
  from dbt_platform_helper.providers.platform_config_schema import PlatformConfigSchema
10
10
  from dbt_platform_helper.providers.yaml_file import FileNotFoundException
11
11
  from dbt_platform_helper.providers.yaml_file import FileProviderException
12
12
  from dbt_platform_helper.providers.yaml_file import YamlFileProvider
13
- from dbt_platform_helper.utils.messages import abort_with_error
14
13
 
15
14
 
16
15
  class ConfigProvider:
17
16
  def __init__(
18
17
  self,
19
- config_validator: ConfigValidator,
18
+ config_validator: ConfigValidator = None,
20
19
  file_provider: YamlFileProvider = None,
21
- echo=click.secho,
20
+ io: ClickIOProvider = None,
22
21
  ):
23
22
  self.config = {}
24
- self.validator = config_validator
25
- self.echo = echo
23
+ self.validator = config_validator or ConfigValidator()
24
+ self.io = io or ClickIOProvider()
26
25
  self.file_provider = file_provider or YamlFileProvider
27
26
 
28
27
  # TODO refactor so that apply_environment_defaults isn't set, discarded and set again
29
28
  def get_enriched_config(self):
30
29
  return self.apply_environment_defaults(self.load_and_validate_platform_config())
31
30
 
32
- def validate_platform_config(self):
31
+ def _validate_platform_config(self):
33
32
  PlatformConfigSchema.schema().validate(self.config)
34
33
 
35
34
  # TODO= logically this isn't validation but loading + parsing, to move.
@@ -42,24 +41,23 @@ class ConfigProvider:
42
41
  try:
43
42
  self.config = self.file_provider.load(path)
44
43
  except FileNotFoundException as e:
45
- abort_with_error(
44
+ self.io.abort_with_error(
46
45
  f"{e} Please check it exists and you are in the root directory of your deployment project."
47
46
  )
48
47
  except FileProviderException as e:
49
- abort_with_error(f"Error loading configuration from {path}: {e}")
48
+ self.io.abort_with_error(f"Error loading configuration from {path}: {e}")
50
49
 
51
50
  try:
52
- self.validate_platform_config()
51
+ self._validate_platform_config()
53
52
  except SchemaError as e:
54
- abort_with_error(f"Schema error in {path}. {e}")
53
+ self.io.abort_with_error(f"Schema error in {path}. {e}")
55
54
 
56
55
  return self.config
57
56
 
58
- @staticmethod
59
57
  # TODO this general function should be moved out of ConfigProvider
60
- def config_file_check(path=PLATFORM_CONFIG_FILE):
58
+ def config_file_check(self, path=PLATFORM_CONFIG_FILE):
61
59
  if not Path(path).exists():
62
- abort_with_error(
60
+ self.io.abort_with_error(
63
61
  f"`{path}` is missing. "
64
62
  "Please check it exists and you are in the root directory of your deployment project."
65
63
  )
@@ -0,0 +1,20 @@
1
+ from boto3 import Session
2
+
3
+ from dbt_platform_helper.utils.aws import get_aws_session_or_abort
4
+
5
+
6
+ class ECRProvider:
7
+ def __init__(self, session: Session = None):
8
+ self.session = session
9
+ self.client = None
10
+
11
+ def _get_client(self):
12
+ if not self.session:
13
+ self.session = get_aws_session_or_abort()
14
+ return self.session.client("ecr")
15
+
16
+ def get_ecr_repo_names(self) -> list[str]:
17
+ out = []
18
+ for page in self._get_client().get_paginator("describe_repositories").paginate():
19
+ out.extend([repo["repositoryName"] for repo in page.get("repositories", {})])
20
+ return out
@@ -8,7 +8,7 @@ class FileProvider:
8
8
  pass
9
9
 
10
10
  @staticmethod
11
- def mkfile(base_path: str, file_path: str, contents, overwrite=False) -> str:
11
+ def mkfile(base_path: str, file_path: str, contents: str, overwrite=False) -> str:
12
12
  file_path = Path(file_path)
13
13
  file = Path(base_path).joinpath(file_path)
14
14
  file_exists = file.exists()
@@ -0,0 +1,31 @@
1
+ import click
2
+
3
+ from dbt_platform_helper.platform_exception import PlatformException
4
+
5
+
6
+ class ClickIOProvider:
7
+ def warn(self, message: str):
8
+ click.secho(message, fg="magenta")
9
+
10
+ def error(self, message: str):
11
+ click.secho(f"Error: {message}", fg="red")
12
+
13
+ def info(self, message: str):
14
+ click.secho(message)
15
+
16
+ def input(self, message: str) -> str:
17
+ return click.prompt(message)
18
+
19
+ def confirm(self, message: str) -> bool:
20
+ try:
21
+ return click.confirm(message)
22
+ except click.Abort:
23
+ raise ClickIOProviderException(message + " [y/N]: Error: invalid input")
24
+
25
+ def abort_with_error(self, message: str):
26
+ click.secho(f"Error: {message}", err=True, fg="red")
27
+ exit(1)
28
+
29
+
30
+ class ClickIOProviderException(PlatformException):
31
+ pass
@@ -2,8 +2,11 @@ import boto3
2
2
 
3
3
  from dbt_platform_helper.platform_exception import PlatformException
4
4
 
5
+ # TODO - a good candidate for a dataclass when this is refactored into a class.
6
+ # Below methods should also really be refactored to not be so tightly coupled with eachother.
5
7
 
6
- def find_load_balancer(session: boto3.Session, app: str, env: str) -> str:
8
+
9
+ def get_load_balancer_for_application(session: boto3.Session, app: str, env: str) -> str:
7
10
  lb_client = session.client("elbv2")
8
11
 
9
12
  describe_response = lb_client.describe_load_balancers()
@@ -23,8 +26,8 @@ def find_load_balancer(session: boto3.Session, app: str, env: str) -> str:
23
26
  return load_balancer_arn
24
27
 
25
28
 
26
- def find_https_listener(session: boto3.Session, app: str, env: str) -> str:
27
- load_balancer_arn = find_load_balancer(session, app, env)
29
+ def get_https_listener_for_application(session: boto3.Session, app: str, env: str) -> str:
30
+ load_balancer_arn = get_load_balancer_for_application(session, app, env)
28
31
  lb_client = session.client("elbv2")
29
32
  listeners = lb_client.describe_listeners(LoadBalancerArn=load_balancer_arn)["Listeners"]
30
33
 
@@ -41,6 +44,22 @@ def find_https_listener(session: boto3.Session, app: str, env: str) -> str:
41
44
  return listener_arn
42
45
 
43
46
 
47
+ def get_https_certificate_for_application(session: boto3.Session, app: str, env: str) -> str:
48
+
49
+ listener_arn = get_https_listener_for_application(session, app, env)
50
+ cert_client = session.client("elbv2")
51
+ certificates = cert_client.describe_listener_certificates(ListenerArn=listener_arn)[
52
+ "Certificates"
53
+ ]
54
+
55
+ try:
56
+ certificate_arn = next(c["CertificateArn"] for c in certificates if c["IsDefault"])
57
+ except StopIteration:
58
+ raise CertificateNotFoundException(env)
59
+
60
+ return certificate_arn
61
+
62
+
44
63
  class LoadBalancerException(PlatformException):
45
64
  pass
46
65
 
@@ -55,3 +74,10 @@ class ListenerNotFoundException(LoadBalancerException):
55
74
 
56
75
  class ListenerRuleNotFoundException(LoadBalancerException):
57
76
  pass
77
+
78
+
79
+ class CertificateNotFoundException(PlatformException):
80
+ def __init__(self, environment_name: str):
81
+ super().__init__(
82
+ f"""No certificate found with domain name matching environment {environment_name}."."""
83
+ )
@@ -16,9 +16,7 @@ class PlatformConfigSchema:
16
16
  {
17
17
  # The following line is for the AWS Copilot version, will be removed under DBTP-1002
18
18
  "application": str,
19
- Optional("legacy_project", default=False): bool,
20
19
  Optional("default_versions"): PlatformConfigSchema.__default_versions_schema(),
21
- Optional("accounts"): list[str],
22
20
  Optional("environments"): PlatformConfigSchema.__environments_schema(),
23
21
  Optional("codebase_pipelines"): PlatformConfigSchema.__codebase_pipelines_schema(),
24
22
  Optional(
@@ -80,7 +78,7 @@ class PlatformConfigSchema:
80
78
  "cache": str,
81
79
  "request": str,
82
80
  },
83
- Optional("additional"): list[
81
+ Optional("additional"): [
84
82
  {
85
83
  "path": str,
86
84
  "cache": str,
@@ -94,12 +92,12 @@ class PlatformConfigSchema:
94
92
  Optional("environments"): {
95
93
  PlatformConfigSchema.__valid_environment_name(): Or(
96
94
  {
97
- Optional("additional_address_list"): list,
98
- Optional("allowed_methods"): list,
99
- Optional("cached_methods"): list,
95
+ Optional("additional_address_list"): [str],
96
+ Optional("allowed_methods"): [str],
97
+ Optional("cached_methods"): [str],
100
98
  Optional("cdn_compress"): bool,
101
99
  Optional("cdn_domains_list"): dict,
102
- Optional("cdn_geo_locations"): list,
100
+ Optional("cdn_geo_locations"): [str],
103
101
  Optional("cdn_geo_restriction_type"): str,
104
102
  Optional("cdn_logging_bucket"): str,
105
103
  Optional("cdn_logging_bucket_prefix"): str,
@@ -109,10 +107,10 @@ class PlatformConfigSchema:
109
107
  Optional("enable_logging"): bool,
110
108
  Optional("env_root"): str,
111
109
  Optional("forwarded_values_forward"): str,
112
- Optional("forwarded_values_headers"): list,
110
+ Optional("forwarded_values_headers"): [str],
113
111
  Optional("forwarded_values_query_string"): bool,
114
112
  Optional("origin_protocol_policy"): str,
115
- Optional("origin_ssl_protocols"): list,
113
+ Optional("origin_ssl_protocols"): [str],
116
114
  Optional("slack_alert_channel_alb_secret_rotation"): str,
117
115
  Optional("viewer_certificate_minimum_protocol_version"): str,
118
116
  Optional("viewer_certificate_ssl_support_method"): str,
@@ -127,14 +125,15 @@ class PlatformConfigSchema:
127
125
  }
128
126
 
129
127
  @staticmethod
130
- def __codebase_pipelines_schema() -> list[dict]:
131
- return [
132
- {
133
- "name": str,
128
+ def __codebase_pipelines_schema() -> dict:
129
+ return {
130
+ str: {
134
131
  "repository": str,
132
+ Optional("slack_channel"): str,
133
+ Optional("requires_image_build"): bool,
135
134
  Optional("additional_ecr_repository"): str,
136
135
  Optional("deploy_repository_branch"): str,
137
- "services": list[str],
136
+ "services": [{str: [str]}],
138
137
  "pipelines": [
139
138
  Or(
140
139
  {
@@ -160,7 +159,7 @@ class PlatformConfigSchema:
160
159
  ),
161
160
  ],
162
161
  },
163
- ]
162
+ }
164
163
 
165
164
  @staticmethod
166
165
  def __default_versions_schema() -> dict:
@@ -443,14 +442,17 @@ class PlatformConfigSchema:
443
442
  error=f"{key} must contain a valid ARN for an S3 bucket",
444
443
  )
445
444
 
445
+ _single_import_config = {
446
+ Optional("source_kms_key_arn"): PlatformConfigSchema.__valid_kms_key_arn(
447
+ "source_kms_key_arn"
448
+ ),
449
+ "source_bucket_arn": _valid_s3_bucket_arn("source_bucket_arn"),
450
+ "worker_role_arn": PlatformConfigSchema.__valid_iam_role_arn("worker_role_arn"),
451
+ }
452
+
446
453
  _valid_s3_data_migration = {
447
- "import": {
448
- Optional("source_kms_key_arn"): PlatformConfigSchema.__valid_kms_key_arn(
449
- "source_kms_key_arn"
450
- ),
451
- "source_bucket_arn": _valid_s3_bucket_arn("source_bucket_arn"),
452
- "worker_role_arn": PlatformConfigSchema.__valid_iam_role_arn("worker_role_arn"),
453
- },
454
+ Optional("import"): _single_import_config,
455
+ Optional("import_sources"): [_single_import_config],
454
456
  }
455
457
 
456
458
  _valid_s3_bucket_retention_policy = Or(