dbt-platform-helper 12.2.4__py3-none-any.whl → 12.4.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.
Files changed (33) hide show
  1. dbt_platform_helper/COMMANDS.md +6 -1
  2. dbt_platform_helper/commands/codebase.py +9 -80
  3. dbt_platform_helper/commands/conduit.py +25 -45
  4. dbt_platform_helper/commands/config.py +4 -4
  5. dbt_platform_helper/commands/copilot.py +13 -15
  6. dbt_platform_helper/commands/database.py +17 -4
  7. dbt_platform_helper/commands/environment.py +3 -2
  8. dbt_platform_helper/commands/secrets.py +1 -1
  9. dbt_platform_helper/domain/codebase.py +81 -63
  10. dbt_platform_helper/domain/conduit.py +42 -93
  11. dbt_platform_helper/domain/database_copy.py +48 -42
  12. dbt_platform_helper/domain/maintenance_page.py +8 -8
  13. dbt_platform_helper/platform_exception.py +5 -0
  14. dbt_platform_helper/providers/aws.py +32 -0
  15. dbt_platform_helper/providers/cloudformation.py +129 -100
  16. dbt_platform_helper/providers/copilot.py +33 -16
  17. dbt_platform_helper/providers/ecs.py +97 -74
  18. dbt_platform_helper/providers/load_balancers.py +11 -5
  19. dbt_platform_helper/providers/secrets.py +100 -59
  20. dbt_platform_helper/providers/validation.py +19 -0
  21. dbt_platform_helper/utils/application.py +14 -2
  22. dbt_platform_helper/utils/arn_parser.py +1 -1
  23. dbt_platform_helper/utils/aws.py +38 -12
  24. dbt_platform_helper/utils/git.py +2 -2
  25. dbt_platform_helper/utils/validation.py +57 -18
  26. dbt_platform_helper/utils/versioning.py +8 -8
  27. {dbt_platform_helper-12.2.4.dist-info → dbt_platform_helper-12.4.0.dist-info}/METADATA +1 -1
  28. {dbt_platform_helper-12.2.4.dist-info → dbt_platform_helper-12.4.0.dist-info}/RECORD +31 -30
  29. dbt_platform_helper/addons-template-map.yml +0 -29
  30. dbt_platform_helper/exceptions.py +0 -81
  31. {dbt_platform_helper-12.2.4.dist-info → dbt_platform_helper-12.4.0.dist-info}/LICENSE +0 -0
  32. {dbt_platform_helper-12.2.4.dist-info → dbt_platform_helper-12.4.0.dist-info}/WHEEL +0 -0
  33. {dbt_platform_helper-12.2.4.dist-info → dbt_platform_helper-12.4.0.dist-info}/entry_points.txt +0 -0
@@ -4,12 +4,8 @@ import time
4
4
  from botocore.exceptions import ClientError
5
5
 
6
6
  from dbt_platform_helper.constants import CONDUIT_DOCKER_IMAGE_LOCATION
7
- from dbt_platform_helper.exceptions import CreateTaskTimeoutError
8
- from dbt_platform_helper.providers.ecs import get_ecs_task_arns
9
- from dbt_platform_helper.providers.secrets import get_connection_secret_arn
10
- from dbt_platform_helper.providers.secrets import (
11
- get_postgres_connection_data_updated_with_master_secret,
12
- )
7
+ from dbt_platform_helper.providers.aws import CreateTaskTimeoutException
8
+ from dbt_platform_helper.providers.secrets import Secrets
13
9
  from dbt_platform_helper.utils.application import Application
14
10
  from dbt_platform_helper.utils.messages import abort_with_error
15
11
 
@@ -17,7 +13,6 @@ from dbt_platform_helper.utils.messages import abort_with_error
17
13
  def create_addon_client_task(
18
14
  iam_client,
19
15
  ssm_client,
20
- secrets_manager_client,
21
16
  subprocess,
22
17
  application: Application,
23
18
  env: str,
@@ -36,7 +31,6 @@ def create_addon_client_task(
36
31
  elif access == "admin":
37
32
  create_postgres_admin_task(
38
33
  ssm_client,
39
- secrets_manager_client,
40
34
  subprocess,
41
35
  application,
42
36
  addon_name,
@@ -59,7 +53,7 @@ def create_addon_client_task(
59
53
  # We cannot check for botocore.errorfactory.NoSuchEntityException as botocore generates that class on the fly as part of errorfactory.
60
54
  # factory. Checking the error code is the recommended way of handling these exceptions.
61
55
  if ex.response.get("Error", {}).get("Code", None) != "NoSuchEntity":
62
- # TODO Raise an exception to be caught at the command layer
56
+ # TODO When we are refactoring this, raise an exception to be caught at the command layer
63
57
  abort_with_error(
64
58
  f"cannot obtain Role {role_name}: {ex.response.get('Error', {}).get('Message', '')}"
65
59
  )
@@ -69,7 +63,7 @@ def create_addon_client_task(
69
63
  f"--task-group-name {task_name} "
70
64
  f"{execution_role}"
71
65
  f"--image {CONDUIT_DOCKER_IMAGE_LOCATION}:{addon_type} "
72
- f"--secrets CONNECTION_SECRET={get_connection_secret_arn(ssm_client,secrets_manager_client, secret_name)} "
66
+ f"--secrets CONNECTION_SECRET={_get_secrets_provider(application, env).get_connection_secret_arn(secret_name)} "
73
67
  "--platform-os linux "
74
68
  "--platform-arch arm64",
75
69
  shell=True,
@@ -78,7 +72,6 @@ def create_addon_client_task(
78
72
 
79
73
  def create_postgres_admin_task(
80
74
  ssm_client,
81
- secrets_manager_client,
82
75
  subprocess,
83
76
  app: Application,
84
77
  addon_name: str,
@@ -95,8 +88,8 @@ def create_postgres_admin_task(
95
88
  "Parameter"
96
89
  ]["Value"]
97
90
  connection_string = json.dumps(
98
- get_postgres_connection_data_updated_with_master_secret(
99
- ssm_client, secrets_manager_client, read_only_secret_name, master_secret_arn
91
+ _get_secrets_provider(app, env).get_postgres_connection_data_updated_with_master_secret(
92
+ read_only_secret_name, master_secret_arn
100
93
  )
101
94
  )
102
95
 
@@ -111,6 +104,19 @@ def create_postgres_admin_task(
111
104
  )
112
105
 
113
106
 
107
+ def _temp_until_refactor_get_ecs_task_arns(ecs_client, cluster_arn: str, task_name: str):
108
+ tasks = ecs_client.list_tasks(
109
+ cluster=cluster_arn,
110
+ desiredStatus="RUNNING",
111
+ family=f"copilot-{task_name}",
112
+ )
113
+
114
+ if not tasks["taskArns"]:
115
+ return []
116
+
117
+ return tasks["taskArns"]
118
+
119
+
114
120
  def connect_to_addon_client_task(
115
121
  ecs_client,
116
122
  subprocess,
@@ -118,13 +124,14 @@ def connect_to_addon_client_task(
118
124
  env,
119
125
  cluster_arn,
120
126
  task_name,
121
- addon_client_is_running_fn=get_ecs_task_arns,
127
+ get_ecs_task_arns=_temp_until_refactor_get_ecs_task_arns,
122
128
  ):
123
129
  running = False
124
130
  tries = 0
125
131
  while tries < 15 and not running:
126
132
  tries += 1
127
- if addon_client_is_running_fn(ecs_client, cluster_arn, task_name):
133
+ # Todo: Use from ECS provider when we refactor this
134
+ if get_ecs_task_arns(ecs_client, cluster_arn, task_name):
128
135
  subprocess.call(
129
136
  "copilot task exec "
130
137
  f"--app {application_name} --env {env} "
@@ -137,8 +144,18 @@ def connect_to_addon_client_task(
137
144
  time.sleep(1)
138
145
 
139
146
  if not running:
140
- raise CreateTaskTimeoutError
147
+ raise CreateTaskTimeoutException(task_name, application_name, env)
141
148
 
142
149
 
143
150
  def _normalise_secret_name(addon_name: str) -> str:
144
151
  return addon_name.replace("-", "_").upper()
152
+
153
+
154
+ def _get_secrets_provider(application: Application, env: str) -> Secrets:
155
+ # Todo: We instantiate the secrets provider here to avoid rabbit holing, but something better probably possible when we are refactoring this area
156
+ return Secrets(
157
+ application.environments[env].session.client("ssm"),
158
+ application.environments[env].session.client("secretsmanager"),
159
+ application.name,
160
+ env,
161
+ )
@@ -3,77 +3,100 @@ import string
3
3
  import time
4
4
  from typing import List
5
5
 
6
- from dbt_platform_helper.exceptions import ECSAgentNotRunning
7
- from dbt_platform_helper.exceptions import NoClusterError
8
-
9
-
10
- # Todo: Refactor to a class, review, then perhaps do the others
11
- def get_cluster_arn(ecs_client, application_name: str, env: str) -> str:
12
- for cluster_arn in ecs_client.list_clusters()["clusterArns"]:
13
- tags_response = ecs_client.list_tags_for_resource(resourceArn=cluster_arn)
14
- tags = tags_response["tags"]
15
-
16
- app_key_found = False
17
- env_key_found = False
18
- cluster_key_found = False
19
-
20
- for tag in tags:
21
- if tag["key"] == "copilot-application" and tag["value"] == application_name:
22
- app_key_found = True
23
- if tag["key"] == "copilot-environment" and tag["value"] == env:
24
- env_key_found = True
25
- if tag["key"] == "aws:cloudformation:logical-id" and tag["value"] == "Cluster":
26
- cluster_key_found = True
27
-
28
- if app_key_found and env_key_found and cluster_key_found:
29
- return cluster_arn
30
-
31
- raise NoClusterError
32
-
33
-
34
- def get_or_create_task_name(
35
- ssm_client, application_name: str, env: str, addon_name: str, parameter_name: str
36
- ) -> str:
37
- try:
38
- return ssm_client.get_parameter(Name=parameter_name)["Parameter"]["Value"]
39
- except ssm_client.exceptions.ParameterNotFound:
40
- random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
41
- return f"conduit-{application_name}-{env}-{addon_name}-{random_id}"
42
-
43
-
44
- def get_ecs_task_arns(ecs_client, cluster_arn: str, task_name: str):
45
-
46
- tasks = ecs_client.list_tasks(
47
- cluster=cluster_arn,
48
- desiredStatus="RUNNING",
49
- family=f"copilot-{task_name}",
50
- )
51
-
52
- if not tasks["taskArns"]:
53
- return []
54
-
55
- return tasks["taskArns"]
56
-
57
-
58
- def ecs_exec_is_available(ecs_client, cluster_arn: str, task_arns: List[str]):
59
-
60
- current_attemps = 0
61
- execute_command_agent_status = ""
62
-
63
- while execute_command_agent_status != "RUNNING" and current_attemps < 25:
64
-
65
- current_attemps += 1
66
-
67
- task_details = ecs_client.describe_tasks(cluster=cluster_arn, tasks=task_arns)
68
-
69
- managed_agents = task_details["tasks"][0]["containers"][0]["managedAgents"]
70
- execute_command_agent_status = [
71
- agent["lastStatus"]
72
- for agent in managed_agents
73
- if agent["name"] == "ExecuteCommandAgent"
74
- ][0]
75
-
76
- time.sleep(1)
77
-
78
- if execute_command_agent_status != "RUNNING":
79
- raise ECSAgentNotRunning
6
+ from dbt_platform_helper.platform_exception import PlatformException
7
+
8
+
9
+ class ECS:
10
+ def __init__(self, ecs_client, ssm_client, application_name: str, env: str):
11
+ self.ecs_client = ecs_client
12
+ self.ssm_client = ssm_client
13
+ self.application_name = application_name
14
+ self.env = env
15
+
16
+ def get_cluster_arn(self) -> str:
17
+ """Returns the ARN of the ECS cluster for the given application and
18
+ environment."""
19
+ for cluster_arn in self.ecs_client.list_clusters()["clusterArns"]:
20
+ tags_response = self.ecs_client.list_tags_for_resource(resourceArn=cluster_arn)
21
+ tags = tags_response["tags"]
22
+
23
+ app_key_found = False
24
+ env_key_found = False
25
+ cluster_key_found = False
26
+
27
+ for tag in tags:
28
+ if tag["key"] == "copilot-application" and tag["value"] == self.application_name:
29
+ app_key_found = True
30
+ if tag["key"] == "copilot-environment" and tag["value"] == self.env:
31
+ env_key_found = True
32
+ if tag["key"] == "aws:cloudformation:logical-id" and tag["value"] == "Cluster":
33
+ cluster_key_found = True
34
+
35
+ if app_key_found and env_key_found and cluster_key_found:
36
+ return cluster_arn
37
+
38
+ raise NoClusterException(self.application_name, self.env)
39
+
40
+ def get_or_create_task_name(self, addon_name: str, parameter_name: str) -> str:
41
+ """Fetches the task name from SSM or creates a new one if not found."""
42
+ try:
43
+ return self.ssm_client.get_parameter(Name=parameter_name)["Parameter"]["Value"]
44
+ except self.ssm_client.exceptions.ParameterNotFound:
45
+ random_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
46
+ return f"conduit-{self.application_name}-{self.env}-{addon_name}-{random_id}"
47
+
48
+ def get_ecs_task_arns(self, cluster_arn: str, task_name: str):
49
+ """Gets the ECS task ARNs for a given task name and cluster ARN."""
50
+ tasks = self.ecs_client.list_tasks(
51
+ cluster=cluster_arn,
52
+ desiredStatus="RUNNING",
53
+ family=f"copilot-{task_name}",
54
+ )
55
+
56
+ if not tasks["taskArns"]:
57
+ return []
58
+
59
+ return tasks["taskArns"]
60
+
61
+ def ecs_exec_is_available(self, cluster_arn: str, task_arns: List[str]):
62
+ """
63
+ Checks if the ExecuteCommandAgent is running on the specified ECS task.
64
+
65
+ Waits for up to 25 attempts, then raises ECSAgentNotRunning if still not
66
+ running.
67
+ """
68
+ current_attempts = 0
69
+ execute_command_agent_status = ""
70
+
71
+ while execute_command_agent_status != "RUNNING" and current_attempts < 25:
72
+ current_attempts += 1
73
+
74
+ task_details = self.ecs_client.describe_tasks(cluster=cluster_arn, tasks=task_arns)
75
+
76
+ managed_agents = task_details["tasks"][0]["containers"][0]["managedAgents"]
77
+ execute_command_agent_status = [
78
+ agent["lastStatus"]
79
+ for agent in managed_agents
80
+ if agent["name"] == "ExecuteCommandAgent"
81
+ ][0]
82
+ if execute_command_agent_status != "RUNNING":
83
+ time.sleep(1)
84
+
85
+ if execute_command_agent_status != "RUNNING":
86
+ raise ECSAgentNotRunningException
87
+
88
+
89
+ class ECSException(PlatformException):
90
+ pass
91
+
92
+
93
+ class ECSAgentNotRunningException(ECSException):
94
+ def __init__(self):
95
+ super().__init__("""ECS exec agent never reached "RUNNING" status""")
96
+
97
+
98
+ class NoClusterException(ECSException):
99
+ def __init__(self, application_name: str, environment: str):
100
+ super().__init__(
101
+ f"""No ECS cluster found for "{application_name}" in "{environment}" environment."""
102
+ )
@@ -1,5 +1,7 @@
1
1
  import boto3
2
2
 
3
+ from dbt_platform_helper.platform_exception import PlatformException
4
+
3
5
 
4
6
  def find_load_balancer(session: boto3.Session, app: str, env: str) -> str:
5
7
  lb_client = session.client("elbv2")
@@ -16,7 +18,7 @@ def find_load_balancer(session: boto3.Session, app: str, env: str) -> str:
16
18
  load_balancer_arn = lb["ResourceArn"]
17
19
 
18
20
  if not load_balancer_arn:
19
- raise LoadBalancerNotFoundError()
21
+ raise LoadBalancerNotFoundException()
20
22
 
21
23
  return load_balancer_arn
22
24
 
@@ -34,18 +36,22 @@ def find_https_listener(session: boto3.Session, app: str, env: str) -> str:
34
36
  pass
35
37
 
36
38
  if not listener_arn:
37
- raise ListenerNotFoundError()
39
+ raise ListenerNotFoundException()
38
40
 
39
41
  return listener_arn
40
42
 
41
43
 
42
- class LoadBalancerNotFoundError(Exception):
44
+ class LoadBalancerException(PlatformException):
45
+ pass
46
+
47
+
48
+ class LoadBalancerNotFoundException(LoadBalancerException):
43
49
  pass
44
50
 
45
51
 
46
- class ListenerNotFoundError(Exception):
52
+ class ListenerNotFoundException(LoadBalancerException):
47
53
  pass
48
54
 
49
55
 
50
- class ListenerRuleNotFoundError(Exception):
56
+ class ListenerRuleNotFoundException(LoadBalancerException):
51
57
  pass
@@ -2,84 +2,125 @@ import json
2
2
  import urllib
3
3
 
4
4
  from dbt_platform_helper.constants import CONDUIT_ADDON_TYPES
5
- from dbt_platform_helper.exceptions import AddonNotFoundError
6
- from dbt_platform_helper.exceptions import AddonTypeMissingFromConfigError
7
- from dbt_platform_helper.exceptions import InvalidAddonTypeError
8
- from dbt_platform_helper.exceptions import ParameterNotFoundError
9
- from dbt_platform_helper.exceptions import SecretNotFoundError
5
+ from dbt_platform_helper.platform_exception import PlatformException
10
6
 
11
7
 
12
- def get_postgres_connection_data_updated_with_master_secret(
13
- ssm_client, secrets_manager_client, parameter_name, secret_arn
14
- ):
15
- response = ssm_client.get_parameter(Name=parameter_name, WithDecryption=True)
16
- parameter_value = response["Parameter"]["Value"]
8
+ class Secrets:
9
+ def __init__(self, ssm_client, secrets_manager_client, application_name, env):
10
+ self.ssm_client = ssm_client
11
+ self.secrets_manager_client = secrets_manager_client
12
+ self.application_name = application_name
13
+ self.env = env
17
14
 
18
- parameter_data = json.loads(parameter_value)
15
+ def get_postgres_connection_data_updated_with_master_secret(self, parameter_name, secret_arn):
16
+ response = self.ssm_client.get_parameter(Name=parameter_name, WithDecryption=True)
17
+ parameter_value = response["Parameter"]["Value"]
19
18
 
20
- secret_response = secrets_manager_client.get_secret_value(SecretId=secret_arn)
21
- secret_value = json.loads(secret_response["SecretString"])
19
+ parameter_data = json.loads(parameter_value)
22
20
 
23
- parameter_data["username"] = urllib.parse.quote(secret_value["username"])
24
- parameter_data["password"] = urllib.parse.quote(secret_value["password"])
21
+ secret_response = self.secrets_manager_client.get_secret_value(SecretId=secret_arn)
22
+ secret_value = json.loads(secret_response["SecretString"])
25
23
 
26
- return parameter_data
24
+ parameter_data["username"] = urllib.parse.quote(secret_value["username"])
25
+ parameter_data["password"] = urllib.parse.quote(secret_value["password"])
27
26
 
27
+ return parameter_data
28
28
 
29
- def get_connection_secret_arn(ssm_client, secrets_manager_client, secret_name: str) -> str:
29
+ def get_connection_secret_arn(self, secret_name: str) -> str:
30
+ try:
31
+ return self.ssm_client.get_parameter(Name=secret_name, WithDecryption=False)[
32
+ "Parameter"
33
+ ]["ARN"]
34
+ except self.ssm_client.exceptions.ParameterNotFound:
35
+ pass
30
36
 
31
- try:
32
- return ssm_client.get_parameter(Name=secret_name, WithDecryption=False)["Parameter"]["ARN"]
33
- except ssm_client.exceptions.ParameterNotFound:
34
- pass
37
+ try:
38
+ return self.secrets_manager_client.describe_secret(SecretId=secret_name)["ARN"]
39
+ except self.secrets_manager_client.exceptions.ResourceNotFoundException:
40
+ pass
35
41
 
36
- try:
37
- return secrets_manager_client.describe_secret(SecretId=secret_name)["ARN"]
38
- except secrets_manager_client.exceptions.ResourceNotFoundException:
39
- pass
42
+ raise SecretNotFoundException(secret_name)
40
43
 
41
- raise SecretNotFoundError(secret_name)
44
+ # Todo: This probably does not belong in the secrets provider. When it moves, take the Todoed exceptions from below
45
+ def get_addon_type(self, addon_name: str) -> str:
46
+ addon_type = None
47
+ try:
48
+ addon_config = json.loads(
49
+ self.ssm_client.get_parameter(
50
+ Name=f"/copilot/applications/{self.application_name}/environments/{self.env}/addons"
51
+ )["Parameter"]["Value"]
52
+ )
53
+ except self.ssm_client.exceptions.ParameterNotFound:
54
+ raise ParameterNotFoundException(self.application_name, self.env)
42
55
 
56
+ if addon_name not in addon_config.keys():
57
+ raise AddonNotFoundException(addon_name)
58
+
59
+ for name, config in addon_config.items():
60
+ if name == addon_name:
61
+ if not config.get("type"):
62
+ raise AddonTypeMissingFromConfigException(addon_name)
63
+ addon_type = config["type"]
64
+
65
+ if not addon_type or addon_type not in CONDUIT_ADDON_TYPES:
66
+ raise InvalidAddonTypeException(addon_type)
67
+
68
+ if "postgres" in addon_type:
69
+ addon_type = "postgres"
70
+
71
+ return addon_type
72
+
73
+ def get_parameter_name(self, addon_type: str, addon_name: str, access: str) -> str:
74
+ if addon_type == "postgres":
75
+ return f"/copilot/{self.application_name}/{self.env}/conduits/{self._normalise_secret_name(addon_name)}_{access.upper()}"
76
+ elif addon_type == "redis" or addon_type == "opensearch":
77
+ return f"/copilot/{self.application_name}/{self.env}/conduits/{self._normalise_secret_name(addon_name)}_ENDPOINT"
78
+ else:
79
+ return f"/copilot/{self.application_name}/{self.env}/conduits/{self._normalise_secret_name(addon_name)}"
80
+
81
+ def _normalise_secret_name(self, addon_name: str) -> str:
82
+ return addon_name.replace("-", "_").upper()
43
83
 
44
- def get_addon_type(ssm_client, application_name: str, env: str, addon_name: str) -> str:
45
- addon_type = None
46
- try:
47
- addon_config = json.loads(
48
- ssm_client.get_parameter(
49
- Name=f"/copilot/applications/{application_name}/environments/{env}/addons"
50
- )["Parameter"]["Value"]
51
- )
52
- except ssm_client.exceptions.ParameterNotFound:
53
- raise ParameterNotFoundError
54
84
 
55
- if addon_name not in addon_config.keys():
56
- raise AddonNotFoundError
85
+ # Todo: This probably does not belong in the secrets provider. Move it when we find a better home for get_addon_type()
86
+ class AddonException(PlatformException):
87
+ pass
57
88
 
58
- for name, config in addon_config.items():
59
- if name == addon_name:
60
- if not config.get("type"):
61
- raise AddonTypeMissingFromConfigError()
62
- addon_type = config["type"]
63
89
 
64
- if not addon_type or addon_type not in CONDUIT_ADDON_TYPES:
65
- raise InvalidAddonTypeError(addon_type)
90
+ # Todo: This probably does not belong in the secrets provider. Move it when we find a better home for get_addon_type()
91
+ class AddonNotFoundException(AddonException):
92
+ def __init__(self, addon_name: str):
93
+ super().__init__(f"""Addon "{addon_name}" does not exist.""")
66
94
 
67
- if "postgres" in addon_type:
68
- addon_type = "postgres"
69
95
 
70
- return addon_type
96
+ # Todo: This probably does not belong in the secrets provider. Move it when we find a better home for get_addon_type()
97
+ class AddonTypeMissingFromConfigException(AddonException):
98
+ def __init__(self, addon_name: str):
99
+ super().__init__(
100
+ f"""The configuration for the addon {addon_name}, is misconfigured and missing the addon type."""
101
+ )
102
+
103
+
104
+ # Todo: This probably does not belong in the secrets provider. Move it when we find a better home for get_addon_type()
105
+ class InvalidAddonTypeException(AddonException):
106
+ def __init__(self, addon_type):
107
+ self.addon_type = addon_type
108
+ super().__init__(
109
+ f"""Addon type "{self.addon_type}" is not supported, we support: {", ".join(CONDUIT_ADDON_TYPES)}."""
110
+ )
71
111
 
72
112
 
73
- def get_parameter_name(
74
- application_name: str, env: str, addon_type: str, addon_name: str, access: str
75
- ) -> str:
76
- if addon_type == "postgres":
77
- return f"/copilot/{application_name}/{env}/conduits/{_normalise_secret_name(addon_name)}_{access.upper()}"
78
- elif addon_type == "redis" or addon_type == "opensearch":
79
- return f"/copilot/{application_name}/{env}/conduits/{_normalise_secret_name(addon_name)}_ENDPOINT"
80
- else:
81
- return f"/copilot/{application_name}/{env}/conduits/{_normalise_secret_name(addon_name)}"
113
+ class SecretException(PlatformException):
114
+ pass
115
+
116
+
117
+ class ParameterNotFoundException(SecretException):
118
+ def __init__(self, application_name: str, environment: str):
119
+ super().__init__(
120
+ f"""No parameter called "/copilot/applications/{application_name}/environments/{environment}/addons". Try deploying the "{application_name}" "{environment}" environment."""
121
+ )
82
122
 
83
123
 
84
- def _normalise_secret_name(addon_name: str) -> str:
85
- return addon_name.replace("-", "_").upper()
124
+ class SecretNotFoundException(SecretException):
125
+ def __init__(self, secret_name: str):
126
+ super().__init__(f"""No secret called "{secret_name}".""")
@@ -0,0 +1,19 @@
1
+ from dbt_platform_helper.platform_exception import PlatformException
2
+
3
+
4
+ class ValidationException(PlatformException):
5
+ pass
6
+
7
+
8
+ class IncompatibleMajorVersionException(ValidationException):
9
+ def __init__(self, app_version: str, check_version: str):
10
+ super().__init__()
11
+ self.app_version = app_version
12
+ self.check_version = check_version
13
+
14
+
15
+ class IncompatibleMinorVersionException(ValidationException):
16
+ def __init__(self, app_version: str, check_version: str):
17
+ super().__init__()
18
+ self.app_version = app_version
19
+ self.check_version = check_version
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import os
2
3
  import re
3
4
  from pathlib import Path
4
5
  from typing import Dict
@@ -8,7 +9,7 @@ import yaml
8
9
  from boto3 import Session
9
10
  from yaml.parser import ParserError
10
11
 
11
- from dbt_platform_helper.exceptions import ApplicationNotFoundError
12
+ from dbt_platform_helper.platform_exception import PlatformException
12
13
  from dbt_platform_helper.utils.aws import get_aws_session_or_abort
13
14
  from dbt_platform_helper.utils.aws import get_profile_name_from_account_id
14
15
  from dbt_platform_helper.utils.aws import get_ssm_secrets
@@ -80,7 +81,7 @@ def load_application(app: str = None, default_session: Session = None) -> Applic
80
81
  WithDecryption=False,
81
82
  )
82
83
  except ssm_client.exceptions.ParameterNotFound:
83
- raise ApplicationNotFoundError
84
+ raise ApplicationNotFoundException(app)
84
85
 
85
86
  path = f"/copilot/applications/{application.name}/environments"
86
87
  secrets = get_ssm_secrets(app, None, current_session, path)
@@ -135,3 +136,14 @@ def get_application_name():
135
136
  abort_with_error("Cannot get application name. No copilot/.workspace file found")
136
137
 
137
138
  return app_name
139
+
140
+
141
+ class ApplicationException(PlatformException):
142
+ pass
143
+
144
+
145
+ class ApplicationNotFoundException(ApplicationException):
146
+ def __init__(self, application_name: str):
147
+ super().__init__(
148
+ 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."""
149
+ )
@@ -1,4 +1,4 @@
1
- from dbt_platform_helper.exceptions import ValidationException
1
+ from dbt_platform_helper.providers.validation import ValidationException
2
2
 
3
3
 
4
4
  class ARN: