zenml-nightly 0.68.0.dev20241027__py3-none-any.whl → 0.68.1.dev20241101__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.
Files changed (125) hide show
  1. README.md +17 -11
  2. RELEASE_NOTES.md +9 -0
  3. zenml/VERSION +1 -1
  4. zenml/__init__.py +1 -1
  5. zenml/analytics/context.py +16 -1
  6. zenml/analytics/utils.py +18 -7
  7. zenml/artifacts/utils.py +40 -216
  8. zenml/cli/__init__.py +63 -90
  9. zenml/cli/base.py +3 -3
  10. zenml/cli/login.py +951 -0
  11. zenml/cli/server.py +462 -353
  12. zenml/cli/service_accounts.py +4 -4
  13. zenml/cli/stack.py +77 -2
  14. zenml/cli/stack_components.py +5 -16
  15. zenml/cli/user_management.py +0 -12
  16. zenml/cli/utils.py +24 -77
  17. zenml/client.py +46 -14
  18. zenml/config/compiler.py +1 -0
  19. zenml/config/global_config.py +9 -0
  20. zenml/config/pipeline_configurations.py +2 -1
  21. zenml/config/pipeline_run_configuration.py +2 -1
  22. zenml/constants.py +3 -9
  23. zenml/enums.py +1 -1
  24. zenml/exceptions.py +11 -0
  25. zenml/integrations/github/code_repositories/github_code_repository.py +1 -1
  26. zenml/login/__init__.py +16 -0
  27. zenml/login/credentials.py +346 -0
  28. zenml/login/credentials_store.py +603 -0
  29. zenml/login/pro/__init__.py +16 -0
  30. zenml/login/pro/client.py +496 -0
  31. zenml/login/pro/constants.py +34 -0
  32. zenml/login/pro/models.py +25 -0
  33. zenml/login/pro/organization/__init__.py +14 -0
  34. zenml/login/pro/organization/client.py +79 -0
  35. zenml/login/pro/organization/models.py +32 -0
  36. zenml/login/pro/tenant/__init__.py +14 -0
  37. zenml/login/pro/tenant/client.py +92 -0
  38. zenml/login/pro/tenant/models.py +174 -0
  39. zenml/login/pro/utils.py +121 -0
  40. zenml/{cli → login}/web_login.py +64 -28
  41. zenml/materializers/base_materializer.py +43 -9
  42. zenml/materializers/built_in_materializer.py +1 -1
  43. zenml/metadata/metadata_types.py +49 -0
  44. zenml/model/model.py +0 -38
  45. zenml/models/__init__.py +3 -0
  46. zenml/models/v2/base/base.py +12 -8
  47. zenml/models/v2/base/filter.py +9 -0
  48. zenml/models/v2/core/artifact_version.py +49 -10
  49. zenml/models/v2/core/component.py +54 -19
  50. zenml/models/v2/core/flavor.py +13 -13
  51. zenml/models/v2/core/model.py +3 -1
  52. zenml/models/v2/core/model_version.py +3 -5
  53. zenml/models/v2/core/model_version_artifact.py +3 -1
  54. zenml/models/v2/core/model_version_pipeline_run.py +3 -1
  55. zenml/models/v2/core/pipeline.py +3 -1
  56. zenml/models/v2/core/pipeline_run.py +23 -1
  57. zenml/models/v2/core/run_template.py +3 -1
  58. zenml/models/v2/core/stack.py +7 -3
  59. zenml/models/v2/core/step_run.py +43 -2
  60. zenml/models/v2/misc/auth_models.py +11 -2
  61. zenml/models/v2/misc/server_models.py +2 -0
  62. zenml/orchestrators/base_orchestrator.py +8 -4
  63. zenml/orchestrators/step_launcher.py +1 -0
  64. zenml/orchestrators/step_run_utils.py +10 -2
  65. zenml/orchestrators/step_runner.py +67 -55
  66. zenml/orchestrators/utils.py +45 -22
  67. zenml/pipelines/pipeline_decorator.py +5 -0
  68. zenml/pipelines/pipeline_definition.py +206 -160
  69. zenml/pipelines/run_utils.py +11 -10
  70. zenml/services/local/local_daemon_entrypoint.py +4 -4
  71. zenml/services/service.py +2 -2
  72. zenml/stack/stack.py +2 -6
  73. zenml/stack/stack_component.py +2 -7
  74. zenml/stack/utils.py +26 -14
  75. zenml/steps/base_step.py +8 -2
  76. zenml/steps/step_context.py +0 -3
  77. zenml/steps/step_invocation.py +14 -5
  78. zenml/steps/utils.py +1 -0
  79. zenml/utils/materializer_utils.py +1 -1
  80. zenml/utils/requirements_utils.py +71 -0
  81. zenml/utils/singleton.py +15 -3
  82. zenml/utils/source_utils.py +39 -2
  83. zenml/utils/visualization_utils.py +1 -1
  84. zenml/zen_server/auth.py +44 -39
  85. zenml/zen_server/deploy/__init__.py +7 -7
  86. zenml/zen_server/deploy/base_provider.py +46 -73
  87. zenml/zen_server/deploy/{local → daemon}/__init__.py +3 -3
  88. zenml/zen_server/deploy/{local/local_provider.py → daemon/daemon_provider.py} +44 -63
  89. zenml/zen_server/deploy/{local/local_zen_server.py → daemon/daemon_zen_server.py} +50 -22
  90. zenml/zen_server/deploy/deployer.py +90 -171
  91. zenml/zen_server/deploy/deployment.py +20 -12
  92. zenml/zen_server/deploy/docker/docker_provider.py +9 -28
  93. zenml/zen_server/deploy/docker/docker_zen_server.py +19 -3
  94. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  95. zenml/zen_server/deploy/helm/README.md +2 -2
  96. zenml/zen_server/exceptions.py +11 -0
  97. zenml/zen_server/jwt.py +9 -9
  98. zenml/zen_server/routers/auth_endpoints.py +30 -8
  99. zenml/zen_server/routers/stack_components_endpoints.py +1 -1
  100. zenml/zen_server/routers/workspaces_endpoints.py +1 -1
  101. zenml/zen_server/template_execution/runner_entrypoint_configuration.py +7 -4
  102. zenml/zen_server/template_execution/utils.py +6 -61
  103. zenml/zen_server/utils.py +64 -36
  104. zenml/zen_stores/base_zen_store.py +4 -49
  105. zenml/zen_stores/migrations/versions/0.68.1_release.py +23 -0
  106. zenml/zen_stores/migrations/versions/c22561cbb3a9_add_artifact_unique_constraints.py +86 -0
  107. zenml/zen_stores/rest_zen_store.py +325 -147
  108. zenml/zen_stores/schemas/api_key_schemas.py +9 -4
  109. zenml/zen_stores/schemas/artifact_schemas.py +21 -2
  110. zenml/zen_stores/schemas/artifact_visualization_schemas.py +1 -1
  111. zenml/zen_stores/schemas/component_schemas.py +49 -6
  112. zenml/zen_stores/schemas/device_schemas.py +9 -4
  113. zenml/zen_stores/schemas/flavor_schemas.py +1 -1
  114. zenml/zen_stores/schemas/model_schemas.py +1 -1
  115. zenml/zen_stores/schemas/service_schemas.py +1 -1
  116. zenml/zen_stores/schemas/step_run_schemas.py +1 -1
  117. zenml/zen_stores/schemas/trigger_schemas.py +1 -1
  118. zenml/zen_stores/sql_zen_store.py +393 -140
  119. zenml/zen_stores/template_utils.py +3 -1
  120. {zenml_nightly-0.68.0.dev20241027.dist-info → zenml_nightly-0.68.1.dev20241101.dist-info}/METADATA +18 -12
  121. {zenml_nightly-0.68.0.dev20241027.dist-info → zenml_nightly-0.68.1.dev20241101.dist-info}/RECORD +124 -107
  122. zenml/api.py +0 -60
  123. {zenml_nightly-0.68.0.dev20241027.dist-info → zenml_nightly-0.68.1.dev20241101.dist-info}/LICENSE +0 -0
  124. {zenml_nightly-0.68.0.dev20241027.dist-info → zenml_nightly-0.68.1.dev20241101.dist-info}/WHEEL +0 -0
  125. {zenml_nightly-0.68.0.dev20241027.dist-info → zenml_nightly-0.68.1.dev20241101.dist-info}/entry_points.txt +0 -0
zenml/cli/server.py CHANGED
@@ -14,34 +14,36 @@
14
14
  """CLI for managing ZenML server deployments."""
15
15
 
16
16
  import ipaddress
17
- import os
18
- import sys
19
- from typing import Any, Dict, Optional, Union
17
+ import re
18
+ from typing import List, Optional, Union
20
19
 
21
20
  import click
22
- import yaml
23
21
  from rich.errors import MarkupError
24
22
 
25
23
  import zenml
26
24
  from zenml.cli import utils as cli_utils
27
- from zenml.cli.cli import cli
28
- from zenml.cli.web_login import web_login
25
+ from zenml.cli.cli import TagGroup, cli
26
+ from zenml.cli.login import login, logout
29
27
  from zenml.client import Client
30
28
  from zenml.config.global_config import GlobalConfiguration
31
29
  from zenml.console import console
32
- from zenml.constants import ENV_ZENML_LOCAL_SERVER
33
- from zenml.enums import ServerProviderType, StoreType
34
- from zenml.exceptions import AuthorizationException, IllegalOperationError
30
+ from zenml.enums import CliCategories, StoreType
31
+ from zenml.exceptions import AuthorizationException
35
32
  from zenml.logger import get_logger
36
- from zenml.utils import yaml_utils
37
- from zenml.zen_server.utils import get_local_server
33
+ from zenml.login.credentials import ServerCredentials, ServerType
34
+ from zenml.services.service_status import ServiceState
35
+ from zenml.zen_server.utils import connected_to_local_server, get_local_server
38
36
 
39
37
  logger = get_logger(__name__)
40
38
 
41
- LOCAL_ZENML_SERVER_NAME = "local"
42
39
 
40
+ @cli.command(
41
+ "up",
42
+ help="""Start the ZenML dashboard locally.
43
43
 
44
- @cli.command("up", help="Start the ZenML dashboard locally.")
44
+ DEPRECATED: Please use `zenml login --local` instead.
45
+ """,
46
+ )
45
47
  @click.option(
46
48
  "--docker",
47
49
  is_flag=True,
@@ -71,14 +73,6 @@ LOCAL_ZENML_SERVER_NAME = "local"
71
73
  default=False,
72
74
  type=click.BOOL,
73
75
  )
74
- @click.option(
75
- "--connect",
76
- is_flag=True,
77
- help="Connect the client to the local server even when already connected "
78
- "to a remote ZenML server.",
79
- default=False,
80
- type=click.BOOL,
81
- )
82
76
  @click.option(
83
77
  "--image",
84
78
  type=str,
@@ -99,7 +93,6 @@ def up(
99
93
  ] = None,
100
94
  port: Optional[int] = None,
101
95
  blocking: bool = False,
102
- connect: bool = False,
103
96
  image: Optional[str] = None,
104
97
  ngrok_token: Optional[str] = None,
105
98
  ) -> None:
@@ -110,133 +103,44 @@ def up(
110
103
  ip_address: The IP address to bind the server to.
111
104
  port: The port to bind the server to.
112
105
  blocking: Block the CLI while the server is running.
113
- connect: Connect the client to the local server even when already
114
- connected to a remote ZenML server.
115
106
  image: A custom Docker image to use for the server, when the
116
107
  `--docker` flag is set.
117
108
  ngrok_token: An ngrok auth token to use for exposing the ZenML dashboard
118
109
  on a public domain. Primarily used for accessing the dashboard in
119
110
  Colab.
120
111
  """
121
- from zenml.zen_server.deploy.deployer import ServerDeployer
122
-
123
- if connect:
124
- logger.warning(
125
- "The `--connect` flag is deprecated, has no effect, and will be "
126
- "removed in a future release."
127
- )
128
-
129
- gc = GlobalConfiguration()
130
-
131
- # Raise an error if the client is already connected to a remote server.
132
- if gc.store_configuration.type == StoreType.REST:
133
- if not gc.zen_store.is_local_store():
134
- cli_utils.error(
135
- "Your ZenML client is already connected to a remote server. If "
136
- "you want to spin up a local ZenML server, please disconnect "
137
- "from the remote server first by running `zenml disconnect`."
138
- )
139
-
140
- if docker:
141
- from zenml.utils.docker_utils import check_docker
142
-
143
- if not check_docker():
144
- cli_utils.error(
145
- "Docker does not seem to be installed on your system. Please "
146
- "install Docker to use the Docker ZenML server local "
147
- "deployment or use one of the other deployment options."
148
- )
149
- provider = ServerProviderType.DOCKER
150
- else:
151
- if sys.platform == "win32" and not blocking:
152
- cli_utils.error(
153
- "Running the ZenML server locally as a background process is "
154
- "not supported on Windows. Please use the `--blocking` flag "
155
- "to run the server in blocking mode, or run the server in "
156
- "a Docker container by setting `--docker` instead."
157
- )
158
- else:
159
- pass
160
- provider = ServerProviderType.LOCAL
161
- if cli_utils.requires_mac_env_var_warning():
162
- cli_utils.error(
163
- "The `OBJC_DISABLE_INITIALIZE_FORK_SAFETY` environment variable "
164
- "is recommended to run the ZenML server locally on a Mac. "
165
- "Please set it to `YES` and try again."
166
- )
167
-
168
- os.environ[ENV_ZENML_LOCAL_SERVER] = str(True)
169
-
170
- deployer = ServerDeployer()
171
-
172
- server = get_local_server()
173
- if server and server.config.provider != provider:
174
- deployer.remove_server(LOCAL_ZENML_SERVER_NAME)
175
-
176
- config_attrs: Dict[str, Any] = dict(
177
- name=LOCAL_ZENML_SERVER_NAME,
178
- provider=provider,
112
+ cli_utils.warning(
113
+ "The `zenml up` command is deprecated and will be removed in a "
114
+ "future release. Please use the `zenml login --local` command instead."
179
115
  )
180
- if not docker:
181
- config_attrs["blocking"] = blocking
182
- elif image:
183
- config_attrs["image"] = image
184
- if port is not None:
185
- config_attrs["port"] = port
186
- if ip_address is not None and provider in [
187
- ServerProviderType.LOCAL,
188
- ServerProviderType.DOCKER,
189
- ]:
190
- config_attrs["ip_address"] = ip_address
191
-
192
- from zenml.zen_server.deploy.deployment import ServerDeploymentConfig
193
-
194
- server_config = ServerDeploymentConfig(**config_attrs)
195
- if blocking:
196
- from zenml.constants import (
197
- DEFAULT_USERNAME,
198
- )
199
-
200
- cli_utils.declare(
201
- "The local ZenML dashboard is about to deploy in a "
202
- "blocking process. You can connect to it using the "
203
- f"'{DEFAULT_USERNAME}' username and an empty password."
204
- )
205
- server = deployer.deploy_server(server_config)
206
116
 
207
- if not blocking:
208
- from zenml.constants import (
209
- DEFAULT_PASSWORD,
210
- DEFAULT_USERNAME,
211
- )
212
-
213
- deployer.connect_to_server(
214
- LOCAL_ZENML_SERVER_NAME,
215
- DEFAULT_USERNAME,
216
- DEFAULT_PASSWORD,
217
- )
117
+ # Calling the `zenml login` command
118
+ cli_utils.declare("Calling `zenml login --local`...")
119
+ login.callback( # type: ignore[misc]
120
+ local=True,
121
+ docker=docker,
122
+ ip_address=ip_address,
123
+ port=port,
124
+ blocking=blocking,
125
+ image=image,
126
+ ngrok_token=ngrok_token,
127
+ )
218
128
 
219
- if server.status and server.status.url:
220
- cli_utils.declare(
221
- f"The local ZenML dashboard is available at "
222
- f"'{server.status.url}'. You can connect to it using the "
223
- f"'{DEFAULT_USERNAME}' username and an empty password. "
224
- )
225
- zenml.show(
226
- ngrok_token=ngrok_token,
227
- username=DEFAULT_USERNAME,
228
- password=DEFAULT_PASSWORD,
229
- )
230
129
 
130
+ @cli.command(
131
+ "show",
132
+ help="""Show the ZenML dashboard.
231
133
 
134
+ DEPRECATED: Please use `zenml server show` instead.
135
+ """,
136
+ )
232
137
  @click.option(
233
138
  "--ngrok-token",
234
139
  type=str,
235
140
  default=None,
236
141
  help="Specify an ngrok auth token to use for exposing the ZenML server.",
237
142
  )
238
- @cli.command("show", help="Show the ZenML dashboard.")
239
- def show(ngrok_token: Optional[str] = None) -> None:
143
+ def legacy_show(ngrok_token: Optional[str] = None) -> None:
240
144
  """Show the ZenML dashboard.
241
145
 
242
146
  Args:
@@ -244,31 +148,36 @@ def show(ngrok_token: Optional[str] = None) -> None:
244
148
  on a public domain. Primarily used for accessing the dashboard in
245
149
  Colab.
246
150
  """
247
- try:
248
- zenml.show(ngrok_token=ngrok_token)
249
- except RuntimeError as e:
250
- cli_utils.error(str(e))
251
-
252
-
253
- @cli.command("down", help="Shut down the local ZenML dashboard.")
254
- def down() -> None:
255
- """Shut down the local ZenML dashboard."""
256
- server = get_local_server()
151
+ cli_utils.warning(
152
+ "The `zenml show` command is deprecated and will be removed in a "
153
+ "future release. Please use the `zenml server show` command "
154
+ "instead."
155
+ )
257
156
 
258
- if not server:
259
- cli_utils.declare("The local ZenML dashboard is not running.")
157
+ # Calling the `zenml server show` command
158
+ cli_utils.declare("Calling `zenml server show`...")
159
+ show(local=False, ngrok_token=ngrok_token)
260
160
 
261
- else:
262
- from zenml.zen_server.deploy.deployer import ServerDeployer
263
161
 
264
- deployer = ServerDeployer()
265
- deployer.remove_server(server.config.name)
266
- cli_utils.declare("The local ZenML dashboard has been shut down.")
162
+ @cli.command(
163
+ "down",
164
+ help="""Shut down the local ZenML dashboard.
267
165
 
268
- os.environ[ENV_ZENML_LOCAL_SERVER] = str(False)
166
+ DEPRECATED: Please use `zenml logout local` instead.
167
+ """,
168
+ )
169
+ def down() -> None:
170
+ """Shut down the local ZenML dashboard."""
171
+ cli_utils.warning(
172
+ "The `zenml down` command is deprecated and will be removed in a "
173
+ "future release. Please use the `zenml logout --local` command instead."
174
+ )
269
175
 
270
- gc = GlobalConfiguration()
271
- gc.set_default_store()
176
+ # Calling the `zenml logout` command
177
+ cli_utils.declare("Calling `zenml logout --local`...")
178
+ logout.callback( # type: ignore[misc]
179
+ local=True
180
+ )
272
181
 
273
182
 
274
183
  @cli.command(
@@ -276,30 +185,87 @@ def down() -> None:
276
185
  )
277
186
  def status() -> None:
278
187
  """Show details about the current configuration."""
188
+ from zenml.login.credentials_store import get_credentials_store
189
+ from zenml.login.pro.client import ZenMLProClient
190
+
279
191
  gc = GlobalConfiguration()
280
192
  client = Client()
281
193
 
282
194
  store_cfg = gc.store_configuration
283
195
 
284
- # Write about the current ZenML server
285
- cli_utils.declare("-----ZenML Server Status-----")
196
+ # Write about the current ZenML client
197
+ cli_utils.declare("-----ZenML Client Status-----")
286
198
  if gc.uses_default_store():
287
199
  cli_utils.declare(
288
- f"Connected to a local ZenML database: ('{store_cfg.url}')"
200
+ f"Connected to the local ZenML database: '{store_cfg.url}'"
201
+ )
202
+ elif connected_to_local_server():
203
+ cli_utils.declare(
204
+ f"Connected to the local ZenML server: {store_cfg.url}"
205
+ )
206
+ elif re.match(r"^mysql://", store_cfg.url):
207
+ cli_utils.declare(
208
+ f"Connected directly to a SQL database: '{store_cfg.url}'"
289
209
  )
290
210
  else:
291
- cli_utils.declare(f"Connected to a ZenML server: '{store_cfg.url}'")
211
+ credentials_store = get_credentials_store()
212
+ server = credentials_store.get_credentials(store_cfg.url)
213
+ if server:
214
+ if server.type == ServerType.PRO:
215
+ # If connected to a ZenML Pro server, refresh the server info
216
+ pro_credentials = credentials_store.get_pro_credentials(
217
+ allow_expired=False
218
+ )
219
+ if pro_credentials:
220
+ pro_client = ZenMLProClient()
221
+ pro_servers = pro_client.tenant.list(
222
+ url=store_cfg.url, member_only=True
223
+ )
224
+ if pro_servers:
225
+ credentials_store.update_server_info(
226
+ server_url=store_cfg.url,
227
+ server_info=pro_servers[0],
228
+ )
229
+
230
+ cli_utils.declare(
231
+ f"Connected to a ZenML Pro server: `{server.server_name_hyperlink}`"
232
+ f" [{server.server_id_hyperlink}]"
233
+ )
234
+
235
+ cli_utils.declare(
236
+ f" ZenML Pro Organization: {server.organization_hyperlink}"
237
+ )
238
+ if pro_credentials:
239
+ cli_utils.declare(
240
+ f" ZenML Pro authentication: {pro_credentials.auth_status}"
241
+ )
242
+ else:
243
+ cli_utils.declare(
244
+ f"Connected to a remote ZenML server: `{server.dashboard_hyperlink}`"
245
+ )
246
+
247
+ cli_utils.declare(f" Dashboard: {server.dashboard_hyperlink}")
248
+ cli_utils.declare(f" API: {server.api_hyperlink}")
249
+ cli_utils.declare(f" Server status: '{server.status}'")
250
+ cli_utils.declare(f" Server authentication: {server.auth_status}")
292
251
 
293
- # Write about the active entities
294
- scope = "repository" if client.uses_local_configuration else "global"
295
- cli_utils.declare(f" The active user is: '{client.active_user.name}'")
296
- cli_utils.declare(
297
- f" The active workspace is: '{client.active_workspace.name}' "
298
- f"({scope})"
299
- )
300
- cli_utils.declare(
301
- f" The active stack is: '{client.active_stack_model.name}' ({scope})"
302
- )
252
+ else:
253
+ cli_utils.declare(
254
+ f"Connected to a remote ZenML server: [link={store_cfg.url}]"
255
+ f"{store_cfg.url}[/link]"
256
+ )
257
+
258
+ try:
259
+ client.zen_store.get_store_info()
260
+ except Exception as e:
261
+ cli_utils.warning(f"Error while initializing client: {e}")
262
+ else:
263
+ # Write about the active entities
264
+ scope = "repository" if client.uses_local_configuration else "global"
265
+ cli_utils.declare(f" The active user is: '{client.active_user.name}'")
266
+ cli_utils.declare(
267
+ f" The active stack is: '{client.active_stack_model.name}' ({scope})"
268
+ )
303
269
 
304
270
  if client.root:
305
271
  cli_utils.declare(f"Active repository root: {client.root}")
@@ -310,10 +276,34 @@ def status() -> None:
310
276
  f"Local store files are located at: '{gc.local_stores_path}'"
311
277
  )
312
278
 
313
- server = get_local_server()
314
- if server:
315
- cli_utils.declare("The status of the local dashboard:")
316
- cli_utils.print_server_deployment(server)
279
+ cli_utils.declare("\n-----Local ZenML Server Status-----")
280
+ local_server = get_local_server()
281
+ if local_server:
282
+ if local_server.status:
283
+ if local_server.status.status == ServiceState.ACTIVE:
284
+ cli_utils.declare(
285
+ f"The local {local_server.config.provider} server is "
286
+ f"running at: {local_server.status.url}"
287
+ )
288
+ else:
289
+ cli_utils.declare(
290
+ f"The local {local_server.config.provider} server is not "
291
+ "available."
292
+ )
293
+ cli_utils.declare(
294
+ f" Server state: {local_server.status.status}"
295
+ )
296
+ if local_server.status.status_message:
297
+ cli_utils.declare(
298
+ f" Status message: {local_server.status.status_message}"
299
+ )
300
+ else:
301
+ cli_utils.declare(
302
+ f"The local {local_server.config.provider} server is not "
303
+ "running."
304
+ )
305
+ else:
306
+ cli_utils.declare("The local server has not been started.")
317
307
 
318
308
 
319
309
  @cli.command(
@@ -321,57 +311,17 @@ def status() -> None:
321
311
  help=(
322
312
  """Connect to a remote ZenML server.
323
313
 
324
- Examples:
325
-
326
- * to connect to a ZenML deployment using web login:
327
-
328
- zenml connect --url=http://zenml.example.com:8080
329
-
330
- * to connect to a ZenML deployment using command line arguments:
331
-
332
- zenml connect --url=http://zenml.example.com:8080 --username=admin
314
+ DEPRECATED: Please use `zenml login` instead.
333
315
 
334
- * to use a configuration file:
335
-
336
- zenml connect --config=/path/to/zenml_config.yaml
337
-
338
- * when no arguments are supplied, ZenML will attempt to connect to the
339
- last ZenML server deployed from the local host using the 'zenml deploy'
340
- command.
341
-
342
- The configuration file must be a YAML or JSON file with the following
343
- attributes:
344
-
345
- url: The URL of the ZenML server.
346
-
347
- username: The username to use for authentication.
348
-
349
- password: The password to use for authentication.
350
-
351
- verify_ssl: Either a boolean, in which case it controls whether the
352
- server's TLS certificate is verified, or a string, in which case it
353
- must be a path to a CA certificate bundle to use or the CA bundle
354
- value itself.
316
+ Examples:
355
317
 
356
- http_timeout: The number of seconds to wait for HTTP requests to the
357
- ZenML server to be successful before issuing a timeout error
358
- (default: 5).
318
+ * to re-login to the current ZenML server or connect to a ZenML Pro server:
359
319
 
360
- Example configuration:
320
+ zenml connect
361
321
 
362
- url: https://ac8ef63af203226194a7725ee71d85a-7635928635.us-east-1.elb.amazonaws.com/zenml\n
363
- username: admin\n
364
- password: Pa$$word123\n
365
- verify_ssl: |\n
366
- -----BEGIN CERTIFICATE-----
367
- MIIDETCCAfmgAwIBAgIQYUmQg2LR/pHAMZb/vQwwXjANBgkqhkiG9w0BAQsFADAT
368
- MREwDwYDVQQDEwh6ZW5tbC1jYTAeFw0yMjA5MjYxMzI3NDhaFw0yMzA5MjYxMzI3\n
369
- ...\n
370
- ULnzA0JkRWRnFqH6uXeJo1KAVqtxn1xf8PYxx3NlNDr9wi8KKwARf2lwm6sH4mvq
371
- 1aZ/0iYnGKCu7rLJzxeguliMf69E\n
372
- -----END CERTIFICATE-----
373
- http_timeout: 10
322
+ * to log in to a particular ZenML server:
374
323
 
324
+ zenml connect --url=http://zenml.example.com:8080
375
325
  """
376
326
  ),
377
327
  )
@@ -384,15 +334,15 @@ def status() -> None:
384
334
  )
385
335
  @click.option(
386
336
  "--username",
387
- help="The username that is used to authenticate with a ZenML server. If "
388
- "omitted, the web login will be used.",
337
+ help="(Deprecated) The username that is used to authenticate with a ZenML "
338
+ "server. If omitted, the web login will be used.",
389
339
  required=False,
390
340
  type=str,
391
341
  )
392
342
  @click.option(
393
343
  "--password",
394
- help="The password that is used to authenticate with a ZenML server. If "
395
- "omitted, a prompt will be shown to enter the password.",
344
+ help="(Deprecated) The password that is used to authenticate with a ZenML "
345
+ "server. If omitted, a prompt will be shown to enter the password.",
396
346
  required=False,
397
347
  type=str,
398
348
  )
@@ -416,19 +366,6 @@ def status() -> None:
416
366
  required=False,
417
367
  type=str,
418
368
  )
419
- @click.option(
420
- "--config",
421
- help="Use a YAML or JSON configuration or configuration file.",
422
- required=False,
423
- type=str,
424
- )
425
- @click.option(
426
- "--raw-config",
427
- is_flag=True,
428
- help="Whether to use the configuration without prompting for missing "
429
- "fields.",
430
- default=False,
431
- )
432
369
  def connect(
433
370
  url: Optional[str] = None,
434
371
  username: Optional[str] = None,
@@ -436,8 +373,6 @@ def connect(
436
373
  api_key: Optional[str] = None,
437
374
  no_verify_ssl: bool = False,
438
375
  ssl_ca_cert: Optional[str] = None,
439
- config: Optional[str] = None,
440
- raw_config: bool = False,
441
376
  ) -> None:
442
377
  """Connect to a remote ZenML server.
443
378
 
@@ -452,132 +387,50 @@ def connect(
452
387
  no_verify_ssl: Whether to verify the server's TLS certificate.
453
388
  ssl_ca_cert: A path to a CA bundle to use to verify the server's TLS
454
389
  certificate or the CA bundle value itself.
455
- config: A YAML or JSON configuration or configuration file to use.
456
- raw_config: Whether to use the configuration without prompting for
457
- missing fields.
458
390
  """
459
- from zenml.config.store_config import StoreConfiguration
460
- from zenml.zen_stores.base_zen_store import BaseZenStore
461
-
462
- if password is not None:
463
- cli_utils.warning(
464
- "Supplying password values in the command line is not safe. "
465
- "Please consider using the prompt option."
466
- )
467
-
468
- # Raise an error if a local server is running when trying to connect to
469
- # another server
470
- active_deployment = get_local_server()
471
- if (
472
- active_deployment
473
- and active_deployment.status
474
- and active_deployment.status.url != url
475
- ):
476
- cli_utils.error(
477
- "You're trying to connect to a remote ZenML server but already "
478
- "have a local server running. This can lead to unexpected "
479
- "behavior. Please shut down the local server by running "
480
- "`zenml down` before connecting to a remote server."
481
- )
482
-
483
- store_dict: Dict[str, Any] = {}
484
- verify_ssl: Union[str, bool] = (
485
- ssl_ca_cert if ssl_ca_cert is not None else not no_verify_ssl
391
+ cli_utils.warning(
392
+ "The `zenml connect` command is deprecated and will be removed in a "
393
+ "future release. Please use the `zenml login` command instead. "
486
394
  )
487
395
 
488
- if config:
489
- if os.path.isfile(config):
490
- store_dict = yaml_utils.read_yaml(config)
491
- else:
492
- store_dict = yaml.safe_load(config)
493
- if not isinstance(store_dict, dict):
494
- cli_utils.error(
495
- "The configuration argument must be JSON/YAML content or "
496
- "point to a valid configuration file."
497
- )
498
-
499
- if raw_config:
500
- store_config = StoreConfiguration.model_validate(store_dict)
501
- GlobalConfiguration().set_store(store_config)
502
- return
503
-
504
- url = store_dict.get("url", url)
505
- username = username or store_dict.get("username")
506
- password = password or store_dict.get("password")
507
- api_key = api_key or store_dict.get("api_key")
508
- verify_ssl = store_dict.get("verify_ssl", verify_ssl)
509
-
510
- if not url:
511
- url = click.prompt("ZenML server URL", type=str)
512
- else:
513
- cli_utils.declare(f"Connecting to: '{url}'...")
514
- assert url is not None
515
-
516
- store_dict["url"] = url
517
- store_type = BaseZenStore.get_store_type(url)
518
- if store_type == StoreType.REST:
519
- store_dict["verify_ssl"] = verify_ssl
520
-
521
- if not username and not api_key:
522
- if store_type == StoreType.REST:
523
- store_dict["api_token"] = web_login(url=url, verify_ssl=verify_ssl)
524
- else:
525
- username = click.prompt("Username", type=str)
526
-
527
- if username:
396
+ if password is not None or username is not None:
528
397
  cli_utils.warning(
529
398
  "Connecting to a ZenML server using a username and password is "
530
- "not recommended because the password is locally stored on your "
531
- "filesystem. You should consider using the web login workflow by "
532
- "omitting the `--username` and `--password` flags. An alternative "
533
- "for non-interactive environments is to create and use a service "
534
- "account API key (see https://docs.zenml.io/how-to/connecting-to-zenml/connect-with-a-service-account "
399
+ "insecure because the password is locally stored on your "
400
+ "filesystem and is no longer supported. The web login workflow will "
401
+ "be used instead. An alternative for non-interactive environments "
402
+ "is to create and use a service account API key (see "
403
+ "https://docs.zenml.io/how-to/connecting-to-zenml/connect-with-a-service-account "
535
404
  "for more information)."
536
405
  )
537
406
 
538
- store_dict["username"] = username
539
-
540
- if password is None:
541
- password = click.prompt(
542
- f"Password for user {username} (press ENTER for empty password)",
543
- default="",
544
- hide_input=True,
545
- )
546
- store_dict["password"] = password
547
- elif api_key:
548
- store_dict["api_key"] = api_key
549
-
550
- store_config_class = BaseZenStore.get_store_config_class(store_type)
551
- assert store_config_class is not None
407
+ # Calling the `zenml login` command
408
+ cli_utils.declare("Calling `zenml login`...")
409
+ login.callback( # type: ignore[misc]
410
+ server=url,
411
+ api_key=api_key,
412
+ no_verify_ssl=no_verify_ssl,
413
+ ssl_ca_cert=ssl_ca_cert,
414
+ )
552
415
 
553
- store_config = store_config_class.model_validate(store_dict)
554
- try:
555
- GlobalConfiguration().set_store(store_config)
556
- except IllegalOperationError:
557
- cli_utils.warning(
558
- f"User '{username}' does not have sufficient permissions to "
559
- f"access the server at '{url}'."
560
- )
561
- except AuthorizationException as e:
562
- cli_utils.warning(f"Authorization error: {e}")
563
416
 
417
+ @cli.command(
418
+ "disconnect",
419
+ help="""Disconnect from a ZenML server.
564
420
 
565
- @cli.command("disconnect", help="Disconnect from a ZenML server.")
421
+ DEPRECATED: Please use `zenml logout` instead.
422
+ """,
423
+ )
566
424
  def disconnect_server() -> None:
567
425
  """Disconnect from a ZenML server."""
568
- from zenml.zen_server.deploy.deployer import ServerDeployer
569
- from zenml.zen_stores.base_zen_store import BaseZenStore
426
+ cli_utils.warning(
427
+ "The `zenml disconnect` command is deprecated and will be removed in a "
428
+ "future release. Please use the `zenml logout` command instead."
429
+ )
570
430
 
571
- gc = GlobalConfiguration()
572
-
573
- url = gc.store_configuration.url
574
- store_type = BaseZenStore.get_store_type(url)
575
- if store_type == StoreType.REST:
576
- deployer = ServerDeployer()
577
- deployer.disconnect_from_server()
578
- else:
579
- gc.set_default_store()
580
- cli_utils.declare("Restored default store configuration.")
431
+ # Calling the `zenml logout` command
432
+ cli_utils.declare("Calling `zenml logout`...")
433
+ logout.callback() # type: ignore[misc]
581
434
 
582
435
 
583
436
  @cli.command("logs", help="Show the logs for the local ZenML server.")
@@ -619,20 +472,20 @@ def logs(
619
472
  "up` first to start the ZenML dashboard locally."
620
473
  )
621
474
 
622
- server_name = server.config.name
623
-
624
- from zenml.zen_server.deploy.deployer import ServerDeployer
475
+ from zenml.zen_server.deploy.deployer import LocalServerDeployer
625
476
 
626
- deployer = ServerDeployer()
477
+ deployer = LocalServerDeployer()
627
478
 
628
- cli_utils.declare(f"Showing logs for server: {server_name}")
479
+ cli_utils.declare(
480
+ f"Showing logs for the local {server.config.provider} server"
481
+ )
629
482
 
630
483
  from zenml.zen_server.deploy.exceptions import (
631
484
  ServerDeploymentNotFoundError,
632
485
  )
633
486
 
634
487
  try:
635
- logs = deployer.get_server_logs(server_name, follow=follow, tail=tail)
488
+ logs = deployer.get_server_logs(follow=follow, tail=tail)
636
489
  except ServerDeploymentNotFoundError as e:
637
490
  cli_utils.error(f"Server not found: {e}")
638
491
 
@@ -645,3 +498,259 @@ def logs(
645
498
  console.print(line)
646
499
  except MarkupError:
647
500
  console.print(line, markup=False)
501
+
502
+
503
+ @cli.group(cls=TagGroup, tag=CliCategories.MANAGEMENT_TOOLS)
504
+ def server() -> None:
505
+ """Commands for managing ZenML servers."""
506
+
507
+
508
+ @server.command(
509
+ "list",
510
+ help="""List all ZenML servers that this client is authenticated to.
511
+
512
+ The CLI can be authenticated to multiple ZenML servers at the same time,
513
+ even though it can only be connected to one server at a time. You can list
514
+ all the ZenML servers that the client is currently authenticated to by
515
+ using this command.
516
+
517
+ When logged in to ZenML Pro, this list will also include all ZenML Pro
518
+ servers that the authenticated user can access or could potentially access,
519
+ including details such as their current state and the organization they
520
+ belong to.
521
+
522
+ The complete list of servers displayed by this command includes the
523
+ following:
524
+
525
+ * ZenML Pro servers that the authenticated ZenML Pro user can or could
526
+ access. The client needs to be logged to ZenML Pro via
527
+ `zenml login --pro` to access these servers.
528
+
529
+ * ZenML servers that the client has logged in to via
530
+ `zenml login --url` in the past.
531
+
532
+ * the local ZenML server started with `zenml login --local`, if one is
533
+ running.
534
+
535
+ By default, this command does not display ZenML servers that are not
536
+ accessible: servers that are not running, are no longer accessible due to
537
+ an expired authentication and ZenML Pro servers where the user is not a
538
+ member. To include these servers in the list, use the `--all` flag.
539
+ """,
540
+ )
541
+ @click.option(
542
+ "--verbose",
543
+ "-v",
544
+ is_flag=True,
545
+ help="Show verbose output.",
546
+ )
547
+ @click.option(
548
+ "--all",
549
+ "-a",
550
+ is_flag=True,
551
+ help="Show all ZenML servers, including those that are not running "
552
+ "and those with an expired authentication.",
553
+ )
554
+ def server_list(verbose: bool = False, all: bool = False) -> None:
555
+ """List all ZenML servers that this client is authorized to access.
556
+
557
+ Args:
558
+ verbose: Whether to show verbose output.
559
+ all: Whether to show all ZenML servers.
560
+ """
561
+ from zenml.login.credentials_store import get_credentials_store
562
+ from zenml.login.pro.client import ZenMLProClient
563
+ from zenml.login.pro.tenant.models import TenantRead, TenantStatus
564
+
565
+ credentials_store = get_credentials_store()
566
+ pro_token = credentials_store.get_pro_token(allow_expired=True)
567
+ current_store_config = GlobalConfiguration().store_configuration
568
+
569
+ # The list of ZenML Pro servers kept in the credentials store
570
+ pro_servers = credentials_store.list_credentials(type=ServerType.PRO)
571
+ # The list of regular remote ZenML servers kept in the credentials store
572
+ servers = list(credentials_store.list_credentials(type=ServerType.REMOTE))
573
+ # The list of local ZenML servers kept in the credentials store
574
+ local_servers = list(
575
+ credentials_store.list_credentials(type=ServerType.LOCAL)
576
+ )
577
+
578
+ if pro_token and not pro_token.expired:
579
+ # If the ZenML Pro authentication is still valid, we include all ZenML
580
+ # Pro servers that the current ZenML Pro user can access, even those
581
+ # that the user has never connected to (and are therefore not stored in
582
+ # the credentials store).
583
+
584
+ accessible_pro_servers: List[TenantRead] = []
585
+ try:
586
+ client = ZenMLProClient()
587
+ accessible_pro_servers = client.tenant.list(member_only=not all)
588
+ except AuthorizationException as e:
589
+ cli_utils.warning(f"ZenML Pro authorization error: {e}")
590
+ else:
591
+ if not all:
592
+ accessible_pro_servers = [
593
+ s
594
+ for s in accessible_pro_servers
595
+ if s.status == TenantStatus.AVAILABLE
596
+ ]
597
+
598
+ if not accessible_pro_servers:
599
+ cli_utils.declare(
600
+ "No ZenML Pro servers that are accessible to the current "
601
+ "user could be found."
602
+ )
603
+ if not all:
604
+ cli_utils.declare(
605
+ "Hint: use the `--all` flag to show all ZenML servers, "
606
+ "including those that the client is not currently "
607
+ "authorized to access or are not running."
608
+ )
609
+
610
+ # We update the list of stored ZenML Pro servers with the ones that the
611
+ # client is a member of
612
+ for accessible_server in accessible_pro_servers:
613
+ for idx, stored_server in enumerate(pro_servers):
614
+ if stored_server.server_id == accessible_server.id:
615
+ # All ZenML Pro servers accessible by the current ZenML Pro
616
+ # user have an authentication that is valid at least until
617
+ # the current ZenML Pro authentication token expires.
618
+ stored_server.update_server_info(
619
+ accessible_server,
620
+ )
621
+ updated_server = stored_server.model_copy()
622
+ # Replace the current server API token with the current
623
+ # ZenML Pro API token to reflect the current authentication
624
+ # status.
625
+ updated_server.api_token = pro_token
626
+ pro_servers[idx] = updated_server
627
+ break
628
+ else:
629
+ stored_server = ServerCredentials(
630
+ url=accessible_server.url or "",
631
+ api_token=pro_token,
632
+ )
633
+ stored_server.update_server_info(accessible_server)
634
+ pro_servers.append(stored_server)
635
+
636
+ elif pro_servers:
637
+ cli_utils.warning(
638
+ "The ZenML Pro authentication has expired. Please re-login "
639
+ "to ZenML Pro using `zenml login` to include all ZenML Pro servers "
640
+ "that you are a member of in the list."
641
+ )
642
+
643
+ # We add the local server to the list of servers, if it is running
644
+ local_server = get_local_server()
645
+ if local_server:
646
+ url = (
647
+ local_server.status.url if local_server.status else None
648
+ ) or local_server.config.url
649
+ status = local_server.status.status if local_server.status else ""
650
+ local_servers.append(
651
+ ServerCredentials(
652
+ url=url or "",
653
+ status=status,
654
+ version=zenml.__version__,
655
+ server_id=GlobalConfiguration().user_id,
656
+ server_name=f"local {local_server.config.provider} server",
657
+ )
658
+ )
659
+
660
+ all_servers = pro_servers + local_servers + servers
661
+
662
+ if not all:
663
+ # Filter out servers that are expired or not running
664
+ all_servers = [s for s in all_servers if s.is_available]
665
+
666
+ if verbose:
667
+ columns = [
668
+ "type",
669
+ "server_id_hyperlink",
670
+ "server_name_hyperlink",
671
+ "organization_hyperlink" if pro_servers else "",
672
+ "version",
673
+ "status",
674
+ "dashboard_url",
675
+ "api_hyperlink",
676
+ "auth_status",
677
+ ]
678
+ elif all:
679
+ columns = [
680
+ "type",
681
+ "server_id_hyperlink",
682
+ "server_name_hyperlink",
683
+ "organization_hyperlink" if pro_servers else "",
684
+ "version",
685
+ "status",
686
+ "api_hyperlink",
687
+ ]
688
+ else:
689
+ columns = [
690
+ "type",
691
+ "server_id_hyperlink" if pro_servers else "",
692
+ "server_name_hyperlink",
693
+ "organization_hyperlink" if pro_servers else "",
694
+ "version",
695
+ "api_hyperlink" if servers else "",
696
+ ]
697
+
698
+ # Remove empty columns
699
+ columns = [c for c in columns if c]
700
+
701
+ # Figure out if the client is already connected to one of the
702
+ # servers in the list
703
+ current_server: List[ServerCredentials] = []
704
+ if current_store_config.type == StoreType.REST:
705
+ current_server = [
706
+ s for s in all_servers if s.url == current_store_config.url
707
+ ]
708
+
709
+ cli_utils.print_pydantic_models( # type: ignore[type-var]
710
+ all_servers,
711
+ columns=columns,
712
+ rename_columns={
713
+ "server_name_hyperlink": "name",
714
+ "server_id_hyperlink": "ID",
715
+ "organization_hyperlink": "organization",
716
+ "dashboard_url": "dashboard URL",
717
+ "api_hyperlink": "API URL",
718
+ "auth_status": "auth status",
719
+ },
720
+ active_models=current_server,
721
+ show_active=True,
722
+ )
723
+
724
+
725
+ @server.command(
726
+ "show",
727
+ help="Show the ZenML dashboard for the server that the client is connected to.",
728
+ )
729
+ @click.option(
730
+ "--local",
731
+ is_flag=True,
732
+ help="Show the ZenML dashboard for the local server.",
733
+ default=False,
734
+ type=click.BOOL,
735
+ )
736
+ @click.option(
737
+ "--ngrok-token",
738
+ type=str,
739
+ default=None,
740
+ help="Specify an ngrok auth token to use for exposing the local ZenML "
741
+ "server. Only used when `--local` is set. Primarily used for accessing the "
742
+ "local dashboard in Colab.",
743
+ )
744
+ def show(local: bool = False, ngrok_token: Optional[str] = None) -> None:
745
+ """Show the ZenML dashboard.
746
+
747
+ Args:
748
+ local: Whether to show the ZenML dashboard for the local server.
749
+ ngrok_token: An ngrok auth token to use for exposing the ZenML dashboard
750
+ on a public domain. Primarily used for accessing the local dashboard
751
+ in Colab.
752
+ """
753
+ try:
754
+ zenml.show(ngrok_token=ngrok_token)
755
+ except RuntimeError as e:
756
+ cli_utils.error(str(e))