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.
- dbt_platform_helper/COMMANDS.md +45 -42
- dbt_platform_helper/commands/codebase.py +7 -10
- dbt_platform_helper/commands/conduit.py +2 -2
- dbt_platform_helper/commands/config.py +1 -1
- dbt_platform_helper/commands/environment.py +32 -18
- dbt_platform_helper/commands/notify.py +5 -3
- dbt_platform_helper/commands/pipeline.py +17 -11
- dbt_platform_helper/constants.py +3 -1
- dbt_platform_helper/domain/codebase.py +48 -36
- dbt_platform_helper/domain/conduit.py +10 -12
- dbt_platform_helper/domain/config_validator.py +42 -31
- dbt_platform_helper/domain/copilot_environment.py +133 -129
- dbt_platform_helper/domain/database_copy.py +38 -37
- dbt_platform_helper/domain/maintenance_page.py +243 -193
- dbt_platform_helper/domain/pipelines.py +60 -135
- dbt_platform_helper/domain/terraform_environment.py +7 -3
- dbt_platform_helper/providers/aws.py +5 -0
- dbt_platform_helper/providers/cloudformation.py +12 -1
- dbt_platform_helper/providers/config.py +12 -14
- dbt_platform_helper/providers/ecr.py +20 -0
- dbt_platform_helper/providers/files.py +1 -1
- dbt_platform_helper/providers/io.py +31 -0
- dbt_platform_helper/providers/load_balancers.py +29 -3
- dbt_platform_helper/providers/platform_config_schema.py +24 -22
- dbt_platform_helper/providers/terraform_manifest.py +120 -0
- dbt_platform_helper/providers/vpc.py +81 -32
- dbt_platform_helper/templates/COMMANDS.md.jinja +5 -3
- dbt_platform_helper/templates/environment-pipelines/main.tf +2 -2
- dbt_platform_helper/templates/environments/main.tf +3 -4
- dbt_platform_helper/utils/aws.py +16 -5
- dbt_platform_helper/utils/messages.py +2 -3
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/METADATA +2 -2
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/RECORD +36 -44
- dbt_platform_helper/templates/pipelines/codebase/manifest.yml +0 -56
- dbt_platform_helper/templates/pipelines/codebase/overrides/.gitignore +0 -12
- dbt_platform_helper/templates/pipelines/codebase/overrides/bin/override.ts +0 -8
- dbt_platform_helper/templates/pipelines/codebase/overrides/buildspec.deploy.yml +0 -29
- dbt_platform_helper/templates/pipelines/codebase/overrides/buildspec.image.yml +0 -48
- dbt_platform_helper/templates/pipelines/codebase/overrides/cdk.json +0 -20
- dbt_platform_helper/templates/pipelines/codebase/overrides/package-lock.json +0 -4232
- dbt_platform_helper/templates/pipelines/codebase/overrides/package.json +0 -27
- dbt_platform_helper/templates/pipelines/codebase/overrides/stack.ts +0 -521
- dbt_platform_helper/templates/pipelines/codebase/overrides/tsconfig.json +0 -30
- dbt_platform_helper/templates/pipelines/codebase/overrides/types.ts +0 -52
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/LICENSE +0 -0
- {dbt_platform_helper-12.5.1.dist-info → dbt_platform_helper-13.0.0.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
-
|
|
26
|
-
|
|
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,
|
|
37
|
-
|
|
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
|
|
40
|
-
has_environment_pipelines = ENVIRONMENT_PIPELINES_KEY in
|
|
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.
|
|
48
|
+
self.io.warn("No pipelines defined: nothing to do.")
|
|
44
49
|
return
|
|
45
50
|
|
|
46
|
-
platform_config_terraform_modules_default_version =
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
70
|
-
aws_account = config.get("account")
|
|
83
|
+
for account in accounts:
|
|
71
84
|
self._generate_terraform_environment_pipeline_manifest(
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
162
|
-
|
|
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.
|
|
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
|
-
|
|
63
|
+
echo=click.echo,
|
|
60
64
|
):
|
|
61
|
-
self.echo =
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
io: ClickIOProvider = None,
|
|
22
21
|
):
|
|
23
22
|
self.config = {}
|
|
24
|
-
self.validator = config_validator
|
|
25
|
-
self.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
27
|
-
load_balancer_arn =
|
|
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"):
|
|
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"):
|
|
98
|
-
Optional("allowed_methods"):
|
|
99
|
-
Optional("cached_methods"):
|
|
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"):
|
|
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"):
|
|
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"):
|
|
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() ->
|
|
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":
|
|
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
|
-
|
|
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(
|