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
|
@@ -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
|
+
)
|
|
@@ -443,14 +443,17 @@ class PlatformConfigSchema:
|
|
|
443
443
|
error=f"{key} must contain a valid ARN for an S3 bucket",
|
|
444
444
|
)
|
|
445
445
|
|
|
446
|
+
_single_import_config = {
|
|
447
|
+
Optional("source_kms_key_arn"): PlatformConfigSchema.__valid_kms_key_arn(
|
|
448
|
+
"source_kms_key_arn"
|
|
449
|
+
),
|
|
450
|
+
"source_bucket_arn": _valid_s3_bucket_arn("source_bucket_arn"),
|
|
451
|
+
"worker_role_arn": PlatformConfigSchema.__valid_iam_role_arn("worker_role_arn"),
|
|
452
|
+
}
|
|
453
|
+
|
|
446
454
|
_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
|
-
},
|
|
455
|
+
Optional("import"): _single_import_config,
|
|
456
|
+
Optional("import_sources"): [_single_import_config],
|
|
454
457
|
}
|
|
455
458
|
|
|
456
459
|
_valid_s3_bucket_retention_policy = Or(
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from dbt_platform_helper.platform_exception import PlatformException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VpcProviderException(PlatformException):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SubnetsNotFoundException(VpcProviderException):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PrivateSubnetsNotFoundException(VpcProviderException):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PublicSubnetsNotFoundException(VpcProviderException):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SecurityGroupNotFoundException(VpcProviderException):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VpcNotFoundForNameException(VpcProviderException):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class VpcIdMissingException(VpcProviderException):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Vpc:
|
|
36
|
+
id: str
|
|
37
|
+
public_subnets: list[str]
|
|
38
|
+
private_subnets: list[str]
|
|
39
|
+
security_groups: list[str]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class VpcProvider:
|
|
43
|
+
def __init__(self, session):
|
|
44
|
+
self.ec2_client = session.client("ec2")
|
|
45
|
+
|
|
46
|
+
def _get_subnet_ids(self, vpc_id):
|
|
47
|
+
subnets = self.ec2_client.describe_subnets(
|
|
48
|
+
Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]
|
|
49
|
+
).get("Subnets")
|
|
50
|
+
|
|
51
|
+
if not subnets:
|
|
52
|
+
raise SubnetsNotFoundException(f"No subnets found for VPC with id: {vpc_id}.")
|
|
53
|
+
|
|
54
|
+
public_tag = {"Key": "subnet_type", "Value": "public"}
|
|
55
|
+
public_subnets = [subnet["SubnetId"] for subnet in subnets if public_tag in subnet["Tags"]]
|
|
56
|
+
|
|
57
|
+
private_tag = {"Key": "subnet_type", "Value": "private"}
|
|
58
|
+
private_subnets = [
|
|
59
|
+
subnet["SubnetId"] for subnet in subnets if private_tag in subnet["Tags"]
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
return public_subnets, private_subnets
|
|
63
|
+
|
|
64
|
+
def _get_vpc_id_by_name(self, vpc_name: str) -> str:
|
|
65
|
+
vpcs = self.ec2_client.describe_vpcs(
|
|
66
|
+
Filters=[{"Name": "tag:Name", "Values": [vpc_name]}]
|
|
67
|
+
).get("Vpcs", [])
|
|
68
|
+
|
|
69
|
+
if not vpcs:
|
|
70
|
+
raise VpcNotFoundForNameException(f"VPC not found for name '{vpc_name}'")
|
|
71
|
+
|
|
72
|
+
vpc_id = vpcs[0].get("VpcId")
|
|
73
|
+
|
|
74
|
+
if not vpc_id:
|
|
75
|
+
raise VpcIdMissingException(f"VPC id not present in vpc '{vpc_name}'")
|
|
76
|
+
|
|
77
|
+
return vpc_id
|
|
78
|
+
|
|
79
|
+
def _get_security_groups(self, app: str, env: str, vpc_id: str) -> list:
|
|
80
|
+
|
|
81
|
+
vpc_filter = {"Name": "vpc-id", "Values": [vpc_id]}
|
|
82
|
+
tag_filter = {"Name": f"tag:Name", "Values": [f"copilot-{app}-{env}-env"]}
|
|
83
|
+
response = self.ec2_client.describe_security_groups(Filters=[vpc_filter, tag_filter])
|
|
84
|
+
|
|
85
|
+
return [sg.get("GroupId") for sg in response.get("SecurityGroups")]
|
|
86
|
+
|
|
87
|
+
def get_vpc(self, app: str, env: str, vpc_name: str) -> Vpc:
|
|
88
|
+
|
|
89
|
+
vpc_id = self._get_vpc_id_by_name(vpc_name)
|
|
90
|
+
|
|
91
|
+
public_subnets, private_subnets = self._get_subnet_ids(vpc_id)
|
|
92
|
+
|
|
93
|
+
if not private_subnets:
|
|
94
|
+
raise PrivateSubnetsNotFoundException(f"No private subnets found in vpc '{vpc_name}'")
|
|
95
|
+
|
|
96
|
+
if not public_subnets:
|
|
97
|
+
raise PublicSubnetsNotFoundException(f"No public subnets found in vpc '{vpc_name}'")
|
|
98
|
+
|
|
99
|
+
sec_groups = self._get_security_groups(app, env, vpc_id)
|
|
100
|
+
|
|
101
|
+
if not sec_groups:
|
|
102
|
+
raise SecurityGroupNotFoundException(
|
|
103
|
+
f"No matching security groups found in vpc '{vpc_name}'"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return Vpc(vpc_id, public_subnets, private_subnets, sec_groups)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from abc import abstractmethod
|
|
3
1
|
from pathlib import Path
|
|
4
2
|
|
|
5
3
|
import yaml
|
|
@@ -16,7 +14,7 @@ class YamlFileProviderException(FileProviderException):
|
|
|
16
14
|
pass
|
|
17
15
|
|
|
18
16
|
|
|
19
|
-
class FileNotFoundException(
|
|
17
|
+
class FileNotFoundException(FileProviderException):
|
|
20
18
|
pass
|
|
21
19
|
|
|
22
20
|
|
|
@@ -28,13 +26,7 @@ class DuplicateKeysException(YamlFileProviderException):
|
|
|
28
26
|
pass
|
|
29
27
|
|
|
30
28
|
|
|
31
|
-
class
|
|
32
|
-
@abstractmethod
|
|
33
|
-
def load(path: str) -> dict:
|
|
34
|
-
raise NotImplementedError("Implement this in the subclass")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class YamlFileProvider(FileProvider):
|
|
29
|
+
class YamlFileProvider:
|
|
38
30
|
def load(path: str) -> dict:
|
|
39
31
|
"""
|
|
40
32
|
Raises:
|
|
@@ -43,10 +35,7 @@ class YamlFileProvider(FileProvider):
|
|
|
43
35
|
DuplicateKeysException: yaml contains duplicate keys
|
|
44
36
|
"""
|
|
45
37
|
if not Path(path).exists():
|
|
46
|
-
|
|
47
|
-
raise FileNotFoundException(
|
|
48
|
-
f"`{path}` is missing. Please check it exists and you are in the root directory of your deployment project."
|
|
49
|
-
)
|
|
38
|
+
raise FileNotFoundException(f"`{path}` is missing.")
|
|
50
39
|
try:
|
|
51
40
|
yaml_content = yaml.safe_load(Path(path).read_text())
|
|
52
41
|
except ParserError:
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Commands Reference
|
|
2
2
|
|
|
3
|
-
{% for
|
|
4
|
-
|
|
5
|
-
{
|
|
3
|
+
{% for meta in metadata -%}
|
|
4
|
+
{% if meta.name -%}
|
|
5
|
+
{{ meta.indent }}- [{{ meta.name }}](#{{ meta.name.lower() | replace(" ", "-") }})
|
|
6
|
+
{% endif -%}
|
|
7
|
+
{% endfor -%}
|
|
6
8
|
|
|
7
9
|
{%- for meta in metadata +%}
|
|
8
10
|
{% if meta.name -%}
|