dc-python-sdk 1.5.42__tar.gz → 1.5.43__tar.gz
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.
- {dc_python_sdk-1.5.42/src/dc_python_sdk.egg-info → dc_python_sdk-1.5.43}/PKG-INFO +2 -1
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/pyproject.toml +3 -2
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/setup.cfg +2 -1
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43/src/dc_python_sdk.egg-info}/PKG-INFO +2 -1
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_python_sdk.egg-info/requires.txt +1 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/models/pipeline_details.py +2 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/pipeline.py +33 -6
- dc_python_sdk-1.5.43/src/dc_sdk/src/services/api.py +205 -0
- dc_python_sdk-1.5.43/src/dc_sdk/src/services/aws.py +283 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/services/environment.py +4 -0
- dc_python_sdk-1.5.42/src/dc_sdk/src/services/api.py +0 -123
- dc_python_sdk-1.5.42/src/dc_sdk/src/services/aws.py +0 -141
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/LICENSE +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/README.md +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_python_sdk.egg-info/SOURCES.txt +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_python_sdk.egg-info/dependency_links.txt +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_python_sdk.egg-info/entry_points.txt +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_python_sdk.egg-info/top_level.txt +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/__init__.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/app.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/cli.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/errors.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/handler.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/__init__.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/ai.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/ai_http.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/mapping.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/models/__init__.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/models/enums.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/models/errors.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/models/log_templates.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/server.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/services/__init__.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/services/loader.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/services/logger.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/src/services/session.py +0 -0
- {dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_sdk/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dc-python-sdk
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.43
|
|
4
4
|
Summary: Data Connector Python SDK
|
|
5
5
|
Home-page: https://github.com/data-connector/dc-python-sdk
|
|
6
6
|
Author: DataConnector
|
|
@@ -18,6 +18,7 @@ Requires-Dist: awslambdaric
|
|
|
18
18
|
Requires-Dist: requests
|
|
19
19
|
Requires-Dist: boto3>=1.40.0
|
|
20
20
|
Requires-Dist: openai
|
|
21
|
+
Requires-Dist: pycryptodome
|
|
21
22
|
Provides-Extra: test
|
|
22
23
|
Requires-Dist: python-dotenv>=0.20.0; extra == "test"
|
|
23
24
|
Requires-Dist: faker>=13.12.0; extra == "test"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dc-python-sdk"
|
|
7
|
-
version = "1.5.
|
|
7
|
+
version = "1.5.43"
|
|
8
8
|
description = "Data Connector Python SDK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.6"
|
|
@@ -22,7 +22,8 @@ dependencies = [
|
|
|
22
22
|
"awslambdaric",
|
|
23
23
|
"requests",
|
|
24
24
|
"boto3>=1.40.0",
|
|
25
|
-
"openai"
|
|
25
|
+
"openai",
|
|
26
|
+
"pycryptodome",
|
|
26
27
|
]
|
|
27
28
|
|
|
28
29
|
[project.optional-dependencies]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = dc-python-sdk
|
|
3
|
-
version = 1.5.
|
|
3
|
+
version = 1.5.43
|
|
4
4
|
author = DataConnector
|
|
5
5
|
author_email = josh@dataconnector.com
|
|
6
6
|
description = A small example package
|
|
@@ -23,6 +23,7 @@ install_requires =
|
|
|
23
23
|
fastapi
|
|
24
24
|
uvicorn
|
|
25
25
|
awslambdaric
|
|
26
|
+
pycryptodome
|
|
26
27
|
requests
|
|
27
28
|
boto3>=1.40.0
|
|
28
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dc-python-sdk
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.43
|
|
4
4
|
Summary: Data Connector Python SDK
|
|
5
5
|
Home-page: https://github.com/data-connector/dc-python-sdk
|
|
6
6
|
Author: DataConnector
|
|
@@ -18,6 +18,7 @@ Requires-Dist: awslambdaric
|
|
|
18
18
|
Requires-Dist: requests
|
|
19
19
|
Requires-Dist: boto3>=1.40.0
|
|
20
20
|
Requires-Dist: openai
|
|
21
|
+
Requires-Dist: pycryptodome
|
|
21
22
|
Provides-Extra: test
|
|
22
23
|
Requires-Dist: python-dotenv>=0.20.0; extra == "test"
|
|
23
24
|
Requires-Dist: faker>=13.12.0; extra == "test"
|
|
@@ -33,6 +33,8 @@ class PipelineDetails:
|
|
|
33
33
|
self.options = json.loads(row_data['pipeline_object_options_json']) if 'pipeline_object_options_json' in row_data and row_data['pipeline_object_options_json'] else dict()
|
|
34
34
|
self.max_allowed_retrieval = row_data.get('max_allowed_retrieval')
|
|
35
35
|
self.primary_key_column_nm = row_data.get('primary_key_column_nm')
|
|
36
|
+
self.destination_credential_information = row_data.get('destination_credential_information')
|
|
37
|
+
self.source_credential_information = row_data.get('source_credential_information')
|
|
36
38
|
|
|
37
39
|
def increment_stage(self):
|
|
38
40
|
self.stage += 1
|
|
@@ -67,27 +67,54 @@ class PipelineConductor:
|
|
|
67
67
|
if self.authentication_tries == 3 or "firewall" in str(e): # todo: fix add retry flg in raised errors
|
|
68
68
|
raise e
|
|
69
69
|
self.authentication_tries += 1
|
|
70
|
-
self.
|
|
70
|
+
self._refresh_source_credentials()
|
|
71
71
|
time.sleep(10)
|
|
72
72
|
|
|
73
|
-
if authenticated
|
|
73
|
+
if authenticated:
|
|
74
74
|
self.api.send_new_credentials(True, self.pipeline_id, self.connector.credentials)
|
|
75
75
|
self.log(self.log_templates.AUTHENTICATION_FINISH.format(self.pipeline_details.source_credential_nm))
|
|
76
76
|
|
|
77
|
-
def
|
|
77
|
+
def _refresh_source_credentials(self):
|
|
78
78
|
response = self.api.get_credential_updates(True, self.pipeline_id)
|
|
79
79
|
|
|
80
|
-
creds = self.aws.decrypt_customer_data_object(response['credential'], response['organization']
|
|
80
|
+
creds = self.aws.decrypt_customer_data_object(response['credential'], response['organization'],
|
|
81
|
+
encrypted_data_key_txt=self.pipeline_details.source_credential_information.get('encrypted_data_key_txt'),
|
|
82
|
+
encryption_iv_txt=self.pipeline_details.source_credential_information.get('encryption_iv_txt'),
|
|
83
|
+
encryption_auth_tag_txt=self.pipeline_details.source_credential_information.get('encryption_auth_tag_txt')
|
|
84
|
+
)
|
|
81
85
|
|
|
82
86
|
self.connector.credentials = creds
|
|
87
|
+
|
|
88
|
+
def _refresh_destination_credentials(self):
|
|
89
|
+
response = self.api.get_credential_updates(False, self.pipeline_id)
|
|
90
|
+
|
|
91
|
+
creds = self.aws.decrypt_customer_data_object(response['credential'], response['organization'],
|
|
92
|
+
encrypted_data_key_txt=self.pipeline_details.destination_credential_information.get('encrypted_data_key_txt'),
|
|
93
|
+
encryption_iv_txt=self.pipeline_details.destination_credential_information.get('encryption_iv_txt'),
|
|
94
|
+
encryption_auth_tag_txt=self.pipeline_details.destination_credential_information.get('encryption_auth_tag_txt')
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.connector.credentials = creds
|
|
83
98
|
|
|
84
99
|
def authenticate_destination(self):
|
|
85
100
|
if not self.credentials:
|
|
86
101
|
raise errors.AuthenticationError("Credentials are not set, please update your credentials.")
|
|
87
102
|
|
|
88
103
|
self.log(self.log_templates.AUTHENTICATION_START.format(self.pipeline_details.destination_credential_nm, self.pipeline_details.destination_object_id))
|
|
89
|
-
|
|
90
|
-
self.
|
|
104
|
+
authenticated = False
|
|
105
|
+
while self.authentication_tries <= 3 and not authenticated:
|
|
106
|
+
try:
|
|
107
|
+
authenticated = self.connector.authenticate()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
if self.authentication_tries == 3 or "firewall" in str(e): # todo: fix add retry flg in raised errors
|
|
110
|
+
raise e
|
|
111
|
+
self.authentication_tries += 1
|
|
112
|
+
self._refresh_destination_credentials()
|
|
113
|
+
time.sleep(10)
|
|
114
|
+
|
|
115
|
+
if authenticated:
|
|
116
|
+
self.api.send_new_credentials(False, self.pipeline_id, self.connector.credentials)
|
|
117
|
+
self.log(self.log_templates.AUTHENTICATION_FINISH.format(self.pipeline_details.destination_credential_nm))
|
|
91
118
|
|
|
92
119
|
def get_data(self):
|
|
93
120
|
# Determine batch size
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
from ..models.enums import EnvironmentVariablesEnum
|
|
9
|
+
from ..models.pipeline_details import PipelineDetails
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataConnectorAPI:
|
|
13
|
+
RETRY_STATUS_CODES = {408, 429, 500, 502, 503, 504}
|
|
14
|
+
|
|
15
|
+
def __init__(self, environment=None):
|
|
16
|
+
self.server_endpoint = os.environ.get(EnvironmentVariablesEnum.SERVER_ENDPOINT.value)
|
|
17
|
+
self.pipeline_id = os.environ.get(EnvironmentVariablesEnum.PIPELINE_ID.value)
|
|
18
|
+
self.server_api_key = os.environ.get(EnvironmentVariablesEnum.SERVER_API_KEY.value)
|
|
19
|
+
self.pipelines_base_url = f"{self.server_endpoint}/api/pipelines"
|
|
20
|
+
|
|
21
|
+
def get(self, endpoint):
|
|
22
|
+
return self._request("GET", endpoint)
|
|
23
|
+
|
|
24
|
+
def post(self, endpoint, body=None):
|
|
25
|
+
return self._request("POST", endpoint, body)
|
|
26
|
+
|
|
27
|
+
def put(self, endpoint, body=None):
|
|
28
|
+
return self._request("PUT", endpoint, body)
|
|
29
|
+
|
|
30
|
+
def log(self, message, stage, task, error=None, internal=False):
|
|
31
|
+
print("Log Output: ", message)
|
|
32
|
+
|
|
33
|
+
pipeline_run_history_id = os.environ.get(
|
|
34
|
+
EnvironmentVariablesEnum.PIPELINE_RUN_HISTORY_ID.value
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
payload = {
|
|
38
|
+
"PipelineID": self.pipeline_id,
|
|
39
|
+
"PipelineRunHistoryID": pipeline_run_history_id,
|
|
40
|
+
"LogStageID": stage,
|
|
41
|
+
"LogTXT": message,
|
|
42
|
+
"ExternalFacingFLG": 0 if internal else 1,
|
|
43
|
+
"ErrorLocationDSC": None,
|
|
44
|
+
"LogTypeCD": 2 if error else 1,
|
|
45
|
+
"LogTypeDSC": "Error" if error else "Info",
|
|
46
|
+
"task": task,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
self.post("log", payload)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"WARNING: Failed to post pipeline log. Continuing. Error: {e}")
|
|
53
|
+
|
|
54
|
+
def logv2(self, logger_payload):
|
|
55
|
+
pipeline_run_history_id = os.environ.get(
|
|
56
|
+
EnvironmentVariablesEnum.PIPELINE_RUN_HISTORY_ID.value
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
payload = {
|
|
60
|
+
"PipelineID": self.pipeline_id,
|
|
61
|
+
"PipelineRunHistoryID": pipeline_run_history_id,
|
|
62
|
+
**logger_payload,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
self.post("logv2", payload)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"WARNING: Failed to post pipeline logv2. Continuing. Error: {e}")
|
|
69
|
+
|
|
70
|
+
def get_pipeline_details(
|
|
71
|
+
self,
|
|
72
|
+
pipeline_id: str,
|
|
73
|
+
task,
|
|
74
|
+
pipeline_run_history_id: str,
|
|
75
|
+
pipeline_mapping_id: str = None,
|
|
76
|
+
):
|
|
77
|
+
url = f"{pipeline_id}"
|
|
78
|
+
|
|
79
|
+
if pipeline_mapping_id:
|
|
80
|
+
url = f"{url}?PipelineMappingID={pipeline_mapping_id}"
|
|
81
|
+
|
|
82
|
+
response_json = self.get(url)
|
|
83
|
+
|
|
84
|
+
return PipelineDetails(response_json, task, pipeline_id, pipeline_run_history_id)
|
|
85
|
+
|
|
86
|
+
def create_new_history(self, pipeline_id, pipeline_mapping_id: str = None):
|
|
87
|
+
body = {"PipelineMappingID": pipeline_mapping_id} if pipeline_mapping_id else None
|
|
88
|
+
response = self.post(f"{pipeline_id}/history", body)
|
|
89
|
+
|
|
90
|
+
return response["PipelineRunHistoryID"]
|
|
91
|
+
|
|
92
|
+
def send_new_credentials(self, source_flg, pipeline_id, credentials):
|
|
93
|
+
self.post(
|
|
94
|
+
f"credentials/{pipeline_id}",
|
|
95
|
+
{
|
|
96
|
+
"source": source_flg,
|
|
97
|
+
"credentials": json.dumps(credentials),
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def get_credential_updates(self, source_flg, pipeline_id):
|
|
102
|
+
response = self.get(
|
|
103
|
+
f"credentials/{pipeline_id}?source={'true' if source_flg else 'false'}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return response
|
|
107
|
+
|
|
108
|
+
def create_pipeline_mapping(self, pipeline_id, pipeline_mapping_json):
|
|
109
|
+
body = {
|
|
110
|
+
"pipeline_id": pipeline_id,
|
|
111
|
+
"pipeline_mapping_json": pipeline_mapping_json,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
self.post(f"{pipeline_id}/mapping", body)
|
|
115
|
+
|
|
116
|
+
def _request(self, method, endpoint, body=None, max_attempts=4):
|
|
117
|
+
endpoint = endpoint.lstrip("/")
|
|
118
|
+
url = f"{self.pipelines_base_url}/{endpoint}"
|
|
119
|
+
|
|
120
|
+
headers = {
|
|
121
|
+
"Accept": "application/json",
|
|
122
|
+
"x-data-connector-access-token": self.server_api_key,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
last_error = None
|
|
126
|
+
|
|
127
|
+
for attempt in range(1, max_attempts + 1):
|
|
128
|
+
try:
|
|
129
|
+
response = requests.request(
|
|
130
|
+
method=method,
|
|
131
|
+
url=url,
|
|
132
|
+
json=body,
|
|
133
|
+
headers=headers,
|
|
134
|
+
timeout=(5, 30),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
response_data = response.json()
|
|
139
|
+
except ValueError:
|
|
140
|
+
response_data = response.text
|
|
141
|
+
|
|
142
|
+
if response.ok:
|
|
143
|
+
return response_data
|
|
144
|
+
|
|
145
|
+
if response.status_code in self.RETRY_STATUS_CODES and attempt < max_attempts:
|
|
146
|
+
sleep_seconds = self._get_retry_delay(attempt)
|
|
147
|
+
print(
|
|
148
|
+
f"WARNING: API {method} {endpoint} failed with "
|
|
149
|
+
f"{response.status_code}. Retrying attempt "
|
|
150
|
+
f"{attempt + 1}/{max_attempts} in {sleep_seconds:.2f}s."
|
|
151
|
+
)
|
|
152
|
+
time.sleep(sleep_seconds)
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
raise Exception(
|
|
156
|
+
f"API ERROR: {method} {endpoint} failed | "
|
|
157
|
+
f"Status: {response.status_code} | "
|
|
158
|
+
f"Response: {response_data}"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
|
|
162
|
+
last_error = e
|
|
163
|
+
|
|
164
|
+
if attempt < max_attempts:
|
|
165
|
+
sleep_seconds = self._get_retry_delay(attempt)
|
|
166
|
+
print(
|
|
167
|
+
f"WARNING: API {method} {endpoint} connection/timeout error. "
|
|
168
|
+
f"Retrying attempt {attempt + 1}/{max_attempts} "
|
|
169
|
+
f"in {sleep_seconds:.2f}s. Error: {e}"
|
|
170
|
+
)
|
|
171
|
+
time.sleep(sleep_seconds)
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
raise Exception(
|
|
175
|
+
f"API ERROR: {method} {endpoint} failed after "
|
|
176
|
+
f"{max_attempts} attempts | Error: {str(e)}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
except requests.exceptions.RequestException as e:
|
|
180
|
+
last_error = e
|
|
181
|
+
|
|
182
|
+
if attempt < max_attempts:
|
|
183
|
+
sleep_seconds = self._get_retry_delay(attempt)
|
|
184
|
+
print(
|
|
185
|
+
f"WARNING: API {method} {endpoint} unexpected request error. "
|
|
186
|
+
f"Retrying attempt {attempt + 1}/{max_attempts} "
|
|
187
|
+
f"in {sleep_seconds:.2f}s. Error: {e}"
|
|
188
|
+
)
|
|
189
|
+
time.sleep(sleep_seconds)
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
raise Exception(
|
|
193
|
+
f"API ERROR: {method} {endpoint} unexpected error after "
|
|
194
|
+
f"{max_attempts} attempts | Error: {str(e)}"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
raise Exception(
|
|
198
|
+
f"API ERROR: {method} {endpoint} failed after "
|
|
199
|
+
f"{max_attempts} attempts | Last error: {str(last_error)}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def _get_retry_delay(self, attempt):
|
|
203
|
+
base_delay = min(2 ** attempt, 10)
|
|
204
|
+
jitter = random.uniform(0, 0.5)
|
|
205
|
+
return base_delay + jitter
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import boto3
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from base64 import b64decode
|
|
6
|
+
from Crypto.Cipher import AES
|
|
7
|
+
|
|
8
|
+
kms_client = boto3.client('kms')
|
|
9
|
+
|
|
10
|
+
APP_ENV = os.environ.get("APP_ENV")
|
|
11
|
+
|
|
12
|
+
s3_client = boto3.client('s3')
|
|
13
|
+
s3_resource = boto3.resource('s3')
|
|
14
|
+
ecs_resource = boto3.client('ecs')
|
|
15
|
+
kms_client = boto3.client('kms')
|
|
16
|
+
|
|
17
|
+
class AwsService:
|
|
18
|
+
def __init__(self, s3_bucket) -> None:
|
|
19
|
+
self.s3_bucket = s3_bucket
|
|
20
|
+
self._ecs_cluster_name = APP_ENV if APP_ENV != "local" else "development"
|
|
21
|
+
|
|
22
|
+
def get_keys(self, pipeline_run_history_id):
|
|
23
|
+
keys = []
|
|
24
|
+
response = s3_client.list_objects(Bucket=self.s3_bucket, Prefix=f"transfers/e{pipeline_run_history_id}")
|
|
25
|
+
if 'Contents' in response:
|
|
26
|
+
for key in response['Contents']:
|
|
27
|
+
keys.append(key['Key'])
|
|
28
|
+
return keys
|
|
29
|
+
|
|
30
|
+
def get_dev_keys(self, prefix):
|
|
31
|
+
keys = []
|
|
32
|
+
response = s3_client.list_objects(Bucket=self.s3_bucket, Prefix=f"devTransfers/e{prefix}")
|
|
33
|
+
if 'Contents' in response:
|
|
34
|
+
for key in response['Contents']:
|
|
35
|
+
keys.append(key['Key'])
|
|
36
|
+
return keys
|
|
37
|
+
|
|
38
|
+
def download_object(self, key_name):
|
|
39
|
+
file = s3_resource.Object(self.s3_bucket, key_name)
|
|
40
|
+
return file.get()['Body']
|
|
41
|
+
|
|
42
|
+
def upload_object(self, key_name, json_buffer=None, file_path=None):
|
|
43
|
+
if json_buffer is not None:
|
|
44
|
+
s3_resource.Object(
|
|
45
|
+
self.s3_bucket, key_name).put(Body=json_buffer.getvalue())
|
|
46
|
+
elif file_path is not None:
|
|
47
|
+
s3_client.upload_file(file_path, self.s3_bucket, key_name)
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError("Either json_buffer or file_path must be provided")
|
|
50
|
+
|
|
51
|
+
def get_object_size(self, key_name):
|
|
52
|
+
response = s3_client.head_object(
|
|
53
|
+
Bucket=self.s3_bucket, Key=key_name)
|
|
54
|
+
return float(response['ContentLength'])
|
|
55
|
+
|
|
56
|
+
def delete_object(self, key_name):
|
|
57
|
+
s3_client.delete_object(
|
|
58
|
+
Bucket=self.s3_bucket,
|
|
59
|
+
Key=key_name,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def start_ecs_task(self, task_name:str, task_version:str, pipeline_id: str, pipeline_run_history_id: str):
|
|
63
|
+
response = ecs_resource.run_task(
|
|
64
|
+
cluster=self._ecs_cluster_name,
|
|
65
|
+
count=1,
|
|
66
|
+
enableECSManagedTags=False,
|
|
67
|
+
enableExecuteCommand=False,
|
|
68
|
+
launchType='FARGATE',
|
|
69
|
+
networkConfiguration={
|
|
70
|
+
'awsvpcConfiguration': {
|
|
71
|
+
'subnets': [
|
|
72
|
+
'subnet-095c3230c890e0841',
|
|
73
|
+
],
|
|
74
|
+
'securityGroups': [
|
|
75
|
+
'sg-08d46d487577cbdf9',
|
|
76
|
+
],
|
|
77
|
+
'assignPublicIp': 'ENABLED'
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
overrides={
|
|
81
|
+
'containerOverrides': [
|
|
82
|
+
{
|
|
83
|
+
'name': task_name,
|
|
84
|
+
'command': ['app.py'],
|
|
85
|
+
'environment': [
|
|
86
|
+
{
|
|
87
|
+
'name': 'PIPELINE_ID',
|
|
88
|
+
'value': pipeline_id
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
'name': 'PIPELINE_RUN_HISTORY_ID',
|
|
92
|
+
'value': pipeline_run_history_id
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
'name': 'TASK',
|
|
96
|
+
'value': 'DESTINATION'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
'name': 'APP_ENV',
|
|
100
|
+
'value': os.environ.get("APP_ENV")
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
'name': 'SERVER_ENDPOINT',
|
|
104
|
+
'value': os.environ.get("SERVER_ENDPOINT")
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
'name': 'SERVER_API_KEY',
|
|
108
|
+
'value': os.environ.get("SERVER_API_KEY")
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
'name': 'AWS_DEFAULT_REGION',
|
|
112
|
+
'value': os.environ.get("AWS_DEFAULT_REGION")
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
platformVersion='LATEST',
|
|
119
|
+
taskDefinition=f"{task_name}:{task_version}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if len(response['tasks']) == 0:
|
|
123
|
+
raise Exception("Failed to start next connector")
|
|
124
|
+
|
|
125
|
+
return response['tasks'][0]['taskArn'].split("/")[-1]
|
|
126
|
+
|
|
127
|
+
def decrypt_customer_data(
|
|
128
|
+
self,
|
|
129
|
+
encrypted_data: str,
|
|
130
|
+
customer_id: str,
|
|
131
|
+
encrypted_data_key_txt: str = None,
|
|
132
|
+
encryption_iv_txt: str = None,
|
|
133
|
+
encryption_auth_tag_txt: str = None
|
|
134
|
+
) -> str:
|
|
135
|
+
"""Decrypt data with customer context using legacy or envelope encryption"""
|
|
136
|
+
encryption_service = EncryptionService()
|
|
137
|
+
|
|
138
|
+
return encryption_service.decrypt_customer_data(
|
|
139
|
+
encrypted_data=encrypted_data,
|
|
140
|
+
customer_id=customer_id,
|
|
141
|
+
encrypted_data_key_txt=encrypted_data_key_txt,
|
|
142
|
+
encryption_iv_txt=encryption_iv_txt,
|
|
143
|
+
encryption_auth_tag_txt=encryption_auth_tag_txt
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def decrypt_customer_data_object(
|
|
148
|
+
self,
|
|
149
|
+
encrypted_data: str,
|
|
150
|
+
customer_id: str,
|
|
151
|
+
encrypted_data_key_txt: str = None,
|
|
152
|
+
encryption_iv_txt: str = None,
|
|
153
|
+
encryption_auth_tag_txt: str = None
|
|
154
|
+
) -> Dict:
|
|
155
|
+
"""Decrypt JSON data with customer context using legacy or envelope encryption"""
|
|
156
|
+
encryption_service = EncryptionService()
|
|
157
|
+
|
|
158
|
+
return encryption_service.decrypt_customer_data_object(
|
|
159
|
+
encrypted_data=encrypted_data,
|
|
160
|
+
customer_id=customer_id,
|
|
161
|
+
encrypted_data_key_txt=encrypted_data_key_txt,
|
|
162
|
+
encryption_iv_txt=encryption_iv_txt,
|
|
163
|
+
encryption_auth_tag_txt=encryption_auth_tag_txt
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class EncryptionService:
|
|
171
|
+
def decrypt_customer_data(
|
|
172
|
+
self,
|
|
173
|
+
encrypted_data: str,
|
|
174
|
+
customer_id: str,
|
|
175
|
+
encrypted_data_key_txt: str = None,
|
|
176
|
+
encryption_iv_txt: str = None,
|
|
177
|
+
encryption_auth_tag_txt: str = None
|
|
178
|
+
) -> str:
|
|
179
|
+
"""
|
|
180
|
+
Decrypt customer data using either:
|
|
181
|
+
- legacy KMS direct encryption
|
|
182
|
+
- envelope encryption
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
is_envelope = (
|
|
187
|
+
encrypted_data_key_txt is not None and
|
|
188
|
+
encryption_iv_txt is not None and
|
|
189
|
+
encryption_auth_tag_txt is not None
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if is_envelope:
|
|
193
|
+
return self._decrypt_customer_data_envelope(
|
|
194
|
+
encrypted_data=encrypted_data,
|
|
195
|
+
customer_id=customer_id,
|
|
196
|
+
encrypted_data_key_txt=encrypted_data_key_txt,
|
|
197
|
+
encryption_iv_txt=encryption_iv_txt,
|
|
198
|
+
encryption_auth_tag_txt=encryption_auth_tag_txt
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return self._decrypt_customer_data_legacy(
|
|
202
|
+
encrypted_data=encrypted_data,
|
|
203
|
+
customer_id=customer_id
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
raise Exception(f'Decryption failed: {str(e)}')
|
|
208
|
+
|
|
209
|
+
def _decrypt_customer_data_legacy(
|
|
210
|
+
self,
|
|
211
|
+
encrypted_data: str,
|
|
212
|
+
customer_id: str
|
|
213
|
+
) -> str:
|
|
214
|
+
|
|
215
|
+
response = kms_client.decrypt(
|
|
216
|
+
CiphertextBlob=b64decode(encrypted_data.encode('utf-8')),
|
|
217
|
+
EncryptionContext={
|
|
218
|
+
'CustomerId': customer_id
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if 'Plaintext' not in response:
|
|
223
|
+
raise Exception('Legacy decryption failed')
|
|
224
|
+
|
|
225
|
+
return response['Plaintext'].decode('utf-8')
|
|
226
|
+
|
|
227
|
+
def _decrypt_customer_data_envelope(
|
|
228
|
+
self,
|
|
229
|
+
encrypted_data: str,
|
|
230
|
+
customer_id: str,
|
|
231
|
+
encrypted_data_key_txt: str,
|
|
232
|
+
encryption_iv_txt: str,
|
|
233
|
+
encryption_auth_tag_txt: str
|
|
234
|
+
) -> str:
|
|
235
|
+
|
|
236
|
+
# decrypt data key using KMS
|
|
237
|
+
data_key_response = kms_client.decrypt(
|
|
238
|
+
CiphertextBlob=b64decode(
|
|
239
|
+
encrypted_data_key_txt.encode('utf-8')
|
|
240
|
+
),
|
|
241
|
+
EncryptionContext={
|
|
242
|
+
'CustomerId': customer_id
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if 'Plaintext' not in data_key_response:
|
|
247
|
+
raise Exception('Data key decryption failed')
|
|
248
|
+
|
|
249
|
+
plaintext_data_key = data_key_response['Plaintext']
|
|
250
|
+
|
|
251
|
+
cipher = AES.new(
|
|
252
|
+
plaintext_data_key,
|
|
253
|
+
AES.MODE_GCM,
|
|
254
|
+
nonce=b64decode(encryption_iv_txt.encode('utf-8'))
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
cipher.update(b'')
|
|
258
|
+
|
|
259
|
+
decrypted_data = cipher.decrypt_and_verify(
|
|
260
|
+
b64decode(encrypted_data.encode('utf-8')),
|
|
261
|
+
b64decode(encryption_auth_tag_txt.encode('utf-8'))
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return decrypted_data.decode('utf-8')
|
|
265
|
+
|
|
266
|
+
def decrypt_customer_data_object(
|
|
267
|
+
self,
|
|
268
|
+
encrypted_data: str,
|
|
269
|
+
customer_id: str,
|
|
270
|
+
encrypted_data_key_txt: str = None,
|
|
271
|
+
encryption_iv_txt: str = None,
|
|
272
|
+
encryption_auth_tag_txt: str = None
|
|
273
|
+
) -> dict:
|
|
274
|
+
|
|
275
|
+
data = self.decrypt_customer_data(
|
|
276
|
+
encrypted_data=encrypted_data,
|
|
277
|
+
customer_id=customer_id,
|
|
278
|
+
encrypted_data_key_txt=encrypted_data_key_txt,
|
|
279
|
+
encryption_iv_txt=encryption_iv_txt,
|
|
280
|
+
encryption_auth_tag_txt=encryption_auth_tag_txt
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return json.loads(data)
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import os, requests, sys, json
|
|
2
|
-
from ..models.enums import EnvironmentVariablesEnum
|
|
3
|
-
from ..models.pipeline_details import PipelineDetails
|
|
4
|
-
|
|
5
|
-
class DataConnectorAPI:
|
|
6
|
-
def __init__(self, environment = None):
|
|
7
|
-
self.server_endpoint = os.environ.get(EnvironmentVariablesEnum.SERVER_ENDPOINT.value)
|
|
8
|
-
self.pipeline_id = os.environ.get(EnvironmentVariablesEnum.PIPELINE_ID.value)
|
|
9
|
-
self.server_api_key = os.environ.get(EnvironmentVariablesEnum.SERVER_API_KEY.value)
|
|
10
|
-
self.pipelines_base_url = f"{self.server_endpoint}/api/pipelines"
|
|
11
|
-
|
|
12
|
-
def get(self, endpoint):
|
|
13
|
-
return self._request("GET", endpoint)
|
|
14
|
-
|
|
15
|
-
def post(self, endpoint, body=None):
|
|
16
|
-
return self._request("POST", endpoint, body)
|
|
17
|
-
|
|
18
|
-
def put(self, endpoint, body=None):
|
|
19
|
-
return self._request("PUT", endpoint, body)
|
|
20
|
-
|
|
21
|
-
def log(self, message, stage, task, error=None, internal=False):
|
|
22
|
-
print("Log Output: ", message)
|
|
23
|
-
PIPELINE_RUN_HISTORY_ID = os.environ.get(EnvironmentVariablesEnum.PIPELINE_RUN_HISTORY_ID.value)
|
|
24
|
-
payload = {
|
|
25
|
-
'PipelineID': self.pipeline_id,
|
|
26
|
-
'PipelineRunHistoryID': PIPELINE_RUN_HISTORY_ID,
|
|
27
|
-
'LogStageID': stage,
|
|
28
|
-
'LogTXT': message,
|
|
29
|
-
'ExternalFacingFLG': 0 if internal else 1,
|
|
30
|
-
'ErrorLocationDSC': None, # TODO: UPDATE THIS
|
|
31
|
-
'LogTypeCD': 2 if error else 1,
|
|
32
|
-
'LogTypeDSC': 'Error' if error else 'Info',
|
|
33
|
-
'task': task,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
self.post('log', payload)
|
|
37
|
-
|
|
38
|
-
def logv2(self, logger_payload):
|
|
39
|
-
PIPELINE_RUN_HISTORY_ID = os.environ.get(EnvironmentVariablesEnum.PIPELINE_RUN_HISTORY_ID.value)
|
|
40
|
-
payload = {
|
|
41
|
-
'PipelineID': self.pipeline_id,
|
|
42
|
-
'PipelineRunHistoryID': PIPELINE_RUN_HISTORY_ID,
|
|
43
|
-
**logger_payload
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
self.post('logv2', payload)
|
|
47
|
-
|
|
48
|
-
def get_pipeline_details(self, pipeline_id: str, task, pipeline_run_history_id: str, pipeline_mapping_id: str = None):
|
|
49
|
-
url = f"{pipeline_id}"
|
|
50
|
-
if pipeline_mapping_id:
|
|
51
|
-
url = f"{url}?PipelineMappingID={pipeline_mapping_id}"
|
|
52
|
-
json = self.get(url)
|
|
53
|
-
|
|
54
|
-
return PipelineDetails(json, task, pipeline_id, pipeline_run_history_id)
|
|
55
|
-
|
|
56
|
-
def create_new_history(self, pipeline_id, pipeline_mapping_id: str = None):
|
|
57
|
-
if pipeline_mapping_id:
|
|
58
|
-
body = {"PipelineMappingID": pipeline_mapping_id}
|
|
59
|
-
else:
|
|
60
|
-
body = None
|
|
61
|
-
response = self.post(f"{pipeline_id}/history", body)
|
|
62
|
-
|
|
63
|
-
return response['PipelineRunHistoryID']
|
|
64
|
-
|
|
65
|
-
def send_new_credentials(self, source_flg, pipeline_id, credentials):
|
|
66
|
-
self.post(f"credentials/{pipeline_id}", {"source": source_flg, "credentials": json.dumps(credentials)})
|
|
67
|
-
|
|
68
|
-
def get_credential_updates(self, source_flg, pipeline_id):
|
|
69
|
-
response = self.get(f"credentials/{pipeline_id}?source={'true' if source_flg else 'false'}")
|
|
70
|
-
|
|
71
|
-
return response
|
|
72
|
-
|
|
73
|
-
def create_pipeline_mapping(self, pipeline_id, pipeline_mapping_json):
|
|
74
|
-
body = {
|
|
75
|
-
"pipeline_id": pipeline_id,
|
|
76
|
-
"pipeline_mapping_json": pipeline_mapping_json
|
|
77
|
-
}
|
|
78
|
-
self.post(f"/{pipeline_id}/mapping", body)
|
|
79
|
-
|
|
80
|
-
def _request(self, method, endpoint, body=None):
|
|
81
|
-
url = f"{self.pipelines_base_url}/{endpoint}"
|
|
82
|
-
|
|
83
|
-
try:
|
|
84
|
-
response = requests.request(
|
|
85
|
-
method=method,
|
|
86
|
-
url=url,
|
|
87
|
-
data=body,
|
|
88
|
-
headers={
|
|
89
|
-
'Accept': 'application/json',
|
|
90
|
-
'x-data-connector-access-token': self.server_api_key
|
|
91
|
-
},
|
|
92
|
-
timeout=10 # 🔥 IMPORTANT
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
# Try to parse JSON safely
|
|
96
|
-
try:
|
|
97
|
-
response_data = response.json()
|
|
98
|
-
except ValueError:
|
|
99
|
-
response_data = response.text
|
|
100
|
-
|
|
101
|
-
if response.ok:
|
|
102
|
-
return response_data
|
|
103
|
-
else:
|
|
104
|
-
raise Exception(
|
|
105
|
-
f"API ERROR: {method} {endpoint} failed | "
|
|
106
|
-
f"Status: {response.status_code} | "
|
|
107
|
-
f"Response: {response_data}"
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
except requests.exceptions.Timeout:
|
|
111
|
-
raise Exception(
|
|
112
|
-
f"API ERROR: {method} {endpoint} timed out (10s)"
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
except requests.exceptions.ConnectionError:
|
|
116
|
-
raise Exception(
|
|
117
|
-
f"API ERROR: {method} {endpoint} failed — no response from server"
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
except requests.exceptions.RequestException as e:
|
|
121
|
-
raise Exception(
|
|
122
|
-
f"API ERROR: {method} {endpoint} unexpected error | {str(e)}"
|
|
123
|
-
)
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import boto3
|
|
3
|
-
import json
|
|
4
|
-
from typing import Dict
|
|
5
|
-
from base64 import b64decode
|
|
6
|
-
|
|
7
|
-
APP_ENV = os.environ.get("APP_ENV")
|
|
8
|
-
|
|
9
|
-
s3_client = boto3.client('s3')
|
|
10
|
-
s3_resource = boto3.resource('s3')
|
|
11
|
-
ecs_resource = boto3.client('ecs')
|
|
12
|
-
kms_client = boto3.client('kms')
|
|
13
|
-
|
|
14
|
-
class AwsService:
|
|
15
|
-
def __init__(self, s3_bucket) -> None:
|
|
16
|
-
self.s3_bucket = s3_bucket
|
|
17
|
-
self._ecs_cluster_name = APP_ENV if APP_ENV != "local" else "development"
|
|
18
|
-
|
|
19
|
-
def get_keys(self, pipeline_run_history_id):
|
|
20
|
-
keys = []
|
|
21
|
-
response = s3_client.list_objects(Bucket=self.s3_bucket, Prefix=f"transfers/e{pipeline_run_history_id}")
|
|
22
|
-
if 'Contents' in response:
|
|
23
|
-
for key in response['Contents']:
|
|
24
|
-
keys.append(key['Key'])
|
|
25
|
-
return keys
|
|
26
|
-
|
|
27
|
-
def get_dev_keys(self, prefix):
|
|
28
|
-
keys = []
|
|
29
|
-
response = s3_client.list_objects(Bucket=self.s3_bucket, Prefix=f"devTransfers/e{prefix}")
|
|
30
|
-
if 'Contents' in response:
|
|
31
|
-
for key in response['Contents']:
|
|
32
|
-
keys.append(key['Key'])
|
|
33
|
-
return keys
|
|
34
|
-
|
|
35
|
-
def download_object(self, key_name):
|
|
36
|
-
file = s3_resource.Object(self.s3_bucket, key_name)
|
|
37
|
-
return file.get()['Body']
|
|
38
|
-
|
|
39
|
-
def upload_object(self, key_name, json_buffer=None, file_path=None):
|
|
40
|
-
if json_buffer is not None:
|
|
41
|
-
s3_resource.Object(
|
|
42
|
-
self.s3_bucket, key_name).put(Body=json_buffer.getvalue())
|
|
43
|
-
elif file_path is not None:
|
|
44
|
-
s3_client.upload_file(file_path, self.s3_bucket, key_name)
|
|
45
|
-
else:
|
|
46
|
-
raise ValueError("Either json_buffer or file_path must be provided")
|
|
47
|
-
|
|
48
|
-
def get_object_size(self, key_name):
|
|
49
|
-
response = s3_client.head_object(
|
|
50
|
-
Bucket=self.s3_bucket, Key=key_name)
|
|
51
|
-
return float(response['ContentLength'])
|
|
52
|
-
|
|
53
|
-
def delete_object(self, key_name):
|
|
54
|
-
s3_client.delete_object(
|
|
55
|
-
Bucket=self.s3_bucket,
|
|
56
|
-
Key=key_name,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
def start_ecs_task(self, task_name:str, task_version:str, pipeline_id: str, pipeline_run_history_id: str):
|
|
60
|
-
response = ecs_resource.run_task(
|
|
61
|
-
cluster=self._ecs_cluster_name,
|
|
62
|
-
count=1,
|
|
63
|
-
enableECSManagedTags=False,
|
|
64
|
-
enableExecuteCommand=False,
|
|
65
|
-
launchType='FARGATE',
|
|
66
|
-
networkConfiguration={
|
|
67
|
-
'awsvpcConfiguration': {
|
|
68
|
-
'subnets': [
|
|
69
|
-
'subnet-095c3230c890e0841',
|
|
70
|
-
],
|
|
71
|
-
'securityGroups': [
|
|
72
|
-
'sg-08d46d487577cbdf9',
|
|
73
|
-
],
|
|
74
|
-
'assignPublicIp': 'ENABLED'
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
overrides={
|
|
78
|
-
'containerOverrides': [
|
|
79
|
-
{
|
|
80
|
-
'name': task_name,
|
|
81
|
-
'command': ['app.py'],
|
|
82
|
-
'environment': [
|
|
83
|
-
{
|
|
84
|
-
'name': 'PIPELINE_ID',
|
|
85
|
-
'value': pipeline_id
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
'name': 'PIPELINE_RUN_HISTORY_ID',
|
|
89
|
-
'value': pipeline_run_history_id
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
'name': 'TASK',
|
|
93
|
-
'value': 'DESTINATION'
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
'name': 'APP_ENV',
|
|
97
|
-
'value': os.environ.get("APP_ENV")
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
'name': 'SERVER_ENDPOINT',
|
|
101
|
-
'value': os.environ.get("SERVER_ENDPOINT")
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
'name': 'SERVER_API_KEY',
|
|
105
|
-
'value': os.environ.get("SERVER_API_KEY")
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
'name': 'AWS_DEFAULT_REGION',
|
|
109
|
-
'value': os.environ.get("AWS_DEFAULT_REGION")
|
|
110
|
-
}
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
},
|
|
115
|
-
platformVersion='LATEST',
|
|
116
|
-
taskDefinition=f"{task_name}:{task_version}"
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
if len(response['tasks']) == 0:
|
|
120
|
-
raise Exception("Failed to start next connector")
|
|
121
|
-
|
|
122
|
-
return response['tasks'][0]['taskArn'].split("/")[-1]
|
|
123
|
-
|
|
124
|
-
def decrypt_customer_data(self, encrypted_data: str, customer_id: str) -> str:
|
|
125
|
-
"""Decrypt data with customer context"""
|
|
126
|
-
try:
|
|
127
|
-
response = kms_client.decrypt(
|
|
128
|
-
CiphertextBlob=b64decode(encrypted_data.encode('utf-8')),
|
|
129
|
-
EncryptionContext={'CustomerId': customer_id}
|
|
130
|
-
)
|
|
131
|
-
if 'Plaintext' not in response:
|
|
132
|
-
raise Exception('Decryption failed')
|
|
133
|
-
|
|
134
|
-
return response['Plaintext'].decode('utf-8')
|
|
135
|
-
except Exception as e:
|
|
136
|
-
raise Exception(f'Decryption failed: {str(e)}')
|
|
137
|
-
|
|
138
|
-
def decrypt_customer_data_object(self, encrypted_data: str, customer_id: str) -> Dict:
|
|
139
|
-
"""Decrypt JSON data with customer context"""
|
|
140
|
-
data = self.decrypt_customer_data(encrypted_data, customer_id)
|
|
141
|
-
return json.loads(data)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dc_python_sdk-1.5.42 → dc_python_sdk-1.5.43}/src/dc_python_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|