mlrun 1.7.0rc4__py3-none-any.whl → 1.7.0rc20__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.

Files changed (200) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +25 -111
  3. mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
  4. mlrun/alerts/alert.py +144 -0
  5. mlrun/api/schemas/__init__.py +4 -3
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +38 -254
  8. mlrun/artifacts/dataset.py +9 -190
  9. mlrun/artifacts/manager.py +41 -47
  10. mlrun/artifacts/model.py +30 -158
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +68 -0
  13. mlrun/common/formatters/__init__.py +19 -0
  14. mlrun/{model_monitoring/stores/models/sqlite.py → common/formatters/artifact.py} +6 -8
  15. mlrun/common/formatters/base.py +78 -0
  16. mlrun/common/formatters/function.py +41 -0
  17. mlrun/common/formatters/pipeline.py +53 -0
  18. mlrun/common/formatters/project.py +51 -0
  19. mlrun/{runtimes → common/runtimes}/constants.py +32 -4
  20. mlrun/common/schemas/__init__.py +25 -4
  21. mlrun/common/schemas/alert.py +203 -0
  22. mlrun/common/schemas/api_gateway.py +148 -0
  23. mlrun/common/schemas/artifact.py +15 -5
  24. mlrun/common/schemas/auth.py +8 -2
  25. mlrun/common/schemas/client_spec.py +2 -0
  26. mlrun/common/schemas/frontend_spec.py +1 -0
  27. mlrun/common/schemas/function.py +4 -0
  28. mlrun/common/schemas/hub.py +7 -9
  29. mlrun/common/schemas/model_monitoring/__init__.py +19 -3
  30. mlrun/common/schemas/model_monitoring/constants.py +96 -26
  31. mlrun/common/schemas/model_monitoring/grafana.py +9 -5
  32. mlrun/common/schemas/model_monitoring/model_endpoints.py +86 -2
  33. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  34. mlrun/common/schemas/pipeline.py +0 -9
  35. mlrun/common/schemas/project.py +22 -21
  36. mlrun/common/types.py +7 -1
  37. mlrun/config.py +87 -19
  38. mlrun/data_types/data_types.py +4 -0
  39. mlrun/data_types/to_pandas.py +9 -9
  40. mlrun/datastore/__init__.py +5 -8
  41. mlrun/datastore/alibaba_oss.py +130 -0
  42. mlrun/datastore/azure_blob.py +4 -5
  43. mlrun/datastore/base.py +69 -30
  44. mlrun/datastore/datastore.py +10 -2
  45. mlrun/datastore/datastore_profile.py +90 -6
  46. mlrun/datastore/google_cloud_storage.py +1 -1
  47. mlrun/datastore/hdfs.py +5 -0
  48. mlrun/datastore/inmem.py +2 -2
  49. mlrun/datastore/redis.py +2 -2
  50. mlrun/datastore/s3.py +5 -0
  51. mlrun/datastore/snowflake_utils.py +43 -0
  52. mlrun/datastore/sources.py +172 -44
  53. mlrun/datastore/store_resources.py +7 -7
  54. mlrun/datastore/targets.py +285 -41
  55. mlrun/datastore/utils.py +68 -5
  56. mlrun/datastore/v3io.py +27 -50
  57. mlrun/db/auth_utils.py +152 -0
  58. mlrun/db/base.py +149 -14
  59. mlrun/db/factory.py +1 -1
  60. mlrun/db/httpdb.py +608 -178
  61. mlrun/db/nopdb.py +191 -7
  62. mlrun/errors.py +11 -0
  63. mlrun/execution.py +37 -20
  64. mlrun/feature_store/__init__.py +0 -2
  65. mlrun/feature_store/api.py +21 -52
  66. mlrun/feature_store/feature_set.py +48 -23
  67. mlrun/feature_store/feature_vector.py +2 -1
  68. mlrun/feature_store/ingestion.py +7 -6
  69. mlrun/feature_store/retrieval/base.py +9 -4
  70. mlrun/feature_store/retrieval/conversion.py +9 -9
  71. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  72. mlrun/feature_store/retrieval/job.py +9 -3
  73. mlrun/feature_store/retrieval/local_merger.py +2 -0
  74. mlrun/feature_store/retrieval/spark_merger.py +34 -24
  75. mlrun/feature_store/steps.py +30 -19
  76. mlrun/features.py +4 -13
  77. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
  78. mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
  79. mlrun/frameworks/lgbm/__init__.py +1 -1
  80. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  81. mlrun/frameworks/lgbm/model_handler.py +1 -1
  82. mlrun/frameworks/parallel_coordinates.py +2 -1
  83. mlrun/frameworks/pytorch/__init__.py +2 -2
  84. mlrun/frameworks/sklearn/__init__.py +1 -1
  85. mlrun/frameworks/tf_keras/__init__.py +5 -2
  86. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
  87. mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
  88. mlrun/frameworks/xgboost/__init__.py +1 -1
  89. mlrun/k8s_utils.py +10 -11
  90. mlrun/launcher/__init__.py +1 -1
  91. mlrun/launcher/base.py +6 -5
  92. mlrun/launcher/client.py +8 -6
  93. mlrun/launcher/factory.py +1 -1
  94. mlrun/launcher/local.py +9 -3
  95. mlrun/launcher/remote.py +9 -3
  96. mlrun/lists.py +6 -2
  97. mlrun/model.py +58 -19
  98. mlrun/model_monitoring/__init__.py +1 -1
  99. mlrun/model_monitoring/api.py +127 -301
  100. mlrun/model_monitoring/application.py +5 -296
  101. mlrun/model_monitoring/applications/__init__.py +11 -0
  102. mlrun/model_monitoring/applications/_application_steps.py +157 -0
  103. mlrun/model_monitoring/applications/base.py +282 -0
  104. mlrun/model_monitoring/applications/context.py +214 -0
  105. mlrun/model_monitoring/applications/evidently_base.py +211 -0
  106. mlrun/model_monitoring/applications/histogram_data_drift.py +224 -93
  107. mlrun/model_monitoring/applications/results.py +99 -0
  108. mlrun/model_monitoring/controller.py +30 -36
  109. mlrun/model_monitoring/db/__init__.py +18 -0
  110. mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
  111. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  112. mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +58 -32
  113. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  114. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  115. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
  116. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -0
  117. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  118. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +684 -0
  119. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  120. mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +302 -155
  121. mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
  122. mlrun/model_monitoring/db/tsdb/base.py +329 -0
  123. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  124. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  125. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
  126. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
  127. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
  128. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  129. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
  130. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
  131. mlrun/model_monitoring/evidently_application.py +6 -118
  132. mlrun/model_monitoring/features_drift_table.py +34 -22
  133. mlrun/model_monitoring/helpers.py +100 -7
  134. mlrun/model_monitoring/model_endpoint.py +3 -2
  135. mlrun/model_monitoring/stream_processing.py +93 -228
  136. mlrun/model_monitoring/tracking_policy.py +7 -1
  137. mlrun/model_monitoring/writer.py +152 -124
  138. mlrun/package/packagers_manager.py +1 -0
  139. mlrun/package/utils/_formatter.py +2 -2
  140. mlrun/platforms/__init__.py +11 -10
  141. mlrun/platforms/iguazio.py +21 -202
  142. mlrun/projects/operations.py +30 -16
  143. mlrun/projects/pipelines.py +92 -99
  144. mlrun/projects/project.py +757 -268
  145. mlrun/render.py +15 -14
  146. mlrun/run.py +160 -162
  147. mlrun/runtimes/__init__.py +55 -3
  148. mlrun/runtimes/base.py +33 -19
  149. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  150. mlrun/runtimes/funcdoc.py +0 -28
  151. mlrun/runtimes/kubejob.py +28 -122
  152. mlrun/runtimes/local.py +5 -2
  153. mlrun/runtimes/mpijob/__init__.py +0 -20
  154. mlrun/runtimes/mpijob/abstract.py +8 -8
  155. mlrun/runtimes/mpijob/v1.py +1 -1
  156. mlrun/runtimes/nuclio/__init__.py +1 -0
  157. mlrun/runtimes/nuclio/api_gateway.py +709 -0
  158. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  159. mlrun/runtimes/nuclio/application/application.py +523 -0
  160. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  161. mlrun/runtimes/nuclio/function.py +98 -58
  162. mlrun/runtimes/nuclio/serving.py +36 -42
  163. mlrun/runtimes/pod.py +196 -45
  164. mlrun/runtimes/remotesparkjob.py +1 -1
  165. mlrun/runtimes/sparkjob/spark3job.py +1 -1
  166. mlrun/runtimes/utils.py +6 -73
  167. mlrun/secrets.py +6 -2
  168. mlrun/serving/remote.py +2 -3
  169. mlrun/serving/routers.py +7 -4
  170. mlrun/serving/server.py +7 -8
  171. mlrun/serving/states.py +73 -43
  172. mlrun/serving/v2_serving.py +8 -7
  173. mlrun/track/tracker.py +2 -1
  174. mlrun/utils/async_http.py +25 -5
  175. mlrun/utils/helpers.py +141 -75
  176. mlrun/utils/http.py +1 -1
  177. mlrun/utils/logger.py +39 -7
  178. mlrun/utils/notifications/notification/__init__.py +14 -9
  179. mlrun/utils/notifications/notification/base.py +12 -0
  180. mlrun/utils/notifications/notification/console.py +2 -0
  181. mlrun/utils/notifications/notification/git.py +3 -1
  182. mlrun/utils/notifications/notification/ipython.py +2 -0
  183. mlrun/utils/notifications/notification/slack.py +101 -21
  184. mlrun/utils/notifications/notification/webhook.py +11 -1
  185. mlrun/utils/notifications/notification_pusher.py +147 -16
  186. mlrun/utils/retryer.py +3 -2
  187. mlrun/utils/v3io_clients.py +0 -1
  188. mlrun/utils/version/version.json +2 -2
  189. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +33 -18
  190. mlrun-1.7.0rc20.dist-info/RECORD +353 -0
  191. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +1 -1
  192. mlrun/kfpops.py +0 -868
  193. mlrun/model_monitoring/batch.py +0 -974
  194. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  195. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  196. mlrun/platforms/other.py +0 -305
  197. mlrun-1.7.0rc4.dist-info/RECORD +0 -321
  198. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
  199. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
  200. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,15 @@
1
+ # Copyright 2024 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from .application import ApplicationRuntime
@@ -0,0 +1,523 @@
1
+ # Copyright 2024 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import pathlib
15
+ import typing
16
+
17
+ import nuclio
18
+
19
+ import mlrun.common.schemas as schemas
20
+ import mlrun.errors
21
+ from mlrun.common.runtimes.constants import NuclioIngressAddTemplatedIngressModes
22
+ from mlrun.runtimes import RemoteRuntime
23
+ from mlrun.runtimes.nuclio import min_nuclio_versions
24
+ from mlrun.runtimes.nuclio.api_gateway import (
25
+ APIGateway,
26
+ APIGatewayMetadata,
27
+ APIGatewaySpec,
28
+ )
29
+ from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
30
+ from mlrun.utils import logger
31
+
32
+
33
+ class ApplicationSpec(NuclioSpec):
34
+ _dict_fields = NuclioSpec._dict_fields + [
35
+ "internal_application_port",
36
+ ]
37
+
38
+ def __init__(
39
+ self,
40
+ command=None,
41
+ args=None,
42
+ image=None,
43
+ mode=None,
44
+ entry_points=None,
45
+ description=None,
46
+ replicas=None,
47
+ min_replicas=None,
48
+ max_replicas=None,
49
+ volumes=None,
50
+ volume_mounts=None,
51
+ env=None,
52
+ resources=None,
53
+ config=None,
54
+ base_spec=None,
55
+ no_cache=None,
56
+ source=None,
57
+ image_pull_policy=None,
58
+ function_kind=None,
59
+ build=None,
60
+ service_account=None,
61
+ readiness_timeout=None,
62
+ readiness_timeout_before_failure=None,
63
+ default_handler=None,
64
+ node_name=None,
65
+ node_selector=None,
66
+ affinity=None,
67
+ disable_auto_mount=False,
68
+ priority_class_name=None,
69
+ pythonpath=None,
70
+ workdir=None,
71
+ image_pull_secret=None,
72
+ tolerations=None,
73
+ preemption_mode=None,
74
+ security_context=None,
75
+ service_type=None,
76
+ add_templated_ingress_host_mode=None,
77
+ clone_target_dir=None,
78
+ state_thresholds=None,
79
+ disable_default_http_trigger=None,
80
+ internal_application_port=None,
81
+ ):
82
+ super().__init__(
83
+ command=command,
84
+ args=args,
85
+ image=image,
86
+ mode=mode,
87
+ entry_points=entry_points,
88
+ description=description,
89
+ replicas=replicas,
90
+ min_replicas=min_replicas,
91
+ max_replicas=max_replicas,
92
+ volumes=volumes,
93
+ volume_mounts=volume_mounts,
94
+ env=env,
95
+ resources=resources,
96
+ config=config,
97
+ base_spec=base_spec,
98
+ no_cache=no_cache,
99
+ source=source,
100
+ image_pull_policy=image_pull_policy,
101
+ function_kind=function_kind,
102
+ build=build,
103
+ service_account=service_account,
104
+ readiness_timeout=readiness_timeout,
105
+ readiness_timeout_before_failure=readiness_timeout_before_failure,
106
+ default_handler=default_handler,
107
+ node_name=node_name,
108
+ node_selector=node_selector,
109
+ affinity=affinity,
110
+ disable_auto_mount=disable_auto_mount,
111
+ priority_class_name=priority_class_name,
112
+ pythonpath=pythonpath,
113
+ workdir=workdir,
114
+ image_pull_secret=image_pull_secret,
115
+ tolerations=tolerations,
116
+ preemption_mode=preemption_mode,
117
+ security_context=security_context,
118
+ service_type=service_type,
119
+ add_templated_ingress_host_mode=add_templated_ingress_host_mode,
120
+ clone_target_dir=clone_target_dir,
121
+ state_thresholds=state_thresholds,
122
+ disable_default_http_trigger=disable_default_http_trigger,
123
+ )
124
+ self.internal_application_port = (
125
+ internal_application_port
126
+ or mlrun.mlconf.function.application.default_sidecar_internal_port
127
+ )
128
+
129
+ @property
130
+ def internal_application_port(self):
131
+ return self._internal_application_port
132
+
133
+ @internal_application_port.setter
134
+ def internal_application_port(self, port):
135
+ port = int(port)
136
+ if port < 0 or port > 65535:
137
+ raise ValueError("Port must be in the range 0-65535")
138
+ self._internal_application_port = port
139
+
140
+
141
+ class ApplicationStatus(NuclioStatus):
142
+ def __init__(
143
+ self,
144
+ state=None,
145
+ nuclio_name=None,
146
+ address=None,
147
+ internal_invocation_urls=None,
148
+ external_invocation_urls=None,
149
+ build_pod=None,
150
+ container_image=None,
151
+ application_image=None,
152
+ sidecar_name=None,
153
+ api_gateway_name=None,
154
+ api_gateway=None,
155
+ url=None,
156
+ ):
157
+ super().__init__(
158
+ state=state,
159
+ nuclio_name=nuclio_name,
160
+ address=address,
161
+ internal_invocation_urls=internal_invocation_urls,
162
+ external_invocation_urls=external_invocation_urls,
163
+ build_pod=build_pod,
164
+ container_image=container_image,
165
+ )
166
+ self.application_image = application_image or None
167
+ self.sidecar_name = sidecar_name or None
168
+ self.api_gateway_name = api_gateway_name or None
169
+ self.api_gateway = api_gateway or None
170
+ self.url = url or None
171
+
172
+
173
+ class ApplicationRuntime(RemoteRuntime):
174
+ kind = "application"
175
+
176
+ @min_nuclio_versions("1.13.1")
177
+ def __init__(self, spec=None, metadata=None):
178
+ super().__init__(spec=spec, metadata=metadata)
179
+
180
+ @property
181
+ def spec(self) -> ApplicationSpec:
182
+ return self._spec
183
+
184
+ @spec.setter
185
+ def spec(self, spec):
186
+ self._spec = self._verify_dict(spec, "spec", ApplicationSpec)
187
+
188
+ @property
189
+ def status(self) -> ApplicationStatus:
190
+ return self._status
191
+
192
+ @status.setter
193
+ def status(self, status):
194
+ self._status = self._verify_dict(status, "status", ApplicationStatus)
195
+
196
+ @property
197
+ def api_gateway(self):
198
+ return self.status.api_gateway
199
+
200
+ @api_gateway.setter
201
+ def api_gateway(self, api_gateway: APIGateway):
202
+ self.status.api_gateway = api_gateway
203
+
204
+ @property
205
+ def url(self):
206
+ if not self.status.api_gateway:
207
+ self._sync_api_gateway()
208
+ return self.status.api_gateway.invoke_url
209
+
210
+ @url.setter
211
+ def url(self, url):
212
+ self.status.url = url
213
+
214
+ def set_internal_application_port(self, port: int):
215
+ self.spec.internal_application_port = port
216
+
217
+ def pre_deploy_validation(self):
218
+ super().pre_deploy_validation()
219
+ if not self.spec.config.get("spec.sidecars"):
220
+ raise mlrun.errors.MLRunBadRequestError(
221
+ "Application spec must include a sidecar configuration"
222
+ )
223
+
224
+ sidecars = self.spec.config["spec.sidecars"]
225
+ for sidecar in sidecars:
226
+ if not sidecar.get("image"):
227
+ raise mlrun.errors.MLRunBadRequestError(
228
+ "Application sidecar spec must include an image"
229
+ )
230
+
231
+ if not sidecar.get("ports"):
232
+ raise mlrun.errors.MLRunBadRequestError(
233
+ "Application sidecar spec must include at least one port"
234
+ )
235
+
236
+ ports = sidecar["ports"]
237
+ for port in ports:
238
+ if not port.get("containerPort"):
239
+ raise mlrun.errors.MLRunBadRequestError(
240
+ "Application sidecar port spec must include a containerPort"
241
+ )
242
+
243
+ if not port.get("name"):
244
+ raise mlrun.errors.MLRunBadRequestError(
245
+ "Application sidecar port spec must include a name"
246
+ )
247
+
248
+ if not sidecar.get("command") and sidecar.get("args"):
249
+ raise mlrun.errors.MLRunBadRequestError(
250
+ "Application sidecar spec must include a command if args are provided"
251
+ )
252
+
253
+ def deploy(
254
+ self,
255
+ project="",
256
+ tag="",
257
+ verbose=False,
258
+ auth_info: schemas.AuthInfo = None,
259
+ builder_env: dict = None,
260
+ force_build: bool = False,
261
+ with_mlrun=None,
262
+ skip_deployed=False,
263
+ is_kfp=False,
264
+ mlrun_version_specifier=None,
265
+ show_on_failure: bool = False,
266
+ skip_access_key_auth: bool = False,
267
+ direct_port_access: bool = False,
268
+ authentication_mode: schemas.APIGatewayAuthenticationMode = None,
269
+ authentication_creds: tuple[str] = None,
270
+ ):
271
+ """
272
+ Deploy function, builds the application image if required (self.requires_build()) or force_build is True,
273
+ Once the image is built, the function is deployed.
274
+ :param project: Project name
275
+ :param tag: Function tag
276
+ :param verbose: Set True for verbose logging
277
+ :param auth_info: Service AuthInfo (deprecated and ignored)
278
+ :param builder_env: Env vars dict for source archive config/credentials
279
+ e.g. builder_env={"GIT_TOKEN": token}
280
+ :param force_build: Set True for force building the application image
281
+ :param with_mlrun: Add the current mlrun package to the container build
282
+ :param skip_deployed: Skip the build if we already have an image for the function
283
+ :param is_kfp: Deploy as part of a kfp pipeline
284
+ :param mlrun_version_specifier: Which mlrun package version to include (if not current)
285
+ :param show_on_failure: Show logs only in case of build failure
286
+ :param skip_access_key_auth: Skip adding access key auth to the API Gateway
287
+ :param direct_port_access: Set True to allow direct port access to the application sidecar
288
+ :param authentication_mode: API Gateway authentication mode
289
+ :param authentication_creds: API Gateway authentication credentials as a tuple (username, password)
290
+ :return: True if the function is ready (deployed)
291
+ """
292
+ if self.requires_build() or force_build:
293
+ self._fill_credentials()
294
+ self._build_application_image(
295
+ builder_env=builder_env,
296
+ force_build=force_build,
297
+ watch=True,
298
+ with_mlrun=with_mlrun,
299
+ skip_deployed=skip_deployed,
300
+ is_kfp=is_kfp,
301
+ mlrun_version_specifier=mlrun_version_specifier,
302
+ show_on_failure=show_on_failure,
303
+ )
304
+
305
+ self._ensure_reverse_proxy_configurations()
306
+ self._configure_application_sidecar()
307
+
308
+ # we only allow accessing the application via the API Gateway
309
+ name_tag = tag or self.metadata.tag
310
+ self.status.api_gateway_name = (
311
+ f"{self.metadata.name}-{name_tag}" if name_tag else self.metadata.name
312
+ )
313
+ self.spec.add_templated_ingress_host_mode = (
314
+ NuclioIngressAddTemplatedIngressModes.never
315
+ )
316
+
317
+ super().deploy(
318
+ project,
319
+ tag,
320
+ verbose,
321
+ auth_info,
322
+ builder_env,
323
+ )
324
+
325
+ ports = self.spec.internal_application_port if direct_port_access else []
326
+ self.create_api_gateway(
327
+ name=self.status.api_gateway_name,
328
+ ports=ports,
329
+ authentication_mode=authentication_mode,
330
+ authentication_creds=authentication_creds,
331
+ )
332
+
333
+ def with_source_archive(
334
+ self, source, workdir=None, pull_at_runtime=True, target_dir=None
335
+ ):
336
+ """load the code from git/tar/zip archive at runtime or build
337
+
338
+ :param source: valid absolute path or URL to git, zip, or tar file, e.g.
339
+ git://github.com/mlrun/something.git
340
+ http://some/url/file.zip
341
+ note path source must exist on the image or exist locally when run is local
342
+ (it is recommended to use 'workdir' when source is a filepath instead)
343
+ :param workdir: working dir relative to the archive root (e.g. './subdir') or absolute to the image root
344
+ :param pull_at_runtime: load the archive into the container at job runtime vs on build/deploy
345
+ :param target_dir: target dir on runtime pod or repo clone / archive extraction
346
+ """
347
+ self._configure_mlrun_build_with_source(
348
+ source=source,
349
+ workdir=workdir,
350
+ pull_at_runtime=pull_at_runtime,
351
+ target_dir=target_dir,
352
+ )
353
+
354
+ @classmethod
355
+ def get_filename_and_handler(cls) -> (str, str):
356
+ reverse_proxy_file_path = pathlib.Path(__file__).parent / "reverse_proxy.go"
357
+ return str(reverse_proxy_file_path), "Handler"
358
+
359
+ def create_api_gateway(
360
+ self,
361
+ name: str = None,
362
+ path: str = None,
363
+ ports: list[int] = None,
364
+ authentication_mode: schemas.APIGatewayAuthenticationMode = None,
365
+ authentication_creds: tuple[str] = None,
366
+ ):
367
+ api_gateway = APIGateway(
368
+ APIGatewayMetadata(
369
+ name=name,
370
+ namespace=self.metadata.namespace,
371
+ labels=self.metadata.labels,
372
+ annotations=self.metadata.annotations,
373
+ ),
374
+ APIGatewaySpec(
375
+ functions=[self],
376
+ project=self.metadata.project,
377
+ path=path,
378
+ ports=mlrun.utils.helpers.as_list(ports) if ports else None,
379
+ ),
380
+ )
381
+
382
+ authentication_mode = (
383
+ authentication_mode
384
+ or mlrun.mlconf.function.application.default_authentication_mode
385
+ )
386
+ if authentication_mode == schemas.APIGatewayAuthenticationMode.access_key:
387
+ api_gateway.with_access_key_auth()
388
+ elif authentication_mode == schemas.APIGatewayAuthenticationMode.basic:
389
+ api_gateway.with_basic_auth(*authentication_creds)
390
+
391
+ db = self._get_db()
392
+ api_gateway_scheme = db.store_api_gateway(
393
+ api_gateway=api_gateway.to_scheme(), project=self.metadata.project
394
+ )
395
+ if not self.status.api_gateway_name:
396
+ self.status.api_gateway_name = api_gateway_scheme.metadata.name
397
+ self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
398
+ self.status.api_gateway.wait_for_readiness()
399
+ self.url = self.status.api_gateway.invoke_url
400
+
401
+ def invoke(
402
+ self,
403
+ path: str,
404
+ body: typing.Union[str, bytes, dict] = None,
405
+ method: str = None,
406
+ headers: dict = None,
407
+ dashboard: str = "",
408
+ force_external_address: bool = False,
409
+ auth_info: schemas.AuthInfo = None,
410
+ mock: bool = None,
411
+ **http_client_kwargs,
412
+ ):
413
+ self._sync_api_gateway()
414
+ # If the API Gateway is not ready or not set, try to invoke the function directly (without the API Gateway)
415
+ if not self.status.api_gateway:
416
+ super().invoke(
417
+ path,
418
+ body,
419
+ method,
420
+ headers,
421
+ dashboard,
422
+ force_external_address,
423
+ auth_info,
424
+ mock,
425
+ **http_client_kwargs,
426
+ )
427
+
428
+ credentials = (auth_info.username, auth_info.password) if auth_info else None
429
+
430
+ if not method:
431
+ method = "POST" if body else "GET"
432
+ return self.status.api_gateway.invoke(
433
+ method=method,
434
+ headers=headers,
435
+ credentials=credentials,
436
+ path=path,
437
+ **http_client_kwargs,
438
+ )
439
+
440
+ def _build_application_image(
441
+ self,
442
+ builder_env: dict = None,
443
+ force_build: bool = False,
444
+ watch=True,
445
+ with_mlrun=None,
446
+ skip_deployed=False,
447
+ is_kfp=False,
448
+ mlrun_version_specifier=None,
449
+ show_on_failure: bool = False,
450
+ ):
451
+ if not self.spec.command:
452
+ logger.warning(
453
+ "Building the application image without a command. "
454
+ "Use spec.command and spec.args to specify the application entrypoint",
455
+ command=self.spec.command,
456
+ args=self.spec.args,
457
+ )
458
+
459
+ with_mlrun = self._resolve_build_with_mlrun(with_mlrun)
460
+ return self._build_image(
461
+ builder_env=builder_env,
462
+ force_build=force_build,
463
+ mlrun_version_specifier=mlrun_version_specifier,
464
+ show_on_failure=show_on_failure,
465
+ skip_deployed=skip_deployed,
466
+ watch=watch,
467
+ is_kfp=is_kfp,
468
+ with_mlrun=with_mlrun,
469
+ )
470
+
471
+ def _ensure_reverse_proxy_configurations(self):
472
+ if self.spec.build.functionSourceCode or self.status.container_image:
473
+ return
474
+
475
+ filename, handler = ApplicationRuntime.get_filename_and_handler()
476
+ name, spec, code = nuclio.build_file(
477
+ filename,
478
+ name=self.metadata.name,
479
+ handler=handler,
480
+ )
481
+ self.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
482
+ self.spec.build.functionSourceCode = mlrun.utils.get_in(
483
+ spec, "spec.build.functionSourceCode"
484
+ )
485
+ self.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
486
+
487
+ def _configure_application_sidecar(self):
488
+ # Save the application image in the status to allow overriding it with the reverse proxy entry point
489
+ if self.spec.image and (
490
+ not self.status.application_image
491
+ or self.spec.image != self.status.container_image
492
+ ):
493
+ self.status.application_image = self.spec.image
494
+ self.spec.image = ""
495
+
496
+ if self.status.container_image:
497
+ self.from_image(self.status.container_image)
498
+ # nuclio implementation detail - when providing the image and emptying out the source code,
499
+ # nuclio skips rebuilding the image and simply takes the prebuilt image
500
+ self.spec.build.functionSourceCode = ""
501
+
502
+ self.status.sidecar_name = f"{self.metadata.name}-sidecar"
503
+ self.with_sidecar(
504
+ name=self.status.sidecar_name,
505
+ image=self.status.application_image,
506
+ ports=self.spec.internal_application_port,
507
+ command=self.spec.command,
508
+ args=self.spec.args,
509
+ )
510
+ self.set_env("SIDECAR_PORT", self.spec.internal_application_port)
511
+ self.set_env("SIDECAR_HOST", "http://localhost")
512
+
513
+ def _sync_api_gateway(self):
514
+ if not self.status.api_gateway_name:
515
+ return
516
+
517
+ db = self._get_db()
518
+ api_gateway_scheme = db.get_api_gateway(
519
+ name=self.status.api_gateway_name, project=self.metadata.project
520
+ )
521
+ self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
522
+ self.status.api_gateway.wait_for_readiness()
523
+ self.url = self.status.api_gateway.invoke_url
@@ -0,0 +1,95 @@
1
+ // Copyright 2024 Iguazio
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ package main
15
+
16
+ import (
17
+ "bytes"
18
+ "fmt"
19
+ "net/http"
20
+ "net/http/httptest"
21
+ "net/http/httputil"
22
+ "net/url"
23
+ "os"
24
+ "strings"
25
+
26
+ nuclio "github.com/nuclio/nuclio-sdk-go"
27
+ )
28
+
29
+ func Handler(context *nuclio.Context, event nuclio.Event) (interface{}, error) {
30
+ reverseProxy := context.UserData.(map[string]interface{})["reverseProxy"].(*httputil.ReverseProxy)
31
+ sidecarUrl := context.UserData.(map[string]interface{})["server"].(string)
32
+
33
+ // populate reverse proxy http request
34
+ httpRequest, err := http.NewRequest(event.GetMethod(), event.GetPath(), bytes.NewReader(event.GetBody()))
35
+ if err != nil {
36
+ context.Logger.ErrorWith("Failed to create a reverse proxy request")
37
+ return nil, err
38
+ }
39
+ for k, v := range event.GetHeaders() {
40
+ httpRequest.Header[k] = []string{v.(string)}
41
+ }
42
+
43
+ // populate query params
44
+ query := httpRequest.URL.Query()
45
+ for k, v := range event.GetFields() {
46
+ query.Set(k, v.(string))
47
+ }
48
+ httpRequest.URL.RawQuery = query.Encode()
49
+
50
+ recorder := httptest.NewRecorder()
51
+ reverseProxy.ServeHTTP(recorder, httpRequest)
52
+
53
+ // send request to sidecar
54
+ context.Logger.DebugWith("Forwarding request to sidecar", "sidecarUrl", sidecarUrl, "query", httpRequest.URL.Query())
55
+ response := recorder.Result()
56
+
57
+ headers := make(map[string]interface{})
58
+ for key, value := range response.Header {
59
+ headers[key] = value[0]
60
+ }
61
+
62
+ // let the processor calculate the content length
63
+ delete(headers, "Content-Length")
64
+ return nuclio.Response{
65
+ StatusCode: response.StatusCode,
66
+ Body: recorder.Body.Bytes(),
67
+ ContentType: response.Header.Get("Content-Type"),
68
+ Headers: headers,
69
+ }, nil
70
+ }
71
+
72
+ func InitContext(context *nuclio.Context) error {
73
+ sidecarHost := os.Getenv("SIDECAR_HOST")
74
+ sidecarPort := os.Getenv("SIDECAR_PORT")
75
+ if sidecarHost == "" {
76
+ sidecarHost = "http://localhost"
77
+ } else if !strings.Contains(sidecarHost, "://") {
78
+ sidecarHost = fmt.Sprintf("http://%s", sidecarHost)
79
+ }
80
+
81
+ // url for request forwarding
82
+ sidecarUrl := fmt.Sprintf("%s:%s", sidecarHost, sidecarPort)
83
+ parsedURL, err := url.Parse(sidecarUrl)
84
+ if err != nil {
85
+ context.Logger.ErrorWith("Failed to parse sidecar url", "sidecarUrl", sidecarUrl)
86
+ return err
87
+ }
88
+ reverseProxy := httputil.NewSingleHostReverseProxy(parsedURL)
89
+
90
+ context.UserData = map[string]interface{}{
91
+ "server": sidecarUrl,
92
+ "reverseProxy": reverseProxy,
93
+ }
94
+ return nil
95
+ }