From 0766cc2bfa9325a8fd7f925b8195673732f02982 Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Sat, 16 May 2026 11:54:47 +0700 Subject: [PATCH 1/2] feat(status): add --watch flag to stream logs after status Co-Authored-By: Claude Sonnet 4.6 --- tests/test_status_watch.py | 62 ++++++++++++++++++++++++++++++++++ trobz_deploy/command/status.py | 15 ++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/test_status_watch.py diff --git a/tests/test_status_watch.py b/tests/test_status_watch.py new file mode 100644 index 0000000..b110b89 --- /dev/null +++ b/tests/test_status_watch.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest +from click.testing import CliRunner + +from trobz_deploy.command.status import status + + +@pytest.fixture +def runner(): + return CliRunner() + + +def _executor_mock(): + mock = MagicMock() + mock.capture.side_effect = [ + "git@github.com:trobz/myapp.git", # remote url + "main", # branch + "abc1234", # commit + "ActiveState=active\nSubState=running\nActiveEnterTimestamp=", # systemd props + ] + return mock + + +def _invoke(runner, extra_args: list[str], side_effect=None): + with ( + patch("trobz_deploy.command.status.Executor") as MockExecutor, + patch("trobz_deploy.command.status.load_config", return_value={}), + ): + mock_exec = _executor_mock() + if side_effect: + mock_exec.stream.side_effect = side_effect + MockExecutor.return_value = mock_exec + result = runner.invoke( + status, + ["service-myapp-production", *extra_args], + obj={"config": "", "verbose": False}, + ) + return result, mock_exec + + +def test_watch_streams_journalctl_after_status(runner): + result, mock_exec = _invoke(runner, ["--watch"]) + + assert result.exit_code == 0 + mock_exec.stream.assert_called_once_with("journalctl --user -u service-myapp-production -f") + + +def test_no_watch_does_not_stream(runner): + result, mock_exec = _invoke(runner, []) + + assert result.exit_code == 0 + mock_exec.stream.assert_not_called() + + +def test_watch_handles_keyboard_interrupt(runner): + result, mock_exec = _invoke(runner, ["--watch"], side_effect=KeyboardInterrupt) + + assert result.exit_code == 0 + mock_exec.stream.assert_called_once() diff --git a/trobz_deploy/command/status.py b/trobz_deploy/command/status.py index 3d8ff55..9b90b05 100644 --- a/trobz_deploy/command/status.py +++ b/trobz_deploy/command/status.py @@ -17,12 +17,20 @@ default=None, help="SSH port on the remote host.", ) +@click.option( + "--watch", + "watch", + is_flag=True, + default=False, + help="Stream service logs with journalctl after showing status.", +) @click.pass_context def status( ctx: click.Context, instance_name: str, ssh_host: str | None, ssh_port: int | None, + watch: bool, ) -> None: """Show status of a deployment instance.""" cfg = load_config(ctx.obj["config"], instance_name) @@ -77,3 +85,10 @@ def status( click.echo(f"Remote: {remote_url}") click.echo(f"Branch: {branch} ({commit})") click.echo(f"Unit: {unit_line}") + + if watch: + click.secho("\nWatching service logs (Ctrl+C to stop)…", fg="cyan") + try: + executor.stream(f"journalctl --user -u {instance_name} -f") + except KeyboardInterrupt: + click.echo() From 262267193a51e9ad3c07bd5ce08a48e75fd7ca4d Mon Sep 17 00:00:00 2001 From: Nils Hamerlinck Date: Sat, 16 May 2026 11:55:15 +0700 Subject: [PATCH 2/2] docs(status): document --watch flag Co-Authored-By: Claude Sonnet 4.6 --- site-docs/docs/commands/status.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/site-docs/docs/commands/status.md b/site-docs/docs/commands/status.md index 1be51e6..df60774 100644 --- a/site-docs/docs/commands/status.md +++ b/site-docs/docs/commands/status.md @@ -5,7 +5,7 @@ Show the current state of a deployment instance: git info and systemd unit statu ## Signature ```bash -deploy [--config FILE] status [] [-p PORT] +deploy [--config FILE] status [] [-p PORT] [--watch] ``` ## Arguments @@ -20,6 +20,7 @@ deploy [--config FILE] status [] [-p PORT] | Option | Default | Description | |--------|---------|-------------| | `-p`, `--port` | — | SSH port on the remote host | +| `--watch` | `False` | Stream service logs with journalctl after showing status | ## Output @@ -38,6 +39,7 @@ Unit: active (running) since 2026-03-09 08:12:03 - `git rev-parse --abbrev-ref HEAD` → branch - `git rev-parse --short HEAD` → commit hash 3. **Unit status** — via `systemctl --user show`. +4. If **`--watch`**, stream live logs via `journalctl --user -u -f`. Press `Ctrl+C` to exit. ## Exit codes @@ -58,4 +60,7 @@ deploy status odoo-myproject-production deploy@myserver.example.com # Custom SSH port deploy status odoo-myproject-production -p 2222 + +# Show status then tail the logs +deploy status odoo-myproject-production --watch ```