mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-09 22:51:12 +02:00
Track the progress of the long-running actions `reboot`, `reset`, `upgrade` and `shutdown` on the client side by default, unless `--no-wait=true` is specified. Use the events API to follow the events using the actor ID of the action and display it using an stderr reporter with a spinner. Closes siderolabs/talos#5499. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
125 lines
2.7 KiB
Go
125 lines
2.7 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package reporter
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/mattn/go-isatty"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// Status represents the status of an Update.
|
|
type Status int
|
|
|
|
const (
|
|
// StatusError represents an error status.
|
|
StatusError Status = iota
|
|
// StatusRunning represents a running status.
|
|
StatusRunning
|
|
// StatusSucceeded represents a success status.
|
|
StatusSucceeded
|
|
// StatusSkip represents a skipped status.
|
|
StatusSkip
|
|
)
|
|
|
|
var spinner = []string{"◰", "◳", "◲", "◱"}
|
|
|
|
// Update represents an update to be reported.
|
|
type Update struct {
|
|
Message string
|
|
Status Status
|
|
}
|
|
|
|
// Reporter is a console reporter with stderr output.
|
|
type Reporter struct {
|
|
w *os.File
|
|
lastLine string
|
|
lastLineTemporary bool
|
|
|
|
colorized bool
|
|
spinnerIdx int
|
|
}
|
|
|
|
// New returns a console reporter with stderr output.
|
|
func New() *Reporter {
|
|
return &Reporter{
|
|
w: os.Stderr,
|
|
colorized: isatty.IsTerminal(os.Stderr.Fd()),
|
|
}
|
|
}
|
|
|
|
// Report reports an update to the reporter.
|
|
//
|
|
//nolint:gocyclo
|
|
func (r *Reporter) Report(update Update) {
|
|
line := strings.TrimSpace(update.Message)
|
|
// replace tabs with spaces to get consistent output length
|
|
line = strings.ReplaceAll(line, "\t", " ")
|
|
|
|
if !r.colorized {
|
|
if line != r.lastLine {
|
|
fmt.Fprintln(r.w, line)
|
|
r.lastLine = line
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
w, _, _ := term.GetSize(int(r.w.Fd())) //nolint:errcheck
|
|
if w <= 0 {
|
|
w = 80
|
|
}
|
|
|
|
var coloredLine string
|
|
|
|
showSpinner := false
|
|
prevLineTemporary := r.lastLineTemporary
|
|
|
|
switch update.Status {
|
|
case StatusRunning:
|
|
line = fmt.Sprintf("%s %s", spinner[r.spinnerIdx], line)
|
|
coloredLine = color.YellowString("%s", line)
|
|
r.lastLineTemporary = true
|
|
showSpinner = true
|
|
case StatusSucceeded:
|
|
coloredLine = color.GreenString("%s", line)
|
|
r.lastLineTemporary = false
|
|
case StatusSkip:
|
|
coloredLine = color.BlueString("%s", line)
|
|
r.lastLineTemporary = false
|
|
case StatusError:
|
|
fallthrough
|
|
default:
|
|
line = fmt.Sprintf("%s %s", spinner[r.spinnerIdx], line)
|
|
coloredLine = color.RedString("%s", line)
|
|
r.lastLineTemporary = true
|
|
showSpinner = true
|
|
}
|
|
|
|
if line == r.lastLine {
|
|
return
|
|
}
|
|
|
|
if showSpinner {
|
|
r.spinnerIdx = (r.spinnerIdx + 1) % len(spinner)
|
|
}
|
|
|
|
if prevLineTemporary {
|
|
for _, outputLine := range strings.Split(r.lastLine, "\n") {
|
|
for i := 0; i < (utf8.RuneCountInString(outputLine)+w-1)/w; i++ {
|
|
fmt.Fprint(r.w, "\033[A\033[K") // cursor up, clear line
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Fprintln(r.w, coloredLine)
|
|
r.lastLine = line
|
|
}
|