Skip to content

Commit c024650

Browse files
authored
feat(core): add support for ssh connections into sessions (#3318)
1 parent 38aa53c commit c024650

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1327
-469
lines changed

renku/command/format/session.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ def tabular(sessions, *, columns=None):
2525
if not columns:
2626
columns = "id,status,url"
2727

28+
if any(s.ssh_enabled for s in sessions):
29+
columns += ",ssh"
30+
2831
return tabulate(collection=sessions, columns=columns, columns_mapping=SESSION_COLUMNS)
2932

3033

@@ -35,4 +38,5 @@ def tabular(sessions, *, columns=None):
3538
"id": ("id", "id"),
3639
"status": ("status", "status"),
3740
"url": ("url", "url"),
41+
"ssh": ("ssh_enabled", "SSH enabled"),
3842
}

renku/command/migrate.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,23 @@ def _template_migration_check():
8888
Returns:
8989
Dictionary of template migration status.
9090
"""
91+
from renku.core.config import get_value
9192
from renku.core.template.usecase import check_for_template_update
9293

9394
try:
9495
project = project_context.project
9596
template_source = project.template_metadata.template_source
9697
template_ref = project.template_metadata.template_ref
9798
template_id = project.template_metadata.template_id
99+
ssh_supported = project.template_metadata.ssh_supported
98100
except (ValueError, AttributeError):
99101
project = None
100102
template_source = None
101103
template_ref = None
102104
template_id = None
105+
ssh_supported = False
106+
107+
ssh_supported = get_value("renku", "ssh_supported") == "true" or ssh_supported
103108

104109
update_available, update_allowed, current_version, new_version = check_for_template_update(project)
105110

@@ -111,6 +116,7 @@ def _template_migration_check():
111116
"template_source": template_source,
112117
"template_ref": template_ref,
113118
"template_id": template_id,
119+
"ssh_supported": ssh_supported,
114120
}
115121

116122

renku/command/session.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

2121
from renku.command.command_builder.command import Command
22-
from renku.core.session.session import session_list, session_open, session_start, session_stop
22+
from renku.core.session.session import session_list, session_open, session_start, session_stop, ssh_setup
2323

2424

2525
def session_list_command():
@@ -29,7 +29,7 @@ def session_list_command():
2929

3030
def session_start_command():
3131
"""Start an interactive session."""
32-
return Command().command(session_start)
32+
return Command().command(session_start).with_database()
3333

3434

3535
def session_stop_command():
@@ -40,3 +40,8 @@ def session_stop_command():
4040
def session_open_command():
4141
"""Open a running interactive session."""
4242
return Command().command(session_open)
43+
44+
45+
def ssh_setup_command():
46+
"""Setup SSH keys for SSH connections to sessions."""
47+
return Command().command(ssh_setup)

renku/command/view_model/project.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from datetime import datetime
2222
from typing import List, Optional
2323

24+
from renku.core.config import get_value
2425
from renku.domain_model.project import Project
2526
from renku.domain_model.provenance.agent import Person
2627

@@ -39,6 +40,7 @@ def __init__(
3940
annotations: Optional[str],
4041
template_info: str,
4142
keywords: Optional[List[str]],
43+
ssh_supported: bool = False,
4244
):
4345
self.id = id
4446
self.name = name
@@ -52,6 +54,7 @@ def __init__(
5254
self.template_info = template_info
5355
self.keywords = keywords
5456
self.keywords_str = ", ".join(keywords) if keywords else ""
57+
self.ssh_supported = ssh_supported
5558

5659
@classmethod
5760
def from_project(cls, project: Project):
@@ -88,4 +91,5 @@ def from_project(cls, project: Project):
8891
else None,
8992
template_info=template_info,
9093
keywords=project.keywords,
94+
ssh_supported=get_value("renku", "ssh_supported") == "true" or project.template_metadata.ssh_supported,
9195
)

renku/core/errors.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ class AuthenticationError(RenkuException):
110110
"""Raise when there is a problem with authentication."""
111111

112112

113+
class KeyNotFoundError(RenkuException):
114+
"""Raise when an SSH private or public key couldn't be found."""
115+
116+
113117
class DirtyRepository(RenkuException):
114118
"""Raise when trying to work with dirty repository."""
115119

@@ -526,6 +530,24 @@ def __init__(self):
526530
super(NodeNotFoundError, self).__init__(msg)
527531

528532

533+
class SSHNotFoundError(RenkuException):
534+
"""Raised when SSH client is not installed on the system."""
535+
536+
def __init__(self):
537+
"""Build a custom message."""
538+
msg = "SSH client (ssh) could not be found on this system"
539+
super(SSHNotFoundError, self).__init__(msg)
540+
541+
542+
class SSHNotSetupError(RenkuException):
543+
"""Raised when SSH client is not installed on the system."""
544+
545+
def __init__(self):
546+
"""Build a custom message."""
547+
msg = "SSH is not set up correctly, use 'renku session ssh-setup' to set it up."
548+
super(SSHNotSetupError, self).__init__(msg)
549+
550+
529551
class ObjectNotFoundError(RenkuException):
530552
"""Raised when an object is not found in the storage."""
531553

renku/core/init.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ def create_from_template_local(
338338
description: Optional[str] = None,
339339
keywords: Optional[List[str]] = None,
340340
data_dir: Optional[str] = None,
341+
ssh_supported: bool = False,
341342
):
342343
"""Initialize a new project from a template.
343344
@@ -381,6 +382,7 @@ def create_from_template_local(
381382
description="",
382383
parameters={},
383384
icon="",
385+
ssh_supported=ssh_supported,
384386
immutable_files=immutable_template_files or [],
385387
allow_update=automated_template_update,
386388
source=metadata["__template_source__"],

renku/core/login.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ def login(endpoint: Optional[str], git_login: bool, yes: bool):
6464
remote_name, remote_url = remote.name, remote.url
6565

6666
if remote_name and remote_url:
67-
if not yes and not get_value("renku", "show_login_warning"):
67+
show_login_warning = get_value("renku", "show_login_warning")
68+
if not yes and (show_login_warning is None or show_login_warning.lower() == "true"):
6869
message = (
6970
"Remote URL will be changed. Do you want to continue "
7071
"(to disable this warning, pass '--yes' or run 'renku config set show_login_warning False')?"
@@ -140,7 +141,8 @@ def login(endpoint: Optional[str], git_login: bool, yes: bool):
140141
)
141142
if backup_exists:
142143
communication.echo(f"Backup remote '{backup_remote_name}' already exists.")
143-
elif not remote:
144+
145+
if not remote and not backup_exists:
144146
communication.error(f"Cannot create backup remote '{backup_remote_name}' for '{remote_url}'")
145147
else:
146148
_set_renku_url_for_remote(
@@ -182,7 +184,6 @@ def _set_renku_url_for_remote(repository: "Repository", remote_name: str, remote
182184
remote_name(str): Name of the remote.
183185
remote_url(str): Url of the remote.
184186
hostname(str): Hostname.
185-
186187
Raises:
187188
errors.GitCommandError: If remote doesn't exist.
188189
"""

renku/core/session/docker.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
# limitations under the License.
1818
"""Docker based interactive session provider."""
1919

20+
import webbrowser
2021
from pathlib import Path
21-
from typing import Any, Dict, Iterable, List, Optional, Tuple, cast
22+
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, cast
2223
from uuid import uuid4
2324

2425
import docker
@@ -32,6 +33,9 @@
3233
from renku.domain_model.project_context import project_context
3334
from renku.domain_model.session import ISessionProvider, Session
3435

36+
if TYPE_CHECKING:
37+
from renku.core.dataset.providers.models import ProviderParameter
38+
3539

3640
class DockerSessionProvider(ISessionProvider):
3741
"""A docker based interactive session provider."""
@@ -71,7 +75,8 @@ def _get_jupyter_urls(ports: Dict[str, Any], auth_token: str, jupyter_port: int
7175
def _get_docker_containers(self, project_name: str) -> List[docker.models.containers.Container]:
7276
return self.docker_client().containers.list(filters={"label": f"renku_project={project_name}"})
7377

74-
def get_name(self) -> str:
78+
@property
79+
def name(self) -> str:
7580
"""Return session provider's name."""
7681
return "docker"
7782

@@ -109,10 +114,18 @@ def session_provider(self) -> ISessionProvider:
109114
"""Supported session provider.
110115
111116
Returns:
112-
a reference to ``self``.
117+
A reference to ``self``.
113118
"""
114119
return self
115120

121+
def get_start_parameters(self) -> List["ProviderParameter"]:
122+
"""Returns parameters that can be set for session start."""
123+
return []
124+
125+
def get_open_parameters(self) -> List["ProviderParameter"]:
126+
"""Returns parameters that can be set for session open."""
127+
return []
128+
116129
def session_list(self, project_name: str, config: Optional[Dict[str, Any]]) -> List[Session]:
117130
"""Lists all the sessions currently running by the given session provider.
118131
@@ -140,6 +153,7 @@ def session_start(
140153
mem_request: Optional[str] = None,
141154
disk_request: Optional[str] = None,
142155
gpu_request: Optional[str] = None,
156+
**kwargs,
143157
) -> Tuple[str, str]:
144158
"""Creates an interactive session.
145159
@@ -259,6 +273,21 @@ def session_stop(self, project_name: str, session_name: Optional[str], stop_all:
259273
except docker.errors.APIError as error:
260274
raise errors.DockerError(error.msg)
261275

276+
def session_open(self, project_name: str, session_name: str, **kwargs) -> bool:
277+
"""Open a given interactive session.
278+
279+
Args:
280+
project_name(str): Renku project name.
281+
session_name(str): The unique id of the interactive session.
282+
"""
283+
url = self.session_url(session_name)
284+
285+
if not url:
286+
return False
287+
288+
webbrowser.open(url)
289+
return True
290+
262291
def session_url(self, session_name: str) -> Optional[str]:
263292
"""Get the URL of the interactive session."""
264293
for c in self.docker_client().containers.list():

0 commit comments

Comments
 (0)