mlrun 1.7.0rc4__py3-none-any.whl → 1.7.0rc6__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 (47) hide show
  1. mlrun/artifacts/base.py +2 -1
  2. mlrun/artifacts/plots.py +9 -5
  3. mlrun/common/constants.py +1 -0
  4. mlrun/common/schemas/__init__.py +10 -0
  5. mlrun/common/schemas/api_gateway.py +85 -0
  6. mlrun/common/schemas/auth.py +2 -2
  7. mlrun/config.py +19 -4
  8. mlrun/datastore/sources.py +5 -4
  9. mlrun/datastore/targets.py +16 -20
  10. mlrun/db/base.py +16 -0
  11. mlrun/db/factory.py +1 -1
  12. mlrun/db/httpdb.py +50 -8
  13. mlrun/db/nopdb.py +13 -0
  14. mlrun/launcher/__init__.py +1 -1
  15. mlrun/launcher/base.py +1 -1
  16. mlrun/launcher/client.py +1 -1
  17. mlrun/launcher/factory.py +1 -1
  18. mlrun/launcher/local.py +1 -1
  19. mlrun/launcher/remote.py +1 -1
  20. mlrun/model_monitoring/api.py +6 -12
  21. mlrun/model_monitoring/application.py +21 -21
  22. mlrun/model_monitoring/applications/histogram_data_drift.py +130 -40
  23. mlrun/model_monitoring/batch.py +1 -42
  24. mlrun/model_monitoring/controller.py +1 -8
  25. mlrun/model_monitoring/features_drift_table.py +34 -22
  26. mlrun/model_monitoring/helpers.py +45 -4
  27. mlrun/model_monitoring/stream_processing.py +2 -0
  28. mlrun/projects/project.py +229 -16
  29. mlrun/run.py +70 -74
  30. mlrun/runtimes/__init__.py +35 -0
  31. mlrun/runtimes/base.py +15 -11
  32. mlrun/runtimes/nuclio/__init__.py +1 -0
  33. mlrun/runtimes/nuclio/api_gateway.py +300 -0
  34. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  35. mlrun/runtimes/nuclio/application/application.py +283 -0
  36. mlrun/runtimes/nuclio/application/reverse_proxy.go +87 -0
  37. mlrun/runtimes/nuclio/function.py +50 -1
  38. mlrun/runtimes/pod.py +1 -1
  39. mlrun/serving/states.py +7 -19
  40. mlrun/utils/logger.py +2 -2
  41. mlrun/utils/version/version.json +2 -2
  42. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/METADATA +1 -1
  43. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/RECORD +47 -42
  44. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/WHEEL +1 -1
  45. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/LICENSE +0 -0
  46. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/entry_points.txt +0 -0
  47. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc6.dist-info}/top_level.txt +0 -0
mlrun/runtimes/base.py CHANGED
@@ -138,20 +138,24 @@ class FunctionSpec(ModelObj):
138
138
 
139
139
  @property
140
140
  def clone_target_dir(self):
141
- warnings.warn(
142
- "The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.8.0. "
143
- "Use spec.build.source_code_target_dir instead.",
144
- FutureWarning,
145
- )
141
+ # TODO: remove this property in 1.9.0
142
+ if self.build.source_code_target_dir:
143
+ warnings.warn(
144
+ "The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.9.0. "
145
+ "Use spec.build.source_code_target_dir instead.",
146
+ FutureWarning,
147
+ )
146
148
  return self.build.source_code_target_dir
147
149
 
148
150
  @clone_target_dir.setter
149
151
  def clone_target_dir(self, clone_target_dir):
150
- warnings.warn(
151
- "The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.8.0. "
152
- "Use spec.build.source_code_target_dir instead.",
153
- FutureWarning,
154
- )
152
+ # TODO: remove this property in 1.9.0
153
+ if clone_target_dir:
154
+ warnings.warn(
155
+ "The clone_target_dir attribute is deprecated in 1.6.2 and will be removed in 1.9.0. "
156
+ "Use spec.build.source_code_target_dir instead.",
157
+ FutureWarning,
158
+ )
155
159
  self.build.source_code_target_dir = clone_target_dir
156
160
 
157
161
  def enrich_function_preemption_spec(self):
@@ -782,7 +786,7 @@ class BaseRuntime(ModelObj):
782
786
  requirements: Optional[list[str]] = None,
783
787
  overwrite: bool = False,
784
788
  prepare_image_for_deploy: bool = True,
785
- requirements_file: str = "",
789
+ requirements_file: Optional[str] = "",
786
790
  ):
787
791
  """add package requirements from file or list to build spec.
788
792
 
@@ -18,3 +18,4 @@ from .function import (
18
18
  min_nuclio_versions,
19
19
  RemoteRuntime,
20
20
  ) # noqa
21
+ from .api_gateway import APIGateway
@@ -0,0 +1,300 @@
1
+ # Copyright 2023 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 base64
15
+ from typing import Optional, Union
16
+ from urllib.parse import urljoin
17
+
18
+ import requests
19
+
20
+ import mlrun
21
+ import mlrun.common.schemas
22
+
23
+ from .function import RemoteRuntime
24
+ from .serving import ServingRuntime
25
+
26
+ NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH = "basicAuth"
27
+ NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE = "none"
28
+ PROJECT_NAME_LABEL = "nuclio.io/project-name"
29
+
30
+
31
+ class APIGateway:
32
+ def __init__(
33
+ self,
34
+ project,
35
+ name: str,
36
+ functions: Union[
37
+ list[str],
38
+ Union[
39
+ list[
40
+ Union[
41
+ RemoteRuntime,
42
+ ServingRuntime,
43
+ ]
44
+ ],
45
+ Union[RemoteRuntime, ServingRuntime],
46
+ ],
47
+ ],
48
+ description: str = "",
49
+ path: str = "/",
50
+ authentication_mode: Optional[
51
+ str
52
+ ] = NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE,
53
+ host: Optional[str] = None,
54
+ canary: Optional[list[int]] = None,
55
+ username: Optional[str] = None,
56
+ password: Optional[str] = None,
57
+ ):
58
+ self.functions = None
59
+ self._validate(
60
+ project=project,
61
+ functions=functions,
62
+ name=name,
63
+ canary=canary,
64
+ username=username,
65
+ password=password,
66
+ )
67
+ self.project = project
68
+ self.name = name
69
+ self.host = host
70
+
71
+ self.path = path
72
+ self.description = description
73
+ self.authentication_mode = (
74
+ authentication_mode
75
+ if authentication_mode
76
+ else self._enrich_authentication_mode(username=username, password=password)
77
+ )
78
+ self.canary = canary
79
+ self._username = username
80
+ self._password = password
81
+
82
+ def invoke(
83
+ self,
84
+ method="POST",
85
+ headers: dict = {},
86
+ auth: Optional[tuple[str, str]] = None,
87
+ **kwargs,
88
+ ):
89
+ if not self.invoke_url:
90
+ raise mlrun.errors.MLRunInvalidArgumentError(
91
+ "Invocation url is not set. Set up gateway's `invoke_url` attribute."
92
+ )
93
+ if (
94
+ self.authentication_mode
95
+ == NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
96
+ and not auth
97
+ ):
98
+ raise mlrun.errors.MLRunInvalidArgumentError(
99
+ "API Gateway invocation requires authentication. Please pass credentials"
100
+ )
101
+ if auth:
102
+ headers["Authorization"] = self._generate_basic_auth(*auth)
103
+ return requests.request(
104
+ method=method, url=self.invoke_url, headers=headers, **kwargs
105
+ )
106
+
107
+ @classmethod
108
+ def from_scheme(cls, api_gateway: mlrun.common.schemas.APIGateway):
109
+ project = api_gateway.metadata.labels.get(PROJECT_NAME_LABEL)
110
+ functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
111
+ return cls(
112
+ project=project,
113
+ description=api_gateway.spec.description,
114
+ name=api_gateway.spec.name,
115
+ host=api_gateway.spec.host,
116
+ path=api_gateway.spec.path,
117
+ authentication_mode=str(api_gateway.spec.authenticationMode),
118
+ functions=functions,
119
+ canary=canary,
120
+ )
121
+
122
+ def to_scheme(self) -> mlrun.common.schemas.APIGateway:
123
+ upstreams = (
124
+ [
125
+ mlrun.common.schemas.APIGatewayUpstream(
126
+ nucliofunction={"name": function_name},
127
+ percentage=percentage,
128
+ )
129
+ for function_name, percentage in zip(self.functions, self.canary)
130
+ ]
131
+ if self.canary
132
+ else [
133
+ mlrun.common.schemas.APIGatewayUpstream(
134
+ nucliofunction={"name": function_name},
135
+ )
136
+ for function_name in self.functions
137
+ ]
138
+ )
139
+ api_gateway = mlrun.common.schemas.APIGateway(
140
+ metadata=mlrun.common.schemas.APIGatewayMetadata(name=self.name, labels={}),
141
+ spec=mlrun.common.schemas.APIGatewaySpec(
142
+ name=self.name,
143
+ description=self.description,
144
+ path=self.path,
145
+ authentication_mode=mlrun.common.schemas.APIGatewayAuthenticationMode.from_str(
146
+ self.authentication_mode
147
+ ),
148
+ upstreams=upstreams,
149
+ ),
150
+ )
151
+ if (
152
+ self.authentication_mode
153
+ is NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
154
+ ):
155
+ api_gateway.spec.authentication = mlrun.common.schemas.APIGatewayBasicAuth(
156
+ username=self._username, password=self._password
157
+ )
158
+ return api_gateway
159
+
160
+ @property
161
+ def invoke_url(
162
+ self,
163
+ ):
164
+ return urljoin(self.host, self.path)
165
+
166
+ def _validate(
167
+ self,
168
+ name: str,
169
+ project: str,
170
+ functions: Union[
171
+ list[str],
172
+ Union[
173
+ list[
174
+ Union[
175
+ RemoteRuntime,
176
+ ServingRuntime,
177
+ ]
178
+ ],
179
+ Union[RemoteRuntime, ServingRuntime],
180
+ ],
181
+ ],
182
+ canary: Optional[list[int]] = None,
183
+ username: Optional[str] = None,
184
+ password: Optional[str] = None,
185
+ ):
186
+ if not name:
187
+ raise mlrun.errors.MLRunInvalidArgumentError(
188
+ "API Gateway name cannot be empty"
189
+ )
190
+
191
+ self.functions = self._validate_functions(project=project, functions=functions)
192
+
193
+ # validating canary
194
+ if canary:
195
+ if len(self.functions) != len(canary):
196
+ raise mlrun.errors.MLRunInvalidArgumentError(
197
+ "Function and canary lists lengths do not match"
198
+ )
199
+ for canary_percent in canary:
200
+ if canary_percent < 0 or canary_percent > 100:
201
+ raise mlrun.errors.MLRunInvalidArgumentError(
202
+ "The percentage value must be in the range from 0 to 100"
203
+ )
204
+ if sum(canary) != 100:
205
+ raise mlrun.errors.MLRunInvalidArgumentError(
206
+ "The sum of canary function percents should be equal to 100"
207
+ )
208
+
209
+ # validating auth
210
+ if username and not password:
211
+ raise mlrun.errors.MLRunInvalidArgumentError("Password is not specified")
212
+
213
+ if password and not username:
214
+ raise mlrun.errors.MLRunInvalidArgumentError("Username is not specified")
215
+
216
+ @staticmethod
217
+ def _validate_functions(
218
+ project: str,
219
+ functions: Union[
220
+ list[str],
221
+ Union[
222
+ list[
223
+ Union[
224
+ RemoteRuntime,
225
+ ServingRuntime,
226
+ ]
227
+ ],
228
+ Union[RemoteRuntime, ServingRuntime],
229
+ ],
230
+ ],
231
+ ):
232
+ if not isinstance(functions, list):
233
+ functions = [functions]
234
+
235
+ # validating functions
236
+ if not 1 <= len(functions) <= 2:
237
+ raise mlrun.errors.MLRunInvalidArgumentError(
238
+ f"Gateway can be created from one or two functions, "
239
+ f"the number of functions passed is {len(functions)}"
240
+ )
241
+
242
+ function_names = []
243
+ for func in functions:
244
+ if isinstance(func, str):
245
+ function_names.append(func)
246
+ continue
247
+
248
+ function_name = (
249
+ func.metadata.name if hasattr(func, "metadata") else func.name
250
+ )
251
+ if func.kind not in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
252
+ raise mlrun.errors.MLRunInvalidArgumentError(
253
+ f"Input function {function_name} is not a Nuclio function"
254
+ )
255
+ if func.metadata.project != project:
256
+ raise mlrun.errors.MLRunInvalidArgumentError(
257
+ f"input function {function_name} "
258
+ f"does not belong to this project"
259
+ )
260
+ function_names.append(func.uri)
261
+ return function_names
262
+
263
+ @staticmethod
264
+ def _enrich_authentication_mode(username, password):
265
+ return (
266
+ NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE
267
+ if username is not None and password is not None
268
+ else NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
269
+ )
270
+
271
+ @staticmethod
272
+ def _generate_basic_auth(username: str, password: str):
273
+ token = base64.b64encode(f"{username}:{password}".encode()).decode()
274
+ return f"Basic {token}"
275
+
276
+ @staticmethod
277
+ def _resolve_canary(
278
+ upstreams: list[mlrun.common.schemas.APIGatewayUpstream],
279
+ ) -> tuple[Union[list[str], None], Union[list[int], None]]:
280
+ if len(upstreams) == 1:
281
+ return [upstreams[0].nucliofunction.get("name")], None
282
+ elif len(upstreams) == 2:
283
+ canary = [0, 0]
284
+ functions = [
285
+ upstreams[0].nucliofunction.get("name"),
286
+ upstreams[1].nucliofunction.get("name"),
287
+ ]
288
+ percentage_1 = upstreams[0].percentage
289
+ percentage_2 = upstreams[1].percentage
290
+
291
+ if not percentage_1 and percentage_2:
292
+ percentage_1 = 100 - percentage_2
293
+ if not percentage_2 and percentage_1:
294
+ percentage_2 = 100 - percentage_1
295
+ if percentage_1 and percentage_2:
296
+ canary = [percentage_1, percentage_2]
297
+ return functions, canary
298
+ else:
299
+ # Nuclio only supports 1 or 2 upstream functions
300
+ return None, None
@@ -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,283 @@
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
+
16
+ import nuclio
17
+
18
+ import mlrun.errors
19
+ from mlrun.common.schemas import AuthInfo
20
+ from mlrun.runtimes import RemoteRuntime
21
+ from mlrun.runtimes.nuclio import min_nuclio_versions
22
+ from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
23
+
24
+
25
+ class ApplicationSpec(NuclioSpec):
26
+ _dict_fields = NuclioSpec._dict_fields + [
27
+ "internal_application_port",
28
+ ]
29
+
30
+ def __init__(
31
+ self,
32
+ command=None,
33
+ args=None,
34
+ image=None,
35
+ mode=None,
36
+ entry_points=None,
37
+ description=None,
38
+ replicas=None,
39
+ min_replicas=None,
40
+ max_replicas=None,
41
+ volumes=None,
42
+ volume_mounts=None,
43
+ env=None,
44
+ resources=None,
45
+ config=None,
46
+ base_spec=None,
47
+ no_cache=None,
48
+ source=None,
49
+ image_pull_policy=None,
50
+ function_kind=None,
51
+ build=None,
52
+ service_account=None,
53
+ readiness_timeout=None,
54
+ readiness_timeout_before_failure=None,
55
+ default_handler=None,
56
+ node_name=None,
57
+ node_selector=None,
58
+ affinity=None,
59
+ disable_auto_mount=False,
60
+ priority_class_name=None,
61
+ pythonpath=None,
62
+ workdir=None,
63
+ image_pull_secret=None,
64
+ tolerations=None,
65
+ preemption_mode=None,
66
+ security_context=None,
67
+ service_type=None,
68
+ add_templated_ingress_host_mode=None,
69
+ clone_target_dir=None,
70
+ state_thresholds=None,
71
+ disable_default_http_trigger=None,
72
+ internal_application_port=None,
73
+ ):
74
+ super().__init__(
75
+ command=command,
76
+ args=args,
77
+ image=image,
78
+ mode=mode,
79
+ entry_points=entry_points,
80
+ description=description,
81
+ replicas=replicas,
82
+ min_replicas=min_replicas,
83
+ max_replicas=max_replicas,
84
+ volumes=volumes,
85
+ volume_mounts=volume_mounts,
86
+ env=env,
87
+ resources=resources,
88
+ config=config,
89
+ base_spec=base_spec,
90
+ no_cache=no_cache,
91
+ source=source,
92
+ image_pull_policy=image_pull_policy,
93
+ function_kind=function_kind,
94
+ build=build,
95
+ service_account=service_account,
96
+ readiness_timeout=readiness_timeout,
97
+ readiness_timeout_before_failure=readiness_timeout_before_failure,
98
+ default_handler=default_handler,
99
+ node_name=node_name,
100
+ node_selector=node_selector,
101
+ affinity=affinity,
102
+ disable_auto_mount=disable_auto_mount,
103
+ priority_class_name=priority_class_name,
104
+ pythonpath=pythonpath,
105
+ workdir=workdir,
106
+ image_pull_secret=image_pull_secret,
107
+ tolerations=tolerations,
108
+ preemption_mode=preemption_mode,
109
+ security_context=security_context,
110
+ service_type=service_type,
111
+ add_templated_ingress_host_mode=add_templated_ingress_host_mode,
112
+ clone_target_dir=clone_target_dir,
113
+ state_thresholds=state_thresholds,
114
+ disable_default_http_trigger=disable_default_http_trigger,
115
+ )
116
+ self.internal_application_port = internal_application_port or 8080
117
+
118
+ @property
119
+ def internal_application_port(self):
120
+ return self._internal_application_port
121
+
122
+ @internal_application_port.setter
123
+ def internal_application_port(self, port):
124
+ port = int(port)
125
+ if port < 0 or port > 65535:
126
+ raise ValueError("Port must be in the range 0-65535")
127
+ self._internal_application_port = port
128
+
129
+
130
+ class ApplicationStatus(NuclioStatus):
131
+ def __init__(
132
+ self,
133
+ state=None,
134
+ nuclio_name=None,
135
+ address=None,
136
+ internal_invocation_urls=None,
137
+ external_invocation_urls=None,
138
+ build_pod=None,
139
+ container_image=None,
140
+ application_image=None,
141
+ sidecar_name=None,
142
+ ):
143
+ super().__init__(
144
+ state=state,
145
+ nuclio_name=nuclio_name,
146
+ address=address,
147
+ internal_invocation_urls=internal_invocation_urls,
148
+ external_invocation_urls=external_invocation_urls,
149
+ build_pod=build_pod,
150
+ container_image=container_image,
151
+ )
152
+ self.application_image = application_image or None
153
+ self.sidecar_name = sidecar_name or None
154
+
155
+
156
+ class ApplicationRuntime(RemoteRuntime):
157
+ kind = "application"
158
+
159
+ @min_nuclio_versions("1.12.7")
160
+ def __init__(self, spec=None, metadata=None):
161
+ super().__init__(spec=spec, metadata=metadata)
162
+
163
+ @property
164
+ def spec(self) -> ApplicationSpec:
165
+ return self._spec
166
+
167
+ @spec.setter
168
+ def spec(self, spec):
169
+ self._spec = self._verify_dict(spec, "spec", ApplicationSpec)
170
+
171
+ @property
172
+ def status(self) -> ApplicationStatus:
173
+ return self._status
174
+
175
+ @status.setter
176
+ def status(self, status):
177
+ self._status = self._verify_dict(status, "status", ApplicationStatus)
178
+
179
+ def set_internal_application_port(self, port: int):
180
+ self.spec.internal_application_port = port
181
+
182
+ def pre_deploy_validation(self):
183
+ super().pre_deploy_validation()
184
+ if not self.spec.config.get("spec.sidecars"):
185
+ raise mlrun.errors.MLRunBadRequestError(
186
+ "Application spec must include a sidecar configuration"
187
+ )
188
+
189
+ sidecars = self.spec.config["spec.sidecars"]
190
+ for sidecar in sidecars:
191
+ if not sidecar.get("image"):
192
+ raise mlrun.errors.MLRunBadRequestError(
193
+ "Application sidecar spec must include an image"
194
+ )
195
+
196
+ if not sidecar.get("ports"):
197
+ raise mlrun.errors.MLRunBadRequestError(
198
+ "Application sidecar spec must include at least one port"
199
+ )
200
+
201
+ ports = sidecar["ports"]
202
+ for port in ports:
203
+ if not port.get("containerPort"):
204
+ raise mlrun.errors.MLRunBadRequestError(
205
+ "Application sidecar port spec must include a containerPort"
206
+ )
207
+
208
+ if not port.get("name"):
209
+ raise mlrun.errors.MLRunBadRequestError(
210
+ "Application sidecar port spec must include a name"
211
+ )
212
+
213
+ if not sidecar.get("command") and sidecar.get("args"):
214
+ raise mlrun.errors.MLRunBadRequestError(
215
+ "Application sidecar spec must include a command if args are provided"
216
+ )
217
+
218
+ def deploy(
219
+ self,
220
+ project="",
221
+ tag="",
222
+ verbose=False,
223
+ auth_info: AuthInfo = None,
224
+ builder_env: dict = None,
225
+ force_build: bool = False,
226
+ ):
227
+ self._ensure_reverse_proxy_configurations()
228
+ self._configure_application_sidecar()
229
+ super().deploy(
230
+ project,
231
+ tag,
232
+ verbose,
233
+ auth_info,
234
+ builder_env,
235
+ force_build,
236
+ )
237
+
238
+ def _ensure_reverse_proxy_configurations(self):
239
+ if self.spec.build.functionSourceCode or self.status.container_image:
240
+ return
241
+
242
+ filename, handler = ApplicationRuntime.get_filename_and_handler()
243
+ name, spec, code = nuclio.build_file(
244
+ filename,
245
+ name=self.metadata.name,
246
+ handler=handler,
247
+ )
248
+ self.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
249
+ self.spec.build.functionSourceCode = mlrun.utils.get_in(
250
+ spec, "spec.build.functionSourceCode"
251
+ )
252
+ self.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
253
+
254
+ def _configure_application_sidecar(self):
255
+ # Save the application image in the status to allow overriding it with the reverse proxy entry point
256
+ if self.spec.image and (
257
+ not self.status.application_image
258
+ or self.spec.image != self.status.container_image
259
+ ):
260
+ self.status.application_image = self.spec.image
261
+ self.spec.image = ""
262
+
263
+ if self.status.container_image:
264
+ self.from_image(self.status.container_image)
265
+ # nuclio implementation detail - when providing the image and emptying out the source code,
266
+ # nuclio skips rebuilding the image and simply takes the prebuilt image
267
+ self.spec.build.functionSourceCode = ""
268
+
269
+ self.status.sidecar_name = f"{self.metadata.name}-sidecar"
270
+ self.with_sidecar(
271
+ name=self.status.sidecar_name,
272
+ image=self.status.application_image,
273
+ ports=self.spec.internal_application_port,
274
+ command=self.spec.command,
275
+ args=self.spec.args,
276
+ )
277
+ self.set_env("SIDECAR_PORT", self.spec.internal_application_port)
278
+ self.set_env("SIDECAR_HOST", "http://localhost")
279
+
280
+ @classmethod
281
+ def get_filename_and_handler(cls) -> (str, str):
282
+ reverse_proxy_file_path = pathlib.Path(__file__).parent / "reverse_proxy.go"
283
+ return str(reverse_proxy_file_path), "Handler"