ob-metaflow-extensions 1.1.110__tar.gz → 1.1.112__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 ob-metaflow-extensions might be problematic. Click here for more details.
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/PKG-INFO +1 -1
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/__init__.py +2 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/secrets/secrets.py +9 -14
- ob-metaflow-extensions-1.1.112/metaflow_extensions/outerbounds/plugins/snowflake/__init__.py +3 -0
- ob-metaflow-extensions-1.1.112/metaflow_extensions/outerbounds/plugins/snowflake/snowflake.py +308 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +1 -0
- ob-metaflow-extensions-1.1.112/metaflow_extensions/outerbounds/toplevel/plugins/snowflake/__init__.py +1 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/ob_metaflow_extensions.egg-info/PKG-INFO +1 -1
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/ob_metaflow_extensions.egg-info/SOURCES.txt +3 -0
- ob-metaflow-extensions-1.1.112/ob_metaflow_extensions.egg-info/requires.txt +3 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/setup.py +2 -2
- ob-metaflow-extensions-1.1.110/ob_metaflow_extensions.egg-info/requires.txt +0 -3
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/README.md +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/config/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/auth_server.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/fast_bakery/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_cli.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/kubernetes/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/kubernetes/kubernetes_client.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/nim/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/nim/nim_manager.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/nvcf/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/nvcf/heartbeat_store.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_cli.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/perimeters.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/profilers/deco_injector.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/profilers/gpu_profile_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/secrets/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_exceptions.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_service_spec.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/plugins/tensorboard/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/profilers/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/profilers/gpu.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/remote_config.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/toplevel/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/toplevel/plugins/azure/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/toplevel/plugins/gcp/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/metaflow_extensions/outerbounds/toplevel/plugins/kubernetes/__init__.py +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/ob_metaflow_extensions.egg-info/dependency_links.txt +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/ob_metaflow_extensions.egg-info/top_level.txt +0 -0
- {ob-metaflow-extensions-1.1.110 → ob-metaflow-extensions-1.1.112}/setup.cfg +0 -0
|
@@ -37,15 +37,13 @@ class OuterboundsSecretsProvider(SecretsProvider):
|
|
|
37
37
|
secrets manager on the core oss and returns the secrets.
|
|
38
38
|
"""
|
|
39
39
|
headers = {"Content-Type": "application/json", "Connection": "keep-alive"}
|
|
40
|
-
perimeter,
|
|
40
|
+
perimeter, integrations_url = self._get_secret_configs()
|
|
41
41
|
integration_name = secret_id
|
|
42
42
|
request_payload = {
|
|
43
43
|
"perimeter_name": perimeter,
|
|
44
44
|
"integration_name": integration_name,
|
|
45
45
|
}
|
|
46
|
-
response = self._make_request(
|
|
47
|
-
integrations_secrets_metadata_url, headers, request_payload
|
|
48
|
-
)
|
|
46
|
+
response = self._make_request(integrations_url, headers, request_payload)
|
|
49
47
|
secret_resource_id = response.secret_resource_id
|
|
50
48
|
secret_backend_type = response.secret_backend_type
|
|
51
49
|
|
|
@@ -105,26 +103,23 @@ class OuterboundsSecretsProvider(SecretsProvider):
|
|
|
105
103
|
# if the perimeter is not in metaflow config, try to get it from the environment
|
|
106
104
|
perimeter = environ.get("OBP_PERIMETER", "")
|
|
107
105
|
|
|
108
|
-
if "
|
|
109
|
-
|
|
110
|
-
"OBP_INTEGRATIONS_SECRETS_METADATA_URL"
|
|
111
|
-
]
|
|
106
|
+
if "OBP_INTEGRATIONS_URL" in conf:
|
|
107
|
+
integrations_url = conf["OBP_INTEGRATIONS_URL"]
|
|
112
108
|
else:
|
|
113
|
-
# if the integrations
|
|
114
|
-
|
|
115
|
-
"OBP_INTEGRATIONS_SECRETS_METADATA_URL", ""
|
|
116
|
-
)
|
|
109
|
+
# if the integrations is not in metaflow config, try to get it from the environment
|
|
110
|
+
integrations_url = environ.get("OBP_INTEGRATIONS_URL", "")
|
|
117
111
|
|
|
118
112
|
if not perimeter:
|
|
119
113
|
raise OuterboundsSecretsException(
|
|
120
114
|
"No perimeter set. Please make sure to run `outerbounds configure <...>` command which can be found on the Ourebounds UI or reach out to your Outerbounds support team."
|
|
121
115
|
)
|
|
122
116
|
|
|
123
|
-
if not
|
|
117
|
+
if not integrations_url:
|
|
124
118
|
raise OuterboundsSecretsException(
|
|
125
|
-
"No integrations
|
|
119
|
+
"No integrations url set. Please notify your Outerbounds support team about this issue."
|
|
126
120
|
)
|
|
127
121
|
|
|
122
|
+
integrations_secrets_metadata_url = f"{integrations_url}/secrets/metadata"
|
|
128
123
|
return perimeter, integrations_secrets_metadata_url
|
|
129
124
|
|
|
130
125
|
def _make_request(self, url, headers: Dict, payload: Dict):
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This library is an abstraction layer for connecting to snowflake using Outerbounds
|
|
3
|
+
OIDC tokens. It expects that a security integration that authenticates tokens minted
|
|
4
|
+
by Outerbounds has already been configured in the target snowflake account.
|
|
5
|
+
"""
|
|
6
|
+
from metaflow.metaflow_config import SERVICE_URL
|
|
7
|
+
from metaflow.metaflow_config_funcs import init_config
|
|
8
|
+
from typing import Dict
|
|
9
|
+
from os import environ
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import requests
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OuterboundsSnowflakeConnectorException(Exception):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OuterboundsSnowflakeIntegrationSpecApiResponse:
|
|
21
|
+
def __init__(self, response):
|
|
22
|
+
self.response = response
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def account(self):
|
|
26
|
+
return self.response["account"]
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def user(self):
|
|
30
|
+
return self.response["user"]
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def default_role(self):
|
|
34
|
+
return self.response["default_role"]
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def warehouse(self):
|
|
38
|
+
return self.response["warehouse"]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def database(self):
|
|
42
|
+
return self.response["database"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_snowflake_token(user: str = "", role: str = "", integration: str = "") -> str:
|
|
46
|
+
"""
|
|
47
|
+
Uses the Outerbounds source token to request for a snowflake compatible OIDC
|
|
48
|
+
token. This token can then be used to connect to snowflake.
|
|
49
|
+
user: str
|
|
50
|
+
The user the token will be minted for
|
|
51
|
+
role: str
|
|
52
|
+
The role to which the token will be scoped to
|
|
53
|
+
integration: str
|
|
54
|
+
The name of the snowflake integration to use. If not set, an existing integration will be used provided that only one exists per perimeter. If integration is not set and more than one exists, then we raise an exception.
|
|
55
|
+
"""
|
|
56
|
+
provisioner = SnowflakeIntegrationProvisioner(integration)
|
|
57
|
+
if not user or not role or not integration:
|
|
58
|
+
integration_spec = provisioner.get_snowflake_integration_spec()
|
|
59
|
+
if not user:
|
|
60
|
+
user = integration_spec.user
|
|
61
|
+
|
|
62
|
+
if not role:
|
|
63
|
+
role = integration_spec.default_role
|
|
64
|
+
|
|
65
|
+
if not integration:
|
|
66
|
+
integration = provisioner.get_integration_name()
|
|
67
|
+
|
|
68
|
+
snowflake_token_url = provisioner.get_snowflake_token_url()
|
|
69
|
+
perimeter = provisioner.get_perimeter()
|
|
70
|
+
payload = {
|
|
71
|
+
"perimeterName": perimeter,
|
|
72
|
+
"snowflakeUser": user,
|
|
73
|
+
"snowflakeRole": role,
|
|
74
|
+
"integrationName": integration,
|
|
75
|
+
}
|
|
76
|
+
json_payload = json.dumps(payload)
|
|
77
|
+
headers = provisioner.get_service_auth_header()
|
|
78
|
+
response = requests.get(snowflake_token_url, data=json_payload, headers=headers)
|
|
79
|
+
response.raise_for_status()
|
|
80
|
+
return response.json()["token"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def connect(user: str = "", role: str = "", integration: str = "", **kwargs):
|
|
84
|
+
"""
|
|
85
|
+
Connect to snowflake using the token minted by Outerbounds
|
|
86
|
+
user: str
|
|
87
|
+
The user name used to authenticate with snowflake
|
|
88
|
+
role: str
|
|
89
|
+
The role to request when connect with snowflake
|
|
90
|
+
integration: str
|
|
91
|
+
The name of the snowflake integration to use. If not set, an existing integration will be used provided that only one exists in the current perimeter. If integration is not set and more than one exists in the current perimeter, then we raise an exception.
|
|
92
|
+
kwargs: dict
|
|
93
|
+
Additional arguments to pass to the python snowflake connector
|
|
94
|
+
"""
|
|
95
|
+
# ensure password is not set
|
|
96
|
+
if "password" in kwargs:
|
|
97
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
98
|
+
"Password should not be set when using Outerbounds snowflake connector."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
provisioner = SnowflakeIntegrationProvisioner(integration)
|
|
102
|
+
get_defaults = any(
|
|
103
|
+
key not in kwargs for key in ["account", "warehouse", "database"]
|
|
104
|
+
)
|
|
105
|
+
if not user or not role or not integration or get_defaults:
|
|
106
|
+
integration_spec = provisioner.get_snowflake_integration_spec()
|
|
107
|
+
if not user:
|
|
108
|
+
user = integration_spec.user
|
|
109
|
+
|
|
110
|
+
if not role:
|
|
111
|
+
role = integration_spec.default_role
|
|
112
|
+
|
|
113
|
+
if not integration:
|
|
114
|
+
integration = provisioner.get_integration_name()
|
|
115
|
+
|
|
116
|
+
if "account" not in kwargs:
|
|
117
|
+
kwargs["account"] = integration_spec.account
|
|
118
|
+
|
|
119
|
+
if "warehouse" not in kwargs:
|
|
120
|
+
kwargs["warehouse"] = integration_spec.warehouse
|
|
121
|
+
|
|
122
|
+
# if the user is attempting to use a warehouse different from what is specified in the
|
|
123
|
+
# integration we will not set the database
|
|
124
|
+
if (
|
|
125
|
+
"database" not in kwargs
|
|
126
|
+
and kwargs["warehouse"] == integration_spec.warehouse
|
|
127
|
+
):
|
|
128
|
+
kwargs["database"] = integration_spec.database
|
|
129
|
+
|
|
130
|
+
# get snowflake token
|
|
131
|
+
token = get_snowflake_token(user=user, role=role, integration=integration)
|
|
132
|
+
kwargs["token"] = token
|
|
133
|
+
kwargs["authenticator"] = "oauth"
|
|
134
|
+
kwargs["role"] = role
|
|
135
|
+
kwargs["user"] = user
|
|
136
|
+
|
|
137
|
+
# connect to snowflake
|
|
138
|
+
try:
|
|
139
|
+
from snowflake.connector import connect
|
|
140
|
+
|
|
141
|
+
cn = connect(**kwargs)
|
|
142
|
+
return cn
|
|
143
|
+
except ImportError as ie:
|
|
144
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
145
|
+
f"Error importing snowflake connector: {ie}.\nPlease make sure the 'snowflake-connector-python' package has been installed by running 'pip install -U \"outerbounds[snowflake]\"' or using the Metaflow decorators @pypi or @conda."
|
|
146
|
+
)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
149
|
+
f"Error connecting to snowflake: {e}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class Snowflake:
|
|
154
|
+
def __init__(
|
|
155
|
+
self, user: str = "", role: str = "", integration: str = "", **kwargs
|
|
156
|
+
) -> None:
|
|
157
|
+
self.cn = connect(user, role, integration, **kwargs)
|
|
158
|
+
|
|
159
|
+
def __enter__(self):
|
|
160
|
+
return self.cn
|
|
161
|
+
|
|
162
|
+
def __exit__(self, exception_type, exception_value, traceback):
|
|
163
|
+
self.cn.close()
|
|
164
|
+
|
|
165
|
+
def close(self):
|
|
166
|
+
self.cn.close()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class SnowflakeIntegrationProvisioner:
|
|
170
|
+
def __init__(self, integration_name: str) -> None:
|
|
171
|
+
self.conf = init_config()
|
|
172
|
+
self.integration_name = integration_name
|
|
173
|
+
|
|
174
|
+
def get_snowflake_integration_spec(
|
|
175
|
+
self,
|
|
176
|
+
) -> OuterboundsSnowflakeIntegrationSpecApiResponse:
|
|
177
|
+
integrations_url = self._get_integration_url()
|
|
178
|
+
perimeter = self.get_perimeter()
|
|
179
|
+
headers = {"Content-Type": "application/json", "Connection": "keep-alive"}
|
|
180
|
+
request_payload = {
|
|
181
|
+
"perimeter_name": perimeter,
|
|
182
|
+
}
|
|
183
|
+
# if integration is not set, list all integrations
|
|
184
|
+
if not self.integration_name:
|
|
185
|
+
list_snowflake_integrations_url = f"{integrations_url}/snowflake"
|
|
186
|
+
response = self._make_request(
|
|
187
|
+
list_snowflake_integrations_url, headers, request_payload
|
|
188
|
+
)
|
|
189
|
+
snowflake_integrations = response.get("integrations", [])
|
|
190
|
+
if not snowflake_integrations:
|
|
191
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
192
|
+
"No snowflake integrations found. Please make sure you have created a Snowflake integration on the Outerbounds UI first."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if len(snowflake_integrations) > 1:
|
|
196
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
197
|
+
f"Multiple snowflake integrations found. Please specify a specific integration name you would like to use."
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
self.integration_name = snowflake_integrations[0]["integration_name"]
|
|
201
|
+
return OuterboundsSnowflakeIntegrationSpecApiResponse(
|
|
202
|
+
snowflake_integrations[0]["integration_spec"]
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
get_snowflake_integration_url = (
|
|
206
|
+
f"{integrations_url}/snowflake/{self.integration_name}"
|
|
207
|
+
)
|
|
208
|
+
response = self._make_request(
|
|
209
|
+
get_snowflake_integration_url, headers, request_payload
|
|
210
|
+
)
|
|
211
|
+
self.integration_name = response["integration_name"]
|
|
212
|
+
return OuterboundsSnowflakeIntegrationSpecApiResponse(
|
|
213
|
+
response["integration_spec"]
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def get_integration_name(self) -> str:
|
|
217
|
+
return self.integration_name
|
|
218
|
+
|
|
219
|
+
def get_perimeter(self) -> str:
|
|
220
|
+
if "OBP_PERIMETER" in self.conf:
|
|
221
|
+
perimeter = self.conf["OBP_PERIMETER"]
|
|
222
|
+
else:
|
|
223
|
+
# if the perimeter is not in metaflow config, try to get it from the environment
|
|
224
|
+
perimeter = environ.get("OBP_PERIMETER", "")
|
|
225
|
+
if not perimeter:
|
|
226
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
227
|
+
"No perimeter set. Please make sure to run `outerbounds configure <...>` command which can be found on the Ourebounds UI or reach out to your Outerbounds support team."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return perimeter
|
|
231
|
+
|
|
232
|
+
def get_snowflake_token_url(self) -> str:
|
|
233
|
+
if "OBP_AUTH_SERVER" in self.conf:
|
|
234
|
+
auth_host = self.conf["OBP_AUTH_SERVER"]
|
|
235
|
+
else:
|
|
236
|
+
from urllib.parse import urlparse
|
|
237
|
+
|
|
238
|
+
auth_host = "auth." + urlparse(SERVICE_URL).hostname.split(".", 1)[1]
|
|
239
|
+
|
|
240
|
+
return "https://" + auth_host + "/generate/snowflake"
|
|
241
|
+
|
|
242
|
+
def get_service_auth_header(self) -> str:
|
|
243
|
+
if "METAFLOW_SERVICE_AUTH_KEY" in self.conf:
|
|
244
|
+
return {"x-api-key": self.conf["METAFLOW_SERVICE_AUTH_KEY"]}
|
|
245
|
+
else:
|
|
246
|
+
return json.loads(environ.get("METAFLOW_SERVICE_HEADERS"))
|
|
247
|
+
|
|
248
|
+
def _get_integration_url(self) -> str:
|
|
249
|
+
from metaflow_extensions.outerbounds.remote_config import init_config
|
|
250
|
+
from os import environ
|
|
251
|
+
|
|
252
|
+
if "OBP_INTEGRATIONS_URL" in self.conf:
|
|
253
|
+
integrations_url = self.conf["OBP_INTEGRATIONS_URL"]
|
|
254
|
+
else:
|
|
255
|
+
# if the integrations url is not in metaflow config, try to get it from the environment
|
|
256
|
+
integrations_url = environ.get("OBP_INTEGRATIONS_URL", "")
|
|
257
|
+
|
|
258
|
+
if not integrations_url:
|
|
259
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
260
|
+
"No integrations url set. Please notify your Outerbounds support team about this issue."
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return integrations_url
|
|
264
|
+
|
|
265
|
+
def _make_request(self, url, headers: Dict, payload: Dict) -> Dict:
|
|
266
|
+
try:
|
|
267
|
+
from metaflow.metaflow_config import SERVICE_HEADERS
|
|
268
|
+
|
|
269
|
+
request_headers = {**headers, **(SERVICE_HEADERS or {})}
|
|
270
|
+
except ImportError:
|
|
271
|
+
headers = headers
|
|
272
|
+
|
|
273
|
+
retryable_status_codes = [409]
|
|
274
|
+
json_payload = json.dumps(payload)
|
|
275
|
+
for attempt in range(2): # 0 = initial attempt, 1-2 = retries
|
|
276
|
+
response = requests.get(url, data=json_payload, headers=request_headers)
|
|
277
|
+
if response.status_code not in retryable_status_codes:
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
if attempt < 2: # Don't sleep after the last attempt
|
|
281
|
+
sleep_time = 0.5 * (attempt + 1)
|
|
282
|
+
time.sleep(sleep_time)
|
|
283
|
+
|
|
284
|
+
response = requests.get(url, data=json_payload, headers=request_headers)
|
|
285
|
+
self._handle_error_response(response)
|
|
286
|
+
return response.json()
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def _handle_error_response(response: requests.Response):
|
|
290
|
+
if response.status_code >= 500:
|
|
291
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
292
|
+
f"Server error: {response.text}. Please reach out to your Outerbounds support team."
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
body = response.json()
|
|
296
|
+
status_code = body.get("error", {}).get("statusCode", response.status_code)
|
|
297
|
+
if status_code == 404:
|
|
298
|
+
raise OuterboundsSnowflakeConnectorException(f"Secret not found: {body}")
|
|
299
|
+
|
|
300
|
+
if status_code >= 400:
|
|
301
|
+
try:
|
|
302
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
303
|
+
f"status_code={status_code}\t*{body['error']['details']['kind']}*\n{body['error']['details']['message']}"
|
|
304
|
+
)
|
|
305
|
+
except KeyError:
|
|
306
|
+
raise OuterboundsSnowflakeConnectorException(
|
|
307
|
+
f"status_code={status_code} Unexpected error: {body}"
|
|
308
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__mf_promote_submodules__ = ["plugins.snowflake"]
|
|
@@ -24,6 +24,8 @@ metaflow_extensions/outerbounds/plugins/profilers/deco_injector.py
|
|
|
24
24
|
metaflow_extensions/outerbounds/plugins/profilers/gpu_profile_decorator.py
|
|
25
25
|
metaflow_extensions/outerbounds/plugins/secrets/__init__.py
|
|
26
26
|
metaflow_extensions/outerbounds/plugins/secrets/secrets.py
|
|
27
|
+
metaflow_extensions/outerbounds/plugins/snowflake/__init__.py
|
|
28
|
+
metaflow_extensions/outerbounds/plugins/snowflake/snowflake.py
|
|
27
29
|
metaflow_extensions/outerbounds/plugins/snowpark/__init__.py
|
|
28
30
|
metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py
|
|
29
31
|
metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py
|
|
@@ -40,6 +42,7 @@ metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py
|
|
|
40
42
|
metaflow_extensions/outerbounds/toplevel/plugins/azure/__init__.py
|
|
41
43
|
metaflow_extensions/outerbounds/toplevel/plugins/gcp/__init__.py
|
|
42
44
|
metaflow_extensions/outerbounds/toplevel/plugins/kubernetes/__init__.py
|
|
45
|
+
metaflow_extensions/outerbounds/toplevel/plugins/snowflake/__init__.py
|
|
43
46
|
ob_metaflow_extensions.egg-info/PKG-INFO
|
|
44
47
|
ob_metaflow_extensions.egg-info/SOURCES.txt
|
|
45
48
|
ob_metaflow_extensions.egg-info/dependency_links.txt
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
version = "1.1.
|
|
5
|
+
version = "1.1.112"
|
|
6
6
|
this_directory = Path(__file__).parent
|
|
7
7
|
long_description = (this_directory / "README.md").read_text()
|
|
8
8
|
|
|
@@ -18,6 +18,6 @@ setup(
|
|
|
18
18
|
install_requires=[
|
|
19
19
|
"boto3",
|
|
20
20
|
"kubernetes",
|
|
21
|
-
"ob-metaflow == 2.12.36.
|
|
21
|
+
"ob-metaflow == 2.12.36.3",
|
|
22
22
|
],
|
|
23
23
|
)
|
|
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
|
|
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
|