mirror of https://github.com/fluxcd/cli-utils.git
Support for prune in the table view
This commit is contained in:
parent
bf15fe7252
commit
f6716aedc1
|
|
@ -72,6 +72,10 @@ type ResourceInfo struct {
|
|||
// ApplyOpResult contains the result after
|
||||
// a resource has been applied to the cluster.
|
||||
ApplyOpResult *event.ApplyEventOperation
|
||||
|
||||
// Pruned contains information about whether
|
||||
// the resources has been pruned.
|
||||
Pruned bool
|
||||
}
|
||||
|
||||
// Identifier returns the identifier for the given resource.
|
||||
|
|
@ -157,6 +161,8 @@ func (r *ResourceStateCollector) processEvent(e event.Event) {
|
|||
r.processStatusEvent(e.StatusEvent)
|
||||
case event.ApplyType:
|
||||
r.processApplyEvent(e.ApplyEvent)
|
||||
case event.PruneType:
|
||||
r.processPruneEvent(e.PruneEvent)
|
||||
case event.ErrorType:
|
||||
r.processErrorEvent(e.ErrorEvent.Err)
|
||||
}
|
||||
|
|
@ -180,11 +186,26 @@ func (r *ResourceStateCollector) processStatusEvent(e pe.Event) {
|
|||
func (r *ResourceStateCollector) processApplyEvent(e event.ApplyEvent) {
|
||||
if e.Type == event.ApplyEventResourceUpdate {
|
||||
identifier := toIdentifier(e.Object)
|
||||
previous := r.resourceInfos[identifier]
|
||||
previous, found := r.resourceInfos[identifier]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
previous.ApplyOpResult = &e.Operation
|
||||
}
|
||||
}
|
||||
|
||||
// processPruneEvent handles event related to prune operations.
|
||||
func (r *ResourceStateCollector) processPruneEvent(e event.PruneEvent) {
|
||||
if e.Type == event.PruneEventResourceUpdate {
|
||||
identifier := toIdentifier(e.Object)
|
||||
previous, found := r.resourceInfos[identifier]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
previous.Pruned = true
|
||||
}
|
||||
}
|
||||
|
||||
// processErrorEvent handles events for errors.
|
||||
func (r *ResourceStateCollector) processErrorEvent(err error) {
|
||||
r.err = err
|
||||
|
|
@ -235,6 +256,7 @@ func (r *ResourceStateCollector) LatestState() *ResourceState {
|
|||
resourceStatus: ri.resourceStatus,
|
||||
ResourceAction: ri.ResourceAction,
|
||||
ApplyOpResult: ri.ApplyOpResult,
|
||||
Pruned: ri.Pruned,
|
||||
})
|
||||
}
|
||||
sort.Sort(resourceInfos)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package table
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
var (
|
||||
depID = object.ObjMetadata{
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Name: "Foo",
|
||||
Namespace: "Bar",
|
||||
}
|
||||
customID = object.ObjMetadata{
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "custom.io",
|
||||
Kind: "Custom",
|
||||
},
|
||||
Name: "Custom",
|
||||
}
|
||||
)
|
||||
|
||||
func TestResourceStateCollector_New(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
resourceGroups []event.ResourceGroup
|
||||
resourceInfos map[object.ObjMetadata]*ResourceInfo
|
||||
}{
|
||||
"no resources": {
|
||||
resourceGroups: []event.ResourceGroup{},
|
||||
resourceInfos: map[object.ObjMetadata]*ResourceInfo{},
|
||||
},
|
||||
"several resources for apply": {
|
||||
resourceGroups: []event.ResourceGroup{
|
||||
{
|
||||
Action: event.ApplyAction,
|
||||
Identifiers: []object.ObjMetadata{
|
||||
depID, customID,
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceInfos: map[object.ObjMetadata]*ResourceInfo{
|
||||
depID: {
|
||||
ResourceAction: event.ApplyAction,
|
||||
},
|
||||
customID: {
|
||||
ResourceAction: event.ApplyAction,
|
||||
},
|
||||
},
|
||||
},
|
||||
"several resources for prune": {
|
||||
resourceGroups: []event.ResourceGroup{
|
||||
{
|
||||
Action: event.ApplyAction,
|
||||
Identifiers: []object.ObjMetadata{
|
||||
customID,
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: event.PruneAction,
|
||||
Identifiers: []object.ObjMetadata{
|
||||
depID,
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceInfos: map[object.ObjMetadata]*ResourceInfo{
|
||||
depID: {
|
||||
ResourceAction: event.PruneAction,
|
||||
},
|
||||
customID: {
|
||||
ResourceAction: event.ApplyAction,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
rsc := newResourceStateCollector(tc.resourceGroups)
|
||||
|
||||
assert.Equal(t, len(tc.resourceInfos), len(rsc.resourceInfos))
|
||||
for expID, expRi := range tc.resourceInfos {
|
||||
actRi, found := rsc.resourceInfos[expID]
|
||||
if !found {
|
||||
t.Errorf("expected to find id %v, but didn't", expID)
|
||||
}
|
||||
assert.Equal(t, expRi.ResourceAction, actRi.ResourceAction)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -26,9 +26,8 @@ func (t *Printer) Print(ch <-chan event.Event, _ bool) {
|
|||
initEvent = e.InitEvent
|
||||
break
|
||||
}
|
||||
// If we get an error event, we just panic for now.
|
||||
// Eventually we need a more graceful shutdown if
|
||||
// this happens.
|
||||
// If we get an error event, we just print it and
|
||||
// exit. The error event signals a fatal error.
|
||||
if e.Type == event.ErrorType {
|
||||
_, _ = fmt.Fprintf(t.IOStreams.Out, "Fatal error: %v\n", e.ErrorEvent.Err)
|
||||
return
|
||||
|
|
@ -64,10 +63,8 @@ func (t *Printer) Print(ch <-chan event.Event, _ bool) {
|
|||
// 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 columns = []table.ColumnDefinition{
|
||||
table.MustColumn("namespace"),
|
||||
table.MustColumn("resource"),
|
||||
table.ColumnDef{
|
||||
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
|
||||
|
|
@ -81,27 +78,40 @@ var columns = []table.ColumnDefinition{
|
|||
switch res := r.(type) {
|
||||
case *ResourceInfo:
|
||||
resInfo = res
|
||||
case *SubResourceInfo:
|
||||
default:
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if resInfo.ResourceAction == event.ApplyAction &&
|
||||
resInfo.ApplyOpResult != nil {
|
||||
text := resInfo.ApplyOpResult.String()
|
||||
if len(text) > width {
|
||||
text = text[:width]
|
||||
var text string
|
||||
switch resInfo.ResourceAction {
|
||||
case event.ApplyAction:
|
||||
if resInfo.ApplyOpResult != nil {
|
||||
text = resInfo.ApplyOpResult.String()
|
||||
}
|
||||
case event.PruneAction:
|
||||
if resInfo.Pruned {
|
||||
text = "Pruned"
|
||||
}
|
||||
_, err := fmt.Fprint(w, text)
|
||||
return len(text), err
|
||||
}
|
||||
return 0, nil
|
||||
|
||||
if len(text) > width {
|
||||
text = text[:width]
|
||||
}
|
||||
_, err := fmt.Fprint(w, text)
|
||||
return len(text), err
|
||||
},
|
||||
},
|
||||
table.MustColumn("status"),
|
||||
table.MustColumn("conditions"),
|
||||
table.MustColumn("age"),
|
||||
table.MustColumn("message"),
|
||||
}
|
||||
}
|
||||
|
||||
columns = []table.ColumnDefinition{
|
||||
table.MustColumn("namespace"),
|
||||
table.MustColumn("resource"),
|
||||
actionColumnDef,
|
||||
table.MustColumn("status"),
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package table
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/print/table"
|
||||
)
|
||||
|
||||
var (
|
||||
createdOpResult = event.Created
|
||||
)
|
||||
|
||||
func TestActionColumnDef(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
resource table.Resource
|
||||
columnWidth int
|
||||
expectedOutput string
|
||||
}{
|
||||
"unexpected implementation of Resource interface": {
|
||||
resource: &SubResourceInfo{},
|
||||
columnWidth: 15,
|
||||
expectedOutput: "",
|
||||
},
|
||||
"neither applied nor pruned": {
|
||||
resource: &ResourceInfo{},
|
||||
columnWidth: 15,
|
||||
expectedOutput: "",
|
||||
},
|
||||
"applied": {
|
||||
resource: &ResourceInfo{
|
||||
ResourceAction: event.ApplyAction,
|
||||
ApplyOpResult: &createdOpResult,
|
||||
},
|
||||
columnWidth: 15,
|
||||
expectedOutput: "Created",
|
||||
},
|
||||
"pruned": {
|
||||
resource: &ResourceInfo{
|
||||
ResourceAction: event.PruneAction,
|
||||
Pruned: true,
|
||||
},
|
||||
columnWidth: 15,
|
||||
expectedOutput: "Pruned",
|
||||
},
|
||||
"trimmed output": {
|
||||
resource: &ResourceInfo{
|
||||
ResourceAction: event.ApplyAction,
|
||||
ApplyOpResult: &createdOpResult,
|
||||
},
|
||||
columnWidth: 5,
|
||||
expectedOutput: "Creat",
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
_, err := actionColumnDef.PrintResource(&buf, tc.columnWidth, tc.resource)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if want, got := tc.expectedOutput, buf.String(); want != got {
|
||||
t.Errorf("expected %q, but got %q", want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue