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.

Files changed (40) hide show
  1. dbt_platform_helper/COMMANDS.md +39 -38
  2. dbt_platform_helper/commands/codebase.py +5 -8
  3. dbt_platform_helper/commands/conduit.py +2 -2
  4. dbt_platform_helper/commands/config.py +1 -1
  5. dbt_platform_helper/commands/copilot.py +4 -2
  6. dbt_platform_helper/commands/environment.py +40 -24
  7. dbt_platform_helper/commands/pipeline.py +6 -171
  8. dbt_platform_helper/constants.py +1 -0
  9. dbt_platform_helper/domain/codebase.py +20 -23
  10. dbt_platform_helper/domain/conduit.py +10 -12
  11. dbt_platform_helper/domain/config_validator.py +40 -7
  12. dbt_platform_helper/domain/copilot_environment.py +135 -131
  13. dbt_platform_helper/domain/database_copy.py +45 -42
  14. dbt_platform_helper/domain/maintenance_page.py +220 -183
  15. dbt_platform_helper/domain/pipelines.py +212 -0
  16. dbt_platform_helper/domain/terraform_environment.py +68 -35
  17. dbt_platform_helper/domain/test_platform_terraform_manifest_generator.py +100 -0
  18. dbt_platform_helper/providers/cache.py +1 -2
  19. dbt_platform_helper/providers/cloudformation.py +12 -1
  20. dbt_platform_helper/providers/config.py +21 -13
  21. dbt_platform_helper/providers/copilot.py +2 -0
  22. dbt_platform_helper/providers/files.py +26 -0
  23. dbt_platform_helper/providers/io.py +31 -0
  24. dbt_platform_helper/providers/load_balancers.py +29 -3
  25. dbt_platform_helper/providers/platform_config_schema.py +10 -7
  26. dbt_platform_helper/providers/vpc.py +106 -0
  27. dbt_platform_helper/providers/yaml_file.py +3 -14
  28. dbt_platform_helper/templates/COMMANDS.md.jinja +5 -3
  29. dbt_platform_helper/templates/pipelines/codebase/overrides/package-lock.json +819 -623
  30. dbt_platform_helper/utils/application.py +32 -34
  31. dbt_platform_helper/utils/aws.py +0 -50
  32. dbt_platform_helper/utils/files.py +8 -23
  33. dbt_platform_helper/utils/messages.py +2 -3
  34. dbt_platform_helper/utils/platform_config.py +0 -7
  35. dbt_platform_helper/utils/versioning.py +12 -0
  36. {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/METADATA +2 -2
  37. {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/RECORD +40 -35
  38. {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/WHEEL +1 -1
  39. {dbt_platform_helper-12.5.0.dist-info → dbt_platform_helper-12.6.0.dist-info}/LICENSE +0 -0
  40. {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
- def find_load_balancer(session: boto3.Session, app: str, env: str) -> str:
8
+
9
+ def get_load_balancer_for_application(session: boto3.Session, app: str, env: str) -> str:
7
10
  lb_client = session.client("elbv2")
8
11
 
9
12
  describe_response = lb_client.describe_load_balancers()
@@ -23,8 +26,8 @@ def find_load_balancer(session: boto3.Session, app: str, env: str) -> str:
23
26
  return load_balancer_arn
24
27
 
25
28
 
26
- def find_https_listener(session: boto3.Session, app: str, env: str) -> str:
27
- load_balancer_arn = find_load_balancer(session, app, env)
29
+ def get_https_listener_for_application(session: boto3.Session, app: str, env: str) -> str:
30
+ load_balancer_arn = get_load_balancer_for_application(session, app, env)
28
31
  lb_client = session.client("elbv2")
29
32
  listeners = lb_client.describe_listeners(LoadBalancerArn=load_balancer_arn)["Listeners"]
30
33
 
@@ -41,6 +44,22 @@ def find_https_listener(session: boto3.Session, app: str, env: str) -> str:
41
44
  return listener_arn
42
45
 
43
46
 
47
+ def get_https_certificate_for_application(session: boto3.Session, app: str, env: str) -> str:
48
+
49
+ listener_arn = get_https_listener_for_application(session, app, env)
50
+ cert_client = session.client("elbv2")
51
+ certificates = cert_client.describe_listener_certificates(ListenerArn=listener_arn)[
52
+ "Certificates"
53
+ ]
54
+
55
+ try:
56
+ certificate_arn = next(c["CertificateArn"] for c in certificates if c["IsDefault"])
57
+ except StopIteration:
58
+ raise CertificateNotFoundException(env)
59
+
60
+ return certificate_arn
61
+
62
+
44
63
  class LoadBalancerException(PlatformException):
45
64
  pass
46
65
 
@@ -55,3 +74,10 @@ class ListenerNotFoundException(LoadBalancerException):
55
74
 
56
75
  class ListenerRuleNotFoundException(LoadBalancerException):
57
76
  pass
77
+
78
+
79
+ class CertificateNotFoundException(PlatformException):
80
+ def __init__(self, environment_name: str):
81
+ super().__init__(
82
+ f"""No certificate found with domain name matching environment {environment_name}."."""
83
+ )
@@ -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
- Optional("source_kms_key_arn"): PlatformConfigSchema.__valid_kms_key_arn(
449
- "source_kms_key_arn"
450
- ),
451
- "source_bucket_arn": _valid_s3_bucket_arn("source_bucket_arn"),
452
- "worker_role_arn": PlatformConfigSchema.__valid_iam_role_arn("worker_role_arn"),
453
- },
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(YamlFileProviderException):
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 FileProvider(ABC):
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
- # TODO this error message is domain specific and should not mention deployment directory project here
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 command in toc -%}
4
- - [{{ command }}](#{{ command.lower() | replace(" ", "-") }})
5
- {% endfor %}
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 -%}