mirror of https://github.com/fluxcd/cli-utils.git
190 lines
4.9 KiB
Go
190 lines
4.9 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package table
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
|
"sigs.k8s.io/cli-utils/pkg/common"
|
|
printcommon "sigs.k8s.io/cli-utils/pkg/print/common"
|
|
"sigs.k8s.io/cli-utils/pkg/print/table"
|
|
)
|
|
|
|
type Printer struct {
|
|
IOStreams genericclioptions.IOStreams
|
|
}
|
|
|
|
func (t *Printer) Print(ch <-chan event.Event, _ common.DryRunStrategy, _ bool) error {
|
|
// Wait for the init event that will give us the set of
|
|
// resources.
|
|
var initEvent event.InitEvent
|
|
for e := range ch {
|
|
if e.Type == event.InitType {
|
|
initEvent = e.InitEvent
|
|
break
|
|
}
|
|
// If we get an error event, we just print it and
|
|
// exit. The error event signals a fatal error.
|
|
if e.Type == event.ErrorType {
|
|
return e.ErrorEvent.Err
|
|
}
|
|
}
|
|
// Create a new collector and initialize it with the resources
|
|
// we are interested in.
|
|
coll := newResourceStateCollector(initEvent.ActionGroups)
|
|
|
|
stop := make(chan struct{})
|
|
|
|
// Start the goroutine that is responsible for
|
|
// printing the latest state on a regular cadence.
|
|
printCompleted := t.runPrintLoop(coll, stop)
|
|
|
|
// Make the collector start listening on the eventChannel.
|
|
done := coll.Listen(ch)
|
|
|
|
// Block until all the collector has shut down. This means the
|
|
// eventChannel has been closed and all events have been processed.
|
|
var err error
|
|
for msg := range done {
|
|
err = msg.err
|
|
}
|
|
|
|
// Close the stop channel to notify the print goroutine that it should
|
|
// shut down.
|
|
close(stop)
|
|
|
|
// Wait until the printCompleted channel is closed. This means
|
|
// the printer has updated the UI with the latest state and
|
|
// exited from the goroutine.
|
|
<-printCompleted
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// If no fatal errors happened, we will return a ResultError if
|
|
// one or more resources failed to apply/prune or reconcile.
|
|
return printcommon.ResultErrorFromStats(coll.Stats())
|
|
}
|
|
|
|
// columns defines the columns we want to print
|
|
//TODO: We should have the number of columns and their widths be
|
|
// dependent on the space available.
|
|
var (
|
|
actionColumnDef = table.ColumnDef{
|
|
// Column containing the resource type and name. Currently it does not
|
|
// print group or version since those are rarely needed to uniquely
|
|
// distinguish two resources from each other. Just name and kind should
|
|
// be enough in almost all cases and saves space in the output.
|
|
ColumnName: "action",
|
|
ColumnHeader: "ACTION",
|
|
ColumnWidth: 12,
|
|
PrintResourceFunc: func(w io.Writer, width int, r table.Resource) (int,
|
|
error) {
|
|
var resInfo *resourceInfo
|
|
switch res := r.(type) {
|
|
case *resourceInfo:
|
|
resInfo = res
|
|
default:
|
|
return 0, nil
|
|
}
|
|
|
|
var text string
|
|
switch resInfo.ResourceAction {
|
|
case event.ApplyAction:
|
|
if resInfo.ApplyOpResult != event.ApplyUnspecified {
|
|
text = resInfo.ApplyOpResult.String()
|
|
}
|
|
case event.PruneAction:
|
|
if resInfo.PruneOpResult != event.PruneUnspecified {
|
|
text = resInfo.PruneOpResult.String()
|
|
}
|
|
}
|
|
|
|
if len(text) > width {
|
|
text = text[:width]
|
|
}
|
|
_, err := fmt.Fprint(w, text)
|
|
return len(text), err
|
|
},
|
|
}
|
|
|
|
reconciledColumnDef = table.ColumnDef{
|
|
// Column containing the reconciliation status.
|
|
ColumnName: "reconciled",
|
|
ColumnHeader: "RECONCILED",
|
|
ColumnWidth: 10,
|
|
PrintResourceFunc: func(w io.Writer, width int, r table.Resource) (
|
|
int,
|
|
error,
|
|
) {
|
|
var resInfo *resourceInfo
|
|
switch res := r.(type) {
|
|
case *resourceInfo:
|
|
resInfo = res
|
|
default:
|
|
return 0, nil
|
|
}
|
|
|
|
var text string
|
|
switch resInfo.ResourceAction {
|
|
case event.WaitAction:
|
|
text = resInfo.WaitOpResult.String()
|
|
}
|
|
|
|
if len(text) > width {
|
|
text = text[:width]
|
|
}
|
|
_, err := fmt.Fprint(w, text)
|
|
return len(text), err
|
|
},
|
|
}
|
|
|
|
columns = []table.ColumnDefinition{
|
|
table.MustColumn("namespace"),
|
|
table.MustColumn("resource"),
|
|
actionColumnDef,
|
|
table.MustColumn("status"),
|
|
reconciledColumnDef,
|
|
table.MustColumn("conditions"),
|
|
table.MustColumn("age"),
|
|
table.MustColumn("message"),
|
|
}
|
|
)
|
|
|
|
// runPrintLoop starts a new goroutine that will regularly fetch the
|
|
// latest state from the collector and update the table.
|
|
func (t *Printer) runPrintLoop(coll *resourceStateCollector, stop chan struct{}) chan struct{} {
|
|
finished := make(chan struct{})
|
|
|
|
baseTablePrinter := table.BaseTablePrinter{
|
|
IOStreams: t.IOStreams,
|
|
Columns: columns,
|
|
}
|
|
|
|
linesPrinted := baseTablePrinter.PrintTable(coll.LatestState(), 0)
|
|
|
|
go func() {
|
|
defer close(finished)
|
|
ticker := time.NewTicker(500 * time.Millisecond)
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
ticker.Stop()
|
|
latestState := coll.LatestState()
|
|
linesPrinted = baseTablePrinter.PrintTable(latestState, linesPrinted)
|
|
_, _ = fmt.Fprint(t.IOStreams.Out, "\n")
|
|
return
|
|
case <-ticker.C:
|
|
latestState := coll.LatestState()
|
|
linesPrinted = baseTablePrinter.PrintTable(latestState, linesPrinted)
|
|
}
|
|
}
|
|
}()
|
|
return finished
|
|
}
|