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.
Potentially problematic release.
This version of dbt-platform-helper might be problematic. Click here for more details.
- 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
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import subprocess
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
|
|
4
|
-
import click
|
|
5
2
|
|
|
6
3
|
from dbt_platform_helper.providers.cloudformation import CloudFormation
|
|
7
4
|
from dbt_platform_helper.providers.copilot import connect_to_addon_client_task
|
|
8
5
|
from dbt_platform_helper.providers.copilot import create_addon_client_task
|
|
9
6
|
from dbt_platform_helper.providers.ecs import ECS
|
|
7
|
+
from dbt_platform_helper.providers.io import ClickIOProvider
|
|
10
8
|
from dbt_platform_helper.providers.secrets import Secrets
|
|
11
9
|
from dbt_platform_helper.utils.application import Application
|
|
12
10
|
|
|
@@ -18,7 +16,7 @@ class Conduit:
|
|
|
18
16
|
secrets_provider: Secrets,
|
|
19
17
|
cloudformation_provider: CloudFormation,
|
|
20
18
|
ecs_provider: ECS,
|
|
21
|
-
|
|
19
|
+
io: ClickIOProvider = ClickIOProvider(),
|
|
22
20
|
subprocess: subprocess = subprocess,
|
|
23
21
|
connect_to_addon_client_task=connect_to_addon_client_task,
|
|
24
22
|
create_addon_client_task=create_addon_client_task,
|
|
@@ -29,7 +27,7 @@ class Conduit:
|
|
|
29
27
|
self.cloudformation_provider = cloudformation_provider
|
|
30
28
|
self.ecs_provider = ecs_provider
|
|
31
29
|
self.subprocess = subprocess
|
|
32
|
-
self.
|
|
30
|
+
self.io = io
|
|
33
31
|
self.connect_to_addon_client_task = connect_to_addon_client_task
|
|
34
32
|
self.create_addon_client_task = create_addon_client_task
|
|
35
33
|
|
|
@@ -39,10 +37,10 @@ class Conduit:
|
|
|
39
37
|
addon_name, access
|
|
40
38
|
)
|
|
41
39
|
|
|
42
|
-
self.
|
|
40
|
+
self.io.info(f"Checking if a conduit task is already running for {addon_type}")
|
|
43
41
|
task_arns = self.ecs_provider.get_ecs_task_arns(cluster_arn, task_name)
|
|
44
42
|
if not task_arns:
|
|
45
|
-
self.
|
|
43
|
+
self.io.info("Creating conduit task")
|
|
46
44
|
self.create_addon_client_task(
|
|
47
45
|
clients["iam"],
|
|
48
46
|
clients["ssm"],
|
|
@@ -55,7 +53,7 @@ class Conduit:
|
|
|
55
53
|
access,
|
|
56
54
|
)
|
|
57
55
|
|
|
58
|
-
self.
|
|
56
|
+
self.io.info("Updating conduit task")
|
|
59
57
|
self._update_stack_resources(
|
|
60
58
|
self.application.name,
|
|
61
59
|
env,
|
|
@@ -69,13 +67,13 @@ class Conduit:
|
|
|
69
67
|
task_arns = self.ecs_provider.get_ecs_task_arns(cluster_arn, task_name)
|
|
70
68
|
|
|
71
69
|
else:
|
|
72
|
-
self.
|
|
70
|
+
self.io.info("Conduit task already running")
|
|
73
71
|
|
|
74
|
-
self.
|
|
72
|
+
self.io.info(f"Checking if exec is available for conduit task...")
|
|
75
73
|
|
|
76
74
|
self.ecs_provider.ecs_exec_is_available(cluster_arn, task_arns)
|
|
77
75
|
|
|
78
|
-
self.
|
|
76
|
+
self.io.info("Connecting to conduit task")
|
|
79
77
|
self.connect_to_addon_client_task(
|
|
80
78
|
clients["ecs"], self.subprocess, self.application.name, env, cluster_arn, task_name
|
|
81
79
|
)
|
|
@@ -115,7 +113,7 @@ class Conduit:
|
|
|
115
113
|
parameter_name,
|
|
116
114
|
access,
|
|
117
115
|
)
|
|
118
|
-
self.
|
|
116
|
+
self.io.info("Waiting for conduit task update to complete...")
|
|
119
117
|
self.cloudformation_provider.wait_for_cloudformation_to_reach_status(
|
|
120
118
|
"stack_update_complete", stack_name
|
|
121
119
|
)
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
3
|
import boto3
|
|
4
|
-
import click
|
|
5
4
|
|
|
6
|
-
from dbt_platform_helper.
|
|
7
|
-
from dbt_platform_helper.constants import ENVIRONMENTS_KEY
|
|
8
|
-
from dbt_platform_helper.constants import PLATFORM_CONFIG_FILE
|
|
5
|
+
from dbt_platform_helper.providers.io import ClickIOProvider
|
|
9
6
|
from dbt_platform_helper.providers.opensearch import OpensearchProvider
|
|
10
7
|
from dbt_platform_helper.providers.redis import RedisProvider
|
|
11
|
-
from dbt_platform_helper.utils.messages import abort_with_error
|
|
12
8
|
|
|
13
9
|
|
|
14
10
|
class ConfigValidator:
|
|
15
11
|
|
|
16
|
-
def __init__(
|
|
12
|
+
def __init__(
|
|
13
|
+
self, validations: Callable[[dict], None] = None, io: ClickIOProvider = ClickIOProvider()
|
|
14
|
+
):
|
|
17
15
|
self.validations = validations or [
|
|
18
16
|
self.validate_supported_redis_versions,
|
|
19
17
|
self.validate_supported_opensearch_versions,
|
|
20
18
|
self.validate_environment_pipelines,
|
|
21
|
-
self.validate_codebase_pipelines,
|
|
22
19
|
self.validate_environment_pipelines_triggers,
|
|
23
20
|
self.validate_database_copy_section,
|
|
21
|
+
self.validate_database_migration_input_sources,
|
|
24
22
|
]
|
|
23
|
+
self.io = io
|
|
25
24
|
|
|
26
25
|
def run_validations(self, config: dict):
|
|
27
26
|
for validation in self.validations:
|
|
@@ -48,9 +47,8 @@ class ConfigValidator:
|
|
|
48
47
|
environments = extension.get("environments", {})
|
|
49
48
|
|
|
50
49
|
if not isinstance(environments, dict):
|
|
51
|
-
|
|
52
|
-
f"
|
|
53
|
-
fg="red",
|
|
50
|
+
self.io.error(
|
|
51
|
+
f"{extension_type} extension definition is invalid type, expected dictionary",
|
|
54
52
|
)
|
|
55
53
|
continue
|
|
56
54
|
for environment, env_config in environments.items():
|
|
@@ -64,9 +62,8 @@ class ConfigValidator:
|
|
|
64
62
|
)
|
|
65
63
|
|
|
66
64
|
for version_failure in extensions_with_invalid_version:
|
|
67
|
-
|
|
65
|
+
self.io.error(
|
|
68
66
|
f"{extension_type} version for environment {version_failure['environment']} is not in the list of supported {extension_type} versions: {supported_extension_versions}. Provided Version: {version_failure['version']}",
|
|
69
|
-
fg="red",
|
|
70
67
|
)
|
|
71
68
|
|
|
72
69
|
def validate_supported_redis_versions(self, config):
|
|
@@ -113,23 +110,7 @@ class ConfigValidator:
|
|
|
113
110
|
envs = detail["bad_envs"]
|
|
114
111
|
acc = detail["account"]
|
|
115
112
|
message += f" '{pipeline}' - these environments are not in the '{acc}' account: {', '.join(envs)}\n"
|
|
116
|
-
abort_with_error(message)
|
|
117
|
-
|
|
118
|
-
def validate_codebase_pipelines(self, config):
|
|
119
|
-
if CODEBASE_PIPELINES_KEY in config:
|
|
120
|
-
for codebase in config[CODEBASE_PIPELINES_KEY]:
|
|
121
|
-
codebase_environments = []
|
|
122
|
-
|
|
123
|
-
for pipeline in codebase["pipelines"]:
|
|
124
|
-
codebase_environments += [e["name"] for e in pipeline[ENVIRONMENTS_KEY]]
|
|
125
|
-
|
|
126
|
-
unique_codebase_environments = sorted(list(set(codebase_environments)))
|
|
127
|
-
|
|
128
|
-
if sorted(codebase_environments) != sorted(unique_codebase_environments):
|
|
129
|
-
abort_with_error(
|
|
130
|
-
f"The {PLATFORM_CONFIG_FILE} file is invalid, each environment can only be "
|
|
131
|
-
"listed in a single pipeline per codebase"
|
|
132
|
-
)
|
|
113
|
+
self.io.abort_with_error(message)
|
|
133
114
|
|
|
134
115
|
def validate_environment_pipelines_triggers(self, config):
|
|
135
116
|
errors = []
|
|
@@ -153,7 +134,7 @@ class ConfigValidator:
|
|
|
153
134
|
|
|
154
135
|
if errors:
|
|
155
136
|
error_message = "The following pipelines are misconfigured: \n"
|
|
156
|
-
abort_with_error(error_message + "\n ".join(errors))
|
|
137
|
+
self.io.abort_with_error(error_message + "\n ".join(errors))
|
|
157
138
|
|
|
158
139
|
def validate_database_copy_section(self, config):
|
|
159
140
|
extensions = config.get("extensions", {})
|
|
@@ -239,4 +220,34 @@ class ConfigValidator:
|
|
|
239
220
|
)
|
|
240
221
|
|
|
241
222
|
if errors:
|
|
242
|
-
abort_with_error("\n".join(errors))
|
|
223
|
+
self.io.abort_with_error("\n".join(errors))
|
|
224
|
+
|
|
225
|
+
def validate_database_migration_input_sources(self, config: dict):
|
|
226
|
+
extensions = config.get("extensions", {})
|
|
227
|
+
if not extensions:
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
s3_extensions = {
|
|
231
|
+
key: ext for key, ext in extensions.items() if ext.get("type", None) == "s3"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if not s3_extensions:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
errors = []
|
|
238
|
+
|
|
239
|
+
for extension_name, extension in s3_extensions.items():
|
|
240
|
+
for env, env_config in extension.get("environments", {}).items():
|
|
241
|
+
if "data_migration" not in env_config:
|
|
242
|
+
continue
|
|
243
|
+
data_migration = env_config.get("data_migration", {})
|
|
244
|
+
if "import" in data_migration and "import_sources" in data_migration:
|
|
245
|
+
errors.append(
|
|
246
|
+
f"Error in '{extension_name}.environments.{env}.data_migration': only the 'import_sources' property is required - 'import' is deprecated."
|
|
247
|
+
)
|
|
248
|
+
if "import" not in data_migration and "import_sources" not in data_migration:
|
|
249
|
+
errors.append(
|
|
250
|
+
f"Error in '{extension_name}.environments.{env}.data_migration': 'import_sources' property is missing."
|
|
251
|
+
)
|
|
252
|
+
if errors:
|
|
253
|
+
self.io.abort_with_error("\n".join(errors))
|
|
@@ -1,162 +1,168 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from typing import Callable
|
|
3
4
|
|
|
4
|
-
import boto3
|
|
5
5
|
import click
|
|
6
|
+
from boto3 import Session
|
|
6
7
|
|
|
7
|
-
from dbt_platform_helper.
|
|
8
|
+
from dbt_platform_helper.domain.terraform_environment import (
|
|
9
|
+
EnvironmentNotFoundException,
|
|
10
|
+
)
|
|
11
|
+
from dbt_platform_helper.providers.cloudformation import CloudFormation
|
|
12
|
+
from dbt_platform_helper.providers.config import ConfigProvider
|
|
8
13
|
from dbt_platform_helper.providers.files import FileProvider
|
|
9
|
-
from dbt_platform_helper.providers.load_balancers import
|
|
10
|
-
|
|
14
|
+
from dbt_platform_helper.providers.load_balancers import (
|
|
15
|
+
get_https_certificate_for_application,
|
|
16
|
+
)
|
|
17
|
+
from dbt_platform_helper.providers.vpc import Vpc
|
|
18
|
+
from dbt_platform_helper.providers.vpc import VpcNotFoundForNameException
|
|
19
|
+
from dbt_platform_helper.providers.vpc import VpcProvider
|
|
11
20
|
from dbt_platform_helper.utils.template import S3_CROSS_ACCOUNT_POLICY
|
|
12
21
|
from dbt_platform_helper.utils.template import camel_case
|
|
13
22
|
from dbt_platform_helper.utils.template import setup_templates
|
|
14
23
|
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# This call and the method declaration can be removed when we stop using AWS Copilot to deploy the services
|
|
32
|
-
public_subnets, private_subnets = _match_subnet_id_order_to_cloudformation_exports(
|
|
33
|
-
session,
|
|
34
|
-
environment_name,
|
|
35
|
-
public_subnets,
|
|
36
|
-
private_subnets,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
return public_subnets, private_subnets
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _match_subnet_id_order_to_cloudformation_exports(
|
|
43
|
-
session, environment_name, public_subnets, private_subnets
|
|
44
|
-
):
|
|
45
|
-
public_subnet_exports = []
|
|
46
|
-
private_subnet_exports = []
|
|
47
|
-
for page in session.client("cloudformation").get_paginator("list_exports").paginate():
|
|
48
|
-
for export in page["Exports"]:
|
|
49
|
-
if f"-{environment_name}-" in export["Name"]:
|
|
50
|
-
if export["Name"].endswith("-PublicSubnets"):
|
|
51
|
-
public_subnet_exports = export["Value"].split(",")
|
|
52
|
-
if export["Name"].endswith("-PrivateSubnets"):
|
|
53
|
-
private_subnet_exports = export["Value"].split(",")
|
|
54
|
-
|
|
55
|
-
# If the elements match, regardless of order, use the list from the CloudFormation exports
|
|
56
|
-
if set(public_subnets) == set(public_subnet_exports):
|
|
57
|
-
public_subnets = public_subnet_exports
|
|
58
|
-
if set(private_subnets) == set(private_subnet_exports):
|
|
59
|
-
private_subnets = private_subnet_exports
|
|
60
|
-
|
|
61
|
-
return public_subnets, private_subnets
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_cert_arn(session, application, env_name):
|
|
65
|
-
try:
|
|
66
|
-
arn = find_https_certificate(session, application, env_name)
|
|
67
|
-
except:
|
|
68
|
-
click.secho(
|
|
69
|
-
f"No certificate found with domain name matching environment {env_name}.", fg="red"
|
|
25
|
+
class CopilotEnvironment:
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
config_provider: ConfigProvider,
|
|
29
|
+
vpc_provider: VpcProvider = None,
|
|
30
|
+
cloudformation_provider: CloudFormation = None,
|
|
31
|
+
session: Session = None, # TODO - this is a temporary fix, will fall away once the Loadbalancer provider is in place.
|
|
32
|
+
copilot_templating=None,
|
|
33
|
+
echo: Callable[[str], str] = click.secho,
|
|
34
|
+
):
|
|
35
|
+
self.config_provider = config_provider
|
|
36
|
+
self.vpc_provider = vpc_provider
|
|
37
|
+
self.copilot_templating = copilot_templating or CopilotTemplating(
|
|
38
|
+
file_provider=FileProvider(),
|
|
70
39
|
)
|
|
71
|
-
|
|
40
|
+
self.echo = echo
|
|
41
|
+
self.session = session
|
|
42
|
+
self.cloudformation_provider = cloudformation_provider
|
|
72
43
|
|
|
73
|
-
|
|
44
|
+
def generate(self, environment_name: str) -> None:
|
|
74
45
|
|
|
46
|
+
platform_config = self.config_provider.get_enriched_config()
|
|
75
47
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
48
|
+
if environment_name not in platform_config.get("environments").keys():
|
|
49
|
+
raise EnvironmentNotFoundException(
|
|
50
|
+
f"Error: cannot generate copilot manifests for environment {environment_name}. It does not exist in your configuration"
|
|
51
|
+
)
|
|
79
52
|
|
|
80
|
-
|
|
81
|
-
|
|
53
|
+
env_config = platform_config["environments"][environment_name]
|
|
54
|
+
profile_for_environment = env_config.get("accounts", {}).get("deploy", {}).get("name")
|
|
82
55
|
|
|
83
|
-
|
|
84
|
-
filters[0]["Values"] = [session.profile_name]
|
|
85
|
-
vpcs = session.client("ec2").describe_vpcs(Filters=filters)["Vpcs"]
|
|
56
|
+
self.echo(f"Using {profile_for_environment} for this AWS session")
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
raise click.Abort
|
|
92
|
-
|
|
93
|
-
return vpcs[0]["VpcId"]
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _generate_copilot_environment_manifests(
|
|
97
|
-
environment_name, application_name, env_config, session
|
|
98
|
-
):
|
|
99
|
-
env_template = setup_templates().get_template("env/manifest.yml")
|
|
100
|
-
vpc_name = env_config.get("vpc", None)
|
|
101
|
-
vpc_id = get_vpc_id(session, environment_name, vpc_name)
|
|
102
|
-
pub_subnet_ids, priv_subnet_ids = get_subnet_ids(session, vpc_id, environment_name)
|
|
103
|
-
cert_arn = get_cert_arn(session, application_name, environment_name)
|
|
104
|
-
contents = env_template.render(
|
|
105
|
-
{
|
|
106
|
-
"name": environment_name,
|
|
107
|
-
"vpc_id": vpc_id,
|
|
108
|
-
"pub_subnet_ids": pub_subnet_ids,
|
|
109
|
-
"priv_subnet_ids": priv_subnet_ids,
|
|
110
|
-
"certificate_arn": cert_arn,
|
|
111
|
-
}
|
|
112
|
-
)
|
|
113
|
-
click.echo(
|
|
114
|
-
FileProvider.mkfile(
|
|
115
|
-
".", f"copilot/environments/{environment_name}/manifest.yml", contents, overwrite=True
|
|
58
|
+
app_name = platform_config["application"]
|
|
59
|
+
|
|
60
|
+
certificate_arn = get_https_certificate_for_application(
|
|
61
|
+
self.session, app_name, environment_name
|
|
116
62
|
)
|
|
117
|
-
)
|
|
118
63
|
|
|
64
|
+
vpc = self._get_environment_vpc(
|
|
65
|
+
self.session, app_name, environment_name, env_config.get("vpc", None)
|
|
66
|
+
)
|
|
119
67
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
68
|
+
copilot_environment_manifest = self.copilot_templating.generate_copilot_environment_manifest(
|
|
69
|
+
environment_name=environment_name,
|
|
70
|
+
# We need to correct the subnet id order before adding it to the template. See pydoc on below method for details.
|
|
71
|
+
vpc=self._match_subnet_id_order_to_cloudformation_exports(environment_name, vpc),
|
|
72
|
+
cert_arn=certificate_arn,
|
|
73
|
+
)
|
|
126
74
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
75
|
+
self.echo(
|
|
76
|
+
self.copilot_templating.write_environment_manifest(
|
|
77
|
+
environment_name, copilot_environment_manifest
|
|
78
|
+
)
|
|
79
|
+
)
|
|
131
80
|
|
|
132
|
-
|
|
81
|
+
# TODO: There should always be a vpc_name as defaults have been applied to the config. This function can
|
|
82
|
+
# probably fall away. We shouldn't need to check 3 different names (vpc_name, session.profile_name, {session.profile_name}-{env_name})
|
|
83
|
+
# To be checked.
|
|
84
|
+
def _get_environment_vpc(self, session: Session, app_name, env_name: str, vpc_name: str) -> Vpc:
|
|
133
85
|
|
|
86
|
+
if not vpc_name:
|
|
87
|
+
vpc_name = f"{session.profile_name}-{env_name}"
|
|
134
88
|
|
|
135
|
-
|
|
136
|
-
|
|
89
|
+
try:
|
|
90
|
+
vpc = self.vpc_provider.get_vpc(app_name, env_name, vpc_name)
|
|
91
|
+
except VpcNotFoundForNameException:
|
|
92
|
+
vpc = self.vpc_provider.get_vpc(app_name, env_name, session.profile_name)
|
|
137
93
|
|
|
94
|
+
if not vpc:
|
|
95
|
+
raise VpcNotFoundForNameException
|
|
138
96
|
|
|
139
|
-
|
|
140
|
-
def __init__(self, config_provider):
|
|
141
|
-
self.config_provider = config_provider
|
|
97
|
+
return vpc
|
|
142
98
|
|
|
143
|
-
def
|
|
144
|
-
|
|
145
|
-
|
|
99
|
+
def _match_subnet_id_order_to_cloudformation_exports(
|
|
100
|
+
self, environment_name: str, vpc: Vpc
|
|
101
|
+
) -> Vpc:
|
|
102
|
+
"""
|
|
103
|
+
Addresses an issue identified in DBTP-1524 'If the order of the subnets
|
|
104
|
+
in the environment manifest has changed, copilot env deploy tries to do
|
|
105
|
+
destructive changes.'.
|
|
146
106
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
session = get_aws_session_or_abort(profile_for_environment)
|
|
107
|
+
Takes a Vpc object which has a private and public subnets attribute and
|
|
108
|
+
sorts them to match the order within cfn exports.
|
|
109
|
+
"""
|
|
151
110
|
|
|
152
|
-
|
|
153
|
-
environment_name
|
|
111
|
+
exports = self.cloudformation_provider.get_cloudformation_exports_for_environment(
|
|
112
|
+
environment_name
|
|
154
113
|
)
|
|
155
114
|
|
|
115
|
+
public_subnet_exports = []
|
|
116
|
+
private_subnet_exports = []
|
|
117
|
+
|
|
118
|
+
for export in exports:
|
|
119
|
+
if export["Name"].endswith("-PublicSubnets"):
|
|
120
|
+
public_subnet_exports = export["Value"].split(",")
|
|
121
|
+
elif export["Name"].endswith("-PrivateSubnets"):
|
|
122
|
+
private_subnet_exports = export["Value"].split(",")
|
|
123
|
+
|
|
124
|
+
# If the elements match, regardless of order, use the list from the CloudFormation exports
|
|
125
|
+
if set(vpc.public_subnets) == set(public_subnet_exports):
|
|
126
|
+
vpc.public_subnets = public_subnet_exports
|
|
127
|
+
if set(vpc.private_subnets) == set(private_subnet_exports):
|
|
128
|
+
vpc.private_subnets = private_subnet_exports
|
|
129
|
+
|
|
130
|
+
return vpc
|
|
131
|
+
|
|
156
132
|
|
|
157
133
|
class CopilotTemplating:
|
|
158
|
-
def __init__(
|
|
159
|
-
self
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
file_provider: FileProvider = None,
|
|
137
|
+
# TODO file_provider can be moved up a layer. File writing can be the responsibility of CopilotEnvironment generate
|
|
138
|
+
# Or we align with PlatformTerraformManifestGenerator and rename from Templating to reflect the file writing responsibility
|
|
139
|
+
):
|
|
140
|
+
self.file_provider = file_provider
|
|
141
|
+
self.templates = setup_templates()
|
|
142
|
+
|
|
143
|
+
def generate_copilot_environment_manifest(
|
|
144
|
+
self, environment_name: str, vpc: Vpc, cert_arn: str
|
|
145
|
+
) -> str:
|
|
146
|
+
env_template = self.templates.get_template("env/manifest.yml")
|
|
147
|
+
|
|
148
|
+
return env_template.render(
|
|
149
|
+
{
|
|
150
|
+
"name": environment_name,
|
|
151
|
+
"vpc_id": vpc.id,
|
|
152
|
+
"pub_subnet_ids": vpc.public_subnets,
|
|
153
|
+
"priv_subnet_ids": vpc.private_subnets,
|
|
154
|
+
"certificate_arn": cert_arn,
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def write_environment_manifest(self, environment_name: str, manifest_contents: str) -> str:
|
|
159
|
+
|
|
160
|
+
return self.file_provider.mkfile(
|
|
161
|
+
".",
|
|
162
|
+
f"copilot/environments/{environment_name}/manifest.yml",
|
|
163
|
+
manifest_contents,
|
|
164
|
+
overwrite=True,
|
|
165
|
+
)
|
|
160
166
|
|
|
161
167
|
def generate_cross_account_s3_policies(self, environments: dict, extensions):
|
|
162
168
|
resource_blocks = defaultdict(list)
|
|
@@ -190,15 +196,13 @@ class CopilotTemplating:
|
|
|
190
196
|
click.echo("\n>>> No cross-environment S3 policies to create.\n")
|
|
191
197
|
return
|
|
192
198
|
|
|
193
|
-
templates = setup_templates()
|
|
194
|
-
|
|
195
199
|
for service in sorted(resource_blocks.keys()):
|
|
196
200
|
resources = resource_blocks[service]
|
|
197
201
|
click.echo(f"\n>>> Creating S3 cross account policies for {service}.\n")
|
|
198
|
-
template = templates.get_template(S3_CROSS_ACCOUNT_POLICY)
|
|
202
|
+
template = self.templates.get_template(S3_CROSS_ACCOUNT_POLICY)
|
|
199
203
|
file_content = template.render({"resources": resources})
|
|
200
204
|
output_dir = Path(".").absolute()
|
|
201
205
|
file_path = f"copilot/{service}/addons/s3-cross-account-policy.yml"
|
|
202
206
|
|
|
203
|
-
self.
|
|
207
|
+
self.file_provider.mkfile(output_dir, file_path, file_content, True)
|
|
204
208
|
click.echo(f"File {file_path} created")
|