mlrun 1.6.1__py3-none-any.whl → 1.6.2rc1__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.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/common/model_monitoring/helpers.py +4 -2
- mlrun/common/schemas/__init__.py +1 -0
- mlrun/common/schemas/common.py +40 -0
- mlrun/common/schemas/project.py +2 -0
- mlrun/config.py +13 -7
- mlrun/datastore/azure_blob.py +9 -9
- mlrun/datastore/google_cloud_storage.py +6 -6
- mlrun/db/base.py +18 -0
- mlrun/db/httpdb.py +28 -25
- mlrun/execution.py +3 -3
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +3 -3
- mlrun/frameworks/tf_keras/model_handler.py +7 -7
- mlrun/k8s_utils.py +10 -5
- mlrun/kfpops.py +19 -10
- mlrun/model.py +5 -0
- mlrun/model_monitoring/api.py +8 -8
- mlrun/model_monitoring/batch.py +1 -1
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +13 -13
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -1
- mlrun/package/packagers/pandas_packagers.py +3 -3
- mlrun/package/utils/_archiver.py +3 -1
- mlrun/platforms/iguazio.py +6 -65
- mlrun/projects/pipelines.py +21 -11
- mlrun/projects/project.py +65 -46
- mlrun/runtimes/base.py +20 -1
- mlrun/runtimes/function.py +9 -9
- mlrun/runtimes/kubejob.py +5 -3
- mlrun/runtimes/local.py +2 -2
- mlrun/runtimes/mpijob/abstract.py +6 -6
- mlrun/runtimes/pod.py +3 -3
- mlrun/runtimes/serving.py +3 -3
- mlrun/runtimes/sparkjob/spark3job.py +3 -3
- mlrun/serving/remote.py +4 -2
- mlrun/utils/async_http.py +3 -3
- mlrun/utils/helpers.py +8 -0
- mlrun/utils/http.py +3 -3
- mlrun/utils/notifications/notification_pusher.py +6 -6
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.6.1.dist-info → mlrun-1.6.2rc1.dist-info}/METADATA +12 -14
- {mlrun-1.6.1.dist-info → mlrun-1.6.2rc1.dist-info}/RECORD +44 -43
- {mlrun-1.6.1.dist-info → mlrun-1.6.2rc1.dist-info}/LICENSE +0 -0
- {mlrun-1.6.1.dist-info → mlrun-1.6.2rc1.dist-info}/WHEEL +0 -0
- {mlrun-1.6.1.dist-info → mlrun-1.6.2rc1.dist-info}/entry_points.txt +0 -0
- {mlrun-1.6.1.dist-info → mlrun-1.6.2rc1.dist-info}/top_level.txt +0 -0
mlrun/package/utils/_archiver.py
CHANGED
|
@@ -179,7 +179,9 @@ class _TarArchiver(_Archiver):
|
|
|
179
179
|
|
|
180
180
|
# Extract:
|
|
181
181
|
with tarfile.open(archive_path, f"r:{cls._MODE_STRING}") as tar_file:
|
|
182
|
-
|
|
182
|
+
# use 'data' to ensure no security risks are imposed by the archive files
|
|
183
|
+
# see: https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall
|
|
184
|
+
tar_file.extractall(directory_path, filter="data")
|
|
183
185
|
|
|
184
186
|
return str(directory_path)
|
|
185
187
|
|
mlrun/platforms/iguazio.py
CHANGED
|
@@ -16,19 +16,15 @@ import json
|
|
|
16
16
|
import os
|
|
17
17
|
import urllib
|
|
18
18
|
from collections import namedtuple
|
|
19
|
-
from datetime import datetime
|
|
20
|
-
from http import HTTPStatus
|
|
21
19
|
from urllib.parse import urlparse
|
|
22
20
|
|
|
23
21
|
import kfp.dsl
|
|
24
22
|
import requests
|
|
25
23
|
import semver
|
|
26
|
-
import urllib3
|
|
27
24
|
import v3io
|
|
28
25
|
|
|
29
26
|
import mlrun.errors
|
|
30
27
|
from mlrun.config import config as mlconf
|
|
31
|
-
from mlrun.errors import err_to_str
|
|
32
28
|
from mlrun.utils import dict_to_json
|
|
33
29
|
|
|
34
30
|
_cached_control_session = None
|
|
@@ -488,25 +484,6 @@ class V3ioStreamClient:
|
|
|
488
484
|
return response.output.records
|
|
489
485
|
|
|
490
486
|
|
|
491
|
-
def create_control_session(url, username, password):
|
|
492
|
-
# for systems without production cert - silence no cert verification WARN
|
|
493
|
-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
494
|
-
if not username or not password:
|
|
495
|
-
raise ValueError("cannot create session key, missing username or password")
|
|
496
|
-
|
|
497
|
-
session = requests.Session()
|
|
498
|
-
session.auth = (username, password)
|
|
499
|
-
try:
|
|
500
|
-
auth = session.post(f"{url}/api/sessions", verify=False)
|
|
501
|
-
except OSError as exc:
|
|
502
|
-
raise OSError(f"error: cannot connect to {url}: {err_to_str(exc)}")
|
|
503
|
-
|
|
504
|
-
if not auth.ok:
|
|
505
|
-
raise OSError(f"failed to create session: {url}, {auth.text}")
|
|
506
|
-
|
|
507
|
-
return auth.json()["data"]["id"]
|
|
508
|
-
|
|
509
|
-
|
|
510
487
|
def is_iguazio_endpoint(endpoint_url: str) -> bool:
|
|
511
488
|
# TODO: find a better heuristic
|
|
512
489
|
return ".default-tenant." in endpoint_url
|
|
@@ -533,21 +510,6 @@ def is_iguazio_session_cookie(session_cookie: str) -> bool:
|
|
|
533
510
|
return False
|
|
534
511
|
|
|
535
512
|
|
|
536
|
-
def is_iguazio_system_2_10_or_above(dashboard_url):
|
|
537
|
-
# for systems without production cert - silence no cert verification WARN
|
|
538
|
-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
539
|
-
response = requests.get(f"{dashboard_url}/api/external_versions", verify=False)
|
|
540
|
-
|
|
541
|
-
if not response.ok:
|
|
542
|
-
if response.status_code == HTTPStatus.NOT_FOUND.value:
|
|
543
|
-
# in iguazio systems prior to 2.10 this endpoint didn't exist, so the api returns 404 cause endpoint not
|
|
544
|
-
# found
|
|
545
|
-
return False
|
|
546
|
-
response.raise_for_status()
|
|
547
|
-
|
|
548
|
-
return True
|
|
549
|
-
|
|
550
|
-
|
|
551
513
|
# we assign the control session or access key to the password since this is iguazio auth scheme
|
|
552
514
|
# (requests should be sent with username:control_session/access_key as auth header)
|
|
553
515
|
def add_or_refresh_credentials(
|
|
@@ -577,33 +539,12 @@ def add_or_refresh_credentials(
|
|
|
577
539
|
# (ideally if we could identify we're in enterprise we would have verify here that token and username have value)
|
|
578
540
|
if not is_iguazio_endpoint(api_url):
|
|
579
541
|
return "", "", token
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
raise ValueError(
|
|
587
|
-
"username and access key required to authenticate against iguazio system"
|
|
588
|
-
)
|
|
589
|
-
return username, token, ""
|
|
590
|
-
|
|
591
|
-
if not username or not password:
|
|
592
|
-
raise ValueError("username and password needed to create session")
|
|
593
|
-
|
|
594
|
-
global _cached_control_session
|
|
595
|
-
now = datetime.now()
|
|
596
|
-
if _cached_control_session:
|
|
597
|
-
if (
|
|
598
|
-
_cached_control_session[2] == username
|
|
599
|
-
and _cached_control_session[3] == password
|
|
600
|
-
and (now - _cached_control_session[1]).seconds < 20 * 60 * 60
|
|
601
|
-
):
|
|
602
|
-
return _cached_control_session[2], _cached_control_session[0], ""
|
|
603
|
-
|
|
604
|
-
control_session = create_control_session(iguazio_dashboard_url, username, password)
|
|
605
|
-
_cached_control_session = (control_session, now, username, password)
|
|
606
|
-
return username, control_session, ""
|
|
542
|
+
|
|
543
|
+
if not username or not token:
|
|
544
|
+
raise ValueError(
|
|
545
|
+
"username and access key required to authenticate against iguazio system"
|
|
546
|
+
)
|
|
547
|
+
return username, token, ""
|
|
607
548
|
|
|
608
549
|
|
|
609
550
|
def parse_path(url, suffix="/"):
|
mlrun/projects/pipelines.py
CHANGED
|
@@ -69,16 +69,16 @@ class WorkflowSpec(mlrun.model.ModelObj):
|
|
|
69
69
|
|
|
70
70
|
def __init__(
|
|
71
71
|
self,
|
|
72
|
-
engine=None,
|
|
73
|
-
code=None,
|
|
74
|
-
path=None,
|
|
75
|
-
args=None,
|
|
76
|
-
name=None,
|
|
77
|
-
handler=None,
|
|
78
|
-
args_schema: dict = None,
|
|
72
|
+
engine: typing.Optional[str] = None,
|
|
73
|
+
code: typing.Optional[str] = None,
|
|
74
|
+
path: typing.Optional[str] = None,
|
|
75
|
+
args: typing.Optional[dict] = None,
|
|
76
|
+
name: typing.Optional[str] = None,
|
|
77
|
+
handler: typing.Optional[str] = None,
|
|
78
|
+
args_schema: typing.Optional[dict] = None,
|
|
79
79
|
schedule: typing.Union[str, mlrun.common.schemas.ScheduleCronTrigger] = None,
|
|
80
|
-
cleanup_ttl: int = None,
|
|
81
|
-
image: str = None,
|
|
80
|
+
cleanup_ttl: typing.Optional[int] = None,
|
|
81
|
+
image: typing.Optional[str] = None,
|
|
82
82
|
):
|
|
83
83
|
self.engine = engine
|
|
84
84
|
self.code = code
|
|
@@ -401,6 +401,9 @@ def enrich_function_object(
|
|
|
401
401
|
else:
|
|
402
402
|
f.spec.build.source = project.spec.source
|
|
403
403
|
f.spec.build.load_source_on_run = project.spec.load_source_on_run
|
|
404
|
+
f.spec.build.source_code_target_dir = (
|
|
405
|
+
project.spec.build.source_code_target_dir
|
|
406
|
+
)
|
|
404
407
|
f.spec.workdir = project.spec.workdir or project.spec.subpath
|
|
405
408
|
f.prepare_image_for_deploy()
|
|
406
409
|
|
|
@@ -862,6 +865,11 @@ class _RemoteRunner(_PipelineRunner):
|
|
|
862
865
|
)
|
|
863
866
|
return
|
|
864
867
|
|
|
868
|
+
logger.debug(
|
|
869
|
+
"Workflow submitted, waiting for pipeline run to start",
|
|
870
|
+
workflow_name=workflow_response.name,
|
|
871
|
+
)
|
|
872
|
+
|
|
865
873
|
# Getting workflow id from run:
|
|
866
874
|
response = retry_until_successful(
|
|
867
875
|
1,
|
|
@@ -988,6 +996,7 @@ def load_and_run(
|
|
|
988
996
|
cleanup_ttl: int = None,
|
|
989
997
|
load_only: bool = False,
|
|
990
998
|
wait_for_completion: bool = False,
|
|
999
|
+
project_context: str = None,
|
|
991
1000
|
):
|
|
992
1001
|
"""
|
|
993
1002
|
Auxiliary function that the RemoteRunner run once or run every schedule.
|
|
@@ -1018,10 +1027,11 @@ def load_and_run(
|
|
|
1018
1027
|
workflow and all its resources are deleted)
|
|
1019
1028
|
:param load_only: for just loading the project, inner use.
|
|
1020
1029
|
:param wait_for_completion: wait for workflow completion before returning
|
|
1030
|
+
:param project_context: project context path (used for loading the project)
|
|
1021
1031
|
"""
|
|
1022
1032
|
try:
|
|
1023
1033
|
project = mlrun.load_project(
|
|
1024
|
-
context=f"./{project_name}",
|
|
1034
|
+
context=project_context or f"./{project_name}",
|
|
1025
1035
|
url=url,
|
|
1026
1036
|
name=project_name,
|
|
1027
1037
|
init_git=init_git,
|
|
@@ -1053,7 +1063,7 @@ def load_and_run(
|
|
|
1053
1063
|
|
|
1054
1064
|
raise error
|
|
1055
1065
|
|
|
1056
|
-
context.logger.info(f"Loaded project {project.name}
|
|
1066
|
+
context.logger.info(f"Loaded project {project.name} successfully")
|
|
1057
1067
|
|
|
1058
1068
|
if load_only:
|
|
1059
1069
|
return
|
mlrun/projects/project.py
CHANGED
|
@@ -24,7 +24,7 @@ import typing
|
|
|
24
24
|
import uuid
|
|
25
25
|
import warnings
|
|
26
26
|
import zipfile
|
|
27
|
-
from os import environ, makedirs, path
|
|
27
|
+
from os import environ, makedirs, path
|
|
28
28
|
from typing import Callable, Dict, List, Optional, Union
|
|
29
29
|
|
|
30
30
|
import dotenv
|
|
@@ -605,9 +605,14 @@ def _load_project_dir(context, name="", subpath=""):
|
|
|
605
605
|
# If there is a setup script do not force having project.yaml file
|
|
606
606
|
project = MlrunProject()
|
|
607
607
|
else:
|
|
608
|
-
|
|
609
|
-
|
|
608
|
+
message = "Project or function YAML not found in path"
|
|
609
|
+
logger.error(
|
|
610
|
+
message,
|
|
611
|
+
context=context,
|
|
612
|
+
name=name,
|
|
613
|
+
subpath=subpath,
|
|
610
614
|
)
|
|
615
|
+
raise mlrun.errors.MLRunNotFoundError(message)
|
|
611
616
|
|
|
612
617
|
project.spec.context = context
|
|
613
618
|
project.metadata.name = name or project.metadata.name
|
|
@@ -1235,20 +1240,20 @@ class MlrunProject(ModelObj):
|
|
|
1235
1240
|
self,
|
|
1236
1241
|
name,
|
|
1237
1242
|
workflow_path: str,
|
|
1238
|
-
embed=False,
|
|
1239
|
-
engine=None,
|
|
1240
|
-
args_schema:
|
|
1241
|
-
handler=None,
|
|
1243
|
+
embed: bool = False,
|
|
1244
|
+
engine: Optional[str] = None,
|
|
1245
|
+
args_schema: list[EntrypointParam] = None,
|
|
1246
|
+
handler: Optional[str] = None,
|
|
1242
1247
|
schedule: typing.Union[str, mlrun.common.schemas.ScheduleCronTrigger] = None,
|
|
1243
|
-
ttl=None,
|
|
1244
|
-
image: str = None,
|
|
1248
|
+
ttl: Optional[int] = None,
|
|
1249
|
+
image: Optional[str] = None,
|
|
1245
1250
|
**args,
|
|
1246
1251
|
):
|
|
1247
1252
|
"""Add or update a workflow, specify a name and the code path
|
|
1248
1253
|
|
|
1249
1254
|
:param name: Name of the workflow
|
|
1250
1255
|
:param workflow_path: URL (remote) / Path (absolute or relative to the project code path i.e.
|
|
1251
|
-
|
|
1256
|
+
<project.spec.get_code_path()>/<workflow_path>) for the workflow file.
|
|
1252
1257
|
:param embed: Add the workflow code into the project.yaml
|
|
1253
1258
|
:param engine: Workflow processing engine ("kfp", "local", "remote" or "remote:local")
|
|
1254
1259
|
:param args_schema: List of arg schema definitions (:py:class`~mlrun.model.EntrypointParam`)
|
|
@@ -2595,40 +2600,45 @@ class MlrunProject(ModelObj):
|
|
|
2595
2600
|
cleanup_ttl: int = None,
|
|
2596
2601
|
notifications: typing.List[mlrun.model.Notification] = None,
|
|
2597
2602
|
) -> _PipelineRunStatus:
|
|
2598
|
-
"""
|
|
2603
|
+
"""Run a workflow using kubeflow pipelines
|
|
2599
2604
|
|
|
2600
|
-
:param name:
|
|
2605
|
+
:param name: Name of the workflow
|
|
2601
2606
|
:param workflow_path:
|
|
2602
|
-
|
|
2607
|
+
URL to a workflow file, if not a project workflow
|
|
2603
2608
|
:param arguments:
|
|
2604
|
-
|
|
2609
|
+
Kubeflow pipelines arguments (parameters)
|
|
2605
2610
|
:param artifact_path:
|
|
2606
|
-
|
|
2611
|
+
Target path/url for workflow artifacts, the string
|
|
2607
2612
|
'{{workflow.uid}}' will be replaced by workflow id
|
|
2608
2613
|
:param workflow_handler:
|
|
2609
|
-
|
|
2610
|
-
:param namespace:
|
|
2611
|
-
:param sync:
|
|
2612
|
-
:param watch:
|
|
2613
|
-
:param dirty:
|
|
2614
|
-
:param engine:
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
:param local:
|
|
2614
|
+
Workflow function handler (for running workflow function directly)
|
|
2615
|
+
:param namespace: Kubernetes namespace if other than default
|
|
2616
|
+
:param sync: Force functions sync before run
|
|
2617
|
+
:param watch: Wait for pipeline completion
|
|
2618
|
+
:param dirty: Allow running the workflow when the git repo is dirty
|
|
2619
|
+
:param engine: Workflow engine running the workflow.
|
|
2620
|
+
Supported values are 'kfp' (default), 'local' or 'remote'.
|
|
2621
|
+
For setting engine for remote running use 'remote:local' or 'remote:kfp'.
|
|
2622
|
+
:param local: Run local pipeline with local functions (set local=True in function.run())
|
|
2618
2623
|
:param schedule: ScheduleCronTrigger class instance or a standard crontab expression string
|
|
2619
2624
|
(which will be converted to the class using its `from_crontab` constructor),
|
|
2620
2625
|
see this link for help:
|
|
2621
2626
|
https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
|
|
2622
2627
|
for using the pre-defined workflow's schedule, set `schedule=True`
|
|
2623
|
-
:param timeout:
|
|
2624
|
-
:param source:
|
|
2625
|
-
|
|
2628
|
+
:param timeout: Timeout in seconds to wait for pipeline completion (watch will be activated)
|
|
2629
|
+
:param source: Source to use instead of the actual `project.spec.source` (used when engine is remote).
|
|
2630
|
+
Can be a one of:
|
|
2631
|
+
1. Remote URL which is loaded dynamically to the workflow runner.
|
|
2632
|
+
2. A path to the project's context on the workflow runner's image.
|
|
2633
|
+
Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
|
|
2634
|
+
(enriched when building a project image with source, see `MlrunProject.build_image`).
|
|
2635
|
+
For other engines the source is used to validate that the code is up-to-date.
|
|
2626
2636
|
:param cleanup_ttl:
|
|
2627
|
-
|
|
2628
|
-
|
|
2637
|
+
Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
|
|
2638
|
+
Workflow and all its resources are deleted)
|
|
2629
2639
|
:param notifications:
|
|
2630
|
-
|
|
2631
|
-
:returns:
|
|
2640
|
+
List of notifications to send for workflow completion
|
|
2641
|
+
:returns: Run id
|
|
2632
2642
|
"""
|
|
2633
2643
|
|
|
2634
2644
|
arguments = arguments or {}
|
|
@@ -2775,7 +2785,7 @@ class MlrunProject(ModelObj):
|
|
|
2775
2785
|
def export(self, filepath=None, include_files: str = None):
|
|
2776
2786
|
"""save the project object into a yaml file or zip archive (default to project.yaml)
|
|
2777
2787
|
|
|
2778
|
-
By default the project object is exported to a yaml file, when the filepath suffix is '.zip'
|
|
2788
|
+
By default, the project object is exported to a yaml file, when the filepath suffix is '.zip'
|
|
2779
2789
|
the project context dir (code files) are also copied into the zip, the archive path can include
|
|
2780
2790
|
DataItem urls (for remote object storage, e.g. s3://<bucket>/<path>).
|
|
2781
2791
|
|
|
@@ -2800,19 +2810,19 @@ class MlrunProject(ModelObj):
|
|
|
2800
2810
|
|
|
2801
2811
|
if archive_code:
|
|
2802
2812
|
files_filter = include_files or "**"
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2813
|
+
with tempfile.NamedTemporaryFile(suffix=".zip") as f:
|
|
2814
|
+
remote_file = "://" in filepath
|
|
2815
|
+
fpath = f.name if remote_file else filepath
|
|
2816
|
+
with zipfile.ZipFile(fpath, "w") as zipf:
|
|
2817
|
+
for file_path in glob.iglob(
|
|
2818
|
+
f"{project_dir}/{files_filter}", recursive=True
|
|
2819
|
+
):
|
|
2820
|
+
write_path = pathlib.Path(file_path)
|
|
2821
|
+
zipf.write(
|
|
2822
|
+
write_path, arcname=write_path.relative_to(project_dir)
|
|
2823
|
+
)
|
|
2824
|
+
if remote_file:
|
|
2825
|
+
mlrun.get_dataitem(filepath).upload(zipf.filename)
|
|
2816
2826
|
|
|
2817
2827
|
def set_model_monitoring_credentials(
|
|
2818
2828
|
self,
|
|
@@ -3027,6 +3037,7 @@ class MlrunProject(ModelObj):
|
|
|
3027
3037
|
requirements_file: str = None,
|
|
3028
3038
|
builder_env: dict = None,
|
|
3029
3039
|
extra_args: str = None,
|
|
3040
|
+
source_code_target_dir: str = None,
|
|
3030
3041
|
):
|
|
3031
3042
|
"""specify builder configuration for the project
|
|
3032
3043
|
|
|
@@ -3047,6 +3058,8 @@ class MlrunProject(ModelObj):
|
|
|
3047
3058
|
e.g. builder_env={"GIT_TOKEN": token}, does not work yet in KFP
|
|
3048
3059
|
:param extra_args: A string containing additional builder arguments in the format of command-line options,
|
|
3049
3060
|
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3061
|
+
:param source_code_target_dir: Path on the image where source code would be extracted
|
|
3062
|
+
(by default `/home/mlrun_code`)
|
|
3050
3063
|
"""
|
|
3051
3064
|
if not overwrite_build_params:
|
|
3052
3065
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
@@ -3070,6 +3083,7 @@ class MlrunProject(ModelObj):
|
|
|
3070
3083
|
overwrite=overwrite_build_params,
|
|
3071
3084
|
builder_env=builder_env,
|
|
3072
3085
|
extra_args=extra_args,
|
|
3086
|
+
source_code_target_dir=source_code_target_dir,
|
|
3073
3087
|
)
|
|
3074
3088
|
|
|
3075
3089
|
if set_as_default and image != self.default_image:
|
|
@@ -3116,7 +3130,7 @@ class MlrunProject(ModelObj):
|
|
|
3116
3130
|
* False: The new params are merged with the existing
|
|
3117
3131
|
* True: The existing params are replaced by the new ones
|
|
3118
3132
|
:param extra_args: A string containing additional builder arguments in the format of command-line options,
|
|
3119
|
-
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3133
|
+
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3120
3134
|
:param target_dir: Path on the image where source code would be extracted (by default `/home/mlrun_code`)
|
|
3121
3135
|
"""
|
|
3122
3136
|
if not base_image:
|
|
@@ -3184,6 +3198,11 @@ class MlrunProject(ModelObj):
|
|
|
3184
3198
|
force_build=True,
|
|
3185
3199
|
)
|
|
3186
3200
|
|
|
3201
|
+
# Get the enriched target dir from the function
|
|
3202
|
+
self.spec.build.source_code_target_dir = (
|
|
3203
|
+
function.spec.build.source_code_target_dir
|
|
3204
|
+
)
|
|
3205
|
+
|
|
3187
3206
|
try:
|
|
3188
3207
|
mlrun.db.get_run_db(secrets=self._secrets).delete_function(
|
|
3189
3208
|
name=function.metadata.name
|
mlrun/runtimes/base.py
CHANGED
|
@@ -15,6 +15,7 @@ import enum
|
|
|
15
15
|
import http
|
|
16
16
|
import re
|
|
17
17
|
import typing
|
|
18
|
+
import warnings
|
|
18
19
|
from base64 import b64encode
|
|
19
20
|
from os import environ
|
|
20
21
|
from typing import Callable, Dict, List, Optional, Union
|
|
@@ -124,7 +125,7 @@ class FunctionSpec(ModelObj):
|
|
|
124
125
|
self.allow_empty_resources = None
|
|
125
126
|
# the build.source is cloned/extracted to the specified clone_target_dir
|
|
126
127
|
# if a relative path is specified, it will be enriched with a temp dir path
|
|
127
|
-
self.
|
|
128
|
+
self._clone_target_dir = clone_target_dir or None
|
|
128
129
|
|
|
129
130
|
@property
|
|
130
131
|
def build(self) -> ImageBuilder:
|
|
@@ -134,6 +135,24 @@ class FunctionSpec(ModelObj):
|
|
|
134
135
|
def build(self, build):
|
|
135
136
|
self._build = self._verify_dict(build, "build", ImageBuilder)
|
|
136
137
|
|
|
138
|
+
@property
|
|
139
|
+
def clone_target_dir(self):
|
|
140
|
+
warnings.warn(
|
|
141
|
+
"The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.8.0. "
|
|
142
|
+
"Use spec.build.source_code_target_dir instead.",
|
|
143
|
+
FutureWarning,
|
|
144
|
+
)
|
|
145
|
+
return self.build.source_code_target_dir
|
|
146
|
+
|
|
147
|
+
@clone_target_dir.setter
|
|
148
|
+
def clone_target_dir(self, clone_target_dir):
|
|
149
|
+
warnings.warn(
|
|
150
|
+
"The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.8.0. "
|
|
151
|
+
"Use spec.build.source_code_target_dir instead.",
|
|
152
|
+
FutureWarning,
|
|
153
|
+
)
|
|
154
|
+
self.build.source_code_target_dir = clone_target_dir
|
|
155
|
+
|
|
137
156
|
def enrich_function_preemption_spec(self):
|
|
138
157
|
pass
|
|
139
158
|
|
mlrun/runtimes/function.py
CHANGED
|
@@ -432,15 +432,15 @@ class RemoteRuntime(KubeResource):
|
|
|
432
432
|
raise ValueError(
|
|
433
433
|
"gateway timeout must be greater than the worker timeout"
|
|
434
434
|
)
|
|
435
|
-
annotations[
|
|
436
|
-
"
|
|
437
|
-
|
|
438
|
-
annotations[
|
|
439
|
-
"
|
|
440
|
-
|
|
441
|
-
annotations[
|
|
442
|
-
"
|
|
443
|
-
|
|
435
|
+
annotations["nginx.ingress.kubernetes.io/proxy-connect-timeout"] = (
|
|
436
|
+
f"{gateway_timeout}"
|
|
437
|
+
)
|
|
438
|
+
annotations["nginx.ingress.kubernetes.io/proxy-read-timeout"] = (
|
|
439
|
+
f"{gateway_timeout}"
|
|
440
|
+
)
|
|
441
|
+
annotations["nginx.ingress.kubernetes.io/proxy-send-timeout"] = (
|
|
442
|
+
f"{gateway_timeout}"
|
|
443
|
+
)
|
|
444
444
|
|
|
445
445
|
trigger = nuclio.HttpTrigger(
|
|
446
446
|
workers=workers,
|
mlrun/runtimes/kubejob.py
CHANGED
|
@@ -73,7 +73,7 @@ class KubejobRuntime(KubeResource):
|
|
|
73
73
|
if workdir:
|
|
74
74
|
self.spec.workdir = workdir
|
|
75
75
|
if target_dir:
|
|
76
|
-
self.spec.
|
|
76
|
+
self.spec.build.source_code_target_dir = target_dir
|
|
77
77
|
|
|
78
78
|
self.spec.build.load_source_on_run = pull_at_runtime
|
|
79
79
|
if (
|
|
@@ -232,8 +232,10 @@ class KubejobRuntime(KubeResource):
|
|
|
232
232
|
self.spec.build.base_image = self.spec.build.base_image or get_in(
|
|
233
233
|
data, "data.spec.build.base_image"
|
|
234
234
|
)
|
|
235
|
-
#
|
|
236
|
-
self.spec.
|
|
235
|
+
# Get the source target dir in case it was enriched due to loading source
|
|
236
|
+
self.spec.build.source_code_target_dir = get_in(
|
|
237
|
+
data, "data.spec.build.source_code_target_dir"
|
|
238
|
+
) or get_in(data, "data.spec.clone_target_dir")
|
|
237
239
|
ready = data.get("ready", False)
|
|
238
240
|
if not ready:
|
|
239
241
|
logger.info(
|
mlrun/runtimes/local.py
CHANGED
|
@@ -218,7 +218,7 @@ class LocalRuntime(BaseRuntime, ParallelRunner):
|
|
|
218
218
|
if workdir:
|
|
219
219
|
self.spec.workdir = workdir
|
|
220
220
|
if target_dir:
|
|
221
|
-
self.spec.
|
|
221
|
+
self.spec.build.source_code_target_dir = target_dir
|
|
222
222
|
|
|
223
223
|
def is_deployed(self):
|
|
224
224
|
return True
|
|
@@ -240,7 +240,7 @@ class LocalRuntime(BaseRuntime, ParallelRunner):
|
|
|
240
240
|
if self.spec.build.source and not hasattr(self, "_is_run_local"):
|
|
241
241
|
target_dir = extract_source(
|
|
242
242
|
self.spec.build.source,
|
|
243
|
-
self.spec.
|
|
243
|
+
self.spec.build.source_code_target_dir,
|
|
244
244
|
secrets=execution._secrets_manager,
|
|
245
245
|
)
|
|
246
246
|
if workdir and not workdir.startswith("/"):
|
|
@@ -196,13 +196,13 @@ class AbstractMPIJobRuntime(KubejobRuntime, abc.ABC):
|
|
|
196
196
|
if steps_per_sample is not None:
|
|
197
197
|
horovod_autotune_settings["autotune-steps-per-sample"] = steps_per_sample
|
|
198
198
|
if bayes_opt_max_samples is not None:
|
|
199
|
-
horovod_autotune_settings[
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
horovod_autotune_settings["autotune-bayes-opt-max-samples"] = (
|
|
200
|
+
bayes_opt_max_samples
|
|
201
|
+
)
|
|
202
202
|
if gaussian_process_noise is not None:
|
|
203
|
-
horovod_autotune_settings[
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
horovod_autotune_settings["autotune-gaussian-process-noise"] = (
|
|
204
|
+
gaussian_process_noise
|
|
205
|
+
)
|
|
206
206
|
|
|
207
207
|
self.set_envs(horovod_autotune_settings)
|
|
208
208
|
|
mlrun/runtimes/pod.py
CHANGED
|
@@ -430,9 +430,9 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
430
430
|
)
|
|
431
431
|
is None
|
|
432
432
|
):
|
|
433
|
-
resources[resource_requirement][
|
|
434
|
-
resource_type
|
|
435
|
-
|
|
433
|
+
resources[resource_requirement][resource_type] = (
|
|
434
|
+
default_resources[resource_requirement][resource_type]
|
|
435
|
+
)
|
|
436
436
|
# This enables the user to define that no defaults would be applied on the resources
|
|
437
437
|
elif resources == {}:
|
|
438
438
|
return resources
|
mlrun/runtimes/serving.py
CHANGED
|
@@ -523,9 +523,9 @@ class ServingRuntime(RemoteRuntime):
|
|
|
523
523
|
function_object.metadata.tag = self.metadata.tag
|
|
524
524
|
|
|
525
525
|
function_object.metadata.labels = function_object.metadata.labels or {}
|
|
526
|
-
function_object.metadata.labels[
|
|
527
|
-
|
|
528
|
-
|
|
526
|
+
function_object.metadata.labels["mlrun/parent-function"] = (
|
|
527
|
+
self.metadata.name
|
|
528
|
+
)
|
|
529
529
|
function_object._is_child_function = True
|
|
530
530
|
if not function_object.spec.graph:
|
|
531
531
|
# copy the current graph only if the child doesnt have a graph of his own
|
|
@@ -345,9 +345,9 @@ class Spark3JobSpec(KubeResourceSpec):
|
|
|
345
345
|
)
|
|
346
346
|
is None
|
|
347
347
|
):
|
|
348
|
-
resources[resource_requirement][
|
|
349
|
-
resource_type
|
|
350
|
-
|
|
348
|
+
resources[resource_requirement][resource_type] = (
|
|
349
|
+
default_resources[resource_requirement][resource_type]
|
|
350
|
+
)
|
|
351
351
|
else:
|
|
352
352
|
resources = default_resources
|
|
353
353
|
|
mlrun/serving/remote.py
CHANGED
|
@@ -21,6 +21,7 @@ import storey
|
|
|
21
21
|
from storey.flow import _ConcurrentJobExecution
|
|
22
22
|
|
|
23
23
|
import mlrun
|
|
24
|
+
import mlrun.config
|
|
24
25
|
from mlrun.errors import err_to_str
|
|
25
26
|
from mlrun.utils import logger
|
|
26
27
|
|
|
@@ -173,7 +174,8 @@ class RemoteStep(storey.SendToHttp):
|
|
|
173
174
|
if not self._session:
|
|
174
175
|
self._session = mlrun.utils.HTTPSessionWithRetry(
|
|
175
176
|
self.retries,
|
|
176
|
-
self.backoff_factor
|
|
177
|
+
self.backoff_factor
|
|
178
|
+
or mlrun.config.config.http_retry_defaults.backoff_factor,
|
|
177
179
|
retry_on_exception=False,
|
|
178
180
|
retry_on_status=self.retries > 0,
|
|
179
181
|
retry_on_post=True,
|
|
@@ -185,7 +187,7 @@ class RemoteStep(storey.SendToHttp):
|
|
|
185
187
|
resp = self._session.request(
|
|
186
188
|
method,
|
|
187
189
|
url,
|
|
188
|
-
verify=
|
|
190
|
+
verify=mlrun.config.config.httpdb.http.verify,
|
|
189
191
|
headers=headers,
|
|
190
192
|
data=body,
|
|
191
193
|
timeout=self.timeout,
|
mlrun/utils/async_http.py
CHANGED
|
@@ -139,9 +139,9 @@ class _CustomRequestContext(_RequestContext):
|
|
|
139
139
|
|
|
140
140
|
# enrich user agent
|
|
141
141
|
# will help traceability and debugging
|
|
142
|
-
headers[
|
|
143
|
-
aiohttp.
|
|
144
|
-
|
|
142
|
+
headers[aiohttp.hdrs.USER_AGENT] = (
|
|
143
|
+
f"{aiohttp.http.SERVER_SOFTWARE} mlrun/{config.version}"
|
|
144
|
+
)
|
|
145
145
|
|
|
146
146
|
response: typing.Optional[
|
|
147
147
|
aiohttp.ClientResponse
|
mlrun/utils/helpers.py
CHANGED
|
@@ -1622,3 +1622,11 @@ def get_local_file_schema() -> List:
|
|
|
1622
1622
|
# The expression `list(string.ascii_lowercase)` generates a list of lowercase alphabets,
|
|
1623
1623
|
# which corresponds to drive letters in Windows file paths such as `C:/Windows/path`.
|
|
1624
1624
|
return ["file"] + list(string.ascii_lowercase)
|
|
1625
|
+
|
|
1626
|
+
|
|
1627
|
+
def is_safe_path(base, filepath, is_symlink=False):
|
|
1628
|
+
# Avoid path traversal attacks by ensuring that the path is safe
|
|
1629
|
+
resolved_filepath = (
|
|
1630
|
+
os.path.abspath(filepath) if not is_symlink else os.path.realpath(filepath)
|
|
1631
|
+
)
|
|
1632
|
+
return base == os.path.commonpath((base, resolved_filepath))
|
mlrun/utils/http.py
CHANGED
|
@@ -110,9 +110,9 @@ class HTTPSessionWithRetry(requests.Session):
|
|
|
110
110
|
def request(self, method, url, **kwargs):
|
|
111
111
|
retry_count = 0
|
|
112
112
|
kwargs.setdefault("headers", {})
|
|
113
|
-
kwargs["headers"][
|
|
114
|
-
"
|
|
115
|
-
|
|
113
|
+
kwargs["headers"]["User-Agent"] = (
|
|
114
|
+
f"{requests.utils.default_user_agent()} mlrun/{config.version}"
|
|
115
|
+
)
|
|
116
116
|
while True:
|
|
117
117
|
try:
|
|
118
118
|
response = super().request(method, url, **kwargs)
|