Support for prune in the table view

This commit is contained in:
Morten Torkildsen 2020-04-25 17:23:10 -07:00
parent bf15fe7252
commit f6716aedc1
4 changed files with 228 additions and 23 deletions

View File

@ -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)

View File

@ -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)
}
})
}
}

View File

@ -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.

View File

@ -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)
}
})
}
}