qwak-core 0.4.272__py3-none-any.whl → 0.4.274__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frogml_storage/__init__.py +1 -0
- frogml_storage/artifactory/__init__.py +1 -0
- frogml_storage/artifactory/_artifactory_api.py +315 -0
- frogml_storage/authentication/login/__init__.py +1 -0
- frogml_storage/authentication/login/_login_cli.py +239 -0
- frogml_storage/authentication/login/_login_command.py +74 -0
- frogml_storage/authentication/models/__init__.py +3 -0
- frogml_storage/authentication/models/_auth.py +24 -0
- frogml_storage/authentication/models/_auth_config.py +70 -0
- frogml_storage/authentication/models/_login.py +22 -0
- frogml_storage/authentication/utils/__init__.py +17 -0
- frogml_storage/authentication/utils/_authentication_utils.py +281 -0
- frogml_storage/authentication/utils/_login_checks_utils.py +114 -0
- frogml_storage/base_storage.py +140 -0
- frogml_storage/constants.py +56 -0
- frogml_storage/exceptions/checksum_verification_error.py +3 -0
- frogml_storage/exceptions/validation_error.py +4 -0
- frogml_storage/frog_ml.py +668 -0
- frogml_storage/http/__init__.py +1 -0
- frogml_storage/http/http_client.py +83 -0
- frogml_storage/logging/__init__.py +1 -0
- frogml_storage/logging/_log_config.py +45 -0
- frogml_storage/logging/log_utils.py +21 -0
- frogml_storage/models/__init__.py +1 -0
- frogml_storage/models/_download_context.py +54 -0
- frogml_storage/models/dataset_manifest.py +13 -0
- frogml_storage/models/entity_manifest.py +93 -0
- frogml_storage/models/frogml_dataset_version.py +21 -0
- frogml_storage/models/frogml_entity_type_info.py +50 -0
- frogml_storage/models/frogml_entity_version.py +34 -0
- frogml_storage/models/frogml_model_version.py +21 -0
- frogml_storage/models/model_manifest.py +60 -0
- frogml_storage/models/serialization_metadata.py +15 -0
- frogml_storage/utils/__init__.py +12 -0
- frogml_storage/utils/_environment.py +21 -0
- frogml_storage/utils/_input_checks_utility.py +104 -0
- frogml_storage/utils/_storage_utils.py +15 -0
- frogml_storage/utils/_url_utils.py +27 -0
- qwak/__init__.py +1 -1
- qwak/clients/batch_job_management/client.py +39 -0
- qwak/clients/instance_template/client.py +6 -4
- qwak/clients/prompt_manager/model_descriptor_mapper.py +21 -19
- qwak/feature_store/_common/artifact_utils.py +3 -3
- qwak/feature_store/data_sources/base.py +4 -4
- qwak/feature_store/data_sources/batch/athena.py +3 -3
- qwak/feature_store/feature_sets/streaming.py +3 -3
- qwak/feature_store/feature_sets/streaming_backfill.py +1 -1
- qwak/feature_store/online/client.py +6 -6
- qwak/feature_store/sinks/streaming/factory.py +1 -1
- qwak/inner/build_logic/phases/phase_010_fetch_model/fetch_strategy_manager/strategy/git/git_strategy.py +3 -3
- qwak/inner/di_configuration/account.py +23 -24
- qwak/inner/tool/auth.py +2 -2
- qwak/llmops/provider/openai/provider.py +3 -3
- qwak/model/tools/adapters/output.py +1 -1
- qwak/model/utils/feature_utils.py +12 -8
- qwak/model_loggers/artifact_logger.py +7 -7
- qwak/tools/logger/logger.py +1 -1
- qwak_core-0.4.274.dist-info/METADATA +415 -0
- {qwak_core-0.4.272.dist-info → qwak_core-0.4.274.dist-info}/RECORD +61 -25
- qwak_services_mock/mocks/batch_job_manager_service.py +24 -1
- _qwak_proto/__init__.py +0 -0
- _qwak_proto/qwak/__init__.py +0 -0
- qwak_core-0.4.272.dist-info/METADATA +0 -53
- {qwak_core-0.4.272.dist-info → qwak_core-0.4.274.dist-info}/WHEEL +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.4.274"
|
@@ -0,0 +1 @@
|
|
1
|
+
from ._artifactory_api import ArtifactoryApi, StartTransactionResponse
|
@@ -0,0 +1,315 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from requests import Response
|
6
|
+
from tqdm.auto import tqdm
|
7
|
+
from tqdm.utils import CallbackIOWrapper
|
8
|
+
from urllib3 import Retry
|
9
|
+
|
10
|
+
from frogml_storage.logging import logger
|
11
|
+
from frogml_storage.utils import join_url
|
12
|
+
from frogml_storage.constants import CHECKSUM_SHA2_HEADER
|
13
|
+
from frogml_storage.models.entity_manifest import Checksums, EntityManifest
|
14
|
+
from frogml_storage.models.frogml_entity_type_info import FrogMLEntityTypeInfo
|
15
|
+
from frogml_storage.http import HTTPClient
|
16
|
+
from frogml_storage.models import DownloadContext
|
17
|
+
|
18
|
+
|
19
|
+
class StartTransactionResponse:
|
20
|
+
files_upload_path: str
|
21
|
+
lead_upload_path: str
|
22
|
+
dependencies_upload_path: str
|
23
|
+
code_upload_path: str
|
24
|
+
transaction_id: str
|
25
|
+
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
files_upload_path,
|
29
|
+
lead_upload_path,
|
30
|
+
dependencies_upload_path,
|
31
|
+
code_upload_path,
|
32
|
+
transaction_id,
|
33
|
+
):
|
34
|
+
self.files_upload_path = files_upload_path
|
35
|
+
self.lead_upload_path = lead_upload_path
|
36
|
+
self.dependencies_upload_path = dependencies_upload_path
|
37
|
+
self.code_upload_path = code_upload_path
|
38
|
+
self.transaction_id = transaction_id
|
39
|
+
|
40
|
+
|
41
|
+
class ArtifactoryApi:
|
42
|
+
def __init__(self, uri, auth=None, http_client=None):
|
43
|
+
self.uri = uri
|
44
|
+
if http_client is not None:
|
45
|
+
self.http_client = http_client
|
46
|
+
else:
|
47
|
+
self.auth = auth
|
48
|
+
self.http_client = HTTPClient(auth=auth)
|
49
|
+
|
50
|
+
def start_transaction(
|
51
|
+
self,
|
52
|
+
entity_type_info: FrogMLEntityTypeInfo,
|
53
|
+
repository: str,
|
54
|
+
entity_name: str,
|
55
|
+
version: Optional[str],
|
56
|
+
) -> StartTransactionResponse:
|
57
|
+
"""
|
58
|
+
Initializes an upload. Returns transaction ID and upload path
|
59
|
+
"""
|
60
|
+
if version is None:
|
61
|
+
start_transaction_url = (
|
62
|
+
f"{self.uri}/api/machinelearning/{repository}/"
|
63
|
+
f"{entity_type_info.entity_type}/{entity_name}/start-transaction"
|
64
|
+
)
|
65
|
+
else:
|
66
|
+
start_transaction_url = (
|
67
|
+
f"{self.uri}/api/machinelearning/{repository}/{entity_type_info.entity_type}"
|
68
|
+
f"/{entity_name}/start-transaction/{version}"
|
69
|
+
)
|
70
|
+
try:
|
71
|
+
response = self.http_client.post(start_transaction_url)
|
72
|
+
response.raise_for_status()
|
73
|
+
files_upload_path = response.json()["filesUploadPath"]
|
74
|
+
lead_upload_path = response.json()["leadUploadPath"]
|
75
|
+
dependencies_upload_path = response.json()["dependenciesUploadPath"]
|
76
|
+
code_upload_path = response.json()["codeUploadPath"]
|
77
|
+
transaction_id = response.json()["transactionId"]
|
78
|
+
except Exception as exception:
|
79
|
+
err = (
|
80
|
+
f"Error occurred while trying to start an upload transaction for "
|
81
|
+
f"{entity_type_info.entity_type}: '{entity_name}'"
|
82
|
+
f" Error: '{exception}'"
|
83
|
+
)
|
84
|
+
logger.error(err, exc_info=False)
|
85
|
+
raise exception
|
86
|
+
return StartTransactionResponse(
|
87
|
+
files_upload_path=files_upload_path,
|
88
|
+
lead_upload_path=lead_upload_path,
|
89
|
+
dependencies_upload_path=dependencies_upload_path,
|
90
|
+
code_upload_path=code_upload_path,
|
91
|
+
transaction_id=transaction_id,
|
92
|
+
)
|
93
|
+
|
94
|
+
def end_transaction(
|
95
|
+
self,
|
96
|
+
entity_type: FrogMLEntityTypeInfo,
|
97
|
+
repository: str,
|
98
|
+
entity_name: str,
|
99
|
+
entity_manifest: EntityManifest,
|
100
|
+
transaction_id: str,
|
101
|
+
version: str,
|
102
|
+
properties: Optional[dict[str, str]],
|
103
|
+
) -> None:
|
104
|
+
"""
|
105
|
+
Upload model-manifest.json | dataset-manifest.json file, makes the model | dataset available in the repository
|
106
|
+
"""
|
107
|
+
filename = entity_type.metadata_file_name
|
108
|
+
|
109
|
+
url = join_url(
|
110
|
+
self.uri,
|
111
|
+
"api",
|
112
|
+
"machinelearning",
|
113
|
+
repository,
|
114
|
+
entity_type.entity_type,
|
115
|
+
"entity-manifest",
|
116
|
+
entity_name,
|
117
|
+
version,
|
118
|
+
transaction_id,
|
119
|
+
)
|
120
|
+
|
121
|
+
json_entity_manifest = entity_manifest.to_json()
|
122
|
+
self.upload_entity_manifest(
|
123
|
+
entity_type=entity_type,
|
124
|
+
filename=filename,
|
125
|
+
payload=json_entity_manifest,
|
126
|
+
url=url,
|
127
|
+
properties=properties,
|
128
|
+
)
|
129
|
+
|
130
|
+
def download_file(self, args: DownloadContext) -> None:
|
131
|
+
filename = os.path.basename(args.target_path)
|
132
|
+
try:
|
133
|
+
url = f"{self.uri}/{args.repo_key}/{args.source_url}"
|
134
|
+
with self.http_client.get(url=url, stream=True) as response:
|
135
|
+
response.raise_for_status()
|
136
|
+
total_size = int(response.headers.get("content-length", 0))
|
137
|
+
with open(args.target_path, "wb") as file:
|
138
|
+
with self.__initialize_progress_bar(total_size, filename) as pbar:
|
139
|
+
for chunk in response.iter_content(chunk_size=8192):
|
140
|
+
if chunk:
|
141
|
+
file.write(chunk)
|
142
|
+
pbar.update(len(chunk))
|
143
|
+
|
144
|
+
except Exception as exception:
|
145
|
+
self.__handle_download_exception(exception, args.target_path, filename)
|
146
|
+
|
147
|
+
def get_entity_manifest(
|
148
|
+
self,
|
149
|
+
entity_type_info: FrogMLEntityTypeInfo,
|
150
|
+
repository: str,
|
151
|
+
entity_name: str,
|
152
|
+
namespace: Optional[str],
|
153
|
+
version: Optional[str],
|
154
|
+
) -> dict:
|
155
|
+
url = join_url(
|
156
|
+
self.uri,
|
157
|
+
"api",
|
158
|
+
"machinelearning",
|
159
|
+
repository,
|
160
|
+
entity_type_info.entity_type,
|
161
|
+
"entity-manifest",
|
162
|
+
namespace,
|
163
|
+
entity_name,
|
164
|
+
version,
|
165
|
+
)
|
166
|
+
try:
|
167
|
+
with self.http_client.get(url=url) as r:
|
168
|
+
r.raise_for_status()
|
169
|
+
return r.json()
|
170
|
+
except Exception as exception:
|
171
|
+
err = f"Error occurred while trying to get {entity_type_info.entity_type} info file. Error: '{exception}'"
|
172
|
+
logger.error(err, exc_info=False)
|
173
|
+
raise exception
|
174
|
+
|
175
|
+
@staticmethod
|
176
|
+
def __handle_download_exception(
|
177
|
+
exception: Exception, target_path: str, filename: str
|
178
|
+
) -> None:
|
179
|
+
if os.path.exists(target_path):
|
180
|
+
os.remove(target_path)
|
181
|
+
err = f"Error occurred while trying to download file: '{filename}' Error: '{exception}'"
|
182
|
+
logger.error(err, exc_info=False)
|
183
|
+
raise exception
|
184
|
+
|
185
|
+
def get_artifact_checksum(self, download_context: DownloadContext) -> str:
|
186
|
+
url = f"{self.uri}/{download_context.repo_key}/{download_context.source_url}"
|
187
|
+
try:
|
188
|
+
with self.http_client.head(url=url, stream=True) as response:
|
189
|
+
response.raise_for_status()
|
190
|
+
return response.headers.get(CHECKSUM_SHA2_HEADER)
|
191
|
+
|
192
|
+
except Exception as exception:
|
193
|
+
logger.error(exception.__cause__, exc_info=False)
|
194
|
+
raise exception
|
195
|
+
|
196
|
+
def upload_entity_manifest(
|
197
|
+
self,
|
198
|
+
entity_type: FrogMLEntityTypeInfo,
|
199
|
+
filename: str,
|
200
|
+
payload: str,
|
201
|
+
url: str,
|
202
|
+
properties: Optional[dict[str, str]],
|
203
|
+
stream: bool = False,
|
204
|
+
) -> None:
|
205
|
+
body_part_name = f"{entity_type.body_part_stream}"
|
206
|
+
|
207
|
+
try:
|
208
|
+
files = {
|
209
|
+
f"{body_part_name}": (
|
210
|
+
f"{body_part_name}",
|
211
|
+
payload,
|
212
|
+
"application/octet-stream",
|
213
|
+
), # Include the InputStream
|
214
|
+
"additionalData": (
|
215
|
+
"additionalData",
|
216
|
+
json.dumps(properties),
|
217
|
+
"application/octet-stream",
|
218
|
+
), # Include the object
|
219
|
+
}
|
220
|
+
with self.http_client.put(url=url, files=files, stream=stream) as response:
|
221
|
+
response.raise_for_status()
|
222
|
+
except Exception as exception:
|
223
|
+
err = f"Error occurred while trying to upload file: '{filename}' Error: '{exception}'"
|
224
|
+
logger.error(err, exc_info=False)
|
225
|
+
raise exception
|
226
|
+
|
227
|
+
def upload_file(self, file_path: str, url: str) -> None:
|
228
|
+
wrapped_file = None
|
229
|
+
try:
|
230
|
+
file_size = os.stat(file_path).st_size
|
231
|
+
with (
|
232
|
+
self.__initialize_progress_bar(file_size, file_path) as pbar,
|
233
|
+
open(file_path, "rb") as file,
|
234
|
+
):
|
235
|
+
wrapped_file = CallbackIOWrapper(pbar.update, file, "read")
|
236
|
+
with self.http_client.put(url=url, payload=wrapped_file) as response:
|
237
|
+
response.raise_for_status()
|
238
|
+
except Exception as exception:
|
239
|
+
err = f"Error occurred while trying to upload file: '{file_path}' Error: '{exception}'"
|
240
|
+
logger.error(err, exc_info=False)
|
241
|
+
raise type(exception)(f"{err} File: {file_path}") from exception
|
242
|
+
finally:
|
243
|
+
if wrapped_file is not None:
|
244
|
+
wrapped_file.close()
|
245
|
+
|
246
|
+
def checksum_deployment(
|
247
|
+
self,
|
248
|
+
checksum: Checksums,
|
249
|
+
url: str,
|
250
|
+
full_path: str,
|
251
|
+
stream: bool = False,
|
252
|
+
) -> bool:
|
253
|
+
response = self.http_client.put(
|
254
|
+
url=url,
|
255
|
+
headers={"X-Checksum-Sha256": checksum.sha2, "X-Checksum-Deploy": "true"},
|
256
|
+
stream=stream,
|
257
|
+
)
|
258
|
+
if response.status_code != 200 and response.status_code != 201:
|
259
|
+
return False
|
260
|
+
else:
|
261
|
+
file_size = os.path.getsize(full_path)
|
262
|
+
pbar = self.__initialize_progress_bar(file_size, full_path)
|
263
|
+
pbar.update(file_size)
|
264
|
+
pbar.close()
|
265
|
+
return True
|
266
|
+
|
267
|
+
@staticmethod
|
268
|
+
def __initialize_progress_bar(total_size: int, filename: str) -> tqdm:
|
269
|
+
return tqdm(
|
270
|
+
total=total_size, unit="B", unit_scale=True, desc=filename, initial=0
|
271
|
+
)
|
272
|
+
|
273
|
+
def encrypt_password(self) -> Response:
|
274
|
+
"""
|
275
|
+
returns encrypted password as text
|
276
|
+
"""
|
277
|
+
return self.http_client.get(
|
278
|
+
url=join_url(self.uri, "/api/security/encryptedPassword")
|
279
|
+
)
|
280
|
+
|
281
|
+
def ping(self) -> Response:
|
282
|
+
"""
|
283
|
+
Sends a ping to Artifactory to validate login status
|
284
|
+
"""
|
285
|
+
url = join_url(self.uri, "api/system/ping")
|
286
|
+
return self.http_client.get(url=url)
|
287
|
+
|
288
|
+
def get_artifactory_version(self) -> Response:
|
289
|
+
return self.http_client.get(url=join_url(self.uri, "/api/system/version"))
|
290
|
+
|
291
|
+
def create_machinelearning_local_repo(self, repo_name: str) -> Response:
|
292
|
+
data = {
|
293
|
+
"rclass": "local",
|
294
|
+
"packageType": "machinelearning",
|
295
|
+
}
|
296
|
+
return self.http_client.put(
|
297
|
+
url=join_url(self.uri, "/api/repositories/" + repo_name), json=data
|
298
|
+
)
|
299
|
+
|
300
|
+
def delete_frogml_local_repo(self, repo_name: str) -> Response:
|
301
|
+
return self.http_client.delete(
|
302
|
+
url=join_url(self.uri, "/api/repositories/" + repo_name)
|
303
|
+
)
|
304
|
+
|
305
|
+
|
306
|
+
class RetryWithLog(Retry):
|
307
|
+
"""
|
308
|
+
Adding extra logs before making a retry request
|
309
|
+
"""
|
310
|
+
|
311
|
+
def __init__(self, *args, **kwargs):
|
312
|
+
history = kwargs.get("history")
|
313
|
+
if history is not None:
|
314
|
+
logger.debug(f"Error: ${history[-1].error}\nretrying...")
|
315
|
+
super().__init__(*args, **kwargs)
|
@@ -0,0 +1 @@
|
|
1
|
+
from ._login_cli import login as frogml_login
|
@@ -0,0 +1,239 @@
|
|
1
|
+
from typing import List, Optional, Union
|
2
|
+
|
3
|
+
import typer
|
4
|
+
|
5
|
+
from frogml_storage.utils import assemble_artifact_url, join_url
|
6
|
+
from frogml_storage.authentication.login._login_command import run as run_login
|
7
|
+
from frogml_storage.authentication.models import AuthConfig, LoginArguments
|
8
|
+
from frogml_storage.authentication.utils import (
|
9
|
+
get_frogml_configuration,
|
10
|
+
parse_cli_config_server,
|
11
|
+
read_jfrog_cli_config,
|
12
|
+
)
|
13
|
+
from frogml_storage.authentication.utils import (
|
14
|
+
get_list_of_servers_from_config,
|
15
|
+
login_input_checks,
|
16
|
+
is_login_without_params,
|
17
|
+
)
|
18
|
+
from frogml_storage.constants import CONFIG_FILE_PATH, JFROG_CLI_CONFIG_FILE_PATH
|
19
|
+
|
20
|
+
|
21
|
+
def login(
|
22
|
+
url: Optional[str],
|
23
|
+
username: Optional[str],
|
24
|
+
password: Optional[str],
|
25
|
+
token: Optional[str],
|
26
|
+
anonymous: Optional[bool],
|
27
|
+
is_interactive: Optional[bool],
|
28
|
+
) -> bool:
|
29
|
+
if anonymous is None:
|
30
|
+
anonymous = False
|
31
|
+
if is_login_without_params(url, username, password, token, anonymous):
|
32
|
+
if not is_interactive:
|
33
|
+
return __login_by_frogml_configuration_file_flow()
|
34
|
+
else:
|
35
|
+
return __interactive_flow()
|
36
|
+
elif login_input_checks(url, username, password, token, anonymous):
|
37
|
+
url = assemble_artifact_url(url)
|
38
|
+
return __login_by_command_line_params(url, username, password, token, anonymous)
|
39
|
+
else:
|
40
|
+
typer.echo(
|
41
|
+
f"{__get_failed_reason(url, username, password, token, anonymous)}Login failed"
|
42
|
+
)
|
43
|
+
return False
|
44
|
+
|
45
|
+
|
46
|
+
def __get_failed_reason(
|
47
|
+
url: Optional[str],
|
48
|
+
username: Optional[str],
|
49
|
+
password: Optional[str],
|
50
|
+
token: Optional[str],
|
51
|
+
anonymous: Optional[bool],
|
52
|
+
) -> str:
|
53
|
+
details = ""
|
54
|
+
if url is None:
|
55
|
+
details += "Url is required.\n"
|
56
|
+
if token is not None and (
|
57
|
+
any(params is not None for params in [username, password])
|
58
|
+
):
|
59
|
+
details += "Token is specified when username/password is specified.\n"
|
60
|
+
if anonymous is True and any(
|
61
|
+
params is not None for params in [username, password, token]
|
62
|
+
):
|
63
|
+
details += "Anonymous is specified with authentication details.\n"
|
64
|
+
if username is None and password is not None:
|
65
|
+
details += "Username is required when password is specified.\n"
|
66
|
+
elif username is not None and password is None:
|
67
|
+
details += "Password is required when username is specified.\n"
|
68
|
+
return details
|
69
|
+
|
70
|
+
|
71
|
+
def __run_interactive_mode() -> Union[LoginArguments, None]:
|
72
|
+
jfrog_cli_config = read_jfrog_cli_config()
|
73
|
+
|
74
|
+
if jfrog_cli_config is not None:
|
75
|
+
jfrog_cli_servers = get_list_of_servers_from_config(jfrog_cli_config)
|
76
|
+
if jfrog_cli_servers is not None and len(jfrog_cli_servers) > 0:
|
77
|
+
login_method_id = typer.prompt(
|
78
|
+
f"Please select from the following options:\n" # nosec B608
|
79
|
+
f"1.Login by jfrog-cli configuration file: {JFROG_CLI_CONFIG_FILE_PATH}\n"
|
80
|
+
f"2.Connecting to a new server\n"
|
81
|
+
)
|
82
|
+
|
83
|
+
while (
|
84
|
+
not login_method_id.isdigit()
|
85
|
+
or int(login_method_id) > 2
|
86
|
+
or int(login_method_id) <= 0
|
87
|
+
):
|
88
|
+
login_method_id = typer.prompt(
|
89
|
+
"Bad Input. Choose your preferred login option"
|
90
|
+
)
|
91
|
+
|
92
|
+
login_method_id = int(login_method_id)
|
93
|
+
if login_method_id == 1:
|
94
|
+
return __prompt_jfrog_cli_configuration_list(
|
95
|
+
jfrog_cli_config, jfrog_cli_servers
|
96
|
+
)
|
97
|
+
|
98
|
+
return __prompt_manual_details()
|
99
|
+
|
100
|
+
return None
|
101
|
+
|
102
|
+
|
103
|
+
def __prompt_manual_details() -> Union[LoginArguments, None]:
|
104
|
+
login_args = LoginArguments()
|
105
|
+
login_args.artifactory_url = typer.prompt("Enter artifactory base url")
|
106
|
+
|
107
|
+
if login_args.artifactory_url is not None:
|
108
|
+
login_args.artifactory_url = join_url(login_args.artifactory_url, "artifactory")
|
109
|
+
|
110
|
+
auth_options = ["Username and Password", "Access Token", "Anonymous Access"]
|
111
|
+
authentication_types = ""
|
112
|
+
for index, item in enumerate(auth_options):
|
113
|
+
authentication_types += f"{index}: {item} \n"
|
114
|
+
selected_auth_type = typer.prompt(
|
115
|
+
f"Choose your preferred authentication option:\n{authentication_types}"
|
116
|
+
)
|
117
|
+
while (
|
118
|
+
not selected_auth_type.isdigit()
|
119
|
+
or int(selected_auth_type) >= len(auth_options)
|
120
|
+
or int(selected_auth_type) < 0
|
121
|
+
):
|
122
|
+
selected_auth_type = typer.prompt(
|
123
|
+
"Bad Input. Choose your preferred authentication"
|
124
|
+
)
|
125
|
+
selected_auth_type = int(selected_auth_type)
|
126
|
+
if selected_auth_type == 0:
|
127
|
+
return __prompt_username_password(login_args)
|
128
|
+
elif selected_auth_type == 1:
|
129
|
+
return __prompt_access_token(login_args)
|
130
|
+
elif selected_auth_type == 2:
|
131
|
+
login_args.isAnonymous = True
|
132
|
+
return login_args
|
133
|
+
return None
|
134
|
+
|
135
|
+
|
136
|
+
def __prompt_username_password(login_args: LoginArguments) -> LoginArguments:
|
137
|
+
username = typer.prompt("Enter JFrog user name")
|
138
|
+
password = typer.prompt("Enter JFrog password", hide_input=True)
|
139
|
+
login_args.username = username
|
140
|
+
login_args.password = password
|
141
|
+
return login_args
|
142
|
+
|
143
|
+
|
144
|
+
def __prompt_access_token(login_args: LoginArguments) -> LoginArguments:
|
145
|
+
token = typer.prompt("Enter JFrog access token", hide_input=True)
|
146
|
+
login_args.access_token = token
|
147
|
+
return login_args
|
148
|
+
|
149
|
+
|
150
|
+
def __prompt_jfrog_cli_configuration_list(
|
151
|
+
jfrog_cli_config: dict, jfrog_cli_servers: List[str]
|
152
|
+
) -> Union[LoginArguments, None]:
|
153
|
+
list_server_options = ""
|
154
|
+
for index, item in enumerate(jfrog_cli_servers):
|
155
|
+
list_server_options += f"{index}: {item}\n"
|
156
|
+
server_index_cli_conf = typer.prompt(
|
157
|
+
f"Found the following servers in your JFrog CLI configuration, "
|
158
|
+
f"choose one of the following:\n{list_server_options}"
|
159
|
+
)
|
160
|
+
while (
|
161
|
+
not server_index_cli_conf.isdigit()
|
162
|
+
or int(server_index_cli_conf) < 0
|
163
|
+
or int(server_index_cli_conf) >= len(jfrog_cli_servers)
|
164
|
+
):
|
165
|
+
server_index_cli_conf = typer.prompt(
|
166
|
+
"Invalid choice. Please choose a number from the list"
|
167
|
+
)
|
168
|
+
server_index_cli_conf = int(server_index_cli_conf)
|
169
|
+
typer.echo(f"{jfrog_cli_servers[server_index_cli_conf]} was chosen")
|
170
|
+
servers = jfrog_cli_config.get("servers")
|
171
|
+
if servers is not None:
|
172
|
+
return parse_cli_config_server(servers[server_index_cli_conf])
|
173
|
+
else:
|
174
|
+
raise ValueError("No servers found in the JFrog CLI configuration.")
|
175
|
+
|
176
|
+
|
177
|
+
def __execute_login(auth_config: AuthConfig, anonymous: bool) -> bool:
|
178
|
+
if run_login(auth_config, anonymous):
|
179
|
+
success_message = "Logged in successfully"
|
180
|
+
if auth_config.artifactory_url is not None:
|
181
|
+
success_message += (
|
182
|
+
f" to: {auth_config.artifactory_url.replace('/artifactory', '')}"
|
183
|
+
)
|
184
|
+
typer.echo(success_message)
|
185
|
+
return True
|
186
|
+
else:
|
187
|
+
typer.echo("Failed to login, bad authentication")
|
188
|
+
return False
|
189
|
+
|
190
|
+
|
191
|
+
def __login_by_frogml_configuration_file_flow() -> bool:
|
192
|
+
login_args = get_frogml_configuration()
|
193
|
+
auth_config = None
|
194
|
+
if login_args is not None:
|
195
|
+
typer.echo(
|
196
|
+
f"Using existing frogml authentication config file: {CONFIG_FILE_PATH}"
|
197
|
+
)
|
198
|
+
auth_config = AuthConfig(
|
199
|
+
artifactory_url=login_args.artifactory_url,
|
200
|
+
user=login_args.username,
|
201
|
+
password=login_args.password,
|
202
|
+
access_token=login_args.access_token,
|
203
|
+
)
|
204
|
+
|
205
|
+
if (
|
206
|
+
auth_config is not None
|
207
|
+
and login_args is not None
|
208
|
+
and __execute_login(auth_config, login_args.isAnonymous)
|
209
|
+
):
|
210
|
+
return True
|
211
|
+
|
212
|
+
return __interactive_flow()
|
213
|
+
|
214
|
+
|
215
|
+
def __interactive_flow() -> bool:
|
216
|
+
login_args = __run_interactive_mode()
|
217
|
+
if login_args is not None:
|
218
|
+
auth_config = AuthConfig(
|
219
|
+
artifactory_url=login_args.artifactory_url,
|
220
|
+
user=login_args.username,
|
221
|
+
password=login_args.password,
|
222
|
+
access_token=login_args.access_token,
|
223
|
+
)
|
224
|
+
return __execute_login(auth_config, login_args.isAnonymous)
|
225
|
+
return False
|
226
|
+
|
227
|
+
|
228
|
+
def __login_by_command_line_params(
|
229
|
+
url: str,
|
230
|
+
username: Optional[str],
|
231
|
+
password: Optional[str],
|
232
|
+
token: Optional[str],
|
233
|
+
anonymous: Optional[bool],
|
234
|
+
) -> bool:
|
235
|
+
anonymous_value = anonymous if anonymous is not None else False
|
236
|
+
auth_config = AuthConfig(
|
237
|
+
artifactory_url=url, user=username, password=password, access_token=token
|
238
|
+
)
|
239
|
+
return __execute_login(auth_config, anonymous_value)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import http
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
import requests
|
5
|
+
from requests.auth import AuthBase, HTTPBasicAuth
|
6
|
+
|
7
|
+
from frogml_storage.artifactory import ArtifactoryApi
|
8
|
+
from frogml_storage.logging import logger
|
9
|
+
from frogml_storage.authentication.models import BearerAuth, EmptyAuth
|
10
|
+
from frogml_storage.authentication.utils import (
|
11
|
+
get_encrypted_password,
|
12
|
+
save_auth_config,
|
13
|
+
)
|
14
|
+
from frogml_storage.authentication.models import AuthConfig
|
15
|
+
from frogml_storage.authentication.utils import (
|
16
|
+
is_username_password_login,
|
17
|
+
is_access_token_login,
|
18
|
+
is_anonymous_login,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
def run(auth_config: AuthConfig, anonymous: Optional[bool] = False) -> bool:
|
23
|
+
if is_username_password_login(auth_config, anonymous):
|
24
|
+
connection_validation_result = __login_by_username_password(auth_config)
|
25
|
+
elif is_access_token_login(auth_config, anonymous):
|
26
|
+
connection_validation_result = __validate_server_connection(
|
27
|
+
auth_config, BearerAuth(auth_config.access_token)
|
28
|
+
)
|
29
|
+
elif is_anonymous_login(auth_config, anonymous):
|
30
|
+
connection_validation_result = __validate_server_connection(
|
31
|
+
auth_config, EmptyAuth()
|
32
|
+
)
|
33
|
+
else:
|
34
|
+
connection_validation_result = False
|
35
|
+
|
36
|
+
if connection_validation_result:
|
37
|
+
save_auth_config(auth_config)
|
38
|
+
return connection_validation_result
|
39
|
+
|
40
|
+
|
41
|
+
def __login_by_username_password(auth_config: AuthConfig) -> bool:
|
42
|
+
if auth_config.user is not None and auth_config.password is not None:
|
43
|
+
auth_token = HTTPBasicAuth(auth_config.user, auth_config.password)
|
44
|
+
connection_validation_result = __validate_server_connection(
|
45
|
+
auth_config, auth_token
|
46
|
+
)
|
47
|
+
if connection_validation_result:
|
48
|
+
encrypted_password = get_encrypted_password(auth_config, auth_token)
|
49
|
+
if encrypted_password is not None:
|
50
|
+
auth_config.password = encrypted_password
|
51
|
+
return True
|
52
|
+
return False
|
53
|
+
|
54
|
+
|
55
|
+
def __validate_server_connection(auth_config: AuthConfig, auth_token: AuthBase) -> bool:
|
56
|
+
success = False
|
57
|
+
try:
|
58
|
+
logger.debug("Attempting to ping artifactory")
|
59
|
+
response = ArtifactoryApi(auth_config.artifactory_url, auth_token).ping()
|
60
|
+
if response.status_code == http.HTTPStatus.OK:
|
61
|
+
success = True
|
62
|
+
else:
|
63
|
+
logger.debug(
|
64
|
+
f"Expected {http.HTTPStatus.OK} status but got {response.status_code} "
|
65
|
+
f"when using url {auth_config.artifactory_url}"
|
66
|
+
)
|
67
|
+
except requests.exceptions.ConnectionError as e:
|
68
|
+
logger.debug(f"Unable to connect to the provided url :{e}.")
|
69
|
+
except requests.exceptions.MissingSchema as e:
|
70
|
+
logger.debug(f"Invalid Artifactory URL provided: {e}.")
|
71
|
+
except requests.exceptions.RequestException as e:
|
72
|
+
logger.debug(f"Unexpected request exception: {e}.")
|
73
|
+
|
74
|
+
return success
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from requests import PreparedRequest
|
4
|
+
from requests.auth import AuthBase
|
5
|
+
|
6
|
+
|
7
|
+
class BearerAuth(AuthBase):
|
8
|
+
def __init__(self, token: str):
|
9
|
+
self.token: str = token
|
10
|
+
|
11
|
+
def __call__(self, r: PreparedRequest) -> PreparedRequest:
|
12
|
+
r.headers["Authorization"] = f"Bearer {self.token}"
|
13
|
+
return r
|
14
|
+
|
15
|
+
def __eq__(self, other: Any) -> bool:
|
16
|
+
if not isinstance(other, BearerAuth):
|
17
|
+
return False
|
18
|
+
|
19
|
+
return self.token == other.token
|
20
|
+
|
21
|
+
|
22
|
+
class EmptyAuth(AuthBase):
|
23
|
+
def __call__(self, r: PreparedRequest) -> PreparedRequest:
|
24
|
+
return r
|