mlrun 1.3.3__py3-none-any.whl → 1.4.0__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/__init__.py +3 -3
- mlrun/__main__.py +79 -37
- mlrun/api/__init__.py +1 -1
- mlrun/api/api/__init__.py +1 -1
- mlrun/api/api/api.py +4 -4
- mlrun/api/api/deps.py +10 -21
- mlrun/api/api/endpoints/__init__.py +1 -1
- mlrun/api/api/endpoints/artifacts.py +64 -36
- mlrun/api/api/endpoints/auth.py +4 -4
- mlrun/api/api/endpoints/background_tasks.py +11 -11
- mlrun/api/api/endpoints/client_spec.py +5 -5
- mlrun/api/api/endpoints/clusterization_spec.py +6 -4
- mlrun/api/api/endpoints/feature_store.py +124 -115
- mlrun/api/api/endpoints/files.py +22 -14
- mlrun/api/api/endpoints/frontend_spec.py +28 -21
- mlrun/api/api/endpoints/functions.py +142 -87
- mlrun/api/api/endpoints/grafana_proxy.py +89 -442
- mlrun/api/api/endpoints/healthz.py +20 -7
- mlrun/api/api/endpoints/hub.py +320 -0
- mlrun/api/api/endpoints/internal/__init__.py +1 -1
- mlrun/api/api/endpoints/internal/config.py +1 -1
- mlrun/api/api/endpoints/internal/memory_reports.py +9 -9
- mlrun/api/api/endpoints/logs.py +11 -11
- mlrun/api/api/endpoints/model_endpoints.py +74 -70
- mlrun/api/api/endpoints/operations.py +13 -9
- mlrun/api/api/endpoints/pipelines.py +93 -88
- mlrun/api/api/endpoints/projects.py +35 -35
- mlrun/api/api/endpoints/runs.py +69 -27
- mlrun/api/api/endpoints/runtime_resources.py +28 -28
- mlrun/api/api/endpoints/schedules.py +98 -41
- mlrun/api/api/endpoints/secrets.py +37 -32
- mlrun/api/api/endpoints/submit.py +12 -12
- mlrun/api/api/endpoints/tags.py +20 -22
- mlrun/api/api/utils.py +251 -42
- mlrun/api/constants.py +1 -1
- mlrun/api/crud/__init__.py +18 -15
- mlrun/api/crud/artifacts.py +10 -10
- mlrun/api/crud/client_spec.py +4 -4
- mlrun/api/crud/clusterization_spec.py +3 -3
- mlrun/api/crud/feature_store.py +54 -46
- mlrun/api/crud/functions.py +3 -3
- mlrun/api/crud/hub.py +312 -0
- mlrun/api/crud/logs.py +11 -9
- mlrun/api/crud/model_monitoring/__init__.py +3 -3
- mlrun/api/crud/model_monitoring/grafana.py +435 -0
- mlrun/api/crud/model_monitoring/model_endpoints.py +352 -129
- mlrun/api/crud/notifications.py +149 -0
- mlrun/api/crud/pipelines.py +67 -52
- mlrun/api/crud/projects.py +51 -23
- mlrun/api/crud/runs.py +7 -5
- mlrun/api/crud/runtime_resources.py +13 -13
- mlrun/api/{db/filedb → crud/runtimes}/__init__.py +1 -1
- mlrun/api/crud/runtimes/nuclio/__init__.py +14 -0
- mlrun/api/crud/runtimes/nuclio/function.py +505 -0
- mlrun/api/crud/runtimes/nuclio/helpers.py +310 -0
- mlrun/api/crud/secrets.py +88 -46
- mlrun/api/crud/tags.py +5 -5
- mlrun/api/db/__init__.py +1 -1
- mlrun/api/db/base.py +102 -54
- mlrun/api/db/init_db.py +2 -3
- mlrun/api/db/session.py +4 -12
- mlrun/api/db/sqldb/__init__.py +1 -1
- mlrun/api/db/sqldb/db.py +439 -196
- mlrun/api/db/sqldb/helpers.py +1 -1
- mlrun/api/db/sqldb/models/__init__.py +3 -3
- mlrun/api/db/sqldb/models/models_mysql.py +82 -64
- mlrun/api/db/sqldb/models/models_sqlite.py +76 -64
- mlrun/api/db/sqldb/session.py +27 -20
- mlrun/api/initial_data.py +82 -24
- mlrun/api/launcher.py +196 -0
- mlrun/api/main.py +91 -22
- mlrun/api/middlewares.py +6 -5
- mlrun/api/migrations_mysql/env.py +1 -1
- mlrun/api/migrations_mysql/versions/28383af526f3_market_place_to_hub.py +40 -0
- mlrun/api/migrations_mysql/versions/32bae1b0e29c_increase_timestamp_fields_precision.py +1 -1
- mlrun/api/migrations_mysql/versions/4903aef6a91d_tag_foreign_key_and_cascades.py +1 -1
- mlrun/api/migrations_mysql/versions/5f1351c88a19_adding_background_tasks_table.py +1 -1
- mlrun/api/migrations_mysql/versions/88e656800d6a_add_requested_logs_column_and_index_to_.py +1 -1
- mlrun/api/migrations_mysql/versions/9d16de5f03a7_adding_data_versions_table.py +1 -1
- mlrun/api/migrations_mysql/versions/b86f5b53f3d7_adding_name_and_updated_to_runs_table.py +1 -1
- mlrun/api/migrations_mysql/versions/c4af40b0bf61_init.py +1 -1
- mlrun/api/migrations_mysql/versions/c905d15bd91d_notifications.py +72 -0
- mlrun/api/migrations_mysql/versions/ee041e8fdaa0_adding_next_run_time_column_to_schedule_.py +1 -1
- mlrun/api/migrations_sqlite/env.py +1 -1
- mlrun/api/migrations_sqlite/versions/11f8dd2dc9fe_init.py +1 -1
- mlrun/api/migrations_sqlite/versions/1c954f8cb32d_schedule_last_run_uri.py +1 -1
- mlrun/api/migrations_sqlite/versions/2b6d23c715aa_adding_feature_sets.py +1 -1
- mlrun/api/migrations_sqlite/versions/4acd9430b093_market_place_to_hub.py +77 -0
- mlrun/api/migrations_sqlite/versions/6401142f2d7c_adding_next_run_time_column_to_schedule_.py +1 -1
- mlrun/api/migrations_sqlite/versions/64d90a1a69bc_adding_background_tasks_table.py +1 -1
- mlrun/api/migrations_sqlite/versions/803438ecd005_add_requested_logs_column_to_runs.py +1 -1
- mlrun/api/migrations_sqlite/versions/863114f0c659_refactoring_feature_set.py +1 -1
- mlrun/api/migrations_sqlite/versions/959ae00528ad_notifications.py +63 -0
- mlrun/api/migrations_sqlite/versions/accf9fc83d38_adding_data_versions_table.py +1 -1
- mlrun/api/migrations_sqlite/versions/b68e8e897a28_schedule_labels.py +1 -1
- mlrun/api/migrations_sqlite/versions/bcd0c1f9720c_adding_project_labels.py +1 -1
- mlrun/api/migrations_sqlite/versions/cf21882f938e_schedule_id.py +1 -1
- mlrun/api/migrations_sqlite/versions/d781f58f607f_tag_object_name_string.py +1 -1
- mlrun/api/migrations_sqlite/versions/deac06871ace_adding_marketplace_sources_table.py +1 -1
- mlrun/api/migrations_sqlite/versions/e1dd5983c06b_schedule_concurrency_limit.py +1 -1
- mlrun/api/migrations_sqlite/versions/e5594ed3ab53_adding_name_and_updated_to_runs_table.py +1 -1
- mlrun/api/migrations_sqlite/versions/f4249b4ba6fa_adding_feature_vectors.py +1 -1
- mlrun/api/migrations_sqlite/versions/f7b5a1a03629_adding_feature_labels.py +1 -1
- mlrun/api/schemas/__init__.py +216 -138
- mlrun/api/utils/__init__.py +1 -1
- mlrun/api/utils/asyncio.py +1 -1
- mlrun/api/utils/auth/__init__.py +1 -1
- mlrun/api/utils/auth/providers/__init__.py +1 -1
- mlrun/api/utils/auth/providers/base.py +7 -7
- mlrun/api/utils/auth/providers/nop.py +6 -7
- mlrun/api/utils/auth/providers/opa.py +17 -17
- mlrun/api/utils/auth/verifier.py +36 -34
- mlrun/api/utils/background_tasks.py +24 -24
- mlrun/{builder.py → api/utils/builder.py} +216 -123
- mlrun/api/utils/clients/__init__.py +1 -1
- mlrun/api/utils/clients/chief.py +19 -4
- mlrun/api/utils/clients/iguazio.py +106 -60
- mlrun/api/utils/clients/log_collector.py +1 -1
- mlrun/api/utils/clients/nuclio.py +23 -23
- mlrun/api/utils/clients/protocols/grpc.py +2 -2
- mlrun/api/utils/db/__init__.py +1 -1
- mlrun/api/utils/db/alembic.py +1 -1
- mlrun/api/utils/db/backup.py +1 -1
- mlrun/api/utils/db/mysql.py +24 -25
- mlrun/api/utils/db/sql_collation.py +1 -1
- mlrun/api/utils/db/sqlite_migration.py +2 -2
- mlrun/api/utils/events/__init__.py +14 -0
- mlrun/api/utils/events/base.py +57 -0
- mlrun/api/utils/events/events_factory.py +41 -0
- mlrun/api/utils/events/iguazio.py +217 -0
- mlrun/api/utils/events/nop.py +55 -0
- mlrun/api/utils/helpers.py +16 -13
- mlrun/api/utils/memory_reports.py +1 -1
- mlrun/api/utils/periodic.py +6 -3
- mlrun/api/utils/projects/__init__.py +1 -1
- mlrun/api/utils/projects/follower.py +33 -33
- mlrun/api/utils/projects/leader.py +36 -34
- mlrun/api/utils/projects/member.py +27 -27
- mlrun/api/utils/projects/remotes/__init__.py +1 -1
- mlrun/api/utils/projects/remotes/follower.py +13 -13
- mlrun/api/utils/projects/remotes/leader.py +10 -10
- mlrun/api/utils/projects/remotes/nop_follower.py +27 -21
- mlrun/api/utils/projects/remotes/nop_leader.py +17 -16
- mlrun/api/utils/scheduler.py +140 -51
- mlrun/api/utils/singletons/__init__.py +1 -1
- mlrun/api/utils/singletons/db.py +9 -15
- mlrun/api/utils/singletons/k8s.py +677 -5
- mlrun/api/utils/singletons/logs_dir.py +1 -1
- mlrun/api/utils/singletons/project_member.py +1 -1
- mlrun/api/utils/singletons/scheduler.py +1 -1
- mlrun/artifacts/__init__.py +2 -2
- mlrun/artifacts/base.py +8 -2
- mlrun/artifacts/dataset.py +5 -3
- mlrun/artifacts/manager.py +7 -1
- mlrun/artifacts/model.py +15 -4
- mlrun/artifacts/plots.py +1 -1
- mlrun/common/__init__.py +1 -1
- mlrun/common/constants.py +15 -0
- mlrun/common/model_monitoring.py +209 -0
- mlrun/common/schemas/__init__.py +167 -0
- mlrun/{api → common}/schemas/artifact.py +13 -14
- mlrun/{api → common}/schemas/auth.py +10 -8
- mlrun/{api → common}/schemas/background_task.py +3 -3
- mlrun/{api → common}/schemas/client_spec.py +1 -1
- mlrun/{api → common}/schemas/clusterization_spec.py +3 -3
- mlrun/{api → common}/schemas/constants.py +21 -8
- mlrun/common/schemas/events.py +36 -0
- mlrun/{api → common}/schemas/feature_store.py +2 -1
- mlrun/{api → common}/schemas/frontend_spec.py +7 -6
- mlrun/{api → common}/schemas/function.py +5 -5
- mlrun/{api → common}/schemas/http.py +3 -3
- mlrun/common/schemas/hub.py +134 -0
- mlrun/{api → common}/schemas/k8s.py +3 -3
- mlrun/{api → common}/schemas/memory_reports.py +1 -1
- mlrun/common/schemas/model_endpoints.py +342 -0
- mlrun/common/schemas/notification.py +57 -0
- mlrun/{api → common}/schemas/object.py +6 -6
- mlrun/{api → common}/schemas/pipeline.py +3 -3
- mlrun/{api → common}/schemas/project.py +6 -5
- mlrun/common/schemas/regex.py +24 -0
- mlrun/common/schemas/runs.py +30 -0
- mlrun/{api → common}/schemas/runtime_resource.py +3 -3
- mlrun/{api → common}/schemas/schedule.py +19 -7
- mlrun/{api → common}/schemas/secret.py +3 -3
- mlrun/{api → common}/schemas/tag.py +2 -2
- mlrun/common/types.py +25 -0
- mlrun/config.py +152 -20
- mlrun/data_types/__init__.py +7 -2
- mlrun/data_types/data_types.py +4 -2
- mlrun/data_types/infer.py +1 -1
- mlrun/data_types/spark.py +10 -3
- mlrun/datastore/__init__.py +10 -3
- mlrun/datastore/azure_blob.py +1 -1
- mlrun/datastore/base.py +185 -53
- mlrun/datastore/datastore.py +1 -1
- mlrun/datastore/filestore.py +1 -1
- mlrun/datastore/google_cloud_storage.py +1 -1
- mlrun/datastore/inmem.py +4 -1
- mlrun/datastore/redis.py +1 -1
- mlrun/datastore/s3.py +1 -1
- mlrun/datastore/sources.py +192 -70
- mlrun/datastore/spark_udf.py +44 -0
- mlrun/datastore/store_resources.py +4 -4
- mlrun/datastore/targets.py +115 -45
- mlrun/datastore/utils.py +127 -5
- mlrun/datastore/v3io.py +1 -1
- mlrun/datastore/wasbfs/__init__.py +1 -1
- mlrun/datastore/wasbfs/fs.py +1 -1
- mlrun/db/__init__.py +7 -5
- mlrun/db/base.py +112 -68
- mlrun/db/httpdb.py +445 -277
- mlrun/db/nopdb.py +491 -0
- mlrun/db/sqldb.py +112 -65
- mlrun/errors.py +6 -1
- mlrun/execution.py +44 -22
- mlrun/feature_store/__init__.py +1 -1
- mlrun/feature_store/api.py +143 -95
- mlrun/feature_store/common.py +16 -20
- mlrun/feature_store/feature_set.py +42 -12
- mlrun/feature_store/feature_vector.py +32 -21
- mlrun/feature_store/ingestion.py +9 -12
- mlrun/feature_store/retrieval/__init__.py +3 -2
- mlrun/feature_store/retrieval/base.py +388 -66
- mlrun/feature_store/retrieval/dask_merger.py +63 -151
- mlrun/feature_store/retrieval/job.py +30 -12
- mlrun/feature_store/retrieval/local_merger.py +40 -133
- mlrun/feature_store/retrieval/spark_merger.py +129 -127
- mlrun/feature_store/retrieval/storey_merger.py +173 -0
- mlrun/feature_store/steps.py +132 -15
- mlrun/features.py +8 -3
- mlrun/frameworks/__init__.py +1 -1
- mlrun/frameworks/_common/__init__.py +1 -1
- mlrun/frameworks/_common/artifacts_library.py +1 -1
- mlrun/frameworks/_common/mlrun_interface.py +1 -1
- mlrun/frameworks/_common/model_handler.py +1 -1
- mlrun/frameworks/_common/plan.py +1 -1
- mlrun/frameworks/_common/producer.py +1 -1
- mlrun/frameworks/_common/utils.py +1 -1
- mlrun/frameworks/_dl_common/__init__.py +1 -1
- mlrun/frameworks/_dl_common/loggers/__init__.py +1 -1
- mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +1 -1
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +1 -1
- mlrun/frameworks/_dl_common/model_handler.py +1 -1
- mlrun/frameworks/_dl_common/utils.py +1 -1
- mlrun/frameworks/_ml_common/__init__.py +1 -1
- mlrun/frameworks/_ml_common/artifacts_library.py +1 -1
- mlrun/frameworks/_ml_common/loggers/__init__.py +1 -1
- mlrun/frameworks/_ml_common/loggers/logger.py +1 -1
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
- mlrun/frameworks/_ml_common/model_handler.py +1 -1
- mlrun/frameworks/_ml_common/pkl_model_server.py +13 -1
- mlrun/frameworks/_ml_common/plan.py +1 -1
- mlrun/frameworks/_ml_common/plans/__init__.py +1 -1
- mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +1 -6
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +1 -1
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +1 -1
- mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +1 -1
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +1 -1
- mlrun/frameworks/_ml_common/producer.py +1 -1
- mlrun/frameworks/_ml_common/utils.py +1 -1
- mlrun/frameworks/auto_mlrun/__init__.py +1 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +1 -1
- mlrun/frameworks/huggingface/__init__.py +1 -1
- mlrun/frameworks/huggingface/model_server.py +1 -1
- mlrun/frameworks/lgbm/__init__.py +1 -1
- mlrun/frameworks/lgbm/callbacks/__init__.py +1 -1
- mlrun/frameworks/lgbm/callbacks/callback.py +1 -1
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +1 -1
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +1 -1
- mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -1
- mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -1
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +1 -1
- mlrun/frameworks/lgbm/mlrun_interfaces/model_mlrun_interface.py +1 -1
- mlrun/frameworks/lgbm/model_handler.py +1 -1
- mlrun/frameworks/lgbm/model_server.py +1 -1
- mlrun/frameworks/lgbm/utils.py +1 -1
- mlrun/frameworks/onnx/__init__.py +1 -1
- mlrun/frameworks/onnx/dataset.py +1 -1
- mlrun/frameworks/onnx/mlrun_interface.py +1 -1
- mlrun/frameworks/onnx/model_handler.py +1 -1
- mlrun/frameworks/onnx/model_server.py +1 -1
- mlrun/frameworks/parallel_coordinates.py +1 -1
- mlrun/frameworks/pytorch/__init__.py +1 -1
- mlrun/frameworks/pytorch/callbacks/__init__.py +1 -1
- mlrun/frameworks/pytorch/callbacks/callback.py +1 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +1 -1
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +1 -1
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +1 -1
- mlrun/frameworks/pytorch/callbacks_handler.py +1 -1
- mlrun/frameworks/pytorch/mlrun_interface.py +1 -1
- mlrun/frameworks/pytorch/model_handler.py +1 -1
- mlrun/frameworks/pytorch/model_server.py +1 -1
- mlrun/frameworks/pytorch/utils.py +1 -1
- mlrun/frameworks/sklearn/__init__.py +1 -1
- mlrun/frameworks/sklearn/estimator.py +1 -1
- mlrun/frameworks/sklearn/metric.py +1 -1
- mlrun/frameworks/sklearn/metrics_library.py +1 -1
- mlrun/frameworks/sklearn/mlrun_interface.py +1 -1
- mlrun/frameworks/sklearn/model_handler.py +1 -1
- mlrun/frameworks/sklearn/utils.py +1 -1
- mlrun/frameworks/tf_keras/__init__.py +1 -1
- mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -1
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +1 -1
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +1 -1
- mlrun/frameworks/tf_keras/mlrun_interface.py +1 -1
- mlrun/frameworks/tf_keras/model_handler.py +1 -1
- mlrun/frameworks/tf_keras/model_server.py +1 -1
- mlrun/frameworks/tf_keras/utils.py +1 -1
- mlrun/frameworks/xgboost/__init__.py +1 -1
- mlrun/frameworks/xgboost/mlrun_interface.py +1 -1
- mlrun/frameworks/xgboost/model_handler.py +1 -1
- mlrun/frameworks/xgboost/utils.py +1 -1
- mlrun/k8s_utils.py +14 -765
- mlrun/kfpops.py +14 -17
- mlrun/launcher/__init__.py +13 -0
- mlrun/launcher/base.py +406 -0
- mlrun/launcher/client.py +159 -0
- mlrun/launcher/factory.py +50 -0
- mlrun/launcher/local.py +276 -0
- mlrun/launcher/remote.py +178 -0
- mlrun/lists.py +10 -2
- mlrun/mlutils/__init__.py +1 -1
- mlrun/mlutils/data.py +1 -1
- mlrun/mlutils/models.py +1 -1
- mlrun/mlutils/plots.py +1 -1
- mlrun/model.py +252 -14
- mlrun/model_monitoring/__init__.py +41 -0
- mlrun/model_monitoring/features_drift_table.py +1 -1
- mlrun/model_monitoring/helpers.py +123 -38
- mlrun/model_monitoring/model_endpoint.py +144 -0
- mlrun/model_monitoring/model_monitoring_batch.py +310 -259
- mlrun/model_monitoring/stores/__init__.py +106 -0
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +448 -0
- mlrun/model_monitoring/stores/model_endpoint_store.py +147 -0
- mlrun/model_monitoring/stores/models/__init__.py +23 -0
- mlrun/model_monitoring/stores/models/base.py +18 -0
- mlrun/model_monitoring/stores/models/mysql.py +100 -0
- mlrun/model_monitoring/stores/models/sqlite.py +98 -0
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +370 -0
- mlrun/model_monitoring/stream_processing_fs.py +239 -271
- mlrun/package/__init__.py +163 -0
- mlrun/package/context_handler.py +325 -0
- mlrun/package/errors.py +47 -0
- mlrun/package/packager.py +298 -0
- mlrun/{runtimes/package → package/packagers}/__init__.py +3 -1
- mlrun/package/packagers/default_packager.py +422 -0
- mlrun/package/packagers/numpy_packagers.py +612 -0
- mlrun/package/packagers/pandas_packagers.py +968 -0
- mlrun/package/packagers/python_standard_library_packagers.py +616 -0
- mlrun/package/packagers_manager.py +786 -0
- mlrun/package/utils/__init__.py +53 -0
- mlrun/package/utils/_archiver.py +226 -0
- mlrun/package/utils/_formatter.py +211 -0
- mlrun/package/utils/_pickler.py +234 -0
- mlrun/package/utils/_supported_format.py +71 -0
- mlrun/package/utils/log_hint_utils.py +93 -0
- mlrun/package/utils/type_hint_utils.py +298 -0
- mlrun/platforms/__init__.py +1 -1
- mlrun/platforms/iguazio.py +34 -2
- mlrun/platforms/other.py +1 -1
- mlrun/projects/__init__.py +1 -1
- mlrun/projects/operations.py +14 -9
- mlrun/projects/pipelines.py +31 -13
- mlrun/projects/project.py +762 -238
- mlrun/render.py +49 -19
- mlrun/run.py +57 -326
- mlrun/runtimes/__init__.py +3 -9
- mlrun/runtimes/base.py +247 -784
- mlrun/runtimes/constants.py +1 -1
- mlrun/runtimes/daskjob.py +45 -41
- mlrun/runtimes/funcdoc.py +43 -7
- mlrun/runtimes/function.py +66 -656
- mlrun/runtimes/function_reference.py +1 -1
- mlrun/runtimes/generators.py +1 -1
- mlrun/runtimes/kubejob.py +99 -116
- mlrun/runtimes/local.py +59 -66
- mlrun/runtimes/mpijob/__init__.py +1 -1
- mlrun/runtimes/mpijob/abstract.py +13 -15
- mlrun/runtimes/mpijob/v1.py +3 -1
- mlrun/runtimes/mpijob/v1alpha1.py +1 -1
- mlrun/runtimes/nuclio.py +1 -1
- mlrun/runtimes/pod.py +51 -26
- mlrun/runtimes/remotesparkjob.py +3 -1
- mlrun/runtimes/serving.py +12 -4
- mlrun/runtimes/sparkjob/__init__.py +1 -2
- mlrun/runtimes/sparkjob/abstract.py +44 -31
- mlrun/runtimes/sparkjob/spark3job.py +11 -9
- mlrun/runtimes/utils.py +61 -42
- mlrun/secrets.py +16 -18
- mlrun/serving/__init__.py +3 -2
- mlrun/serving/merger.py +1 -1
- mlrun/serving/remote.py +1 -1
- mlrun/serving/routers.py +39 -42
- mlrun/serving/server.py +23 -13
- mlrun/serving/serving_wrapper.py +1 -1
- mlrun/serving/states.py +172 -39
- mlrun/serving/utils.py +1 -1
- mlrun/serving/v1_serving.py +1 -1
- mlrun/serving/v2_serving.py +29 -21
- mlrun/utils/__init__.py +1 -2
- mlrun/utils/async_http.py +8 -1
- mlrun/utils/azure_vault.py +1 -1
- mlrun/utils/clones.py +2 -2
- mlrun/utils/condition_evaluator.py +65 -0
- mlrun/utils/db.py +52 -0
- mlrun/utils/helpers.py +188 -13
- mlrun/utils/http.py +89 -54
- mlrun/utils/logger.py +48 -8
- mlrun/utils/model_monitoring.py +132 -100
- mlrun/utils/notifications/__init__.py +1 -1
- mlrun/utils/notifications/notification/__init__.py +8 -6
- mlrun/utils/notifications/notification/base.py +20 -14
- mlrun/utils/notifications/notification/console.py +7 -4
- mlrun/utils/notifications/notification/git.py +36 -19
- mlrun/utils/notifications/notification/ipython.py +10 -8
- mlrun/utils/notifications/notification/slack.py +18 -13
- mlrun/utils/notifications/notification_pusher.py +377 -56
- mlrun/utils/regex.py +6 -1
- mlrun/utils/singleton.py +1 -1
- mlrun/utils/v3io_clients.py +1 -1
- mlrun/utils/vault.py +270 -269
- mlrun/utils/version/__init__.py +1 -1
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +1 -1
- {mlrun-1.3.3.dist-info → mlrun-1.4.0.dist-info}/METADATA +16 -10
- mlrun-1.4.0.dist-info/RECORD +434 -0
- mlrun/api/api/endpoints/marketplace.py +0 -257
- mlrun/api/crud/marketplace.py +0 -221
- mlrun/api/crud/model_monitoring/model_endpoint_store.py +0 -847
- mlrun/api/db/filedb/db.py +0 -518
- mlrun/api/schemas/marketplace.py +0 -128
- mlrun/api/schemas/model_endpoints.py +0 -185
- mlrun/db/filedb.py +0 -891
- mlrun/feature_store/retrieval/online.py +0 -92
- mlrun/model_monitoring/constants.py +0 -67
- mlrun/runtimes/package/context_handler.py +0 -711
- mlrun/runtimes/sparkjob/spark2job.py +0 -59
- mlrun-1.3.3.dist-info/RECORD +0 -381
- {mlrun-1.3.3.dist-info → mlrun-1.4.0.dist-info}/LICENSE +0 -0
- {mlrun-1.3.3.dist-info → mlrun-1.4.0.dist-info}/WHEEL +0 -0
- {mlrun-1.3.3.dist-info → mlrun-1.4.0.dist-info}/entry_points.txt +0 -0
- {mlrun-1.3.3.dist-info → mlrun-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2023 Iguazio
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,9 +11,681 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
import base64
|
|
15
|
+
import hashlib
|
|
16
|
+
import time
|
|
17
|
+
import typing
|
|
18
|
+
|
|
19
|
+
from kubernetes import client, config
|
|
20
|
+
from kubernetes.client.rest import ApiException
|
|
21
|
+
|
|
22
|
+
import mlrun.common.schemas
|
|
23
|
+
import mlrun.config as mlconfig
|
|
24
|
+
import mlrun.errors
|
|
25
|
+
import mlrun.platforms.iguazio
|
|
26
|
+
from mlrun.utils import logger
|
|
27
|
+
|
|
28
|
+
_k8s = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_k8s_helper(namespace=None, silent=True, log=False) -> "K8sHelper":
|
|
32
|
+
"""
|
|
33
|
+
Get a k8s helper singleton object
|
|
34
|
+
:param namespace: the namespace to use, if not specified will use the namespace configured in mlrun config
|
|
35
|
+
:param silent: set to true if you're calling this function from a code that might run from remotely (outside of a
|
|
36
|
+
k8s cluster)
|
|
37
|
+
:param log: sometimes we want to avoid logging when executing init_k8s_config
|
|
38
|
+
"""
|
|
39
|
+
global _k8s
|
|
40
|
+
if not _k8s:
|
|
41
|
+
_k8s = K8sHelper(namespace, silent=silent, log=log)
|
|
42
|
+
return _k8s
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SecretTypes:
|
|
46
|
+
opaque = "Opaque"
|
|
47
|
+
v3io_fuse = "v3io/fuse"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class K8sHelper:
|
|
51
|
+
def __init__(self, namespace=None, silent=False, log=True):
|
|
52
|
+
self.namespace = namespace or mlconfig.config.namespace
|
|
53
|
+
self.config_file = mlconfig.config.kubernetes.kubeconfig_path or None
|
|
54
|
+
self.running_inside_kubernetes_cluster = False
|
|
55
|
+
try:
|
|
56
|
+
self._init_k8s_config(log)
|
|
57
|
+
self.v1api = client.CoreV1Api()
|
|
58
|
+
self.crdapi = client.CustomObjectsApi()
|
|
59
|
+
except Exception as exc:
|
|
60
|
+
logger.warning(
|
|
61
|
+
"cannot initialize kubernetes client", exc=mlrun.errors.err_to_str(exc)
|
|
62
|
+
)
|
|
63
|
+
if not silent:
|
|
64
|
+
raise
|
|
65
|
+
|
|
66
|
+
def resolve_namespace(self, namespace=None):
|
|
67
|
+
return namespace or self.namespace
|
|
68
|
+
|
|
69
|
+
def _init_k8s_config(self, log=True):
|
|
70
|
+
try:
|
|
71
|
+
config.load_incluster_config()
|
|
72
|
+
self.running_inside_kubernetes_cluster = True
|
|
73
|
+
if log:
|
|
74
|
+
logger.info("using in-cluster config.")
|
|
75
|
+
except Exception:
|
|
76
|
+
try:
|
|
77
|
+
config.load_kube_config(self.config_file)
|
|
78
|
+
if log:
|
|
79
|
+
logger.info("using local kubernetes config.")
|
|
80
|
+
except Exception:
|
|
81
|
+
raise RuntimeError(
|
|
82
|
+
"cannot find local kubernetes config file,"
|
|
83
|
+
" place it in ~/.kube/config or specify it in "
|
|
84
|
+
"KUBECONFIG env var"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def is_running_inside_kubernetes_cluster(self):
|
|
88
|
+
return self.running_inside_kubernetes_cluster
|
|
89
|
+
|
|
90
|
+
def list_pods(self, namespace=None, selector="", states=None):
|
|
91
|
+
try:
|
|
92
|
+
resp = self.v1api.list_namespaced_pod(
|
|
93
|
+
self.resolve_namespace(namespace), label_selector=selector
|
|
94
|
+
)
|
|
95
|
+
except ApiException as exc:
|
|
96
|
+
logger.error(f"failed to list pods: {mlrun.errors.err_to_str(exc)}")
|
|
97
|
+
raise exc
|
|
98
|
+
|
|
99
|
+
items = []
|
|
100
|
+
for i in resp.items:
|
|
101
|
+
if not states or i.status.phase in states:
|
|
102
|
+
items.append(i)
|
|
103
|
+
return items
|
|
104
|
+
|
|
105
|
+
def create_pod(self, pod, max_retry=3, retry_interval=3):
|
|
106
|
+
if "pod" in dir(pod):
|
|
107
|
+
pod = pod.pod
|
|
108
|
+
pod.metadata.namespace = self.resolve_namespace(pod.metadata.namespace)
|
|
109
|
+
|
|
110
|
+
retry_count = 0
|
|
111
|
+
while True:
|
|
112
|
+
try:
|
|
113
|
+
resp = self.v1api.create_namespaced_pod(pod.metadata.namespace, pod)
|
|
114
|
+
except ApiException as exc:
|
|
115
|
+
|
|
116
|
+
if retry_count > max_retry:
|
|
117
|
+
logger.error(
|
|
118
|
+
"failed to create pod after max retries",
|
|
119
|
+
retry_count=retry_count,
|
|
120
|
+
exc=mlrun.errors.err_to_str(exc),
|
|
121
|
+
pod=pod,
|
|
122
|
+
)
|
|
123
|
+
raise exc
|
|
124
|
+
|
|
125
|
+
logger.error(
|
|
126
|
+
"failed to create pod", exc=mlrun.errors.err_to_str(exc), pod=pod
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# known k8s issue, see https://github.com/kubernetes/kubernetes/issues/67761
|
|
130
|
+
if "gke-resource-quotas" in mlrun.errors.err_to_str(exc):
|
|
131
|
+
logger.warning(
|
|
132
|
+
"failed to create pod due to gke resource error, "
|
|
133
|
+
f"sleeping {retry_interval} seconds and retrying"
|
|
134
|
+
)
|
|
135
|
+
retry_count += 1
|
|
136
|
+
time.sleep(retry_interval)
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
raise exc
|
|
140
|
+
else:
|
|
141
|
+
logger.info(f"Pod {resp.metadata.name} created")
|
|
142
|
+
return resp.metadata.name, resp.metadata.namespace
|
|
143
|
+
|
|
144
|
+
def delete_pod(self, name, namespace=None):
|
|
145
|
+
try:
|
|
146
|
+
api_response = self.v1api.delete_namespaced_pod(
|
|
147
|
+
name,
|
|
148
|
+
self.resolve_namespace(namespace),
|
|
149
|
+
grace_period_seconds=0,
|
|
150
|
+
propagation_policy="Background",
|
|
151
|
+
)
|
|
152
|
+
return api_response
|
|
153
|
+
except ApiException as exc:
|
|
154
|
+
# ignore error if pod is already removed
|
|
155
|
+
if exc.status != 404:
|
|
156
|
+
logger.error(
|
|
157
|
+
f"failed to delete pod: {mlrun.errors.err_to_str(exc)}",
|
|
158
|
+
pod_name=name,
|
|
159
|
+
)
|
|
160
|
+
raise exc
|
|
161
|
+
|
|
162
|
+
def get_pod(self, name, namespace=None, raise_on_not_found=False):
|
|
163
|
+
try:
|
|
164
|
+
api_response = self.v1api.read_namespaced_pod(
|
|
165
|
+
name=name, namespace=self.resolve_namespace(namespace)
|
|
166
|
+
)
|
|
167
|
+
return api_response
|
|
168
|
+
except ApiException as exc:
|
|
169
|
+
if exc.status != 404:
|
|
170
|
+
logger.error(f"failed to get pod: {mlrun.errors.err_to_str(exc)}")
|
|
171
|
+
raise exc
|
|
172
|
+
else:
|
|
173
|
+
if raise_on_not_found:
|
|
174
|
+
raise mlrun.errors.MLRunNotFoundError(f"Pod not found: {name}")
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def get_pod_status(self, name, namespace=None):
|
|
178
|
+
return self.get_pod(
|
|
179
|
+
name, namespace, raise_on_not_found=True
|
|
180
|
+
).status.phase.lower()
|
|
181
|
+
|
|
182
|
+
def delete_crd(self, name, crd_group, crd_version, crd_plural, namespace=None):
|
|
183
|
+
try:
|
|
184
|
+
namespace = self.resolve_namespace(namespace)
|
|
185
|
+
self.crdapi.delete_namespaced_custom_object(
|
|
186
|
+
crd_group,
|
|
187
|
+
crd_version,
|
|
188
|
+
namespace,
|
|
189
|
+
crd_plural,
|
|
190
|
+
name,
|
|
191
|
+
)
|
|
192
|
+
logger.info(
|
|
193
|
+
"Deleted crd object",
|
|
194
|
+
crd_name=name,
|
|
195
|
+
namespace=namespace,
|
|
196
|
+
)
|
|
197
|
+
except ApiException as exc:
|
|
198
|
+
|
|
199
|
+
# ignore error if crd is already removed
|
|
200
|
+
if exc.status != 404:
|
|
201
|
+
logger.error(
|
|
202
|
+
f"failed to delete crd: {mlrun.errors.err_to_str(exc)}",
|
|
203
|
+
crd_name=name,
|
|
204
|
+
crd_group=crd_group,
|
|
205
|
+
crd_version=crd_version,
|
|
206
|
+
crd_plural=crd_plural,
|
|
207
|
+
)
|
|
208
|
+
raise exc
|
|
209
|
+
|
|
210
|
+
def logs(self, name, namespace=None):
|
|
211
|
+
try:
|
|
212
|
+
resp = self.v1api.read_namespaced_pod_log(
|
|
213
|
+
name=name, namespace=self.resolve_namespace(namespace)
|
|
214
|
+
)
|
|
215
|
+
except ApiException as exc:
|
|
216
|
+
logger.error(f"failed to get pod logs: {mlrun.errors.err_to_str(exc)}")
|
|
217
|
+
raise exc
|
|
218
|
+
|
|
219
|
+
return resp
|
|
220
|
+
|
|
221
|
+
def get_logger_pods(self, project, uid, run_kind, namespace=""):
|
|
222
|
+
|
|
223
|
+
# As this file is imported in mlrun.runtimes, we sadly cannot have this import in the top level imports
|
|
224
|
+
# as that will create an import loop.
|
|
225
|
+
# TODO: Fix the import loops already!
|
|
226
|
+
import mlrun.runtimes
|
|
227
|
+
|
|
228
|
+
namespace = self.resolve_namespace(namespace)
|
|
229
|
+
mpijob_crd_version = mlrun.runtimes.utils.resolve_mpijob_crd_version()
|
|
230
|
+
mpijob_role_label = (
|
|
231
|
+
mlrun.runtimes.constants.MPIJobCRDVersions.role_label_by_version(
|
|
232
|
+
mpijob_crd_version
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
extra_selectors = {
|
|
236
|
+
"spark": "spark-role=driver",
|
|
237
|
+
"mpijob": f"{mpijob_role_label}=launcher",
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# TODO: all mlrun labels are sprinkled in a lot of places - they need to all be defined in a central,
|
|
241
|
+
# inclusive place.
|
|
242
|
+
selectors = [
|
|
243
|
+
"mlrun/class",
|
|
244
|
+
f"mlrun/project={project}",
|
|
245
|
+
f"mlrun/uid={uid}",
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
# In order to make the `list_pods` request return a lighter and quicker result, we narrow the search for
|
|
249
|
+
# the relevant pods using the proper label selector according to the run kind
|
|
250
|
+
if run_kind in extra_selectors:
|
|
251
|
+
selectors.append(extra_selectors[run_kind])
|
|
252
|
+
|
|
253
|
+
selector = ",".join(selectors)
|
|
254
|
+
pods = self.list_pods(namespace, selector=selector)
|
|
255
|
+
if not pods:
|
|
256
|
+
logger.error("no pod matches that uid", uid=uid)
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
return {p.metadata.name: p.status.phase for p in pods}
|
|
260
|
+
|
|
261
|
+
def get_project_vault_secret_name(
|
|
262
|
+
self, project, service_account_name, namespace=""
|
|
263
|
+
):
|
|
264
|
+
namespace = self.resolve_namespace(namespace)
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
service_account = self.v1api.read_namespaced_service_account(
|
|
268
|
+
service_account_name, namespace
|
|
269
|
+
)
|
|
270
|
+
except ApiException as exc:
|
|
271
|
+
# It's valid for the service account to not exist. Simply return None
|
|
272
|
+
if exc.status != 404:
|
|
273
|
+
logger.error(
|
|
274
|
+
f"failed to retrieve service accounts: {mlrun.errors.err_to_str(exc)}"
|
|
275
|
+
)
|
|
276
|
+
raise exc
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
if len(service_account.secrets) > 1:
|
|
280
|
+
raise ValueError(
|
|
281
|
+
f"Service account {service_account_name} has more than one secret"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return service_account.secrets[0].name
|
|
285
|
+
|
|
286
|
+
def get_project_secret_name(self, project) -> str:
|
|
287
|
+
return mlconfig.config.secret_stores.kubernetes.project_secret_name.format(
|
|
288
|
+
project=project
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def get_auth_secret_name(self, access_key: str) -> str:
|
|
292
|
+
hashed_access_key = self._hash_access_key(access_key)
|
|
293
|
+
return mlconfig.config.secret_stores.kubernetes.auth_secret_name.format(
|
|
294
|
+
hashed_access_key=hashed_access_key
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def _hash_access_key(access_key: str):
|
|
299
|
+
return hashlib.sha224(access_key.encode()).hexdigest()
|
|
300
|
+
|
|
301
|
+
def store_project_secrets(
|
|
302
|
+
self, project, secrets, namespace=""
|
|
303
|
+
) -> (str, typing.Optional[mlrun.common.schemas.SecretEventActions]):
|
|
304
|
+
secret_name = self.get_project_secret_name(project)
|
|
305
|
+
action = self.store_secrets(secret_name, secrets, namespace)
|
|
306
|
+
return secret_name, action
|
|
307
|
+
|
|
308
|
+
def read_auth_secret(self, secret_name, namespace="", raise_on_not_found=False):
|
|
309
|
+
namespace = self.resolve_namespace(namespace)
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
secret_data = self.v1api.read_namespaced_secret(secret_name, namespace).data
|
|
313
|
+
except ApiException as exc:
|
|
314
|
+
logger.error(
|
|
315
|
+
"Failed to read secret",
|
|
316
|
+
secret_name=secret_name,
|
|
317
|
+
namespace=namespace,
|
|
318
|
+
exc=mlrun.errors.err_to_str(exc),
|
|
319
|
+
)
|
|
320
|
+
if exc.status != 404:
|
|
321
|
+
raise exc
|
|
322
|
+
elif raise_on_not_found:
|
|
323
|
+
raise mlrun.errors.MLRunNotFoundError(
|
|
324
|
+
f"Secret '{secret_name}' was not found in namespace '{namespace}'"
|
|
325
|
+
) from exc
|
|
326
|
+
|
|
327
|
+
return None, None
|
|
328
|
+
|
|
329
|
+
def _get_secret_value(key):
|
|
330
|
+
if secret_data.get(key):
|
|
331
|
+
return base64.b64decode(secret_data[key]).decode("utf-8")
|
|
332
|
+
else:
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
username = _get_secret_value(
|
|
336
|
+
mlrun.common.schemas.AuthSecretData.get_field_secret_key("username")
|
|
337
|
+
)
|
|
338
|
+
access_key = _get_secret_value(
|
|
339
|
+
mlrun.common.schemas.AuthSecretData.get_field_secret_key("access_key")
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return username, access_key
|
|
343
|
+
|
|
344
|
+
def store_auth_secret(
|
|
345
|
+
self, username: str, access_key: str, namespace=""
|
|
346
|
+
) -> (str, typing.Optional[mlrun.common.schemas.SecretEventActions]):
|
|
347
|
+
"""
|
|
348
|
+
Store the given access key as a secret in the cluster. The secret name is generated from the access key
|
|
349
|
+
:return: returns the secret name and the action taken against the secret
|
|
350
|
+
"""
|
|
351
|
+
secret_name = self.get_auth_secret_name(access_key)
|
|
352
|
+
secret_data = {
|
|
353
|
+
mlrun.common.schemas.AuthSecretData.get_field_secret_key(
|
|
354
|
+
"username"
|
|
355
|
+
): username,
|
|
356
|
+
mlrun.common.schemas.AuthSecretData.get_field_secret_key(
|
|
357
|
+
"access_key"
|
|
358
|
+
): access_key,
|
|
359
|
+
}
|
|
360
|
+
action = self.store_secrets(
|
|
361
|
+
secret_name,
|
|
362
|
+
secret_data,
|
|
363
|
+
namespace,
|
|
364
|
+
type_=SecretTypes.v3io_fuse,
|
|
365
|
+
labels={"mlrun/username": username},
|
|
366
|
+
)
|
|
367
|
+
return secret_name, action
|
|
368
|
+
|
|
369
|
+
def store_secrets(
|
|
370
|
+
self,
|
|
371
|
+
secret_name,
|
|
372
|
+
secrets,
|
|
373
|
+
namespace="",
|
|
374
|
+
type_=SecretTypes.opaque,
|
|
375
|
+
labels: typing.Optional[dict] = None,
|
|
376
|
+
) -> typing.Optional[mlrun.common.schemas.SecretEventActions]:
|
|
377
|
+
"""
|
|
378
|
+
Store secrets in a kubernetes secret object
|
|
379
|
+
:param secret_name: the project secret name
|
|
380
|
+
:param secrets: the secrets to delete
|
|
381
|
+
:param namespace: k8s namespace
|
|
382
|
+
:param type_: k8s secret type
|
|
383
|
+
:param labels: k8s labels for the secret
|
|
384
|
+
:return: returns the action if the secret was created or updated, None if nothing changed
|
|
385
|
+
"""
|
|
386
|
+
namespace = self.resolve_namespace(namespace)
|
|
387
|
+
try:
|
|
388
|
+
k8s_secret = self.v1api.read_namespaced_secret(secret_name, namespace)
|
|
389
|
+
except ApiException as exc:
|
|
390
|
+
# If secret doesn't exist, we'll simply create it
|
|
391
|
+
if exc.status != 404:
|
|
392
|
+
logger.error(
|
|
393
|
+
f"failed to retrieve k8s secret: {mlrun.errors.err_to_str(exc)}"
|
|
394
|
+
)
|
|
395
|
+
raise exc
|
|
396
|
+
k8s_secret = client.V1Secret(type=type_)
|
|
397
|
+
k8s_secret.metadata = client.V1ObjectMeta(
|
|
398
|
+
name=secret_name, namespace=namespace, labels=labels
|
|
399
|
+
)
|
|
400
|
+
k8s_secret.string_data = secrets
|
|
401
|
+
self.v1api.create_namespaced_secret(namespace, k8s_secret)
|
|
402
|
+
return mlrun.common.schemas.SecretEventActions.created
|
|
403
|
+
|
|
404
|
+
secret_data = k8s_secret.data.copy()
|
|
405
|
+
for key, value in secrets.items():
|
|
406
|
+
secret_data[key] = base64.b64encode(value.encode()).decode("utf-8")
|
|
407
|
+
|
|
408
|
+
k8s_secret.data = secret_data
|
|
409
|
+
self.v1api.replace_namespaced_secret(secret_name, namespace, k8s_secret)
|
|
410
|
+
return mlrun.common.schemas.SecretEventActions.updated
|
|
411
|
+
|
|
412
|
+
def load_secret(self, secret_name, namespace=""):
|
|
413
|
+
namespace = namespace or self.resolve_namespace(namespace)
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
k8s_secret = self.v1api.read_namespaced_secret(secret_name, namespace)
|
|
417
|
+
except ApiException:
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
return k8s_secret.data
|
|
421
|
+
|
|
422
|
+
def delete_project_secrets(
|
|
423
|
+
self, project, secrets, namespace=""
|
|
424
|
+
) -> (str, typing.Optional[mlrun.common.schemas.SecretEventActions]):
|
|
425
|
+
"""
|
|
426
|
+
Delete secrets from a kubernetes secret object
|
|
427
|
+
:return: returns the secret name and the action taken against the secret
|
|
428
|
+
"""
|
|
429
|
+
secret_name = self.get_project_secret_name(project)
|
|
430
|
+
action = self.delete_secrets(secret_name, secrets, namespace)
|
|
431
|
+
return secret_name, action
|
|
432
|
+
|
|
433
|
+
def delete_auth_secret(self, secret_ref: str, namespace=""):
|
|
434
|
+
self.delete_secrets(secret_ref, {}, namespace)
|
|
435
|
+
|
|
436
|
+
def delete_secrets(
|
|
437
|
+
self, secret_name, secrets, namespace=""
|
|
438
|
+
) -> typing.Optional[mlrun.common.schemas.SecretEventActions]:
|
|
439
|
+
"""
|
|
440
|
+
Delete secrets from a kubernetes secret object
|
|
441
|
+
:param secret_name: the project secret name
|
|
442
|
+
:param secrets: the secrets to delete
|
|
443
|
+
:param namespace: k8s namespace
|
|
444
|
+
:return: returns the action if the secret was deleted or updated, None if nothing changed
|
|
445
|
+
"""
|
|
446
|
+
namespace = self.resolve_namespace(namespace)
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
k8s_secret = self.v1api.read_namespaced_secret(secret_name, namespace)
|
|
450
|
+
except ApiException as exc:
|
|
451
|
+
if exc.status == 404:
|
|
452
|
+
logger.info(
|
|
453
|
+
"Project secret does not exist, nothing to delete.",
|
|
454
|
+
secret_name=secret_name,
|
|
455
|
+
)
|
|
456
|
+
return None
|
|
457
|
+
else:
|
|
458
|
+
logger.error(
|
|
459
|
+
f"failed to retrieve k8s secret: {mlrun.errors.err_to_str(exc)}"
|
|
460
|
+
)
|
|
461
|
+
raise exc
|
|
462
|
+
|
|
463
|
+
secret_data = {}
|
|
464
|
+
if secrets:
|
|
465
|
+
secret_data = k8s_secret.data.copy()
|
|
466
|
+
for secret in secrets:
|
|
467
|
+
secret_data.pop(secret, None)
|
|
468
|
+
|
|
469
|
+
if secret_data:
|
|
470
|
+
k8s_secret.data = secret_data
|
|
471
|
+
self.v1api.replace_namespaced_secret(secret_name, namespace, k8s_secret)
|
|
472
|
+
return mlrun.common.schemas.SecretEventActions.updated
|
|
473
|
+
|
|
474
|
+
self.v1api.delete_namespaced_secret(secret_name, namespace)
|
|
475
|
+
return mlrun.common.schemas.SecretEventActions.deleted
|
|
476
|
+
|
|
477
|
+
def _get_project_secrets_raw_data(self, project, namespace=""):
|
|
478
|
+
secret_name = self.get_project_secret_name(project)
|
|
479
|
+
return self._get_secret_raw_data(secret_name, namespace)
|
|
480
|
+
|
|
481
|
+
def _get_secret_raw_data(self, secret_name, namespace=""):
|
|
482
|
+
namespace = self.resolve_namespace(namespace)
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
k8s_secret = self.v1api.read_namespaced_secret(secret_name, namespace)
|
|
486
|
+
except ApiException:
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
return k8s_secret.data
|
|
490
|
+
|
|
491
|
+
def get_project_secret_keys(self, project, namespace="", filter_internal=False):
|
|
492
|
+
secrets_data = self._get_project_secrets_raw_data(project, namespace)
|
|
493
|
+
if not secrets_data:
|
|
494
|
+
return []
|
|
495
|
+
|
|
496
|
+
secret_keys = list(secrets_data.keys())
|
|
497
|
+
if filter_internal:
|
|
498
|
+
secret_keys = list(
|
|
499
|
+
filter(lambda key: not key.startswith("mlrun."), secret_keys)
|
|
500
|
+
)
|
|
501
|
+
return secret_keys
|
|
502
|
+
|
|
503
|
+
def get_project_secret_data(self, project, secret_keys=None, namespace=""):
|
|
504
|
+
secrets_data = self._get_project_secrets_raw_data(project, namespace)
|
|
505
|
+
return self._decode_secret_data(secrets_data, secret_keys)
|
|
506
|
+
|
|
507
|
+
def get_secret_data(self, secret_name, namespace=""):
|
|
508
|
+
secrets_data = self._get_secret_raw_data(secret_name, namespace)
|
|
509
|
+
return self._decode_secret_data(secrets_data)
|
|
510
|
+
|
|
511
|
+
def _decode_secret_data(self, secrets_data, secret_keys=None):
|
|
512
|
+
results = {}
|
|
513
|
+
if not secrets_data:
|
|
514
|
+
return results
|
|
515
|
+
|
|
516
|
+
# If not asking for specific keys, return all
|
|
517
|
+
secret_keys = secret_keys or secrets_data.keys()
|
|
518
|
+
|
|
519
|
+
for key in secret_keys:
|
|
520
|
+
encoded_value = secrets_data.get(key)
|
|
521
|
+
if encoded_value:
|
|
522
|
+
results[key] = base64.b64decode(secrets_data[key]).decode("utf-8")
|
|
523
|
+
return results
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
class BasePod:
|
|
527
|
+
def __init__(
|
|
528
|
+
self,
|
|
529
|
+
task_name="",
|
|
530
|
+
image=None,
|
|
531
|
+
command=None,
|
|
532
|
+
args=None,
|
|
533
|
+
namespace="",
|
|
534
|
+
kind="job",
|
|
535
|
+
project=None,
|
|
536
|
+
default_pod_spec_attributes=None,
|
|
537
|
+
resources=None,
|
|
538
|
+
):
|
|
539
|
+
self.namespace = namespace
|
|
540
|
+
self.name = ""
|
|
541
|
+
self.task_name = task_name
|
|
542
|
+
self.image = image
|
|
543
|
+
self.command = command
|
|
544
|
+
self.args = args
|
|
545
|
+
self._volumes = []
|
|
546
|
+
self._mounts = []
|
|
547
|
+
self.env = None
|
|
548
|
+
self.node_selector = None
|
|
549
|
+
self.project = project or mlrun.mlconf.default_project
|
|
550
|
+
self._labels = {
|
|
551
|
+
"mlrun/task-name": task_name,
|
|
552
|
+
"mlrun/class": kind,
|
|
553
|
+
"mlrun/project": self.project,
|
|
554
|
+
}
|
|
555
|
+
self._annotations = {}
|
|
556
|
+
self._init_containers = []
|
|
557
|
+
# will be applied on the pod spec only when calling .pod(), allows to override spec attributes
|
|
558
|
+
self.default_pod_spec_attributes = default_pod_spec_attributes
|
|
559
|
+
self.resources = resources
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def pod(self):
|
|
563
|
+
return self._get_spec()
|
|
564
|
+
|
|
565
|
+
@property
|
|
566
|
+
def init_containers(self):
|
|
567
|
+
return self._init_containers
|
|
568
|
+
|
|
569
|
+
@init_containers.setter
|
|
570
|
+
def init_containers(self, containers):
|
|
571
|
+
self._init_containers = containers
|
|
572
|
+
|
|
573
|
+
def append_init_container(
|
|
574
|
+
self,
|
|
575
|
+
image,
|
|
576
|
+
command=None,
|
|
577
|
+
args=None,
|
|
578
|
+
env=None,
|
|
579
|
+
image_pull_policy="IfNotPresent",
|
|
580
|
+
name="init",
|
|
581
|
+
):
|
|
582
|
+
if isinstance(env, dict):
|
|
583
|
+
env = [client.V1EnvVar(name=k, value=v) for k, v in env.items()]
|
|
584
|
+
self._init_containers.append(
|
|
585
|
+
client.V1Container(
|
|
586
|
+
name=name,
|
|
587
|
+
image=image,
|
|
588
|
+
env=env,
|
|
589
|
+
command=command,
|
|
590
|
+
args=args,
|
|
591
|
+
image_pull_policy=image_pull_policy,
|
|
592
|
+
)
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def add_label(self, key, value):
|
|
596
|
+
self._labels[key] = str(value)
|
|
597
|
+
|
|
598
|
+
def add_annotation(self, key, value):
|
|
599
|
+
self._annotations[key] = str(value)
|
|
600
|
+
|
|
601
|
+
def add_volume(self, volume: client.V1Volume, mount_path, name=None, sub_path=None):
|
|
602
|
+
self._mounts.append(
|
|
603
|
+
client.V1VolumeMount(
|
|
604
|
+
name=name or volume.name, mount_path=mount_path, sub_path=sub_path
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
self._volumes.append(volume)
|
|
608
|
+
|
|
609
|
+
def mount_empty(self, name="empty", mount_path="/empty"):
|
|
610
|
+
self.add_volume(
|
|
611
|
+
client.V1Volume(name=name, empty_dir=client.V1EmptyDirVolumeSource()),
|
|
612
|
+
mount_path=mount_path,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
def mount_v3io(
|
|
616
|
+
self, name="v3io", remote="~/", mount_path="/User", access_key="", user=""
|
|
617
|
+
):
|
|
618
|
+
self.add_volume(
|
|
619
|
+
mlrun.platforms.iguazio.v3io_to_vol(name, remote, access_key, user),
|
|
620
|
+
mount_path=mount_path,
|
|
621
|
+
name=name,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
def mount_cfgmap(self, name, path="/config"):
|
|
625
|
+
self.add_volume(
|
|
626
|
+
client.V1Volume(
|
|
627
|
+
name=name, config_map=client.V1ConfigMapVolumeSource(name=name)
|
|
628
|
+
),
|
|
629
|
+
mount_path=path,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
def mount_secret(self, name, path="/secret", items=None, sub_path=None):
|
|
633
|
+
self.add_volume(
|
|
634
|
+
client.V1Volume(
|
|
635
|
+
name=name,
|
|
636
|
+
secret=client.V1SecretVolumeSource(
|
|
637
|
+
secret_name=name,
|
|
638
|
+
items=items,
|
|
639
|
+
),
|
|
640
|
+
),
|
|
641
|
+
mount_path=path,
|
|
642
|
+
sub_path=sub_path,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
def set_node_selector(self, node_selector: typing.Optional[typing.Dict[str, str]]):
|
|
646
|
+
self.node_selector = node_selector
|
|
647
|
+
|
|
648
|
+
def _get_spec(self, template=False):
|
|
649
|
+
|
|
650
|
+
pod_obj = client.V1PodTemplate if template else client.V1Pod
|
|
651
|
+
|
|
652
|
+
if self.env and isinstance(self.env, dict):
|
|
653
|
+
env = [client.V1EnvVar(name=k, value=v) for k, v in self.env.items()]
|
|
654
|
+
else:
|
|
655
|
+
env = self.env
|
|
656
|
+
container = client.V1Container(
|
|
657
|
+
name="base",
|
|
658
|
+
image=self.image,
|
|
659
|
+
env=env,
|
|
660
|
+
command=self.command,
|
|
661
|
+
args=self.args,
|
|
662
|
+
volume_mounts=self._mounts,
|
|
663
|
+
resources=self.resources,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
pod_spec = client.V1PodSpec(
|
|
667
|
+
containers=[container],
|
|
668
|
+
restart_policy="Never",
|
|
669
|
+
volumes=self._volumes,
|
|
670
|
+
node_selector=self.node_selector,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# if attribute isn't defined use default pod spec attributes
|
|
674
|
+
for key, val in self.default_pod_spec_attributes.items():
|
|
675
|
+
if not getattr(pod_spec, key, None):
|
|
676
|
+
setattr(pod_spec, key, val)
|
|
16
677
|
|
|
678
|
+
for init_containers in self._init_containers:
|
|
679
|
+
init_containers.volume_mounts = self._mounts
|
|
680
|
+
pod_spec.init_containers = self._init_containers
|
|
17
681
|
|
|
18
|
-
|
|
19
|
-
|
|
682
|
+
pod = pod_obj(
|
|
683
|
+
metadata=client.V1ObjectMeta(
|
|
684
|
+
generate_name=f"{self.task_name}-",
|
|
685
|
+
namespace=self.namespace,
|
|
686
|
+
labels=self._labels,
|
|
687
|
+
annotations=self._annotations,
|
|
688
|
+
),
|
|
689
|
+
spec=pod_spec,
|
|
690
|
+
)
|
|
691
|
+
return pod
|