dbt-platform-helper 12.5.0__py3-none-any.whl → 12.6.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.
- dbt_platform_helper/COMMANDS.md +39 -38
- dbt_platform_helper/commands/codebase.py +5 -8
- dbt_platform_helper/commands/conduit.py +2 -2
- dbt_platform_helper/commands/config.py +1 -1
- dbt_platform_helper/commands/copilot.py +4 -2
- dbt_platform_helper/commands/environment.py +40 -24
- dbt_platform_helper/commands/pipeline.py +6 -171
- dbt_platform_helper/constants.py +1 -0
- dbt_platform_helper/domain/codebase.py +20 -23
- dbt_platform_helper/domain/conduit.py +10 -12
- dbt_platform_helper/domain/config_validator.py +40 -7
- dbt_platform_helper/domain/copilot_environment.py +135 -131
- dbt_platform_helper/domain/database_copy.py +45 -42
- dbt_platform_helper/domain/maintenance_page.py +220 -183
- dbt_platform_helper/domain/pipelines.py +212 -0
- dbt_platform_helper/domain/terraform_environment.py +68 -35
- dbt_platform_helper/domain/test_platform_terraform_manifest_generator.py +100 -0
- dbt_platform_helper/providers/cache.py +1 -2
- dbt_platform_helper/providers/cloudformation.py +12 -1
- dbt_platform_helper/providers/config.py +21 -13
- dbt_platform_helper/providers/copilot.py +2 -0
- dbt_platform_helper/providers/files.py +26 -0
- 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 +10 -7
- dbt_platform_helper/providers/vpc.py +106 -0
- dbt_platform_helper/providers/yaml_file.py +3 -14
- dbt_platform_helper/templates/COMMANDS.md.jinja +5 -3
- dbt_platform_helper/templates/pipelines/codebase/overrides/package-lock.json +819 -623
- dbt_platform_helper/utils/application.py +32 -34
- dbt_platform_helper/utils/aws.py +0 -50
- dbt_platform_helper/utils/files.py +8 -23
- dbt_platform_helper/utils/messages.py +2 -3
- dbt_platform_helper/utils/platform_config.py +0 -7
- dbt_platform_helper/utils/versioning.py +12 -0
- {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/METADATA +2 -2
- {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/RECORD +40 -35
- {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/WHEEL +1 -1
- {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/LICENSE +0 -0
- {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from os import makedirs
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from shutil import rmtree
|
|
5
|
+
|
|
6
|
+
from dbt_platform_helper.constants import CODEBASE_PIPELINES_KEY
|
|
7
|
+
from dbt_platform_helper.constants import ENVIRONMENT_PIPELINES_KEY
|
|
8
|
+
from dbt_platform_helper.constants import ENVIRONMENTS_KEY
|
|
9
|
+
from dbt_platform_helper.providers.config import ConfigProvider
|
|
10
|
+
from dbt_platform_helper.providers.files import FileProvider
|
|
11
|
+
from dbt_platform_helper.providers.io import ClickIOProvider
|
|
12
|
+
from dbt_platform_helper.utils.application import get_application_name
|
|
13
|
+
from dbt_platform_helper.utils.aws import get_account_details
|
|
14
|
+
from dbt_platform_helper.utils.aws import get_public_repository_arn
|
|
15
|
+
from dbt_platform_helper.utils.files import generate_override_files_from_template
|
|
16
|
+
from dbt_platform_helper.utils.template import setup_templates
|
|
17
|
+
from dbt_platform_helper.utils.versioning import (
|
|
18
|
+
get_required_terraform_platform_modules_version,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Pipelines:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
config_provider: ConfigProvider,
|
|
26
|
+
get_git_remote: Callable[[], str],
|
|
27
|
+
get_codestar_arn: Callable[[str], str],
|
|
28
|
+
io: ClickIOProvider = ClickIOProvider(),
|
|
29
|
+
):
|
|
30
|
+
self.config_provider = config_provider
|
|
31
|
+
self.get_git_remote = get_git_remote
|
|
32
|
+
self.get_codestar_arn = get_codestar_arn
|
|
33
|
+
self.io = io
|
|
34
|
+
|
|
35
|
+
def generate(self, terraform_platform_modules_version, deploy_branch):
|
|
36
|
+
pipeline_config = self.config_provider.load_and_validate_platform_config()
|
|
37
|
+
|
|
38
|
+
has_codebase_pipelines = CODEBASE_PIPELINES_KEY in pipeline_config
|
|
39
|
+
has_environment_pipelines = ENVIRONMENT_PIPELINES_KEY in pipeline_config
|
|
40
|
+
|
|
41
|
+
if not (has_codebase_pipelines or has_environment_pipelines):
|
|
42
|
+
self.io.warn("No pipelines defined: nothing to do.")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
platform_config_terraform_modules_default_version = pipeline_config.get(
|
|
46
|
+
"default_versions", {}
|
|
47
|
+
).get("terraform-platform-modules", "")
|
|
48
|
+
|
|
49
|
+
templates = setup_templates()
|
|
50
|
+
app_name = get_application_name()
|
|
51
|
+
|
|
52
|
+
git_repo = self.get_git_remote()
|
|
53
|
+
if not git_repo:
|
|
54
|
+
self.io.abort_with_error("The current directory is not a git repository")
|
|
55
|
+
|
|
56
|
+
codestar_connection_arn = self.get_codestar_arn(app_name)
|
|
57
|
+
if codestar_connection_arn is None:
|
|
58
|
+
self.io.abort_with_error(f'There is no CodeStar Connection named "{app_name}" to use')
|
|
59
|
+
|
|
60
|
+
base_path = Path(".")
|
|
61
|
+
copilot_pipelines_dir = base_path / f"copilot/pipelines"
|
|
62
|
+
|
|
63
|
+
self._clean_pipeline_config(copilot_pipelines_dir)
|
|
64
|
+
|
|
65
|
+
if has_environment_pipelines:
|
|
66
|
+
environment_pipelines = pipeline_config[ENVIRONMENT_PIPELINES_KEY]
|
|
67
|
+
|
|
68
|
+
for config in environment_pipelines.values():
|
|
69
|
+
aws_account = config.get("account")
|
|
70
|
+
self._generate_terraform_environment_pipeline_manifest(
|
|
71
|
+
pipeline_config["application"],
|
|
72
|
+
aws_account,
|
|
73
|
+
terraform_platform_modules_version,
|
|
74
|
+
platform_config_terraform_modules_default_version,
|
|
75
|
+
deploy_branch,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if has_codebase_pipelines:
|
|
79
|
+
account_id, _ = get_account_details()
|
|
80
|
+
|
|
81
|
+
for codebase in pipeline_config[CODEBASE_PIPELINES_KEY]:
|
|
82
|
+
self._generate_codebase_pipeline(
|
|
83
|
+
account_id,
|
|
84
|
+
app_name,
|
|
85
|
+
codestar_connection_arn,
|
|
86
|
+
git_repo,
|
|
87
|
+
codebase,
|
|
88
|
+
base_path,
|
|
89
|
+
copilot_pipelines_dir,
|
|
90
|
+
templates,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _clean_pipeline_config(self, pipelines_dir):
|
|
94
|
+
if pipelines_dir.exists():
|
|
95
|
+
self.io.info("Deleting copilot/pipelines directory.")
|
|
96
|
+
rmtree(pipelines_dir)
|
|
97
|
+
|
|
98
|
+
def _generate_codebase_pipeline(
|
|
99
|
+
self,
|
|
100
|
+
account_id,
|
|
101
|
+
app_name,
|
|
102
|
+
codestar_connection_arn,
|
|
103
|
+
git_repo,
|
|
104
|
+
codebase,
|
|
105
|
+
base_path,
|
|
106
|
+
pipelines_dir,
|
|
107
|
+
templates,
|
|
108
|
+
):
|
|
109
|
+
makedirs(pipelines_dir / codebase["name"] / "overrides", exist_ok=True)
|
|
110
|
+
environments = []
|
|
111
|
+
for pipelines in codebase["pipelines"]:
|
|
112
|
+
environments += pipelines[ENVIRONMENTS_KEY]
|
|
113
|
+
|
|
114
|
+
additional_ecr = codebase.get("additional_ecr_repository", None)
|
|
115
|
+
add_public_perms = additional_ecr and additional_ecr.startswith("public.ecr.aws")
|
|
116
|
+
additional_ecr_arn = get_public_repository_arn(additional_ecr) if add_public_perms else None
|
|
117
|
+
|
|
118
|
+
template_data = {
|
|
119
|
+
"account_id": account_id,
|
|
120
|
+
"app_name": app_name,
|
|
121
|
+
"deploy_repo": git_repo,
|
|
122
|
+
"codebase": codebase,
|
|
123
|
+
ENVIRONMENTS_KEY: environments,
|
|
124
|
+
"codestar_connection_arn": codestar_connection_arn,
|
|
125
|
+
"codestar_connection_id": codestar_connection_arn.split("/")[-1],
|
|
126
|
+
"additional_ecr_arn": additional_ecr_arn,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
self._create_file_from_template(
|
|
130
|
+
base_path,
|
|
131
|
+
f"{codebase['name']}/manifest.yml",
|
|
132
|
+
pipelines_dir,
|
|
133
|
+
template_data,
|
|
134
|
+
templates,
|
|
135
|
+
"codebase/manifest.yml",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
overrides_path = Path(__file__).parent.parent.joinpath(
|
|
139
|
+
"templates/pipelines/codebase/overrides"
|
|
140
|
+
)
|
|
141
|
+
generate_override_files_from_template(
|
|
142
|
+
base_path, overrides_path, pipelines_dir / codebase["name"] / "overrides", template_data
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _create_file_from_template(
|
|
146
|
+
self, base_path, file_name, pipelines_dir, template_data, templates, template_name=None
|
|
147
|
+
):
|
|
148
|
+
contents = templates.get_template(
|
|
149
|
+
f"pipelines/{file_name if template_name is None else template_name}"
|
|
150
|
+
).render(template_data)
|
|
151
|
+
message = FileProvider.mkfile(
|
|
152
|
+
base_path, pipelines_dir / file_name, contents, overwrite=True
|
|
153
|
+
)
|
|
154
|
+
self.io.info(message)
|
|
155
|
+
|
|
156
|
+
def _generate_terraform_environment_pipeline_manifest(
|
|
157
|
+
self,
|
|
158
|
+
application,
|
|
159
|
+
aws_account,
|
|
160
|
+
cli_terraform_platform_modules_version,
|
|
161
|
+
platform_config_terraform_modules_default_version,
|
|
162
|
+
deploy_branch,
|
|
163
|
+
):
|
|
164
|
+
env_pipeline_template = setup_templates().get_template("environment-pipelines/main.tf")
|
|
165
|
+
|
|
166
|
+
terraform_platform_modules_version = get_required_terraform_platform_modules_version(
|
|
167
|
+
cli_terraform_platform_modules_version,
|
|
168
|
+
platform_config_terraform_modules_default_version,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
contents = env_pipeline_template.render(
|
|
172
|
+
{
|
|
173
|
+
"application": application,
|
|
174
|
+
"aws_account": aws_account,
|
|
175
|
+
"terraform_platform_modules_version": terraform_platform_modules_version,
|
|
176
|
+
"deploy_branch": deploy_branch,
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
dir_path = f"terraform/environment-pipelines/{aws_account}"
|
|
181
|
+
makedirs(dir_path, exist_ok=True)
|
|
182
|
+
|
|
183
|
+
self.io.info(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
|
|
184
|
+
|
|
185
|
+
def generate_terraform_codebase_pipeline_manifest(
|
|
186
|
+
self,
|
|
187
|
+
application,
|
|
188
|
+
aws_account,
|
|
189
|
+
cli_terraform_platform_modules_version,
|
|
190
|
+
platform_config_terraform_modules_default_version,
|
|
191
|
+
deploy_branch,
|
|
192
|
+
):
|
|
193
|
+
env_pipeline_template = setup_templates().get_template("codebase-pipelines/main.tf")
|
|
194
|
+
|
|
195
|
+
terraform_platform_modules_version = get_required_terraform_platform_modules_version(
|
|
196
|
+
cli_terraform_platform_modules_version,
|
|
197
|
+
platform_config_terraform_modules_default_version,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
contents = env_pipeline_template.render(
|
|
201
|
+
{
|
|
202
|
+
"application": application,
|
|
203
|
+
"aws_account": aws_account,
|
|
204
|
+
"terraform_platform_modules_version": terraform_platform_modules_version,
|
|
205
|
+
"deploy_branch": deploy_branch,
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
dir_path = f"terraform/environment-pipelines/{aws_account}"
|
|
210
|
+
makedirs(dir_path, exist_ok=True)
|
|
211
|
+
|
|
212
|
+
self.io.info(FileProvider.mkfile(".", f"{dir_path}/main.tf", contents, overwrite=True))
|
|
@@ -1,53 +1,86 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
3
|
from dbt_platform_helper.constants import DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION
|
|
4
|
-
from dbt_platform_helper.
|
|
4
|
+
from dbt_platform_helper.platform_exception import PlatformException
|
|
5
|
+
from dbt_platform_helper.providers.files import FileProvider
|
|
5
6
|
from dbt_platform_helper.utils.template import setup_templates
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
class PlatformTerraformManifestGenerator:
|
|
10
|
+
def __init__(self, file_provider):
|
|
11
|
+
self.file_provider = file_provider
|
|
12
|
+
self.manifest_template = setup_templates().get_template("environments/main.tf")
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
def generate_manifest(
|
|
15
|
+
self,
|
|
16
|
+
environment_name: str,
|
|
17
|
+
application_name: str,
|
|
18
|
+
environment_config: dict,
|
|
19
|
+
terraform_platform_modules_version_override: str = None,
|
|
20
|
+
):
|
|
21
|
+
terraform_platform_modules_version = (
|
|
22
|
+
terraform_platform_modules_version_override
|
|
23
|
+
or environment_config.get("versions", {}).get(
|
|
24
|
+
"terraform-platform-modules", DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return self.manifest_template.render(
|
|
29
|
+
{
|
|
30
|
+
"application": application_name,
|
|
31
|
+
"environment": environment_name,
|
|
32
|
+
"config": environment_config,
|
|
33
|
+
"terraform_platform_modules_version": terraform_platform_modules_version,
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def write_manifest(self, environment_name: str, manifest_content: str):
|
|
38
|
+
return self.file_provider.mkfile(
|
|
39
|
+
".",
|
|
40
|
+
f"terraform/environments/{environment_name}/main.tf",
|
|
41
|
+
manifest_content,
|
|
42
|
+
overwrite=True,
|
|
43
|
+
)
|
|
16
44
|
|
|
17
|
-
contents = env_template.render(
|
|
18
|
-
{
|
|
19
|
-
"application": application,
|
|
20
|
-
"environment": env,
|
|
21
|
-
"config": env_config,
|
|
22
|
-
"terraform_platform_modules_version": terraform_platform_modules_version,
|
|
23
|
-
}
|
|
24
|
-
)
|
|
25
45
|
|
|
26
|
-
|
|
46
|
+
class TerraformEnvironmentException(PlatformException):
|
|
47
|
+
pass
|
|
27
48
|
|
|
28
49
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
env_conf_terraform_platform_modules_version = env_conf.get("versions", {}).get(
|
|
32
|
-
"terraform-platform-modules"
|
|
33
|
-
)
|
|
34
|
-
version_preference_order = [
|
|
35
|
-
cli_terraform_platform_modules_version,
|
|
36
|
-
env_conf_terraform_platform_modules_version,
|
|
37
|
-
DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION,
|
|
38
|
-
]
|
|
39
|
-
return [version for version in version_preference_order if version][0]
|
|
50
|
+
class EnvironmentNotFoundException(TerraformEnvironmentException):
|
|
51
|
+
pass
|
|
40
52
|
|
|
41
53
|
|
|
42
54
|
class TerraformEnvironment:
|
|
43
|
-
def __init__(
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
config_provider,
|
|
58
|
+
manifest_generator: PlatformTerraformManifestGenerator = None,
|
|
59
|
+
echo=click.echo,
|
|
60
|
+
):
|
|
61
|
+
self.echo = echo
|
|
44
62
|
self.config_provider = config_provider
|
|
63
|
+
self.manifest_generator = manifest_generator or PlatformTerraformManifestGenerator(
|
|
64
|
+
FileProvider()
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def generate(self, environment_name, terraform_platform_modules_version_override=None):
|
|
68
|
+
config = self.config_provider.get_enriched_config()
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
if environment_name not in config.get("environments").keys():
|
|
71
|
+
raise EnvironmentNotFoundException(
|
|
72
|
+
f"cannot generate terraform for environment {environment_name}. It does not exist in your configuration"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
manifest = self.manifest_generator.generate_manifest(
|
|
76
|
+
environment_name=environment_name,
|
|
77
|
+
application_name=config["application"],
|
|
78
|
+
environment_config=config["environments"][environment_name],
|
|
79
|
+
terraform_platform_modules_version_override=terraform_platform_modules_version_override,
|
|
80
|
+
)
|
|
49
81
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
self.echo(
|
|
83
|
+
self.manifest_generator.write_manifest(
|
|
84
|
+
environment_name=environment_name, manifest_content=manifest
|
|
85
|
+
)
|
|
53
86
|
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
from dbt_platform_helper.constants import DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION
|
|
4
|
+
from dbt_platform_helper.domain.terraform_environment import (
|
|
5
|
+
PlatformTerraformManifestGenerator,
|
|
6
|
+
)
|
|
7
|
+
from dbt_platform_helper.providers.files import FileProvider
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestPlatformTerraformManifestGenerator:
|
|
11
|
+
|
|
12
|
+
def test_generator_generates_expected_manifest_content_with_version_override(self):
|
|
13
|
+
test_environment_config = {
|
|
14
|
+
"vpc": "vpc3",
|
|
15
|
+
"accounts": {
|
|
16
|
+
"deploy": {"name": "non-prod-acc", "id": "1122334455"},
|
|
17
|
+
"dns": {"name": "non-prod-dns-acc", "id": "6677889900"},
|
|
18
|
+
},
|
|
19
|
+
"versions": {"terraform-platform-modules": 3},
|
|
20
|
+
}
|
|
21
|
+
expected_header = "# WARNING: This is an autogenerated file, not for manual editing."
|
|
22
|
+
expected_modules = "git::https://github.com/uktrade/terraform-platform-modules.git//extensions?depth=1&ref=123456"
|
|
23
|
+
expected_moved_block = (
|
|
24
|
+
"moved {\n from = module.extensions-tf\n to = module.extensions\n}\n"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
result = PlatformTerraformManifestGenerator(Mock()).generate_manifest(
|
|
28
|
+
"test", "test-app", test_environment_config, 123456
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
assert expected_header in result
|
|
32
|
+
assert expected_modules in result
|
|
33
|
+
assert expected_moved_block in result
|
|
34
|
+
assert 'environment = "test"' in result
|
|
35
|
+
assert 'application = "test-app"' in result
|
|
36
|
+
assert 'vpc_name = "vpc3"'
|
|
37
|
+
|
|
38
|
+
def test_generator_generates_expected_manifest_content_with_tpm_version_set_in_config(self):
|
|
39
|
+
test_environment_config = {
|
|
40
|
+
"vpc": "vpc3",
|
|
41
|
+
"accounts": {
|
|
42
|
+
"deploy": {"name": "non-prod-acc", "id": "1122334455"},
|
|
43
|
+
"dns": {"name": "non-prod-dns-acc", "id": "6677889900"},
|
|
44
|
+
},
|
|
45
|
+
"versions": {"terraform-platform-modules": 3},
|
|
46
|
+
}
|
|
47
|
+
expected_header = "# WARNING: This is an autogenerated file, not for manual editing."
|
|
48
|
+
expected_modules = "git::https://github.com/uktrade/terraform-platform-modules.git//extensions?depth=1&ref=3"
|
|
49
|
+
expected_moved_block = (
|
|
50
|
+
"moved {\n from = module.extensions-tf\n to = module.extensions\n}\n"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
result = PlatformTerraformManifestGenerator(Mock()).generate_manifest(
|
|
54
|
+
"test", "test-app", test_environment_config
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert expected_header in result
|
|
58
|
+
assert expected_modules in result
|
|
59
|
+
assert expected_moved_block in result
|
|
60
|
+
assert 'environment = "test"' in result
|
|
61
|
+
assert 'application = "test-app"' in result
|
|
62
|
+
assert 'vpc_name = "vpc3"'
|
|
63
|
+
|
|
64
|
+
def test_generator_generates_expected_manifest_content_with_default_version(self):
|
|
65
|
+
test_environment_config = {
|
|
66
|
+
"vpc": "vpc3",
|
|
67
|
+
"accounts": {
|
|
68
|
+
"deploy": {"name": "non-prod-acc", "id": "1122334455"},
|
|
69
|
+
"dns": {"name": "non-prod-dns-acc", "id": "6677889900"},
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
expected_header = "# WARNING: This is an autogenerated file, not for manual editing."
|
|
73
|
+
expected_modules = f"git::https://github.com/uktrade/terraform-platform-modules.git//extensions?depth=1&ref={DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION}"
|
|
74
|
+
expected_moved_block = (
|
|
75
|
+
"moved {\n from = module.extensions-tf\n to = module.extensions\n}\n"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
result = PlatformTerraformManifestGenerator(Mock()).generate_manifest(
|
|
79
|
+
"test", "test-app", test_environment_config
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
assert expected_header in result
|
|
83
|
+
assert expected_modules in result
|
|
84
|
+
assert expected_moved_block in result
|
|
85
|
+
assert 'environment = "test"' in result
|
|
86
|
+
assert 'application = "test-app"' in result
|
|
87
|
+
assert 'vpc_name = "vpc3"'
|
|
88
|
+
|
|
89
|
+
def test_generator_write_manifest_makes_the_expected_manifest_file(self):
|
|
90
|
+
mock_file_provider = Mock(spec=FileProvider)
|
|
91
|
+
PlatformTerraformManifestGenerator(mock_file_provider).write_manifest(
|
|
92
|
+
"test-environment", "test-manifest-content"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
mock_file_provider.mkfile.assert_called_once_with(
|
|
96
|
+
".",
|
|
97
|
+
f"terraform/environments/test-environment/main.tf",
|
|
98
|
+
"test-manifest-content",
|
|
99
|
+
overwrite=True,
|
|
100
|
+
)
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
|
-
from dbt_platform_helper.providers.yaml_file import FileProvider
|
|
5
4
|
from dbt_platform_helper.providers.yaml_file import YamlFileProvider
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class CacheProvider:
|
|
9
8
|
def __init__(
|
|
10
9
|
self,
|
|
11
|
-
file_provider:
|
|
10
|
+
file_provider: YamlFileProvider = None,
|
|
12
11
|
):
|
|
13
12
|
self._cache_file = ".platform-helper-config-cache.yml"
|
|
14
13
|
self.file_provider = file_provider or YamlFileProvider
|
|
@@ -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,55 +1,63 @@
|
|
|
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
|
-
from dbt_platform_helper.providers.yaml_file import
|
|
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,
|
|
20
|
-
file_provider:
|
|
21
|
-
|
|
18
|
+
config_validator: ConfigValidator = None,
|
|
19
|
+
file_provider: YamlFileProvider = None,
|
|
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
|
|
|
27
|
+
# TODO refactor so that apply_environment_defaults isn't set, discarded and set again
|
|
28
|
+
def get_enriched_config(self):
|
|
29
|
+
return self.apply_environment_defaults(self.load_and_validate_platform_config())
|
|
30
|
+
|
|
28
31
|
def validate_platform_config(self):
|
|
29
32
|
PlatformConfigSchema.schema().validate(self.config)
|
|
30
33
|
|
|
31
34
|
# TODO= logically this isn't validation but loading + parsing, to move.
|
|
35
|
+
# also, we apply defaults but discard that data. Should we just apply
|
|
36
|
+
# defaults to config returned by load_and_validate
|
|
32
37
|
enriched_config = ConfigProvider.apply_environment_defaults(self.config)
|
|
33
38
|
self.validator.run_validations(enriched_config)
|
|
34
39
|
|
|
35
40
|
def load_and_validate_platform_config(self, path=PLATFORM_CONFIG_FILE):
|
|
36
41
|
try:
|
|
37
42
|
self.config = self.file_provider.load(path)
|
|
43
|
+
except FileNotFoundException as e:
|
|
44
|
+
self.io.abort_with_error(
|
|
45
|
+
f"{e} Please check it exists and you are in the root directory of your deployment project."
|
|
46
|
+
)
|
|
38
47
|
except FileProviderException as e:
|
|
39
|
-
abort_with_error(f"Error loading configuration from {path}: {e}")
|
|
48
|
+
self.io.abort_with_error(f"Error loading configuration from {path}: {e}")
|
|
40
49
|
|
|
41
50
|
try:
|
|
42
51
|
self.validate_platform_config()
|
|
43
52
|
except SchemaError as e:
|
|
44
|
-
abort_with_error(f"Schema error in {path}. {e}")
|
|
53
|
+
self.io.abort_with_error(f"Schema error in {path}. {e}")
|
|
45
54
|
|
|
46
55
|
return self.config
|
|
47
56
|
|
|
48
|
-
@staticmethod
|
|
49
57
|
# TODO this general function should be moved out of ConfigProvider
|
|
50
|
-
def config_file_check(path=PLATFORM_CONFIG_FILE):
|
|
58
|
+
def config_file_check(self, path=PLATFORM_CONFIG_FILE):
|
|
51
59
|
if not Path(path).exists():
|
|
52
|
-
abort_with_error(
|
|
60
|
+
self.io.abort_with_error(
|
|
53
61
|
f"`{path}` is missing. "
|
|
54
62
|
"Please check it exists and you are in the root directory of your deployment project."
|
|
55
63
|
)
|
|
@@ -64,6 +64,7 @@ def create_addon_client_task(
|
|
|
64
64
|
f"{execution_role}"
|
|
65
65
|
f"--image {CONDUIT_DOCKER_IMAGE_LOCATION}:{addon_type} "
|
|
66
66
|
f"--secrets CONNECTION_SECRET={_get_secrets_provider(application, env).get_connection_secret_arn(secret_name)} "
|
|
67
|
+
"--cpu 2048 --memory 4096 "
|
|
67
68
|
"--platform-os linux "
|
|
68
69
|
"--platform-arch arm64",
|
|
69
70
|
shell=True,
|
|
@@ -98,6 +99,7 @@ def create_postgres_admin_task(
|
|
|
98
99
|
f"--task-group-name {task_name} "
|
|
99
100
|
f"--image {CONDUIT_DOCKER_IMAGE_LOCATION}:{addon_type} "
|
|
100
101
|
f"--env-vars CONNECTION_SECRET='{connection_string}' "
|
|
102
|
+
"--cpu 2048 --memory 4096 "
|
|
101
103
|
"--platform-os linux "
|
|
102
104
|
"--platform-arch arm64",
|
|
103
105
|
shell=True,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from os import makedirs
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FileProvider:
|
|
6
|
+
|
|
7
|
+
def load(path: str) -> str:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def mkfile(base_path: str, file_path: str, contents, overwrite=False) -> str:
|
|
12
|
+
file_path = Path(file_path)
|
|
13
|
+
file = Path(base_path).joinpath(file_path)
|
|
14
|
+
file_exists = file.exists()
|
|
15
|
+
|
|
16
|
+
if not file_path.parent.exists():
|
|
17
|
+
makedirs(file_path.parent)
|
|
18
|
+
|
|
19
|
+
if file_exists and not overwrite:
|
|
20
|
+
return f"File {file_path} exists; doing nothing"
|
|
21
|
+
|
|
22
|
+
action = "overwritten" if file_exists and overwrite else "created"
|
|
23
|
+
|
|
24
|
+
file.write_text(contents)
|
|
25
|
+
|
|
26
|
+
return f"File {file_path} {action}"
|
|
@@ -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
|