dataflow-core 2.1.8__tar.gz → 2.1.18rc2__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.
Potentially problematic release.
This version of dataflow-core might be problematic. Click here for more details.
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/PKG-INFO +4 -1
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/authenticator/dataflowhubauthenticator.py +30 -45
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/dataflow.py +162 -62
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/environment.py +62 -33
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/__init__.py +7 -3
- dataflow_core-2.1.18rc2/dataflow/models/app_types.py +15 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/connection.py +5 -4
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/dataflow_zone.py +2 -3
- dataflow_core-2.1.18rc2/dataflow/models/environment.py +125 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/git_ssh.py +2 -1
- dataflow_core-2.1.18rc2/dataflow/models/org_associations.py +38 -0
- dataflow_core-2.1.18rc2/dataflow/models/organization.py +78 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/pinned_projects.py +2 -2
- dataflow_core-2.1.18rc2/dataflow/models/pod_activity.py +17 -0
- dataflow_core-2.1.18rc2/dataflow/models/pod_session_history.py +16 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/project_details.py +9 -6
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/recent_project_studio.py +1 -1
- dataflow_core-2.1.18rc2/dataflow/models/role.py +35 -0
- dataflow_core-2.1.18rc2/dataflow/models/role_server.py +11 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/role_zone.py +8 -3
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/server_config.py +16 -8
- dataflow_core-2.1.18rc2/dataflow/models/team.py +23 -0
- dataflow_core-2.1.18rc2/dataflow/models/user.py +68 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/user_team.py +1 -4
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/variables.py +13 -4
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/schemas/connection.py +7 -4
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/schemas/git_ssh.py +5 -5
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/schemas/secret.py +7 -4
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/scripts/clone_environment.sh +2 -1
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/scripts/create_environment.sh +4 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/secrets_manager/factory.py +14 -8
- dataflow_core-2.1.18rc2/dataflow/secrets_manager/providers/gcp_manager.py +332 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/secrets_manager/service.py +17 -9
- dataflow_core-2.1.18rc2/dataflow/secrets_manager/utils.py +58 -0
- dataflow_core-2.1.18rc2/dataflow/utils/get_current_user.py +60 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow_core.egg-info/PKG-INFO +4 -1
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow_core.egg-info/SOURCES.txt +11 -2
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow_core.egg-info/requires.txt +3 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow_core.egg-info/top_level.txt +1 -0
- dataflow_core-2.1.18rc2/dfmigration/__init__.py +0 -0
- dataflow_core-2.1.18rc2/dfmigration/env.py +45 -0
- dataflow_core-2.1.18rc2/dfmigration/versions/001_initial_baseline_migration.py +20 -0
- dataflow_core-2.1.18rc2/dfmigration/versions/__init__.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/setup.py +6 -3
- dataflow_core-2.1.8/dataflow/models/app_types.py +0 -10
- dataflow_core-2.1.8/dataflow/models/environment.py +0 -75
- dataflow_core-2.1.8/dataflow/models/role.py +0 -29
- dataflow_core-2.1.8/dataflow/models/role_server.py +0 -14
- dataflow_core-2.1.8/dataflow/models/team.py +0 -17
- dataflow_core-2.1.8/dataflow/models/user.py +0 -30
- dataflow_core-2.1.8/dataflow/models/user_environment.py +0 -16
- dataflow_core-2.1.8/dataflow/utils/get_current_user.py +0 -37
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/README.md +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/authenticator/__init__.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/authenticator/dataflowairflowauthenticator.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/authenticator/dataflowsupersetauthenticator.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/__init__.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/configuration.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/database_manager.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/db.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/blacklist_library.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/environment_status.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/recent_projects.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/models/session.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/schemas/__init__.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/scripts/update_environment.sh +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/secrets_manager/__init__.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/secrets_manager/interface.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/secrets_manager/providers/__init__.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/secrets_manager/providers/aws_manager.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/secrets_manager/providers/azure_manager.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/utils/__init__.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/utils/exceptions.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow/utils/logger.py +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow_core.egg-info/dependency_links.txt +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/dataflow_core.egg-info/entry_points.txt +0 -0
- {dataflow_core-2.1.8 → dataflow_core-2.1.18rc2}/setup.cfg +0 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataflow-core
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.18rc2
|
|
4
4
|
Summary: Dataflow core package
|
|
5
5
|
Author: Dataflow
|
|
6
6
|
Author-email:
|
|
7
7
|
Requires-Dist: sqlalchemy
|
|
8
|
+
Requires-Dist: alembic
|
|
8
9
|
Requires-Dist: boto3
|
|
9
10
|
Requires-Dist: psycopg2-binary
|
|
10
11
|
Requires-Dist: pymysql
|
|
11
12
|
Requires-Dist: requests
|
|
12
13
|
Requires-Dist: azure-identity
|
|
13
14
|
Requires-Dist: azure-keyvault-secrets
|
|
15
|
+
Requires-Dist: google-auth
|
|
16
|
+
Requires-Dist: google-cloud-secret-manager
|
|
14
17
|
Dynamic: author
|
|
15
18
|
Dynamic: requires-dist
|
|
16
19
|
Dynamic: summary
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import uuid
|
|
3
|
-
import re
|
|
1
|
+
import os, uuid, re, hashlib, secrets
|
|
4
2
|
from datetime import datetime, timedelta
|
|
5
3
|
from zoneinfo import ZoneInfo
|
|
6
4
|
from traitlets import Bool, Unicode
|
|
@@ -8,7 +6,8 @@ from jupyterhub.auth import Authenticator
|
|
|
8
6
|
from oauthenticator.google import GoogleOAuthenticator
|
|
9
7
|
from oauthenticator.azuread import AzureAdOAuthenticator
|
|
10
8
|
from dataflow.db import get_db
|
|
11
|
-
from dataflow.models import user as m_user, session as m_session
|
|
9
|
+
from dataflow.models import user as m_user, session as m_session
|
|
10
|
+
from sqlalchemy import or_
|
|
12
11
|
|
|
13
12
|
class DataflowBaseAuthenticator(Authenticator):
|
|
14
13
|
enable_dataflow_auth = Bool(True, config=True, help="Enable username/password authentication")
|
|
@@ -28,7 +27,7 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
28
27
|
return str(uuid.uuid4())
|
|
29
28
|
|
|
30
29
|
def set_session_cookie(self, handler, session_id):
|
|
31
|
-
expires = datetime.now(ZoneInfo("UTC")) + timedelta(days=
|
|
30
|
+
expires = datetime.now(ZoneInfo("UTC")) + timedelta(days=60)
|
|
32
31
|
host = handler.request.host
|
|
33
32
|
domain = '.'.join(host.split('.')[-2:]) if len(host.split('.')) >= 2 else host
|
|
34
33
|
handler.set_cookie(
|
|
@@ -44,19 +43,12 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
44
43
|
self.log.info(f"Set session cookie: dataflow_session={session_id} for host={host}")
|
|
45
44
|
|
|
46
45
|
def get_or_create_session(self, user_id):
|
|
47
|
-
existing_session = (
|
|
48
|
-
self.db.query(m_session.Session)
|
|
49
|
-
.filter(m_session.Session.user_id == str(user_id))
|
|
50
|
-
.first()
|
|
51
|
-
)
|
|
52
|
-
if existing_session:
|
|
53
|
-
self.log.info(f"Reusing existing session: {existing_session.session_id}")
|
|
54
|
-
return existing_session.session_id
|
|
55
46
|
session_id = self.generate_session_id()
|
|
56
47
|
while self.db.query(m_session.Session).filter(
|
|
57
48
|
m_session.Session.session_id == session_id
|
|
58
49
|
).first():
|
|
59
50
|
session_id = self.generate_session_id()
|
|
51
|
+
|
|
60
52
|
db_item = m_session.Session(user_id=user_id, session_id=session_id)
|
|
61
53
|
self.db.add(db_item)
|
|
62
54
|
self.db.commit()
|
|
@@ -77,42 +69,28 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
77
69
|
|
|
78
70
|
return super().check_blocked_users(username, authenticated)
|
|
79
71
|
|
|
80
|
-
def get_applicant_role_id(self):
|
|
81
|
-
"""Get the role ID for 'Applicant' role"""
|
|
82
|
-
try:
|
|
83
|
-
applicant_role = (
|
|
84
|
-
self.db.query(m_role.Role)
|
|
85
|
-
.filter(m_role.Role.name == "Applicant")
|
|
86
|
-
.first()
|
|
87
|
-
)
|
|
88
|
-
if applicant_role:
|
|
89
|
-
return applicant_role.id
|
|
90
|
-
else:
|
|
91
|
-
self.log.warning("Applicant role not found in database")
|
|
92
|
-
return None
|
|
93
|
-
except Exception as e:
|
|
94
|
-
self.log.error(f"Error getting Applicant role: {str(e)}")
|
|
95
|
-
return None
|
|
96
|
-
|
|
97
72
|
def extract_username_from_email(self, email):
|
|
98
73
|
"""Extract username from email by removing domain"""
|
|
99
74
|
if '@' in email:
|
|
100
75
|
return email.split('@')[0]
|
|
101
76
|
return email
|
|
102
77
|
|
|
78
|
+
def generate_secure_password(self):
|
|
79
|
+
"""Generate a secure random password hash"""
|
|
80
|
+
salt = secrets.token_hex(16)
|
|
81
|
+
random_uuid = str(uuid.uuid4())
|
|
82
|
+
hash_obj = hashlib.sha256((random_uuid + salt).encode())
|
|
83
|
+
return hash_obj.hexdigest()
|
|
84
|
+
|
|
103
85
|
def create_new_user(self, email, first_name=None, last_name=None):
|
|
104
86
|
"""Create a new user with Applicant role"""
|
|
105
87
|
try:
|
|
106
|
-
role_id = self.get_applicant_role_id()
|
|
107
|
-
if not role_id:
|
|
108
|
-
self.log.error("Cannot create user: Applicant role not found")
|
|
109
|
-
return None
|
|
110
|
-
|
|
111
88
|
username = self.extract_username_from_email(email)
|
|
112
89
|
username = re.sub(r'[^a-z0-9]', '', username.lower())
|
|
113
90
|
if not username:
|
|
114
91
|
self.log.error("Cannot create user: Username is empty")
|
|
115
92
|
return None
|
|
93
|
+
|
|
116
94
|
existing_user = (
|
|
117
95
|
self.db.query(m_user.User)
|
|
118
96
|
.filter(m_user.User.user_name == username)
|
|
@@ -122,7 +100,7 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
122
100
|
counter = 1
|
|
123
101
|
original_username = username
|
|
124
102
|
while existing_user:
|
|
125
|
-
username = f"{original_username}
|
|
103
|
+
username = f"{original_username}{counter}"
|
|
126
104
|
existing_user = (
|
|
127
105
|
self.db.query(m_user.User)
|
|
128
106
|
.filter(m_user.User.user_name == username)
|
|
@@ -130,13 +108,13 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
130
108
|
)
|
|
131
109
|
counter += 1
|
|
132
110
|
|
|
111
|
+
secure_password = self.generate_secure_password()
|
|
133
112
|
new_user = m_user.User(
|
|
134
113
|
user_name=username,
|
|
135
114
|
first_name=first_name or username,
|
|
136
115
|
last_name=last_name or "",
|
|
137
116
|
email=email,
|
|
138
|
-
|
|
139
|
-
password='user@123',
|
|
117
|
+
password=secure_password,
|
|
140
118
|
)
|
|
141
119
|
|
|
142
120
|
self.db.add(new_user)
|
|
@@ -154,22 +132,29 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
154
132
|
async def authenticate_dataflow(self, handler, data):
|
|
155
133
|
if not (self.enable_dataflow_auth and isinstance(data, dict) and data.get("username") and data.get("password")):
|
|
156
134
|
return None
|
|
157
|
-
|
|
135
|
+
user_name_or_email = data["username"]
|
|
158
136
|
password = data["password"]
|
|
159
|
-
self.log.info(f"Attempting Dataflow authentication for user: {
|
|
137
|
+
self.log.info(f"Attempting Dataflow authentication for user: {user_name_or_email}")
|
|
160
138
|
try:
|
|
161
139
|
user = (
|
|
162
140
|
self.db.query(m_user.User)
|
|
163
|
-
.filter(
|
|
141
|
+
.filter(
|
|
142
|
+
or_(
|
|
143
|
+
m_user.User.email == user_name_or_email,
|
|
144
|
+
m_user.User.user_name == user_name_or_email
|
|
145
|
+
)
|
|
146
|
+
)
|
|
164
147
|
.first()
|
|
165
148
|
)
|
|
149
|
+
|
|
166
150
|
if not user or user.password != password:
|
|
167
|
-
self.log.warning(f"Dataflow authentication failed for user: {
|
|
151
|
+
self.log.warning(f"Dataflow authentication failed for user: {user_name_or_email}")
|
|
168
152
|
return None
|
|
153
|
+
|
|
169
154
|
session_id = self.get_or_create_session(user.user_id)
|
|
170
155
|
self.set_session_cookie(handler, session_id)
|
|
171
|
-
self.log.info(f"Dataflow authentication successful for user: {
|
|
172
|
-
return {"name":
|
|
156
|
+
self.log.info(f"Dataflow authentication successful for user: {user.user_name}")
|
|
157
|
+
return {"name": user.user_name, "session_id": session_id, "auth_state": {}}
|
|
173
158
|
except Exception as e:
|
|
174
159
|
self.log.error(f"Dataflow authentication error: {str(e)}")
|
|
175
160
|
return None
|
|
@@ -302,7 +287,7 @@ class DataflowAzureAuthenticator(DataflowBaseAuthenticator, AzureAdOAuthenticato
|
|
|
302
287
|
self.set_session_cookie(handler, session_id)
|
|
303
288
|
self.log.info(f"Azure AD OAuth completed for user: {username}, session_id={session_id}")
|
|
304
289
|
return {
|
|
305
|
-
"name":
|
|
290
|
+
"name": username,
|
|
306
291
|
"session_id": session_id,
|
|
307
292
|
"auth_state": user.get("auth_state", {})
|
|
308
293
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os, requests
|
|
2
2
|
from .database_manager import DatabaseManager
|
|
3
3
|
import json
|
|
4
|
+
import base64
|
|
4
5
|
from .configuration import ConfigurationManager
|
|
5
6
|
|
|
6
7
|
|
|
@@ -8,6 +9,31 @@ class Dataflow:
|
|
|
8
9
|
"""
|
|
9
10
|
Dataflow class to interact with Dataflow services.
|
|
10
11
|
"""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def _json_parse(value):
|
|
15
|
+
try:
|
|
16
|
+
result = json.loads(value)
|
|
17
|
+
if isinstance(result, str):
|
|
18
|
+
try:
|
|
19
|
+
return json.loads(result)
|
|
20
|
+
except json.JSONDecodeError:
|
|
21
|
+
return result
|
|
22
|
+
return result
|
|
23
|
+
except (json.JSONDecodeError, TypeError):
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
def _parse_response_data(self, response):
|
|
27
|
+
"""Parse response data based on datatype field or fallback to JSON parsing."""
|
|
28
|
+
data = response.json()
|
|
29
|
+
if not isinstance(data, dict):
|
|
30
|
+
raise ValueError("Internal Dataflow Error!")
|
|
31
|
+
value = data.get('value', '')
|
|
32
|
+
if data.get('datatype') == 'json':
|
|
33
|
+
return self._json_parse(value)
|
|
34
|
+
else:
|
|
35
|
+
return value
|
|
36
|
+
|
|
11
37
|
def auth(self, session_id: str):
|
|
12
38
|
"""
|
|
13
39
|
Retrieve and return user information using their session ID.
|
|
@@ -41,7 +67,7 @@ class Dataflow:
|
|
|
41
67
|
|
|
42
68
|
except Exception as e:
|
|
43
69
|
return e
|
|
44
|
-
|
|
70
|
+
|
|
45
71
|
def variable(self, variable_name: str):
|
|
46
72
|
"""
|
|
47
73
|
Retrieve a Dataflow variable.
|
|
@@ -56,12 +82,19 @@ class Dataflow:
|
|
|
56
82
|
host_name = os.environ.get("HOSTNAME", "")
|
|
57
83
|
runtime = os.environ.get("RUNTIME")
|
|
58
84
|
slug = os.environ.get("SLUG")
|
|
85
|
+
org_id = os.environ.get("ORGANIZATION")
|
|
59
86
|
|
|
60
87
|
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
88
|
+
query_params = {
|
|
89
|
+
"key": variable_name,
|
|
90
|
+
}
|
|
61
91
|
|
|
62
92
|
variable_api = None
|
|
63
93
|
if runtime and slug:
|
|
64
94
|
variable_api = dataflow_config.get_config_value("auth", "variable_ui_api")
|
|
95
|
+
query_params["runtime"] = runtime
|
|
96
|
+
query_params["slug"] = slug
|
|
97
|
+
query_params["org_id"] = org_id
|
|
65
98
|
elif host_name:
|
|
66
99
|
variable_api = dataflow_config.get_config_value("auth", "variable_manager_api")
|
|
67
100
|
else:
|
|
@@ -70,45 +103,22 @@ class Dataflow:
|
|
|
70
103
|
if not variable_api:
|
|
71
104
|
print("[Dataflow.variable] Variable Unreachable")
|
|
72
105
|
return None
|
|
73
|
-
|
|
74
|
-
if runtime:
|
|
75
|
-
query_params = {
|
|
76
|
-
"key": variable_name,
|
|
77
|
-
"runtime": runtime,
|
|
78
|
-
"slug": slug
|
|
79
|
-
}
|
|
80
|
-
response = requests.get(variable_api, params=query_params)
|
|
81
|
-
if response.status_code == 200:
|
|
82
|
-
response_text = response.text.strip().strip('"')
|
|
83
|
-
return response_text
|
|
84
|
-
|
|
85
|
-
query_params["slug"] = "global"
|
|
86
|
-
response = requests.get(variable_api, params=query_params)
|
|
87
|
-
if response.status_code == 200:
|
|
88
|
-
response_text = response.text.strip().strip('"')
|
|
89
|
-
return response_text
|
|
90
|
-
else:
|
|
91
|
-
return None
|
|
92
|
-
|
|
93
|
-
query_params = {
|
|
94
|
-
"key": variable_name,
|
|
95
|
-
}
|
|
106
|
+
|
|
96
107
|
response = requests.get(variable_api, params=query_params)
|
|
97
108
|
|
|
98
|
-
# Handle different HTTP status codes gracefully
|
|
99
109
|
if response.status_code == 404:
|
|
100
|
-
return None
|
|
110
|
+
return None
|
|
101
111
|
elif response.status_code >= 500:
|
|
102
|
-
response.raise_for_status()
|
|
112
|
+
response.raise_for_status()
|
|
103
113
|
elif response.status_code >= 400:
|
|
104
114
|
print(f"[Dataflow.variable] Client error {response.status_code} for variable '{variable_name}'")
|
|
105
115
|
return None
|
|
106
116
|
elif response.status_code != 200:
|
|
107
117
|
print(f"[Dataflow.variable] Unexpected status {response.status_code} for variable '{variable_name}'")
|
|
108
118
|
return None
|
|
119
|
+
|
|
120
|
+
return self._parse_response_data(response)
|
|
109
121
|
|
|
110
|
-
return response.text.strip().strip('"')
|
|
111
|
-
|
|
112
122
|
except requests.exceptions.RequestException as e:
|
|
113
123
|
raise RuntimeError(f"[Dataflow.variable] Failed to fetch variable '{variable_name}'") from e
|
|
114
124
|
|
|
@@ -130,40 +140,137 @@ class Dataflow:
|
|
|
130
140
|
host_name = os.environ.get("HOSTNAME", "")
|
|
131
141
|
runtime = os.environ.get("RUNTIME")
|
|
132
142
|
slug = os.environ.get("SLUG")
|
|
143
|
+
org_id = os.environ.get("ORGANIZATION")
|
|
133
144
|
|
|
134
145
|
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
146
|
+
query_params = {
|
|
147
|
+
"key": secret_name
|
|
148
|
+
}
|
|
149
|
+
|
|
135
150
|
if runtime:
|
|
136
151
|
secret_api = dataflow_config.get_config_value("auth", "secret_ui_api")
|
|
152
|
+
query_params["runtime"] = runtime
|
|
153
|
+
query_params["slug"] = slug
|
|
154
|
+
query_params["org_id"] = org_id
|
|
137
155
|
else:
|
|
138
156
|
secret_api = dataflow_config.get_config_value("auth", "secret_manager_api")
|
|
139
157
|
if not secret_api:
|
|
140
158
|
print("[Dataflow.secret] Secret API Unreachable")
|
|
141
159
|
return None
|
|
142
160
|
|
|
161
|
+
response = requests.get(secret_api, params=query_params)
|
|
162
|
+
|
|
163
|
+
if response.status_code == 404:
|
|
164
|
+
return None
|
|
165
|
+
elif response.status_code >= 500:
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
elif response.status_code >= 400:
|
|
168
|
+
print(f"[Dataflow.secret] Client error {response.status_code} for secret '{secret_name}'")
|
|
169
|
+
return None
|
|
170
|
+
elif response.status_code != 200:
|
|
171
|
+
print(f"[Dataflow.secret] Unexpected status {response.status_code} for secret '{secret_name}'")
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
return self._parse_response_data(response)
|
|
175
|
+
|
|
176
|
+
except requests.exceptions.RequestException as e:
|
|
177
|
+
raise RuntimeError(f"[Dataflow.secret] Failed to fetch secret '{secret_name}'") from e
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"[Dataflow.secret] Exception occurred: {e}")
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
def secret_file(self, secret_name: str):
|
|
183
|
+
"""
|
|
184
|
+
Retrieve a Dataflow secret file.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
secret_name (str): Name of the secret to retrieve
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
str or None: Secret value if found, None otherwise
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
host_name = os.environ.get("HOSTNAME", "")
|
|
194
|
+
runtime = os.environ.get("RUNTIME")
|
|
195
|
+
slug = os.environ.get("SLUG")
|
|
196
|
+
org_id = os.environ.get("ORGANIZATION")
|
|
197
|
+
|
|
198
|
+
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
143
199
|
query_params = {
|
|
144
200
|
"key": secret_name
|
|
145
201
|
}
|
|
146
202
|
|
|
147
203
|
if runtime:
|
|
204
|
+
secret_api = dataflow_config.get_config_value("auth", "secret_ui_api")
|
|
148
205
|
query_params["runtime"] = runtime
|
|
149
|
-
if slug:
|
|
150
206
|
query_params["slug"] = slug
|
|
207
|
+
query_params["org_id"] = org_id
|
|
208
|
+
else:
|
|
209
|
+
secret_api = dataflow_config.get_config_value("auth", "secret_manager_api")
|
|
210
|
+
if not secret_api:
|
|
211
|
+
print("[Dataflow.secret] Secret API Unreachable")
|
|
212
|
+
return None
|
|
151
213
|
|
|
152
214
|
response = requests.get(secret_api, params=query_params)
|
|
153
|
-
|
|
154
|
-
# Handle different HTTP status codes gracefully
|
|
215
|
+
|
|
155
216
|
if response.status_code == 404:
|
|
156
|
-
return None
|
|
217
|
+
return None
|
|
157
218
|
elif response.status_code >= 500:
|
|
158
|
-
response.raise_for_status()
|
|
219
|
+
response.raise_for_status()
|
|
159
220
|
elif response.status_code >= 400:
|
|
160
221
|
print(f"[Dataflow.secret] Client error {response.status_code} for secret '{secret_name}'")
|
|
161
222
|
return None
|
|
162
223
|
elif response.status_code != 200:
|
|
163
224
|
print(f"[Dataflow.secret] Unexpected status {response.status_code} for secret '{secret_name}'")
|
|
164
225
|
return None
|
|
226
|
+
|
|
227
|
+
response_data = response.json()
|
|
228
|
+
if response.status_code == 200 and response_data.get('filename'):
|
|
229
|
+
# For runtime mode, create file and return filepath
|
|
230
|
+
if runtime:
|
|
231
|
+
import tempfile
|
|
232
|
+
from pathlib import Path
|
|
165
233
|
|
|
166
|
-
|
|
234
|
+
# Create /tmp/secrets directory if it doesn't exist
|
|
235
|
+
secrets_dir = Path("/tmp/secrets")
|
|
236
|
+
secrets_dir.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
|
|
238
|
+
# Get filename and content
|
|
239
|
+
filename = response_data.get('filename')
|
|
240
|
+
file_content = response_data.get('value')
|
|
241
|
+
|
|
242
|
+
if not filename or not file_content:
|
|
243
|
+
print(f"[Dataflow.secret] Missing filename or content for secret '{secret_name}'")
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
file_path = os.path.join(secrets_dir, filename)
|
|
247
|
+
|
|
248
|
+
# Detect if content is Base64 encoded binary or text
|
|
249
|
+
try:
|
|
250
|
+
# Try to decode as Base64
|
|
251
|
+
decoded_content = base64.b64decode(file_content, validate=True)
|
|
252
|
+
# Check if it contains non-printable characters (likely binary)
|
|
253
|
+
is_binary = not all(32 <= byte <= 126 or byte in (9, 10, 13) for byte in decoded_content[:100])
|
|
254
|
+
|
|
255
|
+
if is_binary:
|
|
256
|
+
# Write as binary
|
|
257
|
+
with open(file_path, 'wb') as f:
|
|
258
|
+
f.write(decoded_content)
|
|
259
|
+
else:
|
|
260
|
+
# Decode and write as text
|
|
261
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
262
|
+
f.write(decoded_content.decode('utf-8'))
|
|
263
|
+
except Exception:
|
|
264
|
+
# Not Base64 or decode failed, treat as text
|
|
265
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
266
|
+
f.write(file_content)
|
|
267
|
+
return str(file_path)
|
|
268
|
+
else:
|
|
269
|
+
# For non-runtime mode, return the value as-is
|
|
270
|
+
return response_data.get('value')
|
|
271
|
+
else:
|
|
272
|
+
print(f"[Dataflow.secret] No file found for secret '{secret_name}'! If it is a non-file secret, please use the 'secret' method.")
|
|
273
|
+
return None
|
|
167
274
|
|
|
168
275
|
except requests.exceptions.RequestException as e:
|
|
169
276
|
raise RuntimeError(f"[Dataflow.secret] Failed to fetch secret '{secret_name}'") from e
|
|
@@ -186,31 +293,29 @@ class Dataflow:
|
|
|
186
293
|
host_name = os.environ["HOSTNAME"]
|
|
187
294
|
runtime = os.environ.get("RUNTIME")
|
|
188
295
|
slug = os.environ.get("SLUG")
|
|
189
|
-
|
|
296
|
+
org_id = os.environ.get("ORGANIZATION")
|
|
297
|
+
|
|
190
298
|
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
191
|
-
if runtime:
|
|
192
|
-
connection_api = dataflow_config.get_config_value("auth", "connection_ui_api")
|
|
193
|
-
elif host_name:
|
|
194
|
-
connection_api = dataflow_config.get_config_value("auth", "connection_manager_api")
|
|
195
|
-
else:
|
|
196
|
-
raise Exception("Cannot run dataflow methods here! HOSTNAME or RUNTIME env variable not set.")
|
|
197
|
-
|
|
198
299
|
query_params = {
|
|
199
300
|
"conn_id": conn_id
|
|
200
301
|
}
|
|
201
302
|
|
|
202
303
|
if runtime:
|
|
203
304
|
query_params["runtime"] = runtime
|
|
204
|
-
|
|
305
|
+
query_params["org_id"] = org_id
|
|
205
306
|
query_params["slug"] = slug
|
|
307
|
+
connection_api = dataflow_config.get_config_value("auth", "connection_ui_api")
|
|
308
|
+
elif host_name:
|
|
309
|
+
connection_api = dataflow_config.get_config_value("auth", "connection_manager_api")
|
|
310
|
+
else:
|
|
311
|
+
raise Exception("Cannot run dataflow methods here! HOSTNAME or RUNTIME env variable not set.")
|
|
206
312
|
|
|
207
313
|
response = requests.get(connection_api, params=query_params)
|
|
208
314
|
|
|
209
|
-
# Handle different HTTP status codes gracefully
|
|
210
315
|
if response.status_code == 404:
|
|
211
316
|
raise RuntimeError(f"[Dataflow.connection] Connection '{conn_id}' not found!")
|
|
212
317
|
elif response.status_code >= 500:
|
|
213
|
-
response.raise_for_status()
|
|
318
|
+
response.raise_for_status()
|
|
214
319
|
elif response.status_code >= 400:
|
|
215
320
|
raise RuntimeError(f"[Dataflow.connection] Client error {response.status_code} for connection '{conn_id}'")
|
|
216
321
|
elif response.status_code != 200:
|
|
@@ -222,9 +327,6 @@ class Dataflow:
|
|
|
222
327
|
raise RuntimeError(f"[Dataflow.connection] Connection '{conn_id}' not found!")
|
|
223
328
|
|
|
224
329
|
if mode == "dict":
|
|
225
|
-
with open('/home/jovyan/log.txt', 'w') as log_file:
|
|
226
|
-
log_file.write(f"Connection details for {conn_id}: {connection_details}\n")
|
|
227
|
-
print(f"connection_details: {connection_details}")
|
|
228
330
|
return dict(connection_details)
|
|
229
331
|
|
|
230
332
|
conn_type = connection_details['conn_type'].lower()
|
|
@@ -281,20 +383,20 @@ class Dataflow:
|
|
|
281
383
|
host_name = os.environ.get("HOSTNAME", "")
|
|
282
384
|
runtime = os.environ.get("RUNTIME")
|
|
283
385
|
slug = os.environ.get("SLUG")
|
|
386
|
+
org_id = os.environ.get("ORGANIZATION")
|
|
284
387
|
|
|
285
388
|
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
query_params = {
|
|
289
|
-
"key": key,
|
|
290
|
-
"runtime": runtime,
|
|
291
|
-
"slug": slug
|
|
389
|
+
query_params = {
|
|
390
|
+
"key": key
|
|
292
391
|
}
|
|
392
|
+
|
|
393
|
+
if runtime:
|
|
394
|
+
variableorsecret_api = dataflow_config.get_config_value("auth", "variableorsecret_ui_api")
|
|
395
|
+
query_params["runtime"] = runtime
|
|
396
|
+
query_params["slug"] = slug
|
|
397
|
+
query_params["org_id"] = org_id
|
|
293
398
|
elif host_name:
|
|
294
399
|
variableorsecret_api = dataflow_config.get_config_value("auth", "variableorsecret_manager_api")
|
|
295
|
-
query_params = {
|
|
296
|
-
"key": key
|
|
297
|
-
}
|
|
298
400
|
else:
|
|
299
401
|
raise Exception("Cannot run dataflow methods here!")
|
|
300
402
|
|
|
@@ -304,9 +406,8 @@ class Dataflow:
|
|
|
304
406
|
|
|
305
407
|
response = requests.get(variableorsecret_api, params=query_params)
|
|
306
408
|
|
|
307
|
-
# Handle different HTTP status codes gracefully
|
|
308
409
|
if response.status_code == 404:
|
|
309
|
-
return None
|
|
410
|
+
return None
|
|
310
411
|
elif response.status_code >= 500:
|
|
311
412
|
response.raise_for_status() # Let server errors propagate
|
|
312
413
|
elif response.status_code >= 400:
|
|
@@ -316,8 +417,7 @@ class Dataflow:
|
|
|
316
417
|
print(f"[Dataflow.variable_or_secret] Unexpected status {response.status_code} for key '{key}'")
|
|
317
418
|
return None
|
|
318
419
|
|
|
319
|
-
|
|
320
|
-
return response_text
|
|
420
|
+
return self._parse_response_data(response)
|
|
321
421
|
|
|
322
422
|
except requests.exceptions.RequestException as e:
|
|
323
423
|
raise RuntimeError(f"[Dataflow.variable_or_secret] Failed to fetch '{key}'") from e
|