-
Notifications
You must be signed in to change notification settings - Fork 748
Add a console output backend for condensed deploy output #1918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
enichols
wants to merge
4
commits into
main
Choose a base branch
from
pretty-console-output
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+696
−5
Draft
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
b45dd9b
Add a console output backend for condensed deploy output
enichols 16cdebe
Strip phase-marker punctuation without a backtracking regex
enichols 6cb4f4b
Address review: -v firehose, host attribution, dead severity path
enichols fc03431
Address review: distinguish phase markers from notices; close null FD…
enichols File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Line-based renderer for non-TTY output (CI, piped, redirected). No cursor | ||
| # movement or spinners: phases and per-host results are printed in order as | ||
| # they resolve, so the log stays clean and deterministic. | ||
| class Kamal::Output::Console::PlainRenderer < Kamal::Output::Console::Renderer | ||
| def header(command:, service:, version:, destination:, hosts:, roles:) | ||
| target = [ service, version ].compact.join("@") | ||
| scope = "#{hosts} #{"host".pluralize(hosts)}, #{roles} #{"role".pluralize(roles)}" | ||
| scope += " · #{destination}" if destination | ||
| panel(command, [ "#{target} → #{scope}" ]) | ||
| end | ||
|
|
||
| def phase(name) | ||
| puts | ||
| puts pastel.decorate("#{ARROW} #{name}", :bright_magenta, :bold) | ||
| end | ||
|
|
||
| def end_phase(statuses) | ||
| statuses.each do |host, result| | ||
| if result[:status] == :failed | ||
| puts " #{pastel.red(FAIL)} #{host} #{pastel.red("failed")}" | ||
| else | ||
| puts " #{pastel.green(OK)} #{host} #{pastel.dim(format_duration(result[:duration]))}" | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def summary(ok:, failed:, needs_attention:, runtime:, exception:) | ||
| counts = [ pastel.green("#{OK} #{ok} ok") ] | ||
| counts << pastel.red("#{FAIL} #{failed} failed") if failed > 0 | ||
| lines = [ "#{counts.join(" ")} #{pastel.dim(format_duration(runtime))}" ] | ||
| lines << pastel.red("needs attention: #{needs_attention.join(", ")}") if needs_attention.any? | ||
| panel("Summary", lines, color: failed > 0 ? :red : :green) | ||
| end | ||
|
|
||
| def replay(host, lines) | ||
| puts | ||
| puts pastel.dim("── retained output · #{host} ─────") | ||
| lines.each { |line| puts pastel.dim("#{BAR} ") + line } | ||
| end | ||
|
|
||
| private | ||
| def format_duration(seconds) | ||
| return "" unless seconds | ||
| "#{sprintf("%.1f", seconds)}s" | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| require "pastel" | ||
|
|
||
| # Shared formatting for the console renderers: colors, icons, and the rounded | ||
| # panels used for the header and summary. Subclasses implement the event | ||
| # methods (+header+, +phase+, +host_active+, +end_phase+, +summary+, +replay+) | ||
| # that the ConsoleLogger drives. | ||
| class Kamal::Output::Console::Renderer | ||
| OK = "✔" | ||
| FAIL = "✖" | ||
| ARROW = "❯" | ||
| BAR = "┃" | ||
|
|
||
| def initialize(output:, settings: {}) | ||
| @output = output | ||
| @settings = settings | ||
| @pastel = Pastel.new(enabled: color_enabled?) | ||
| end | ||
|
|
||
| def header(command:, service:, version:, destination:, hosts:, roles:); end | ||
| def phase(name); end | ||
| def host_active(host); end | ||
| def end_phase(statuses); end | ||
| def summary(ok:, failed:, needs_attention:, runtime:, exception:); end | ||
| def replay(host, lines); end | ||
| def host_error(host); end | ||
|
|
||
| private | ||
| attr_reader :output, :settings, :pastel | ||
|
|
||
| def color_enabled? | ||
| return settings["color"] if settings.key?("color") | ||
| output.respond_to?(:tty?) && output.tty? | ||
| end | ||
|
|
||
| def puts(line = "") | ||
| output.puts(line) | ||
| end | ||
|
|
||
| # A rounded panel with a highlighted title, sized to its widest line. | ||
| def panel(title, lines, color: :magenta) | ||
| width = ([ visible_width(title) + 4 ] + lines.map { |line| visible_width(line) }).max | ||
| puts | ||
| puts pastel.decorate("╭─ ", color) + pastel.decorate(title, color, :bold) + pastel.decorate(" #{"─" * (width - visible_width(title) - 1)}╮", color) | ||
| lines.each do |line| | ||
| puts pastel.decorate("│ ", color) + line + " " * (width - visible_width(line)) + pastel.decorate(" │", color) | ||
| end | ||
| puts pastel.decorate("╰#{"─" * (width + 2)}╯", color) | ||
| end | ||
|
|
||
| def visible_width(string) | ||
| pastel.strip(string.to_s).length | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| require "tty-spinner" | ||
|
|
||
| # Interactive renderer: each phase is a TTY::Spinner::Multi whose children are | ||
| # the participating hosts, so their spinners animate concurrently and then | ||
| # settle into ✔/✖ status lines when the phase resolves. The header, summary, | ||
| # and replay panels are inherited from the plain renderer unchanged. | ||
| class Kamal::Output::Console::TtyRenderer < Kamal::Output::Console::PlainRenderer | ||
| SPINNER = :dots | ||
|
|
||
| def phase(name) | ||
| finish_multi | ||
| puts | ||
| @multi = TTY::Spinner::Multi.new( | ||
| pastel.decorate("#{ARROW} #{name}", :bright_magenta, :bold), | ||
| output: output, hide_cursor: true, format: SPINNER | ||
| ) | ||
| @spinners = {} | ||
| end | ||
|
|
||
| def host_active(host) | ||
| return unless @multi | ||
| spinner = @multi.register( | ||
| "[:spinner] #{host}", | ||
| format: SPINNER, | ||
| success_mark: pastel.green(OK), | ||
| error_mark: pastel.red(FAIL) | ||
| ) | ||
| @spinners[host] = spinner | ||
| spinner.auto_spin | ||
| end | ||
|
|
||
| def end_phase(statuses) | ||
| return super unless @multi | ||
|
|
||
| statuses.each do |host, result| | ||
| spinner = @spinners[host] | ||
| next unless spinner | ||
|
|
||
| if result[:status] == :failed | ||
| spinner.error(pastel.red("failed")) | ||
| else | ||
| spinner.success(pastel.dim(format_duration(result[:duration]))) | ||
| end | ||
| end | ||
|
|
||
| finish_multi | ||
| end | ||
|
|
||
| private | ||
| def finish_multi | ||
| return unless @multi | ||
| @spinners.each_value { |spinner| spinner.stop if spinner.spinning? } | ||
| @multi = nil | ||
| @spinners = {} | ||
| end | ||
| end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.