Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion incant/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def provision(ctx, name: Optional[str] = None):
def shell(ctx, name: Optional[str]):
"""Open a shell into an instance. If no name is given and there is only one instance, use it."""
try:
Incant(reporter=ctx.obj["REPORTER"], **ctx.obj["OPTIONS"]).shell(name)
ret = Incant(reporter=ctx.obj["REPORTER"], **ctx.obj["OPTIONS"]).shell(name)
sys.exit(ret)
except IncantError as e:
_handle_error(e, ctx.obj["REPORTER"])

Expand Down
4 changes: 2 additions & 2 deletions incant/incant.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def incant_init(self):

print(f"Example configuration written to {config_path}")

def shell(self, name: Optional[str] = None):
def shell(self, name: Optional[str] = None) -> int:
instance_name = name
if not instance_name:
instance_names = list(self.config_manager.instance_configs.keys())
Expand All @@ -189,4 +189,4 @@ def shell(self, name: Optional[str] = None):
if instance_name not in self.config_manager.instance_configs:
raise InstanceError(f"Instance '{instance_name}' not found in config")

self.incus.shell(instance_name)
return self.incus.shell(instance_name)
20 changes: 9 additions & 11 deletions incant/incus_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,14 @@ def file_push(self, file_push_config: FilePushConfig) -> None:
)
self._run_command(command, capture_output=False, quiet=file_push_config.quiet)

def shell(self, name: str) -> None:
def shell(self, name: str) -> int:
"""Opens an interactive shell in the specified Incus instance."""
self.reporter.success(f"Opening shell in {name}...")
try:
subprocess.run( # nosec B603
[self.incus_cmd, "shell", name],
check=True,
stdin=sys.stdin,
stdout=sys.stdout,
stderr=sys.stderr,
)
except subprocess.CalledProcessError as e:
raise InstanceError(f"Failed to open shell in {name}: {e}") from e
result = subprocess.run( # nosec B603
[self.incus_cmd, "shell", name],
check=False,
stdin=sys.stdin,
stdout=sys.stdout,
stderr=sys.stderr,
)
return result.returncode
21 changes: 21 additions & 0 deletions tests/test_incus_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import subprocess
from unittest.mock import patch
from incant.incus_cli import IncusCLI
from incant.reporter import Reporter

Expand All @@ -6,3 +8,22 @@ class TestIncusCLI:
def test_constructor(self):
reporter = Reporter()
IncusCLI(reporter=reporter)

def test_shell_handles_nonzero_exit(self):
"""
Verify that incus shell is called with check=False to avoid raising exception on exit code.
"""
reporter = Reporter()
incus_cli = IncusCLI(reporter)

with patch("subprocess.run") as mock_run:
mock_run.return_value.returncode = 1

# This should not raise any exception and return the exit code
ret = incus_cli.shell("test-instance")
assert ret == 1

# Verify called arguments
args, kwargs = mock_run.call_args
assert args[0] == ["incus", "shell", "test-instance"]
assert kwargs.get("check") is False