Skip to content

Commit 671d7e4

Browse files
authored
feat(cli): add --force-build flag to docker session start (#3369)
1 parent 2108f65 commit 671d7e4

File tree

9 files changed

+59
-21
lines changed

9 files changed

+59
-21
lines changed

renku/core/session/docker.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def get_start_parameters(self) -> List["ProviderParameter"]:
125125

126126
return [
127127
ProviderParameter("port", help="Local port to use (random if not specified).", type=int),
128+
ProviderParameter("force-build", help="Always build image and don't check if it exists.", is_flag=True),
128129
]
129130

130131
def get_open_parameters(self) -> List["ProviderParameter"]:
@@ -322,3 +323,7 @@ def session_url(self, session_name: str) -> Optional[str]:
322323
host = c.ports[f"{DockerSessionProvider.JUPYTER_PORT}/tcp"][0]
323324
return f'http://{host["HostIp"]}:{host["HostPort"]}/?token={c.labels["jupyter_token"]}'
324325
return None
326+
327+
def force_build_image(self, force_build: bool = False, **kwargs) -> bool:
328+
"""Whether we should force build the image directly or check for an existing image first."""
329+
return force_build

renku/core/session/renkulab.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ class RenkulabSessionProvider(ISessionProvider):
4545
DEFAULT_TIMEOUT_SECONDS = 300
4646

4747
def __init__(self):
48-
self.__renku_url = None
49-
self.__notebooks_url = None
48+
self.__renku_url: Optional[str] = None
49+
self.__notebooks_url: Optional[str] = None
50+
self._force_build: bool = False
5051

5152
def _renku_url(self) -> str:
5253
"""Get the URL of the renku instance."""
@@ -164,13 +165,15 @@ def pre_start_checks(self, ssh: bool = False, **kwargs):
164165
"Your system is not set up for SSH connections to Renkulab. Would you like to set it up?"
165166
):
166167
ssh_setup()
168+
self._force_build = True
167169
else:
168170
raise errors.RenkulabSessionError(
169171
"Can't run ssh session without setting up Renku SSH support. Run without '--ssh' or "
170172
"run 'renku session ssh-setup'."
171173
)
172174

173-
system_config.setup_session_keys()
175+
if system_config.setup_session_keys():
176+
self._force_build = True
174177

175178
def _cleanup_ssh_connection_configs(
176179
self, project_name: str, running_sessions: Optional[List[Session]] = None
@@ -475,3 +478,7 @@ def session_url(self, session_name: str) -> str:
475478
session_name,
476479
]
477480
return urllib.parse.urljoin(self._renku_url(), "/".join(session_url_parts))
481+
482+
def force_build_image(self, **kwargs) -> bool:
483+
"""Whether we should force build the image directly or check for an existing image first."""
484+
return self._force_build

renku/core/session/session.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,19 @@ def session_start(
130130
if repo_host:
131131
image_name = f"{repo_host}/{image_name}"
132132

133-
if not provider_api.find_image(image_name, config):
134-
communication.confirm(
135-
f"The container image '{image_name}' does not exist. Would you like to build it using {provider}?",
136-
abort=True,
137-
)
138-
with communication.busy(msg=f"Building image {image_name}"):
139-
provider_api.build_image(project_context.dockerfile_path.parent, image_name, config)
140-
communication.echo(f"Image {image_name} built successfully.")
141-
else:
142-
if not provider_api.find_image(image_name, config):
143-
raise errors.ParameterError(f"Cannot find the provided container image '{image_name}'!")
133+
force_build_image = provider_api.force_build_image(**kwargs)
134+
135+
if not force_build_image and not provider_api.find_image(image_name, config):
136+
communication.confirm(
137+
f"The container image '{image_name}' does not exist. Would you like to build it using {provider}?",
138+
abort=True,
139+
)
140+
force_build_image = True
141+
142+
if force_build_image:
143+
with communication.busy(msg=f"Building image {image_name}"):
144+
provider_api.build_image(project_context.dockerfile_path.parent, image_name, config)
145+
communication.echo(f"Image {image_name} built successfully.")
144146

145147
# set resource settings
146148
cpu_limit = cpu_request or get_value("interactive", "cpu_request")

renku/core/util/ssh.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def session_config_path(self, project_name: str, session_name: str) -> Path:
133133
"""
134134
return self.renku_ssh_root / f"00-{project_name}-{session_name}.conf"
135135

136-
def setup_session_keys(self):
136+
def setup_session_keys(self) -> bool:
137137
"""Add a users key to a project."""
138138
project_context.ssh_authorized_keys_path.parent.mkdir(parents=True, exist_ok=True)
139139
project_context.ssh_authorized_keys_path.touch(mode=0o600, exist_ok=True)
@@ -142,7 +142,7 @@ def setup_session_keys(self):
142142
raise errors.SSHNotSetupError()
143143

144144
if self.public_key_string in project_context.ssh_authorized_keys_path.read_text():
145-
return
145+
return False
146146

147147
communication.info("Adding SSH public key to project.")
148148
with project_context.ssh_authorized_keys_path.open("at") as f:
@@ -153,6 +153,7 @@ def setup_session_keys(self):
153153
communication.info(
154154
"Added public key. Changes need to be pushed and remote image built for changes to take effect."
155155
)
156+
return True
156157

157158
def setup_session_config(self, project_name: str, session_name: str) -> str:
158159
"""Setup local SSH config for connecting to a session.

renku/domain_model/session.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,7 @@ def pre_start_checks(self, **kwargs):
182182
make sure that the session launches successfully. By default this method does not do any checks.
183183
"""
184184
return None
185+
186+
def force_build_image(self, **kwargs) -> bool:
187+
"""Whether we should force build the image directly or check for an existing image first."""
188+
return False

renku/ui/cli/session.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@
5454
5555
$ renku session start -p docker
5656
57-
The command first looks for a local image to use. If a local image isn't found, it searches the remote Renku deployment
58-
(if any) and pulls the image if it exists. Finally, it prompts the user to build the image locally if no image is found.
57+
The command first looks for a local image to use. If a local image isn't found, it
58+
searches the remote Renku deployment (if any) and pulls the image if it exists.
59+
Finally, it prompts the user to build the image locally if no image is found. You
60+
can force the image to always be built by using the ``--force-build`` flag.
5961
6062
Renkulab provider
6163
~~~~~~~~~~~~~~~~~

tests/core/plugins/test_session.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ def fake_pre_start_checks(self, **kwargs):
6868
pass
6969

7070

71+
def fake_force_build_image(self, **kwargs):
72+
return kwargs.get("force_build", False)
73+
74+
7175
@pytest.mark.parametrize(
7276
"provider_name,session_provider,provider_patches",
7377
[
@@ -80,7 +84,8 @@ def fake_pre_start_checks(self, **kwargs):
8084
[
8185
({}, "0xdeadbeef"),
8286
({"image_name": "fixed_image"}, "0xdeadbeef"),
83-
({"image_name": "missing_image"}, ParameterError),
87+
({"image_name": "missing_image"}, click.Abort),
88+
(({"image_name": "missing_image", "force_build": True}, "0xdeadbeef")),
8489
],
8590
)
8691
def test_session_start(
@@ -101,6 +106,7 @@ def test_session_start(
101106
find_image=fake_find_image,
102107
build_image=fake_build_image,
103108
pre_start_checks=fake_pre_start_checks,
109+
force_build_image=fake_force_build_image,
104110
**provider_patches,
105111
):
106112
provider_implementation = next(

tests/service/views/test_dataset_views.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,13 @@ def test_edit_datasets_view_unset_values(svc_client_with_repo):
13431343

13441344
assert_rpc_response(response)
13451345
assert {"warnings", "edited", "remote_branch"} == set(response.json["result"])
1346-
assert {"keywords": [], "custom_metadata": None, "images": []} == response.json["result"]["edited"]
1346+
assert {
1347+
"keywords": [],
1348+
"custom_metadata": None,
1349+
"images": [],
1350+
} == response.json[
1351+
"result"
1352+
]["edited"]
13471353

13481354
params_list = {
13491355
"project_id": project_id,

tests/service/views/test_project_views.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ def test_edit_project_view_unset(svc_client_with_repo):
143143

144144
assert_rpc_response(response)
145145
assert {"warning", "edited", "remote_branch"} == set(response.json["result"])
146-
assert {"keywords": None, "custom_metadata": None} == response.json["result"]["edited"]
146+
assert {
147+
"keywords": None,
148+
"custom_metadata": None,
149+
} == response.json[
150+
"result"
151+
]["edited"]
147152

148153

149154
@pytest.mark.service

0 commit comments

Comments
 (0)