mlrun 1.7.0rc6__py3-none-any.whl → 1.7.0rc9__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 (84) hide show
  1. mlrun/__main__.py +2 -0
  2. mlrun/common/constants.py +6 -0
  3. mlrun/common/schemas/__init__.py +5 -0
  4. mlrun/common/schemas/api_gateway.py +8 -1
  5. mlrun/common/schemas/hub.py +7 -9
  6. mlrun/common/schemas/model_monitoring/__init__.py +4 -0
  7. mlrun/common/schemas/model_monitoring/constants.py +36 -19
  8. mlrun/{model_monitoring/stores/models/__init__.py → common/schemas/pagination.py} +9 -10
  9. mlrun/common/schemas/project.py +16 -10
  10. mlrun/common/types.py +7 -1
  11. mlrun/config.py +35 -10
  12. mlrun/data_types/data_types.py +4 -0
  13. mlrun/datastore/__init__.py +3 -7
  14. mlrun/datastore/alibaba_oss.py +130 -0
  15. mlrun/datastore/azure_blob.py +4 -5
  16. mlrun/datastore/base.py +22 -16
  17. mlrun/datastore/datastore.py +4 -0
  18. mlrun/datastore/datastore_profile.py +19 -1
  19. mlrun/datastore/google_cloud_storage.py +1 -1
  20. mlrun/datastore/snowflake_utils.py +43 -0
  21. mlrun/datastore/sources.py +11 -29
  22. mlrun/datastore/targets.py +131 -11
  23. mlrun/datastore/utils.py +10 -5
  24. mlrun/db/base.py +58 -6
  25. mlrun/db/httpdb.py +183 -77
  26. mlrun/db/nopdb.py +110 -0
  27. mlrun/feature_store/api.py +3 -2
  28. mlrun/feature_store/retrieval/spark_merger.py +27 -23
  29. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
  30. mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
  31. mlrun/kfpops.py +2 -5
  32. mlrun/launcher/base.py +1 -1
  33. mlrun/launcher/client.py +2 -2
  34. mlrun/model.py +1 -0
  35. mlrun/model_monitoring/__init__.py +1 -1
  36. mlrun/model_monitoring/api.py +104 -295
  37. mlrun/model_monitoring/controller.py +25 -25
  38. mlrun/model_monitoring/db/__init__.py +16 -0
  39. mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -34
  40. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  41. mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +47 -6
  42. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  43. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +49 -0
  44. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +76 -3
  45. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +68 -0
  46. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/sqlite.py +13 -1
  47. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +662 -0
  48. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  49. mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +134 -3
  50. mlrun/model_monitoring/helpers.py +3 -3
  51. mlrun/model_monitoring/stream_processing.py +41 -9
  52. mlrun/model_monitoring/tracking_policy.py +7 -1
  53. mlrun/model_monitoring/writer.py +4 -36
  54. mlrun/projects/pipelines.py +14 -2
  55. mlrun/projects/project.py +141 -122
  56. mlrun/run.py +8 -2
  57. mlrun/runtimes/__init__.py +16 -0
  58. mlrun/runtimes/base.py +10 -1
  59. mlrun/runtimes/kubejob.py +26 -121
  60. mlrun/runtimes/nuclio/api_gateway.py +243 -66
  61. mlrun/runtimes/nuclio/application/application.py +79 -1
  62. mlrun/runtimes/nuclio/application/reverse_proxy.go +9 -1
  63. mlrun/runtimes/nuclio/function.py +14 -8
  64. mlrun/runtimes/nuclio/serving.py +30 -34
  65. mlrun/runtimes/pod.py +171 -0
  66. mlrun/runtimes/utils.py +0 -28
  67. mlrun/serving/remote.py +2 -3
  68. mlrun/serving/routers.py +4 -3
  69. mlrun/serving/server.py +5 -7
  70. mlrun/serving/states.py +40 -23
  71. mlrun/serving/v2_serving.py +4 -3
  72. mlrun/utils/helpers.py +34 -0
  73. mlrun/utils/http.py +1 -1
  74. mlrun/utils/retryer.py +1 -0
  75. mlrun/utils/version/version.json +2 -2
  76. {mlrun-1.7.0rc6.dist-info → mlrun-1.7.0rc9.dist-info}/METADATA +25 -16
  77. {mlrun-1.7.0rc6.dist-info → mlrun-1.7.0rc9.dist-info}/RECORD +81 -75
  78. mlrun/model_monitoring/batch.py +0 -933
  79. mlrun/model_monitoring/stores/models/mysql.py +0 -34
  80. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  81. {mlrun-1.7.0rc6.dist-info → mlrun-1.7.0rc9.dist-info}/LICENSE +0 -0
  82. {mlrun-1.7.0rc6.dist-info → mlrun-1.7.0rc9.dist-info}/WHEEL +0 -0
  83. {mlrun-1.7.0rc6.dist-info → mlrun-1.7.0rc9.dist-info}/entry_points.txt +0 -0
  84. {mlrun-1.7.0rc6.dist-info → mlrun-1.7.0rc9.dist-info}/top_level.txt +0 -0
mlrun/runtimes/kubejob.py CHANGED
@@ -12,7 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import time
16
15
  import warnings
17
16
 
18
17
  import mlrun.common.schemas
@@ -21,7 +20,6 @@ import mlrun.errors
21
20
 
22
21
  from ..kfpops import build_op
23
22
  from ..model import RunObject
24
- from ..utils import get_in, logger
25
23
  from .pod import KubeResource
26
24
 
27
25
 
@@ -65,29 +63,13 @@ class KubejobRuntime(KubeResource):
65
63
  :param pull_at_runtime: load the archive into the container at job runtime vs on build/deploy
66
64
  :param target_dir: target dir on runtime pod or repo clone / archive extraction
67
65
  """
68
- mlrun.utils.helpers.validate_builder_source(source, pull_at_runtime, workdir)
69
-
70
- self.spec.build.source = source
71
- if handler:
72
- self.spec.default_handler = handler
73
- if workdir:
74
- self.spec.workdir = workdir
75
- if target_dir:
76
- self.spec.build.source_code_target_dir = target_dir
77
-
78
- self.spec.build.load_source_on_run = pull_at_runtime
79
- if (
80
- self.spec.build.base_image
81
- and not self.spec.build.commands
82
- and pull_at_runtime
83
- and not self.spec.image
84
- ):
85
- # if we load source from repo and don't need a full build use the base_image as the image
86
- self.spec.image = self.spec.build.base_image
87
- elif not pull_at_runtime:
88
- # clear the image so build will not be skipped
89
- self.spec.build.base_image = self.spec.build.base_image or self.spec.image
90
- self.spec.image = ""
66
+ self._configure_mlrun_build_with_source(
67
+ source=source,
68
+ workdir=workdir,
69
+ handler=handler,
70
+ pull_at_runtime=pull_at_runtime,
71
+ target_dir=target_dir,
72
+ )
91
73
 
92
74
  def build_config(
93
75
  self,
@@ -169,116 +151,39 @@ class KubejobRuntime(KubeResource):
169
151
  show_on_failure: bool = False,
170
152
  force_build: bool = False,
171
153
  ) -> bool:
172
- """deploy function, build container with dependencies
154
+ """Deploy function, build container with dependencies
173
155
 
174
- :param watch: wait for the deploy to complete (and print build logs)
175
- :param with_mlrun: add the current mlrun package to the container build
176
- :param skip_deployed: skip the build if we already have an image for the function
177
- :param is_kfp: deploy as part of a kfp pipeline
178
- :param mlrun_version_specifier: which mlrun package version to include (if not current)
156
+ :param watch: Wait for the deploy to complete (and print build logs)
157
+ :param with_mlrun: Add the current mlrun package to the container build
158
+ :param skip_deployed: Skip the build if we already have an image for the function
159
+ :param is_kfp: Deploy as part of a kfp pipeline
160
+ :param mlrun_version_specifier: Which mlrun package version to include (if not current)
179
161
  :param builder_env: Kaniko builder pod env vars dict (for config/credentials)
180
162
  e.g. builder_env={"GIT_TOKEN": token}
181
- :param show_on_failure: show logs only in case of build failure
182
- :param force_build: set True for force building the image, even when no changes were made
163
+ :param show_on_failure: Show logs only in case of build failure
164
+ :param force_build: Set True for force building the image, even when no changes were made
183
165
 
184
166
  :return: True if the function is ready (deployed)
185
167
  """
186
168
 
187
169
  build = self.spec.build
170
+ with_mlrun = self._resolve_build_with_mlrun(with_mlrun)
188
171
 
189
- if with_mlrun is None:
190
- if build.with_mlrun is not None:
191
- with_mlrun = build.with_mlrun
192
- else:
193
- with_mlrun = build.base_image and not (
194
- build.base_image.startswith("mlrun/")
195
- or "/mlrun/" in build.base_image
196
- )
197
-
198
- if (
199
- not build.source
200
- and not build.commands
201
- and not build.requirements
202
- and not build.extra
203
- and with_mlrun
204
- ):
205
- logger.info(
206
- "Running build to add mlrun package, set "
207
- "with_mlrun=False to skip if its already in the image"
208
- )
209
172
  self.status.state = ""
210
173
  if build.base_image:
211
174
  # clear the image so build will not be skipped
212
175
  self.spec.image = ""
213
176
 
214
- # When we're in pipelines context we must watch otherwise the pipelines pod will exit before the operation
215
- # is actually done. (when a pipelines pod exits, the pipeline step marked as done)
216
- if is_kfp:
217
- watch = True
218
-
219
- ready = False
220
- if self._is_remote_api():
221
- db = self._get_db()
222
- data = db.remote_builder(
223
- self,
224
- with_mlrun,
225
- mlrun_version_specifier,
226
- skip_deployed,
227
- builder_env=builder_env,
228
- force_build=force_build,
229
- )
230
- self.status = data["data"].get("status", None)
231
- self.spec.image = get_in(data, "data.spec.image")
232
- self.spec.build.base_image = self.spec.build.base_image or get_in(
233
- data, "data.spec.build.base_image"
234
- )
235
- # Get the source target dir in case it was enriched due to loading source
236
- self.spec.build.source_code_target_dir = get_in(
237
- data, "data.spec.build.source_code_target_dir"
238
- ) or get_in(data, "data.spec.clone_target_dir")
239
- ready = data.get("ready", False)
240
- if not ready:
241
- logger.info(
242
- f"Started building image: {data.get('data', {}).get('spec', {}).get('build', {}).get('image')}"
243
- )
244
- if watch and not ready:
245
- state = self._build_watch(watch, show_on_failure=show_on_failure)
246
- ready = state == "ready"
247
- self.status.state = state
248
-
249
- if watch and not ready:
250
- raise mlrun.errors.MLRunRuntimeError("Deploy failed")
251
- return ready
252
-
253
- def _build_watch(self, watch=True, logs=True, show_on_failure=False):
254
- db = self._get_db()
255
- offset = 0
256
- try:
257
- text, _ = db.get_builder_status(self, 0, logs=logs)
258
- except mlrun.db.RunDBError:
259
- raise ValueError("function or build process not found")
260
-
261
- def print_log(text):
262
- if text and (not show_on_failure or self.status.state == "error"):
263
- print(text, end="")
264
-
265
- print_log(text)
266
- offset += len(text)
267
- if watch:
268
- while self.status.state in ["pending", "running"]:
269
- time.sleep(2)
270
- if show_on_failure:
271
- text = ""
272
- db.get_builder_status(self, 0, logs=False)
273
- if self.status.state == "error":
274
- # re-read the full log on failure
275
- text, _ = db.get_builder_status(self, offset, logs=logs)
276
- else:
277
- text, _ = db.get_builder_status(self, offset, logs=logs)
278
- print_log(text)
279
- offset += len(text)
280
-
281
- return self.status.state
177
+ return self._build_image(
178
+ builder_env=builder_env,
179
+ force_build=force_build,
180
+ mlrun_version_specifier=mlrun_version_specifier,
181
+ show_on_failure=show_on_failure,
182
+ skip_deployed=skip_deployed,
183
+ watch=watch,
184
+ is_kfp=is_kfp,
185
+ with_mlrun=with_mlrun,
186
+ )
282
187
 
283
188
  def deploy_step(
284
189
  self,
@@ -12,15 +12,18 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  import base64
15
+ import typing
15
16
  from typing import Optional, Union
16
17
  from urllib.parse import urljoin
17
18
 
18
19
  import requests
20
+ from requests.auth import HTTPBasicAuth
19
21
 
20
22
  import mlrun
21
23
  import mlrun.common.schemas
22
24
 
23
- from .function import RemoteRuntime
25
+ from ..utils import logger
26
+ from .function import RemoteRuntime, get_fullname, min_nuclio_versions
24
27
  from .serving import ServingRuntime
25
28
 
26
29
  NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH = "basicAuth"
@@ -28,7 +31,69 @@ NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE = "none"
28
31
  PROJECT_NAME_LABEL = "nuclio.io/project-name"
29
32
 
30
33
 
34
+ class APIGatewayAuthenticator(typing.Protocol):
35
+ @property
36
+ def authentication_mode(self) -> str:
37
+ return NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE
38
+
39
+ @classmethod
40
+ def from_scheme(cls, api_gateway_spec: mlrun.common.schemas.APIGatewaySpec):
41
+ if (
42
+ api_gateway_spec.authenticationMode
43
+ == NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
44
+ ):
45
+ if api_gateway_spec.authentication:
46
+ return BasicAuth(
47
+ username=api_gateway_spec.authentication.get("username", ""),
48
+ password=api_gateway_spec.authentication.get("password", ""),
49
+ )
50
+ else:
51
+ return BasicAuth()
52
+ else:
53
+ return NoneAuth()
54
+
55
+ def to_scheme(
56
+ self,
57
+ ) -> Optional[dict[str, Optional[mlrun.common.schemas.APIGatewayBasicAuth]]]:
58
+ return None
59
+
60
+
61
+ class NoneAuth(APIGatewayAuthenticator):
62
+ """
63
+ An API gateway authenticator with no authentication.
64
+ """
65
+
66
+ pass
67
+
68
+
69
+ class BasicAuth(APIGatewayAuthenticator):
70
+ """
71
+ An API gateway authenticator with basic authentication.
72
+
73
+ :param username: (str) The username for basic authentication.
74
+ :param password: (str) The password for basic authentication.
75
+ """
76
+
77
+ def __init__(self, username=None, password=None):
78
+ self._username = username
79
+ self._password = password
80
+
81
+ @property
82
+ def authentication_mode(self) -> str:
83
+ return NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
84
+
85
+ def to_scheme(
86
+ self,
87
+ ) -> Optional[dict[str, Optional[mlrun.common.schemas.APIGatewayBasicAuth]]]:
88
+ return {
89
+ "basicAuth": mlrun.common.schemas.APIGatewayBasicAuth(
90
+ username=self._username, password=self._password
91
+ )
92
+ }
93
+
94
+
31
95
  class APIGateway:
96
+ @min_nuclio_versions("1.13.1")
32
97
  def __init__(
33
98
  self,
34
99
  project,
@@ -47,22 +112,34 @@ class APIGateway:
47
112
  ],
48
113
  description: str = "",
49
114
  path: str = "/",
50
- authentication_mode: Optional[
51
- str
52
- ] = NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_NONE,
115
+ authentication: Optional[APIGatewayAuthenticator] = NoneAuth(),
53
116
  host: Optional[str] = None,
54
117
  canary: Optional[list[int]] = None,
55
- username: Optional[str] = None,
56
- password: Optional[str] = None,
57
118
  ):
119
+ """
120
+ Initialize the APIGateway instance.
121
+
122
+ :param project: The project name
123
+ :param name: The name of the API gateway
124
+ :param functions: The list of functions associated with the API gateway
125
+ Can be a list of function names (["my-func1", "my-func2"])
126
+ or a list or a single entity of
127
+ :py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
128
+ :py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime`
129
+
130
+ :param description: Optional description of the API gateway
131
+ :param path: Optional path of the API gateway, default value is "/"
132
+ :param authentication: The authentication for the API gateway of type
133
+ :py:class:`~mlrun.runtimes.nuclio.api_gateway.BasicAuth`
134
+ :param host: The host of the API gateway (optional). If not set, it will be automatically generated
135
+ :param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80]
136
+ """
58
137
  self.functions = None
59
138
  self._validate(
60
139
  project=project,
61
140
  functions=functions,
62
141
  name=name,
63
142
  canary=canary,
64
- username=username,
65
- password=password,
66
143
  )
67
144
  self.project = project
68
145
  self.name = name
@@ -70,14 +147,9 @@ class APIGateway:
70
147
 
71
148
  self.path = path
72
149
  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
150
  self.canary = canary
79
- self._username = username
80
- self._password = password
151
+ self.authentication = authentication
152
+ self.state = ""
81
153
 
82
154
  def invoke(
83
155
  self,
@@ -86,47 +158,161 @@ class APIGateway:
86
158
  auth: Optional[tuple[str, str]] = None,
87
159
  **kwargs,
88
160
  ):
161
+ """
162
+ Invoke the API gateway.
163
+
164
+ :param method: (str, optional) The HTTP method for the invocation.
165
+ :param headers: (dict, optional) The HTTP headers for the invocation.
166
+ :param auth: (Optional[tuple[str, str]], optional) The authentication creds for the invocation if required.
167
+ :param kwargs: (dict) Additional keyword arguments.
168
+
169
+ :return: The response from the API gateway invocation.
170
+ """
89
171
  if not self.invoke_url:
90
- raise mlrun.errors.MLRunInvalidArgumentError(
91
- "Invocation url is not set. Set up gateway's `invoke_url` attribute."
172
+ # try to resolve invoke_url before fail
173
+ self.sync()
174
+ if not self.invoke_url:
175
+ raise mlrun.errors.MLRunInvalidArgumentError(
176
+ "Invocation url is not set. Set up gateway's `invoke_url` attribute."
177
+ )
178
+ if not self.is_ready():
179
+ raise mlrun.errors.MLRunPreconditionFailedError(
180
+ f"API gateway is not ready. " f"Current state: {self.state}"
92
181
  )
182
+
93
183
  if (
94
- self.authentication_mode
184
+ self.authentication.authentication_mode
95
185
  == NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
96
186
  and not auth
97
187
  ):
98
188
  raise mlrun.errors.MLRunInvalidArgumentError(
99
189
  "API Gateway invocation requires authentication. Please pass credentials"
100
190
  )
101
- if auth:
102
- headers["Authorization"] = self._generate_basic_auth(*auth)
103
191
  return requests.request(
104
- method=method, url=self.invoke_url, headers=headers, **kwargs
192
+ method=method,
193
+ url=self.invoke_url,
194
+ headers=headers,
195
+ **kwargs,
196
+ auth=HTTPBasicAuth(*auth) if auth else None,
197
+ )
198
+
199
+ def wait_for_readiness(self, max_wait_time=90):
200
+ """
201
+ Wait for the API gateway to become ready within the maximum wait time.
202
+
203
+ Parameters:
204
+ max_wait_time: int - Maximum time to wait in seconds (default is 90 seconds).
205
+
206
+ Returns:
207
+ bool: True if the entity becomes ready within the maximum wait time, False otherwise
208
+ """
209
+
210
+ def _ensure_ready():
211
+ if not self.is_ready():
212
+ raise AssertionError(
213
+ f"Waiting for gateway readiness is taking more than {max_wait_time} seconds"
214
+ )
215
+
216
+ return mlrun.utils.helpers.retry_until_successful(
217
+ 3, max_wait_time, logger, False, _ensure_ready
105
218
  )
106
219
 
220
+ def is_ready(self):
221
+ if self.state is not mlrun.common.schemas.api_gateway.APIGatewayState.ready:
222
+ # try to sync the state
223
+ self.sync()
224
+ return self.state == mlrun.common.schemas.api_gateway.APIGatewayState.ready
225
+
226
+ def sync(self):
227
+ """
228
+ Synchronize the API gateway from the server.
229
+ """
230
+ synced_gateway = mlrun.get_run_db().get_api_gateway(self.name, self.project)
231
+ synced_gateway = self.from_scheme(synced_gateway)
232
+
233
+ self.host = synced_gateway.host
234
+ self.path = synced_gateway.path
235
+ self.authentication = synced_gateway.authentication
236
+ self.functions = synced_gateway.functions
237
+ self.canary = synced_gateway.canary
238
+ self.description = synced_gateway.description
239
+ self.state = synced_gateway.state
240
+
241
+ def with_basic_auth(self, username: str, password: str):
242
+ """
243
+ Set basic authentication for the API gateway.
244
+
245
+ :param username: (str) The username for basic authentication.
246
+ :param password: (str) The password for basic authentication.
247
+ """
248
+ self.authentication = BasicAuth(username=username, password=password)
249
+
250
+ def with_canary(
251
+ self,
252
+ functions: Union[
253
+ list[str],
254
+ list[
255
+ Union[
256
+ RemoteRuntime,
257
+ ServingRuntime,
258
+ ]
259
+ ],
260
+ ],
261
+ canary: list[int],
262
+ ):
263
+ """
264
+ Set canary function for the API gateway
265
+
266
+ :param functions: The list of functions associated with the API gateway
267
+ Can be a list of function names (["my-func1", "my-func2"])
268
+ or a list of nuclio functions of types
269
+ :py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
270
+ :py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime`
271
+ :param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80]
272
+
273
+ """
274
+ if len(functions) != 2:
275
+ raise mlrun.errors.MLRunInvalidArgumentError(
276
+ f"Gateway with canary can be created only with two functions, "
277
+ f"the number of functions passed is {len(functions)}"
278
+ )
279
+ self.functions = self._validate_functions(self.project, functions)
280
+ self.canary = self._validate_canary(canary)
281
+
107
282
  @classmethod
108
283
  def from_scheme(cls, api_gateway: mlrun.common.schemas.APIGateway):
109
284
  project = api_gateway.metadata.labels.get(PROJECT_NAME_LABEL)
110
285
  functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
111
- return cls(
286
+ state = (
287
+ api_gateway.status.state
288
+ if api_gateway.status
289
+ else mlrun.common.schemas.APIGatewayState.none
290
+ )
291
+ api_gateway = cls(
112
292
  project=project,
113
293
  description=api_gateway.spec.description,
114
294
  name=api_gateway.spec.name,
115
295
  host=api_gateway.spec.host,
116
296
  path=api_gateway.spec.path,
117
- authentication_mode=str(api_gateway.spec.authenticationMode),
297
+ authentication=APIGatewayAuthenticator.from_scheme(api_gateway.spec),
118
298
  functions=functions,
119
299
  canary=canary,
120
300
  )
301
+ api_gateway.state = state
302
+ return api_gateway
121
303
 
122
304
  def to_scheme(self) -> mlrun.common.schemas.APIGateway:
123
305
  upstreams = (
124
306
  [
125
307
  mlrun.common.schemas.APIGatewayUpstream(
126
- nucliofunction={"name": function_name},
127
- percentage=percentage,
128
- )
129
- for function_name, percentage in zip(self.functions, self.canary)
308
+ nucliofunction={"name": self.functions[0]},
309
+ percentage=self.canary[0],
310
+ ),
311
+ mlrun.common.schemas.APIGatewayUpstream(
312
+ # do not set percent for the second function,
313
+ # so we can define which function to display as a primary one in UI
314
+ nucliofunction={"name": self.functions[1]},
315
+ ),
130
316
  ]
131
317
  if self.canary
132
318
  else [
@@ -141,27 +327,30 @@ class APIGateway:
141
327
  spec=mlrun.common.schemas.APIGatewaySpec(
142
328
  name=self.name,
143
329
  description=self.description,
330
+ host=self.host,
144
331
  path=self.path,
145
- authentication_mode=mlrun.common.schemas.APIGatewayAuthenticationMode.from_str(
146
- self.authentication_mode
332
+ authenticationMode=mlrun.common.schemas.APIGatewayAuthenticationMode.from_str(
333
+ self.authentication.authentication_mode
147
334
  ),
148
335
  upstreams=upstreams,
149
336
  ),
150
337
  )
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
- )
338
+ api_gateway.spec.authentication = self.authentication.to_scheme()
158
339
  return api_gateway
159
340
 
160
341
  @property
161
342
  def invoke_url(
162
343
  self,
163
344
  ):
164
- return urljoin(self.host, self.path)
345
+ """
346
+ Get the invoke URL.
347
+
348
+ :return: (str) The invoke URL.
349
+ """
350
+ host = self.host
351
+ if not self.host.startswith("http"):
352
+ host = f"https://{self.host}"
353
+ return urljoin(host, self.path)
165
354
 
166
355
  def _validate(
167
356
  self,
@@ -180,8 +369,6 @@ class APIGateway:
180
369
  ],
181
370
  ],
182
371
  canary: Optional[list[int]] = None,
183
- username: Optional[str] = None,
184
- password: Optional[str] = None,
185
372
  ):
186
373
  if not name:
187
374
  raise mlrun.errors.MLRunInvalidArgumentError(
@@ -192,26 +379,23 @@ class APIGateway:
192
379
 
193
380
  # validating canary
194
381
  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:
382
+ self._validate_canary(canary)
383
+
384
+ def _validate_canary(self, canary: list[int]):
385
+ if len(self.functions) != len(canary):
386
+ raise mlrun.errors.MLRunInvalidArgumentError(
387
+ "Function and canary lists lengths do not match"
388
+ )
389
+ for canary_percent in canary:
390
+ if canary_percent < 0 or canary_percent > 100:
205
391
  raise mlrun.errors.MLRunInvalidArgumentError(
206
- "The sum of canary function percents should be equal to 100"
392
+ "The percentage value must be in the range from 0 to 100"
207
393
  )
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")
394
+ if sum(canary) != 100:
395
+ raise mlrun.errors.MLRunInvalidArgumentError(
396
+ "The sum of canary function percents should be equal to 100"
397
+ )
398
+ return canary
215
399
 
216
400
  @staticmethod
217
401
  def _validate_functions(
@@ -257,17 +441,10 @@ class APIGateway:
257
441
  f"input function {function_name} "
258
442
  f"does not belong to this project"
259
443
  )
260
- function_names.append(func.uri)
444
+ nuclio_name = get_fullname(function_name, project, func.metadata.tag)
445
+ function_names.append(nuclio_name)
261
446
  return function_names
262
447
 
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
448
  @staticmethod
272
449
  def _generate_basic_auth(username: str, password: str):
273
450
  token = base64.b64encode(f"{username}:{password}".encode()).decode()