Skip to content

Add a console output backend for condensed deploy output - #1918

Draft
enichols wants to merge 4 commits into
mainfrom
pretty-console-output
Draft

Add a console output backend for condensed deploy output#1918
enichols wants to merge 4 commits into
mainfrom
pretty-console-output

Conversation

@enichols

Copy link
Copy Markdown
Member

What

  • Add a console output backend that condenses a deploy into a header panel, per-phase sections, live per-host status lines, and an end-of-run summary — opt in with output: { console: {} }.
  • Suppress the raw SSHKit firehose on screen while it's active (routed to a null sink, still teed to other backends like file); -v/--verbose restores it.
  • Render concurrent per-host spinners on a TTY (tty-spinner/pastel), with a deterministic line-based fallback for non-TTY/CI.

Why

A full deploy buries prompts and errors under a wall of interleaved SSHKit output; this gives an at-a-glance view of progress while keeping any failure — and the failing host's retained output — prominent. It's built as an opt-in backend on the existing output framework and reconstructed entirely from the line stream every backend already receives (phase markers, host-tagged lines, the modify.kamal exception payload), so it adds no deploy-path coupling and leaves current behavior unchanged when off.

Testing

  • bin/test test/output/console_logger_test.rb test/configuration/output_test.rb → 20 runs, 0 failures.
  • Full unit suite → 850 runs, 3 failures — all three reproduce on clean main (git-email and Docker/arch env issues), none from this change.
  • rubocop on all changed files → clean.
  • Previewed success and failure runs in both plain and TTY (PTY) modes.
  • Docker integration tests not run (require a running daemon).

Composed with Claude Code (Opus 4.8)

Register a `console` logger in the output framework that replaces the raw
SSHKit command firehose on the terminal with a condensed view: a header
panel, one section per deploy phase, a live per-host status line, and a
summary panel. Raw output is suppressed on success and replayed for any
host that fails; -v/--verbose restores the full firehose.

It reconstructs the view from the line stream every backend already
receives — CLI phase markers (say), host-tagged SSHKit lines, and the
modify.kamal exception payload — so no deploy code needs to know it
exists. When active it owns the screen, so the raw stream is routed to a
null sink (still teed to other backends, e.g. file). On a TTY it renders
concurrent per-host spinners via tty-spinner/pastel; non-TTY output uses
a deterministic line-based renderer.

Opt in with `output: { console: {} }`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread lib/kamal/output/console_logger.rb Fixed
CodeQL flagged the trailing-strip regex as polynomial ReDoS: end-anchored
with + but not start-anchored, so it rescans across start positions. The
input is Kamal's own say() markers, not untrusted data, but the linear
non-regex strip is clearer and clears the alert. strip already removes
whitespace, so only trailing dots/colons remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in console output backend that renders a condensed deploy view (header + per-phase progress + per-host results + summary), including a TTY spinner renderer and a deterministic non-TTY renderer. This plugs into the existing output framework and wires CLI markers / SSHKit stream metadata into the console backend.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Changes:

  • Introduces Kamal::Output::ConsoleLogger plus plain/TTY renderers for condensed deploy output.
  • Adds console to output configuration + documentation and extends CLI/SSHKit wiring to support marker/host/severity context.
  • Adds pastel and tty-spinner dependencies and tests for the new backend.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/output/console_logger_test.rb Adds unit coverage for console backend rendering and failure attribution.
test/configuration/output_test.rb Verifies console backend configuration, settings validation, and enablement.
lib/kamal/output/console/tty_renderer.rb Implements interactive TTY spinner rendering per phase/host.
lib/kamal/output/console/renderer.rb Adds shared formatting/panel helpers and color handling for console renderers.
lib/kamal/output/console/plain_renderer.rb Implements deterministic, line-based rendering for non-TTY/CI output.
lib/kamal/output/console_logger.rb Core console backend: reconstructs phases/host statuses and emits summary + retained output.
lib/kamal/configuration/output.rb Registers console as a valid output logger type.
lib/kamal/configuration/docs/output.yml Documents the new output.console settings and behavior.
lib/kamal/commander.rb Routes SSHKit formatter output to a null sink when console backend is enabled.
lib/kamal/cli/base.rb Tags say markers with thread-local color metadata for the console backend.
kamal.gemspec Adds runtime deps for TTY rendering (pastel, tty-spinner).
Gemfile.lock Locks the new dependencies and their transitive requirements.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/kamal/commander.rb Outdated
Comment thread lib/kamal/output/console_logger.rb Outdated
Comment thread lib/kamal/output/console_logger.rb
Comment thread lib/kamal/output/console_logger.rb
- Honor -v/--verbose: skip the null sink and disable the console renderer
  so the raw SSHKit firehose shows; close the null-sink FD at exit.
- Attribute exception failures by whole-host token (10.0.0.1 no longer
  flagged by a failure on 10.0.0.10) and consider all configured hosts, so
  a host that failed before emitting output is still flagged.
- Drop the severity-based failure path: SSHKit logs command output at DEBUG
  (never ERROR) within host context, so it was dead code; failures are
  attributed from the modify.kamal exception payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

lib/kamal/commander.rb:228

  • When @console_output is enabled, @null_output = File.open(File::NULL, "w") will overwrite any previously-open null sink without closing it first (and the else branch never closes it when switching back). If output is configured more than once in a process, this will leak FDs; it also keeps the sink open even after the command finishes. Consider closing any existing sink before replacing it, and closing+nil’ing it when console output is not active.
      if @console_output
        @null_output = File.open(File::NULL, "w")
        SSHKit.config.output = Kamal::Output::Formatter.new(@null_output, output_logger)
      else
        SSHKit.config.output = Kamal::Output::Formatter.new($stdout, output_logger)
      end

Comment thread lib/kamal/cli/base.rb
Comment thread lib/kamal/commander.rb
… on reset

- Only magenta say markers open a phase; red/yellow (errors/warnings) and
  any colorless say now render as notices instead of spurious phases, and a
  say is flagged by origin so non-magenta messages no longer disappear from
  the terminal. TTY notices buffer during a phase and flush once its
  spinners resolve, so the live region isn't corrupted.
- Close @null_output before niling it in reset so repeated commands in a
  long-lived process (aliases) don't leak a /dev/null FD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@enichols

Copy link
Copy Markdown
Member Author

Example output via a script that simulates a deploy, minus the colors. Successful deploy :

bundle exec ruby script/console_output_demo.rb 2>/dev/null

╭─ deploy ─────────────────────────────────────────╮
│ showtime@9f2c1ab → 3 hosts, 2 roles · production │
╰──────────────────────────────────────────────────╯

┌ ❯ Build and push app image
└── [✔] 172.16.0.1 0.8s

┌ ❯ Ensure kamal-proxy is running
├── [✔] 172.16.0.1 0.4s
├── [✔] 172.16.0.2 0.4s
└── [✔] 172.16.0.3 0.4s

┌ ❯ Detect stale containers
├── [✔] 172.16.0.1 0.4s
├── [✔] 172.16.0.2 0.4s
└── [✔] 172.16.0.3 0.4s

┌ ❯ Start container
├── [✔] 172.16.0.1 0.7s
├── [✔] 172.16.0.2 0.7s
└── [✔] 172.16.0.3 0.7s

┌ ❯ Prune old containers and images
├── [✔] 172.16.0.1 0.4s
├── [✔] 172.16.0.2 0.4s
└── [✔] 172.16.0.3 0.4s

╭─ Summary ─────╮
│ ✔ 3 ok   3.4s │
╰───────────────╯

..and one with a failure :

bundle exec ruby script/console_output_demo.rb --fail 2>/dev/null

╭─ deploy ─────────────────────────────────────────╮
│ showtime@9f2c1ab → 3 hosts, 2 roles · production │
╰──────────────────────────────────────────────────╯

┌ ❯ Build and push app image
└── [✔] 172.16.0.1 0.8s

┌ ❯ Ensure kamal-proxy is running
├── [✔] 172.16.0.1 0.4s
├── [✔] 172.16.0.2 0.4s
└── [✔] 172.16.0.3 0.4s

┌ ❯ Detect stale containers
├── [✔] 172.16.0.1 0.4s
├── [✔] 172.16.0.2 0.4s
└── [✔] 172.16.0.3 0.4s

┌ ❯ Start container
├── [✔] 172.16.0.1 1.0s
├── [✔] 172.16.0.2 1.0s
└── [✖] 172.16.0.3 failed

╭─ Summary ───────────────────╮
│ ✔ 2 ok   ✖ 1 failed   3.1s  │
│ needs attention: 172.16.0.3 │
╰─────────────────────────────╯

── retained output · 172.16.0.3 ─────
┃ kamal-proxy is already running
┃ no stale containers
┃ docker run --detach ... showtime:9f2c1ab
┃ Container health check failed after 30s
┃ docker logs: FATAL: could not connect to database

@enichols
enichols requested review from djmb and lewispb July 24, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants