dbt-platform-helper 13.1.1__py3-none-any.whl → 13.2.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 (42) hide show
  1. dbt_platform_helper/commands/application.py +3 -5
  2. dbt_platform_helper/commands/codebase.py +2 -4
  3. dbt_platform_helper/commands/conduit.py +2 -4
  4. dbt_platform_helper/commands/config.py +19 -17
  5. dbt_platform_helper/commands/copilot.py +13 -390
  6. dbt_platform_helper/commands/environment.py +6 -6
  7. dbt_platform_helper/commands/generate.py +2 -3
  8. dbt_platform_helper/commands/notify.py +2 -4
  9. dbt_platform_helper/commands/pipeline.py +2 -4
  10. dbt_platform_helper/commands/secrets.py +2 -4
  11. dbt_platform_helper/commands/version.py +2 -2
  12. dbt_platform_helper/domain/codebase.py +14 -11
  13. dbt_platform_helper/domain/copilot.py +397 -0
  14. dbt_platform_helper/domain/copilot_environment.py +6 -6
  15. dbt_platform_helper/domain/maintenance_page.py +227 -431
  16. dbt_platform_helper/domain/pipelines.py +1 -1
  17. dbt_platform_helper/domain/terraform_environment.py +1 -1
  18. dbt_platform_helper/domain/versioning.py +157 -0
  19. dbt_platform_helper/providers/aws/interfaces.py +13 -0
  20. dbt_platform_helper/providers/aws/opensearch.py +23 -0
  21. dbt_platform_helper/providers/aws/redis.py +21 -0
  22. dbt_platform_helper/providers/cache.py +40 -4
  23. dbt_platform_helper/providers/config_validator.py +15 -14
  24. dbt_platform_helper/providers/copilot.py +1 -1
  25. dbt_platform_helper/providers/io.py +17 -0
  26. dbt_platform_helper/providers/kms.py +22 -0
  27. dbt_platform_helper/providers/load_balancers.py +269 -43
  28. dbt_platform_helper/providers/semantic_version.py +33 -10
  29. dbt_platform_helper/providers/version.py +42 -0
  30. dbt_platform_helper/providers/yaml_file.py +0 -1
  31. dbt_platform_helper/utils/application.py +14 -0
  32. dbt_platform_helper/utils/aws.py +27 -4
  33. dbt_platform_helper/utils/tool_versioning.py +96 -0
  34. {dbt_platform_helper-13.1.1.dist-info → dbt_platform_helper-13.2.0.dist-info}/METADATA +3 -4
  35. {dbt_platform_helper-13.1.1.dist-info → dbt_platform_helper-13.2.0.dist-info}/RECORD +39 -35
  36. dbt_platform_helper/providers/opensearch.py +0 -36
  37. dbt_platform_helper/providers/redis.py +0 -34
  38. dbt_platform_helper/utils/versioning.py +0 -238
  39. /dbt_platform_helper/providers/{aws.py → aws/exceptions.py} +0 -0
  40. {dbt_platform_helper-13.1.1.dist-info → dbt_platform_helper-13.2.0.dist-info}/LICENSE +0 -0
  41. {dbt_platform_helper-13.1.1.dist-info → dbt_platform_helper-13.2.0.dist-info}/WHEEL +0 -0
  42. {dbt_platform_helper-13.1.1.dist-info → dbt_platform_helper-13.2.0.dist-info}/entry_points.txt +0 -0
@@ -1,65 +1,280 @@
1
- import boto3
1
+ from boto3 import Session
2
2
 
3
3
  from dbt_platform_helper.platform_exception import PlatformException
4
+ from dbt_platform_helper.providers.io import ClickIOProvider
5
+ from dbt_platform_helper.utils.aws import get_aws_session_or_abort
4
6
 
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.
7
7
 
8
+ def normalise_to_cidr(ip: str):
9
+ if "/" in ip:
10
+ return ip
11
+ SINGLE_IPV4_CIDR_PREFIX_LENGTH = "32"
12
+ return f"{ip}/{SINGLE_IPV4_CIDR_PREFIX_LENGTH}"
8
13
 
9
- def get_load_balancer_for_application(session: boto3.Session, app: str, env: str) -> str:
10
- lb_client = session.client("elbv2")
11
14
 
12
- describe_response = lb_client.describe_load_balancers()
13
- load_balancers = [lb["LoadBalancerArn"] for lb in describe_response["LoadBalancers"]]
15
+ class LoadBalancerProvider:
14
16
 
15
- load_balancers = lb_client.describe_tags(ResourceArns=load_balancers)["TagDescriptions"]
17
+ def __init__(self, session: Session = None, io: ClickIOProvider = ClickIOProvider()):
18
+ self.session = session
19
+ self.evlb_client = self._get_client("elbv2")
20
+ self.rg_tagging_client = self._get_client("resourcegroupstaggingapi")
21
+ self.io = io
16
22
 
17
- load_balancer_arn = None
18
- for lb in load_balancers:
19
- tags = {t["Key"]: t["Value"] for t in lb["Tags"]}
20
- if tags.get("copilot-application") == app and tags.get("copilot-environment") == env:
21
- load_balancer_arn = lb["ResourceArn"]
23
+ def _get_client(self, client: str):
24
+ if not self.session:
25
+ self.session = get_aws_session_or_abort()
26
+ return self.session.client(client)
22
27
 
23
- if not load_balancer_arn:
24
- raise LoadBalancerNotFoundException(
25
- f"No load balancer found for {app} in the {env} environment"
26
- )
28
+ def find_target_group(self, app: str, env: str, svc: str) -> str:
29
+ target_group_arn = None
30
+
31
+ response = self.rg_tagging_client.get_resources(
32
+ TagFilters=[
33
+ {
34
+ "Key": "copilot-application",
35
+ "Values": [
36
+ app,
37
+ ],
38
+ "Key": "copilot-environment",
39
+ "Values": [
40
+ env,
41
+ ],
42
+ "Key": "copilot-service",
43
+ "Values": [
44
+ svc,
45
+ ],
46
+ },
47
+ ],
48
+ ResourceTypeFilters=[
49
+ "elasticloadbalancing:targetgroup",
50
+ ],
51
+ ) # TODO should be paginated
52
+ for resource in response["ResourceTagMappingList"]:
53
+ tags = {tag["Key"]: tag["Value"] for tag in resource["Tags"]}
54
+
55
+ if (
56
+ "copilot-service" in tags
57
+ and tags["copilot-service"] == svc
58
+ and "copilot-environment" in tags
59
+ and tags["copilot-environment"] == env
60
+ and "copilot-application" in tags
61
+ and tags["copilot-application"] == app
62
+ ):
63
+ target_group_arn = resource["ResourceARN"]
64
+
65
+ if not target_group_arn:
66
+ self.io.error(
67
+ f"No target group found for application: {app}, environment: {env}, service: {svc}",
68
+ )
69
+
70
+ return target_group_arn
71
+
72
+ def get_https_certificate_for_application(self, app: str, env: str) -> str:
73
+ listener_arn = self.get_https_listener_for_application(app, env)
74
+ certificates = self.evlb_client.describe_listener_certificates(ListenerArn=listener_arn)[
75
+ "Certificates"
76
+ ] # TODO should be paginated
77
+
78
+ try:
79
+ certificate_arn = next(c["CertificateArn"] for c in certificates if c["IsDefault"])
80
+ except StopIteration:
81
+ raise CertificateNotFoundException(env)
82
+
83
+ return certificate_arn
84
+
85
+ def get_https_listener_for_application(self, app: str, env: str) -> str:
86
+ load_balancer_arn = self.get_load_balancer_for_application(app, env)
87
+
88
+ listeners = self.evlb_client.describe_listeners(LoadBalancerArn=load_balancer_arn)[
89
+ "Listeners"
90
+ ] # TODO should be paginated
91
+
92
+ listener_arn = None
27
93
 
28
- return load_balancer_arn
94
+ try:
95
+ listener_arn = next(l["ListenerArn"] for l in listeners if l["Protocol"] == "HTTPS")
96
+ except StopIteration:
97
+ pass
29
98
 
99
+ if not listener_arn:
100
+ raise ListenerNotFoundException(app, env)
30
101
 
31
- def get_https_listener_for_application(session: boto3.Session, app: str, env: str) -> str:
32
- load_balancer_arn = get_load_balancer_for_application(session, app, env)
33
- lb_client = session.client("elbv2")
34
- listeners = lb_client.describe_listeners(LoadBalancerArn=load_balancer_arn)["Listeners"]
102
+ return listener_arn
35
103
 
36
- listener_arn = None
104
+ def get_load_balancer_for_application(self, app: str, env: str) -> str:
105
+ describe_response = self.evlb_client.describe_load_balancers()
106
+ load_balancers = [lb["LoadBalancerArn"] for lb in describe_response["LoadBalancers"]]
37
107
 
38
- try:
39
- listener_arn = next(l["ListenerArn"] for l in listeners if l["Protocol"] == "HTTPS")
40
- except StopIteration:
41
- pass
108
+ tag_descriptions = []
109
+ for i in range(0, len(load_balancers), 20):
110
+ chunk = load_balancers[i : i + 20]
111
+ tag_descriptions.extend(
112
+ self.evlb_client.describe_tags(ResourceArns=chunk)["TagDescriptions"]
113
+ )
42
114
 
43
- if not listener_arn:
44
- raise ListenerNotFoundException(f"No HTTPS listener for {app} in the {env} environment")
115
+ for lb in tag_descriptions:
116
+ tags = {t["Key"]: t["Value"] for t in lb["Tags"]}
117
+ # TODO copilot hangover, creates coupling to specific tags could update to check application and environment
118
+ if tags.get("copilot-application") == app and tags.get("copilot-environment") == env:
119
+ return lb["ResourceArn"]
45
120
 
46
- return listener_arn
121
+ raise LoadBalancerNotFoundException(app, env)
47
122
 
123
+ def get_host_header_conditions(self, listener_arn: str, target_group_arn: str) -> list:
124
+ rules = self.evlb_client.describe_rules(ListenerArn=listener_arn)[
125
+ "Rules"
126
+ ] # TODO should be paginated
48
127
 
49
- def get_https_certificate_for_application(session: boto3.Session, app: str, env: str) -> str:
128
+ conditions = []
50
129
 
51
- listener_arn = get_https_listener_for_application(session, app, env)
52
- cert_client = session.client("elbv2")
53
- certificates = cert_client.describe_listener_certificates(ListenerArn=listener_arn)[
54
- "Certificates"
55
- ]
130
+ for rule in rules:
131
+ for action in rule["Actions"]:
132
+ if action["Type"] == "forward" and action["TargetGroupArn"] == target_group_arn:
133
+ conditions = rule["Conditions"]
56
134
 
57
- try:
58
- certificate_arn = next(c["CertificateArn"] for c in certificates if c["IsDefault"])
59
- except StopIteration:
60
- raise CertificateNotFoundException(env)
135
+ if not conditions:
136
+ raise ListenerRuleConditionsNotFoundException(listener_arn)
137
+
138
+ # filter to host-header conditions
139
+ conditions = [
140
+ {i: condition[i] for i in condition if i != "Values"}
141
+ for condition in conditions
142
+ if condition["Field"] == "host-header"
143
+ ]
144
+
145
+ # remove internal hosts
146
+ conditions[0]["HostHeaderConfig"]["Values"] = [
147
+ v for v in conditions[0]["HostHeaderConfig"]["Values"]
148
+ ]
149
+
150
+ return conditions
151
+
152
+ def get_rules_tag_descriptions_by_listener_arn(self, listener_arn: str) -> list:
153
+ rules = self.evlb_client.describe_rules(ListenerArn=listener_arn)[
154
+ "Rules"
155
+ ] # TODO should be paginated
156
+ return self.get_rules_tag_descriptions(rules)
157
+
158
+ def get_rules_tag_descriptions(self, rules: list) -> list:
159
+ tag_descriptions = []
160
+ chunk_size = 20
161
+
162
+ for i in range(0, len(rules), chunk_size):
163
+ chunk = rules[i : i + chunk_size]
164
+ resource_arns = [r["RuleArn"] for r in chunk]
165
+ response = self.evlb_client.describe_tags(
166
+ ResourceArns=resource_arns
167
+ ) # TODO should be paginated
168
+ tag_descriptions.extend(response["TagDescriptions"])
169
+
170
+ return tag_descriptions
171
+
172
+ def create_rule(
173
+ self,
174
+ listener_arn: str,
175
+ actions: list,
176
+ conditions: list,
177
+ priority: int,
178
+ tags: list,
179
+ ):
180
+ return self.evlb_client.create_rule(
181
+ ListenerArn=listener_arn,
182
+ Priority=priority,
183
+ Conditions=conditions,
184
+ Actions=actions,
185
+ Tags=tags,
186
+ )
187
+
188
+ def create_forward_rule(
189
+ self,
190
+ listener_arn: str,
191
+ target_group_arn: str,
192
+ rule_name: str,
193
+ priority: int,
194
+ conditions: list,
195
+ additional_tags: list = [],
196
+ ):
197
+ return self.create_rule(
198
+ listener_arn=listener_arn,
199
+ priority=priority,
200
+ conditions=conditions,
201
+ actions=[{"Type": "forward", "TargetGroupArn": target_group_arn}],
202
+ tags=[{"Key": "name", "Value": rule_name}, *additional_tags],
203
+ )
61
204
 
62
- return certificate_arn
205
+ def create_header_rule(
206
+ self,
207
+ listener_arn: str,
208
+ target_group_arn: str,
209
+ header_name: str,
210
+ values: list,
211
+ rule_name: str,
212
+ priority: int,
213
+ conditions: list,
214
+ additional_tags: list = [],
215
+ ):
216
+
217
+ combined_conditions = [
218
+ {
219
+ "Field": "http-header",
220
+ "HttpHeaderConfig": {"HttpHeaderName": header_name, "Values": values},
221
+ }
222
+ ] + conditions
223
+
224
+ self.create_forward_rule(
225
+ listener_arn,
226
+ target_group_arn,
227
+ rule_name,
228
+ priority,
229
+ combined_conditions,
230
+ additional_tags,
231
+ )
232
+
233
+ self.io.debug(
234
+ f"Creating listener rule {rule_name} for HTTPS Listener with arn {listener_arn}.\nIf request header {header_name} contains one of the values {values}, the request will be forwarded to target group with arn {target_group_arn}.\n\n",
235
+ )
236
+
237
+ def create_source_ip_rule(
238
+ self,
239
+ listener_arn: str,
240
+ target_group_arn: str,
241
+ values: list,
242
+ rule_name: str,
243
+ priority: int,
244
+ conditions: list,
245
+ additional_tags: list = [],
246
+ ):
247
+ combined_conditions = [
248
+ {
249
+ "Field": "source-ip",
250
+ "SourceIpConfig": {"Values": [normalise_to_cidr(value) for value in values]},
251
+ }
252
+ ] + conditions
253
+
254
+ self.create_forward_rule(
255
+ listener_arn,
256
+ target_group_arn,
257
+ rule_name,
258
+ priority,
259
+ combined_conditions,
260
+ additional_tags,
261
+ )
262
+
263
+ self.io.debug(
264
+ f"Creating listener rule {rule_name} for HTTPS Listener with arn {listener_arn}.\nIf request source ip matches one of the values {values}, the request will be forwarded to target group with arn {target_group_arn}.\n\n",
265
+ )
266
+
267
+ def delete_listener_rule_by_tags(self, tag_descriptions: list, tag_name: str) -> list:
268
+ deleted_rules = []
269
+
270
+ for description in tag_descriptions:
271
+ tags = {t["Key"]: t["Value"] for t in description["Tags"]}
272
+ if tags.get("name") == tag_name:
273
+ if description["ResourceArn"]:
274
+ self.evlb_client.delete_rule(RuleArn=description["ResourceArn"])
275
+ deleted_rules.append(description)
276
+
277
+ return deleted_rules
63
278
 
64
279
 
65
280
  class LoadBalancerException(PlatformException):
@@ -67,17 +282,28 @@ class LoadBalancerException(PlatformException):
67
282
 
68
283
 
69
284
  class LoadBalancerNotFoundException(LoadBalancerException):
70
- pass
285
+ def __init__(self, application_name, env):
286
+ super().__init__(
287
+ f"No load balancer found for environment {env} in the application {application_name}."
288
+ )
71
289
 
72
290
 
73
291
  class ListenerNotFoundException(LoadBalancerException):
74
- pass
292
+ def __init__(self, application_name, env):
293
+ super().__init__(
294
+ f"No HTTPS listener found for environment {env} in the application {application_name}."
295
+ )
75
296
 
76
297
 
77
298
  class ListenerRuleNotFoundException(LoadBalancerException):
78
299
  pass
79
300
 
80
301
 
302
+ class ListenerRuleConditionsNotFoundException(LoadBalancerException):
303
+ def __init__(self, listener_arn):
304
+ super().__init__(f"No listener rule conditions found for listener ARN: {listener_arn}")
305
+
306
+
81
307
  class CertificateNotFoundException(PlatformException):
82
308
  def __init__(self, environment_name: str):
83
309
  super().__init__(
@@ -35,6 +35,9 @@ class SemanticVersion:
35
35
  return "unknown"
36
36
  return ".".join([str(s) for s in [self.major, self.minor, self.patch]])
37
37
 
38
+ def __repr__(self) -> str:
39
+ return str(self)
40
+
38
41
  def __lt__(self, other) -> bool:
39
42
  return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
40
43
 
@@ -74,29 +77,49 @@ class SemanticVersion:
74
77
  return SemanticVersion(output_version[0], output_version[1], output_version[2])
75
78
 
76
79
 
80
+ @dataclass
77
81
  class VersionStatus:
78
- def __init__(
79
- self, local_version: SemanticVersion = None, latest_release: SemanticVersion = None
80
- ):
81
- self.local = local_version
82
- self.latest = latest_release
82
+ installed: SemanticVersion = None
83
+ latest: SemanticVersion = None
84
+
85
+ def __str__(self):
86
+ attrs = {
87
+ key: value for key, value in vars(self).items() if isinstance(value, SemanticVersion)
88
+ }
89
+ attrs_str = ", ".join(f"{key}: {value}" for key, value in attrs.items())
90
+ return f"{self.__class__.__name__}: {attrs_str}"
83
91
 
84
92
  def is_outdated(self):
85
- return self.local != self.latest
93
+ return self.installed != self.latest
86
94
 
87
- def warn(self):
95
+ def validate(self):
88
96
  pass
89
97
 
90
98
 
91
99
  @dataclass
92
100
  class PlatformHelperVersionStatus(VersionStatus):
93
- local: Optional[SemanticVersion] = None
101
+ installed: Optional[SemanticVersion] = None
94
102
  latest: Optional[SemanticVersion] = None
95
103
  deprecated_version_file: Optional[SemanticVersion] = None
96
104
  platform_config_default: Optional[SemanticVersion] = None
97
105
  pipeline_overrides: Optional[Dict[str, str]] = field(default_factory=dict)
98
106
 
99
- def warn(self) -> dict:
107
+ def __str__(self):
108
+ semantic_version_attrs = {
109
+ key: value for key, value in vars(self).items() if isinstance(value, SemanticVersion)
110
+ }
111
+
112
+ class_str = ", ".join(f"{key}: {value}" for key, value in semantic_version_attrs.items())
113
+
114
+ if self.pipeline_overrides.items():
115
+ pipeline_overrides_str = "pipeline_overrides: " + ", ".join(
116
+ f"{key}: {value}" for key, value in self.pipeline_overrides.items()
117
+ )
118
+ class_str = ", ".join([class_str, pipeline_overrides_str])
119
+
120
+ return f"{self.__class__.__name__}: {class_str}"
121
+
122
+ def validate(self) -> dict:
100
123
  if self.platform_config_default and not self.deprecated_version_file:
101
124
  return {}
102
125
 
@@ -117,7 +140,7 @@ class PlatformHelperVersionStatus(VersionStatus):
117
140
 
118
141
  if not self.platform_config_default and not self.deprecated_version_file:
119
142
  message = f"Cannot get dbt-platform-helper version from '{PLATFORM_CONFIG_FILE}'.\n"
120
- message += f"{missing_default_version_message}{self.local}\n"
143
+ message += f"{missing_default_version_message}{self.installed}\n"
121
144
  errors.append(message)
122
145
 
123
146
  return {
@@ -1,14 +1,42 @@
1
1
  from abc import ABC
2
+ from importlib.metadata import PackageNotFoundError
3
+ from importlib.metadata import version
4
+ from pathlib import Path
2
5
 
3
6
  import requests
4
7
 
8
+ from dbt_platform_helper.constants import PLATFORM_HELPER_VERSION_FILE
9
+ from dbt_platform_helper.platform_exception import PlatformException
5
10
  from dbt_platform_helper.providers.semantic_version import SemanticVersion
11
+ from dbt_platform_helper.providers.yaml_file import FileProviderException
12
+ from dbt_platform_helper.providers.yaml_file import YamlFileProvider
13
+
14
+
15
+ class InstalledVersionProviderException(PlatformException):
16
+ pass
17
+
18
+
19
+ class InstalledToolNotFoundException(InstalledVersionProviderException):
20
+ def __init__(
21
+ self,
22
+ tool_name: str,
23
+ ):
24
+ super().__init__(f"Package '{tool_name}' not found.")
6
25
 
7
26
 
8
27
  class VersionProvider(ABC):
9
28
  pass
10
29
 
11
30
 
31
+ class InstalledVersionProvider:
32
+ @staticmethod
33
+ def get_installed_tool_version(tool_name: str) -> SemanticVersion:
34
+ try:
35
+ return SemanticVersion.from_string(version(tool_name))
36
+ except PackageNotFoundError:
37
+ raise InstalledToolNotFoundException(tool_name)
38
+
39
+
12
40
  # TODO add timeouts and exception handling for requests
13
41
  # TODO Alternatively use the gitpython package?
14
42
  class GithubVersionProvider(VersionProvider):
@@ -34,3 +62,17 @@ class PyPiVersionProvider(VersionProvider):
34
62
  parsed_released_versions = [SemanticVersion.from_string(v) for v in released_versions]
35
63
  parsed_released_versions.sort(reverse=True)
36
64
  return parsed_released_versions[0]
65
+
66
+
67
+ class DeprecatedVersionFileVersionProvider(VersionProvider):
68
+ def __init__(self, file_provider: YamlFileProvider):
69
+ self.file_provider = file_provider or YamlFileProvider
70
+
71
+ def get_required_version(self) -> SemanticVersion:
72
+ deprecated_version_file = Path(PLATFORM_HELPER_VERSION_FILE)
73
+ try:
74
+ loaded_version = self.file_provider.load(deprecated_version_file)
75
+ version_from_file = SemanticVersion.from_string(loaded_version)
76
+ except FileProviderException:
77
+ version_from_file = None
78
+ return version_from_file
@@ -45,7 +45,6 @@ class YamlFileProvider:
45
45
 
46
46
  if not yaml_content:
47
47
  return {}
48
-
49
48
  YamlFileProvider.lint_yaml_for_duplicate_keys(path)
50
49
 
51
50
  return yaml_content
@@ -146,3 +146,17 @@ class ApplicationNotFoundException(ApplicationException):
146
146
  super().__init__(
147
147
  f"""The account "{os.environ.get("AWS_PROFILE")}" does not contain the application "{application_name}"; ensure you have set the environment variable "AWS_PROFILE" correctly."""
148
148
  )
149
+
150
+
151
+ class ApplicationServiceNotFoundException(ApplicationException):
152
+ def __init__(self, application_name: str, svc_name: str):
153
+ super().__init__(
154
+ f"""The service {svc_name} was not found in the application {application_name}. It either does not exist, or has not been deployed."""
155
+ )
156
+
157
+
158
+ class ApplicationEnvironmentNotFoundException(ApplicationException):
159
+ def __init__(self, application_name: str, environment: str):
160
+ super().__init__(
161
+ f"""The environment "{environment}" either does not exist or has not been deployed for the application {application_name}."""
162
+ )
@@ -12,13 +12,16 @@ import botocore.exceptions
12
12
  import click
13
13
  import yaml
14
14
  from boto3 import Session
15
+ from botocore.exceptions import ClientError
15
16
 
16
17
  from dbt_platform_helper.constants import REFRESH_TOKEN_MESSAGE
17
18
  from dbt_platform_helper.platform_exception import PlatformException
18
- from dbt_platform_helper.providers.aws import CopilotCodebaseNotFoundException
19
- from dbt_platform_helper.providers.aws import ImageNotFoundException
20
- from dbt_platform_helper.providers.aws import LogGroupNotFoundException
21
- from dbt_platform_helper.providers.aws import RepositoryNotFoundException
19
+ from dbt_platform_helper.providers.aws.exceptions import (
20
+ CopilotCodebaseNotFoundException,
21
+ )
22
+ from dbt_platform_helper.providers.aws.exceptions import ImageNotFoundException
23
+ from dbt_platform_helper.providers.aws.exceptions import LogGroupNotFoundException
24
+ from dbt_platform_helper.providers.aws.exceptions import RepositoryNotFoundException
22
25
  from dbt_platform_helper.providers.validation import ValidationException
23
26
 
24
27
  SSM_BASE_PATH = "/copilot/{app}/{env}/secrets/"
@@ -484,3 +487,23 @@ def wait_for_log_group_to_exist(log_client, log_group_name, attempts=30):
484
487
 
485
488
  if not log_group_exists:
486
489
  raise LogGroupNotFoundException(log_group_name)
490
+
491
+
492
+ def get_image_build_project(codebuild_client, application, codebase):
493
+ project_name = f"{application}-{codebase}-codebase-image-build"
494
+ response = codebuild_client.batch_get_projects(names=[project_name])
495
+
496
+ if bool(response.get("projects")):
497
+ return project_name
498
+ else:
499
+ return f"{application}-{codebase}-codebase-pipeline-image-build"
500
+
501
+
502
+ def get_manual_release_pipeline(codepipeline_client, application, codebase):
503
+ pipeline_name = f"{application}-{codebase}-manual-release"
504
+ try:
505
+ codepipeline_client.get_pipeline(name=pipeline_name)
506
+ return pipeline_name
507
+ except ClientError as e:
508
+ if e.response["Error"]["Code"] == "PipelineNotFoundException":
509
+ return f"{pipeline_name}-pipeline"
@@ -0,0 +1,96 @@
1
+ import re
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from dbt_platform_helper.constants import DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION
6
+ from dbt_platform_helper.domain.versioning import PlatformHelperVersioning
7
+ from dbt_platform_helper.providers.config import ConfigProvider
8
+ from dbt_platform_helper.providers.semantic_version import PlatformHelperVersionStatus
9
+ from dbt_platform_helper.providers.semantic_version import SemanticVersion
10
+ from dbt_platform_helper.providers.semantic_version import VersionStatus
11
+ from dbt_platform_helper.providers.validation import ValidationException
12
+ from dbt_platform_helper.providers.version import DeprecatedVersionFileVersionProvider
13
+ from dbt_platform_helper.providers.version import GithubVersionProvider
14
+ from dbt_platform_helper.providers.version import InstalledVersionProvider
15
+ from dbt_platform_helper.providers.version import PyPiVersionProvider
16
+ from dbt_platform_helper.providers.yaml_file import YamlFileProvider
17
+
18
+
19
+ # TODO to be removed after config tests are updated - temporary wrapper mid-refactor
20
+ def get_platform_helper_version_status(
21
+ include_project_versions=True,
22
+ yaml_provider=YamlFileProvider,
23
+ ) -> PlatformHelperVersionStatus:
24
+ return PlatformHelperVersioning(
25
+ pypi_provider=PyPiVersionProvider,
26
+ installed_version_provider=InstalledVersionProvider(),
27
+ version_file_version_provider=DeprecatedVersionFileVersionProvider(yaml_provider),
28
+ config_provider=ConfigProvider(),
29
+ )._get_version_status(include_project_versions=include_project_versions)
30
+
31
+
32
+ def get_required_terraform_platform_modules_version(
33
+ cli_terraform_platform_modules_version, platform_config_terraform_modules_default_version
34
+ ):
35
+ version_preference_order = [
36
+ cli_terraform_platform_modules_version,
37
+ platform_config_terraform_modules_default_version,
38
+ DEFAULT_TERRAFORM_PLATFORM_MODULES_VERSION,
39
+ ]
40
+ return [version for version in version_preference_order if version][0]
41
+
42
+
43
+ ##################################################################################
44
+ # Only used in Config domain
45
+ # TODO Relocate along with tests when we refactor config command in DBTP-1538
46
+ ##################################################################################
47
+
48
+
49
+ # Getting version from the "Generated by" comment in a file that was generated from a template
50
+ # TODO where does this belong? It sort of belongs to our platform-helper templating
51
+ def get_template_generated_with_version(template_file_path: str) -> SemanticVersion:
52
+ try:
53
+ template_contents = Path(template_file_path).read_text()
54
+ template_version = re.match(
55
+ r"# Generated by platform-helper ([v.\-0-9]+)", template_contents
56
+ ).group(1)
57
+ return SemanticVersion.from_string(template_version)
58
+ except (IndexError, AttributeError):
59
+ raise ValidationException(f"Template {template_file_path} has no version information")
60
+
61
+
62
+ def validate_template_version(app_version: SemanticVersion, template_file_path: str):
63
+ app_version.validate_compatibility_with(get_template_generated_with_version(template_file_path))
64
+
65
+
66
+ # Local version and latest release of tool.
67
+ # Used only in config command.
68
+ # TODO Move to config domain
69
+ def get_copilot_versions() -> VersionStatus:
70
+ copilot_version = None
71
+
72
+ try:
73
+ response = subprocess.run("copilot --version", capture_output=True, shell=True)
74
+ [copilot_version] = re.findall(r"[0-9.]+", response.stdout.decode("utf8"))
75
+ except ValueError:
76
+ pass
77
+
78
+ return VersionStatus(
79
+ SemanticVersion.from_string(copilot_version),
80
+ GithubVersionProvider.get_latest_version("aws/copilot-cli"),
81
+ )
82
+
83
+
84
+ # Local version and latest release of tool.
85
+ # Used only in config command.
86
+ # TODO Move to config domain
87
+ def get_aws_versions() -> VersionStatus:
88
+ aws_version = None
89
+ try:
90
+ response = subprocess.run("aws --version", capture_output=True, shell=True)
91
+ matched = re.match(r"aws-cli/([0-9.]+)", response.stdout.decode("utf8"))
92
+ aws_version = SemanticVersion.from_string(matched.group(1))
93
+ except ValueError:
94
+ pass
95
+
96
+ return VersionStatus(aws_version, GithubVersionProvider.get_latest_version("aws/aws-cli", True))