output

package
v0.7.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2023 License: Apache-2.0 Imports: 9 Imported by: 23

Documentation

Overview

Since the CLIs for Konvoy, Kommander, Diagnostics will be bundled together in a single dkp CLI starting with v2.2, they must output information to the user in a consistent way.

We achieve this by offering a common package with a simple interface for user output in this package. By using this interface for all output, the CLIs only communicate what they want to output, this package handles how the output looks like. This also makes eventual future changes to the output formatting easier, because it only needs to happen in one place.

All terminal output must use this interface, direct use of StdOut or StdErr is discouraged.

Usage examples

// output is pre-configured by the common root command (e.g. verbosity set from --verbose flag)
rootCmd, rootOptions := root.NewCommand(os.Stdout, os.Stderr)
output := rootOptions.Output

Displaying information and errors:

// optional output only displayed with higher verbosity
output.V(1).Info("kubeconfig read from file")
err := createNameSpace(namespaceName)
if err != nil {
    output.Errorf(err, "failed to create namespace %q", namespaceName)
    os.Exit(1)
}
output.Infof("namespace %q created" namespaceName)

Long-running operations:

output.StartOperation("installing packages")
for _, package := range packages {
    err := installPackage(package)
    if err != nil {
        output.EndOperation(false)
        output.Errorf(err, "failed to install package %q", package.Name)
        os.Exit(1)
    }
    output.V(1).Infof("package %q installed", package.Name)
}
output.EndOperation(true)
output.Info("All packages installed successfully")

Output results:

pods, err := getPods(namespaceName)
if err != nil {
    output.Error(err, "failed to get pods")
    os.Exit(1)
}
if outputJSON {
    output.Result(pods.ToJSON())
} else {
    output.Result(pods.String())
}

What's the difference between Info() and Result()?

Result() is meant to output the result of an operation. This might be clear text, but can also be e.g. JSON encoded depending on the use case. A command's result is the only thing that's sent to StdOut and will always be output "as is". All other output is sent to StdErr.

Info() is meant to communicate information (e.g. progress, successful execution) to a user. This output (together with error messages and animations) is sent to StdErr and might be formatted in different ways, e.g: For an interactive terminal the output can be colored, progress messages can be animated. If not running in a terminal, these messages might be prefixed with a timestamp or formatted in a more machine readable way.

Why use StdOut only for results?

This makes sure the result can be used directly, e.g. in scripts, piped to other tools, redirected to a file, etc. Informative output only meant for a human user is kept out of StdOut.

kubectl uses the same convention, see e.g. here (result): https://github.com/kubernetes/kubectl/blob/3f7abd9859b92958fcf8f6e5bb9d7b354aee4781/pkg/cmd/get/get.go#L824-L826) vs. here (informative): https://github.com/kubernetes/kubectl/blob/3f7abd9859b92958fcf8f6e5bb9d7b354aee4781/pkg/cmd/get/get.go#L596-L603

See also this StackExchange discussion: https://unix.stackexchange.com/questions/331611/do-progress-reports-logging-information-belong-on-stderr-or-stdout

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HumanReadableDuration added in v0.7.1

func HumanReadableDuration(duration time.Duration) string

HumanReadableDuration converts duration to a human-readable format like:

00s
30s
1m00s
1m30s
61m30s (we never use hours)

func NewOutputLogr

func NewOutputLogr(output Output) logr.Logger

Types

type EndOperationStatus added in v0.7.2

type EndOperationStatus interface {
	Fprintln(w io.Writer, format string, a ...any) (n int, err error)
}

func Failure added in v0.7.2

func Failure() EndOperationStatus

func NewStatus added in v0.7.2

func NewStatus(statusCharacter string, color *gchalk.Builder) EndOperationStatus

func Skipped added in v0.7.2

func Skipped() EndOperationStatus

func Success added in v0.7.2

func Success() EndOperationStatus

type Output

type Output interface {
	// Info displays informative output.
	//
	// Example:
	//  output.Info("namespace created")
	Info(msg string)

	// Infof displays informative output.
	//
	// Example:
	//  output.Infof("namespace %q created", namespace)
	Infof(format string, args ...interface{})

	// InfoWriter returns a writer for informative output.
	//
	// Example:
	//  io.WriteString(output.InfoWriter(), "namespace created")
	InfoWriter() io.Writer

	// Warn communicates a warning to the user.
	//
	// Example:
	//  output.Warn("could not connect, retrying...")
	Warn(msg string)

	// Warnf communicates a warning to the user.
	//
	// Example:
	//  output.Warnf("could not connect to %q, retrying...", service)
	Warnf(format string, args ...interface{})

	// WarnWriter returns a writer for warnings.
	//
	// Example:
	//  io.WriteString(output.WarnWriter(), "could not connect, retrying...")
	WarnWriter() io.Writer

	// Error communicates an error to the users.
	//
	// Example:
	//  output.Error(err, "namespace could not be created")
	Error(err error, msg string)

	// Errorf communicates an error to the users.
	//
	// Example:
	//  output.Errorf(err, "namespace %q could not be created", namespace)
	Errorf(err error, format string, args ...interface{})

	// ErrorWriter returns a writer for errors.
	//
	// Example:
	//  io.WriteString(output.ErrorWriter(), "namespace could not be created")
	ErrorWriter() io.Writer

	// StartOperation communicates the beginning of a long-running operation.
	// If running in a terminal, a progress animation will be shown. Starting a
	// new operation ends any previously running operation.
	//
	// Example:
	//  output.StartOperation("installing package")
	//  err := installPackage()
	//  if err != nil {
	//  	output.EndOperation(false)
	//  	output.Error(err, "")
	//  	return
	//  }
	//  output.EndOperation(true)
	StartOperation(status string)

	// StartOperationWithProgress communicates the beginning of a long-running operation.
	// If running in a terminal, a progress animation will be shown. Starting a
	// new operation ends any previously running operation.
	// This behaves identical to StartOperation above but with an extra progress bar with time elapsed.
	//
	// Example:
	//  gauge := &ProgressGauge{}
	//  gauge.SetStatus("descriptive status that need not be static")
	//  gauge.SetCapacity(10)
	//  output.StartOperationWithProgress(gauge)
	//  for (int i = 0; i < 10; i++) {
	//     err = longWaitFunction()
	//     if err != nil {
	//  	 output.EndOperation(false)
	//		 output.Error(err, "")
	//  	 return
	//     }
	//     gauge.Inc()
	//  }
	//
	//  if err != nil {
	//  	output.EndOperation(false)
	//  	output.Error(err, "")
	//  	return
	//  }
	//  output.EndOperation(true)
	StartOperationWithProgress(gauge *ProgressGauge)

	// Deprecated: Use EndOperationWithStatus instead. EndOperation exists for historical compatibility
	// and should not be used.
	//
	// EndOperation communicates the end of a long-running operation, either because
	// the operation completed successfully or failed (parameter success).
	//
	// Example:
	//  output.StartOperation("installing package")
	//  err := installPackage()
	//  if err != nil {
	//  	output.EndOperation(false)
	//  	output.Error(err, "")
	//  	return
	//  }
	//  output.EndOperation(true)
	EndOperation(success bool)

	// EndOperation communicates the end of a long-running operation, either because
	// the operation completed successfully or failed (parameter success).
	//
	// Example:
	//  output.StartOperation("installing package")
	//  err := installPackage()
	//  if err != nil {
	//  	output.EndOperationWithStatus(output.Failure())
	//  	output.Error(err, "")
	//  	return
	//  }
	//  output.EndOperationWithStatus(output.Success())
	EndOperationWithStatus(endStatus EndOperationStatus)

	// Result outputs the result of an operation, e.g. a "get <something>" command.
	//
	// Example:
	//  output.Result(pods.String())
	Result(result string)

	// ResultWriter returns a writer for command results.
	//
	// Example:
	//  encoder := json.NewEncoder(output.ResultWriter())
	//  encoder.Encode(object)
	ResultWriter() io.Writer

	// V returns an Output with a higher verbosity level (default: 0).
	// Info and Error output with a higher verbosity is only displayed if the
	// "--verbose" flag is set to an equal or higher value.
	//
	// Example:
	//  output.V(1).Info("verbose information")
	V(level int) Output

	// WithValues returns an Output with additional context in the form of
	// structured data (key-value pairs). Not displayed in interactive shells.
	//
	// Example:
	//  output.WithValues("cluster", clusterName).Info("namespace created")
	WithValues(keysAndValues ...interface{}) Output
}

func NewDiscardingOutput added in v0.5.2

func NewDiscardingOutput() Output

NewDiscardingOutput returns an Output that discards all output. Useful for testing, among other purposes.

func NewInteractiveShell

func NewInteractiveShell(out, errOut io.Writer, verbosity int) Output

func NewNonInteractiveShell

func NewNonInteractiveShell(out, errOut io.Writer, verbosity int) Output

type ProgressGauge added in v0.7.1

type ProgressGauge struct {
	// contains filtered or unexported fields
}

ProgressGauge is not really a Gauge in the truest sense and is used only to display a progress bar. It is a gauge in the sense that the value can be incremented or decremented. The correlation with word gauge ends here.

This is used to display a progress bar that looks like:

static-status [====>                                    1/10] (time elapsed 00s)
static-status [============>                            3/10] (time elapsed 00s)
static-status [=======================================>10/10] (time elapsed 00s)

func (*ProgressGauge) Dec added in v0.7.1

func (g *ProgressGauge) Dec()

func (*ProgressGauge) Inc added in v0.7.1

func (g *ProgressGauge) Inc()

func (*ProgressGauge) InitStartTime added in v0.7.1

func (g *ProgressGauge) InitStartTime()

func (*ProgressGauge) IsReady added in v0.7.1

func (g *ProgressGauge) IsReady() bool

func (*ProgressGauge) Set added in v0.7.1

func (g *ProgressGauge) Set(current int)

func (*ProgressGauge) SetCapacity added in v0.7.1

func (g *ProgressGauge) SetCapacity(capacity int)

func (*ProgressGauge) SetStatus added in v0.7.1

func (g *ProgressGauge) SetStatus(status string)

func (*ProgressGauge) String added in v0.7.1

func (g *ProgressGauge) String() string

String generates a string representation of the progress based on the current and capacity values of the gauge. It ensures that the progress bar generated is of fixed length format It also appends the elapsed time to string representation (if timer is not set, this will initialize it).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL