update printer for events

This commit is contained in:
Jingfang Liu 2020-12-08 10:56:00 -08:00
parent b693b81b86
commit 06cf56ef9e
11 changed files with 133 additions and 116 deletions

View File

@ -32,8 +32,8 @@ type formatter struct {
func (ef *formatter) FormatApplyEvent(ae event.ApplyEvent, as *list.ApplyStats, c list.Collector) error {
switch ae.Type {
case event.ApplyEventCompleted:
output := fmt.Sprintf("%d resource(s) applied. %d created, %d unchanged, %d configured",
as.Sum(), as.Created, as.Unchanged, as.Configured)
output := fmt.Sprintf("%d resource(s) applied. %d created, %d unchanged, %d configured, %d failed",
as.Sum(), as.Created, as.Unchanged, as.Configured, as.Failed)
// Only print information about serverside apply if some of the
// resources actually were applied serverside.
if as.ServersideApplied > 0 {
@ -44,10 +44,9 @@ func (ef *formatter) FormatApplyEvent(ae event.ApplyEvent, as *list.ApplyStats,
ef.printResourceStatus(id, se)
}
case event.ApplyEventResourceUpdate:
obj := ae.Object
gvk := obj.GetObjectKind().GroupVersionKind()
name := getName(obj)
ef.print("%s %s", resourceIDToString(gvk.GroupKind(), name),
gk := ae.Identifier.GroupKind
name := ae.Identifier.Name
ef.print("%s %s", resourceIDToString(gk, name),
strings.ToLower(ae.Operation.String()))
}
return nil
@ -64,17 +63,17 @@ func (ef *formatter) FormatStatusEvent(se event.StatusEvent, _ list.Collector) e
func (ef *formatter) FormatPruneEvent(pe event.PruneEvent, ps *list.PruneStats) error {
switch pe.Type {
case event.PruneEventCompleted:
ef.print("%d resource(s) pruned, %d skipped", ps.Pruned, ps.Skipped)
ef.print("%d resource(s) pruned, %d skipped, %d failed", ps.Pruned, ps.Skipped, ps.Failed)
case event.PruneEventResourceUpdate:
obj := pe.Object
gvk := obj.GetObjectKind().GroupVersionKind()
name := getName(obj)
gk := pe.Identifier.GroupKind
switch pe.Operation {
case event.Pruned:
ef.print("%s %s", resourceIDToString(gvk.GroupKind(), name), "pruned")
ef.print("%s %s", resourceIDToString(gk, pe.Identifier.Name), "pruned")
case event.PruneSkipped:
ef.print("%s %s", resourceIDToString(gvk.GroupKind(), name), "prune skipped")
ef.print("%s %s", resourceIDToString(gk, pe.Identifier.Name), "prune skipped")
}
case event.PruneEventFailed:
ef.print("%s %s", resourceIDToString(pe.Identifier.GroupKind, pe.Identifier.Name), "prune failed")
}
return nil
}

View File

@ -31,27 +31,27 @@ func TestFormatter_FormatApplyEvent(t *testing.T) {
"resource created without no dryrun": {
previewStrategy: common.DryRunNone,
event: event.ApplyEvent{
Operation: event.Created,
Type: event.ApplyEventResourceUpdate,
Object: createObject("apps", "Deployment", "default", "my-dep"),
Operation: event.Created,
Type: event.ApplyEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
},
expected: "deployment.apps/my-dep created",
},
"resource updated with client dryrun": {
previewStrategy: common.DryRunClient,
event: event.ApplyEvent{
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Object: createObject("apps", "Deployment", "", "my-dep"),
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
},
expected: "deployment.apps/my-dep configured (preview)",
},
"resource updated with server dryrun": {
previewStrategy: common.DryRunServer,
event: event.ApplyEvent{
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Object: createObject("batch", "CronJob", "foo", "my-cron"),
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Identifier: createIdentifier("batch", "CronJob", "foo", "my-cron"),
},
expected: "cronjob.batch/my-cron configured (preview-server)",
},
@ -81,7 +81,7 @@ func TestFormatter_FormatApplyEvent(t *testing.T) {
},
},
expected: `
1 resource(s) applied. 0 created, 0 unchanged, 0 configured, 1 serverside applied
1 resource(s) applied. 0 created, 0 unchanged, 0 configured, 0 failed, 1 serverside applied
deployment.apps/my-dep is Current: Resource is Current
`,
},
@ -149,18 +149,18 @@ func TestFormatter_FormatPruneEvent(t *testing.T) {
"resource pruned without no dryrun": {
previewStrategy: common.DryRunNone,
event: event.PruneEvent{
Operation: event.Pruned,
Type: event.PruneEventResourceUpdate,
Object: createObject("apps", "Deployment", "default", "my-dep"),
Operation: event.Pruned,
Type: event.PruneEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
},
expected: "deployment.apps/my-dep pruned",
},
"resource skipped with client dryrun": {
previewStrategy: common.DryRunClient,
event: event.PruneEvent{
Operation: event.PruneSkipped,
Type: event.PruneEventResourceUpdate,
Object: createObject("apps", "Deployment", "", "my-dep"),
Operation: event.PruneSkipped,
Type: event.PruneEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
},
expected: "deployment.apps/my-dep prune skipped (preview)",
},
@ -173,7 +173,7 @@ func TestFormatter_FormatPruneEvent(t *testing.T) {
Pruned: 1,
Skipped: 2,
},
expected: "1 resource(s) pruned, 2 skipped",
expected: "1 resource(s) pruned, 2 skipped, 0 failed",
},
}
@ -253,6 +253,17 @@ func createObject(group, kind, namespace, name string) *unstructured.Unstructure
}
}
func createIdentifier(group, kind, namespace, name string) object.ObjMetadata {
return object.ObjMetadata{
Namespace: namespace,
Name: name,
GroupKind: schema.GroupKind{
Group: group,
Kind: kind,
},
}
}
type fakeCollector struct {
m map[object.ObjMetadata]event.StatusEvent
}

View File

@ -8,8 +8,6 @@ import (
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/common"
@ -39,6 +37,7 @@ func (jf *formatter) FormatApplyEvent(ae event.ApplyEvent, as *list.ApplyStats,
"unchangedCount": as.Unchanged,
"configuredCount": as.Configured,
"serverSideCount": as.ServersideApplied,
"failedCount": as.Failed,
}); err != nil {
return err
}
@ -49,13 +48,12 @@ func (jf *formatter) FormatApplyEvent(ae event.ApplyEvent, as *list.ApplyStats,
}
}
case event.ApplyEventResourceUpdate:
obj := ae.Object
gvk := obj.GetObjectKind().GroupVersionKind()
gk := ae.Identifier.GroupKind
return jf.printEvent("apply", "resourceApplied", map[string]interface{}{
"group": gvk.Group,
"kind": gvk.Kind,
"namespace": getNamespace(obj),
"name": getName(obj),
"group": gk.Group,
"kind": gk.Kind,
"namespace": ae.Identifier.Namespace,
"name": ae.Identifier.Name,
"operation": ae.Operation.String(),
})
}
@ -90,15 +88,23 @@ func (jf *formatter) FormatPruneEvent(pe event.PruneEvent, ps *list.PruneStats)
"skipped": ps.Skipped,
})
case event.PruneEventResourceUpdate:
obj := pe.Object
gvk := obj.GetObjectKind().GroupVersionKind()
gk := pe.Identifier.GroupKind
return jf.printEvent("prune", "resourcePruned", map[string]interface{}{
"group": gvk.Group,
"kind": gvk.Kind,
"namespace": getNamespace(obj),
"name": getName(obj),
"group": gk.Group,
"kind": gk.Kind,
"namespace": pe.Identifier.Namespace,
"name": pe.Identifier.Name,
"operation": pe.Operation.String(),
})
case event.PruneEventFailed:
gk := pe.Identifier.GroupKind
return jf.printEvent("prune", "resourceFailed", map[string]interface{}{
"group": gk.Group,
"kind": gk.Kind,
"namespace": pe.Identifier.Namespace,
"name": pe.Identifier.Name,
"error": pe.Error.Error(),
})
}
return nil
}
@ -111,15 +117,23 @@ func (jf *formatter) FormatDeleteEvent(de event.DeleteEvent, ds *list.DeleteStat
"skipped": ds.Skipped,
})
case event.DeleteEventResourceUpdate:
obj := de.Object
gvk := obj.GetObjectKind().GroupVersionKind()
gk := de.Identifier.GroupKind
return jf.printEvent("delete", "resourceDeleted", map[string]interface{}{
"group": gvk.Group,
"kind": gvk.Kind,
"namespace": getNamespace(obj),
"name": getName(obj),
"group": gk.Group,
"kind": gk.Kind,
"namespace": de.Identifier.Namespace,
"name": de.Identifier.Name,
"operation": de.Operation.String(),
})
case event.DeleteEventFailed:
gk := de.Identifier.GroupKind
return jf.printEvent("delete", "resourceFailed", map[string]interface{}{
"group": gk.Group,
"kind": gk.Kind,
"namespace": de.Identifier.Namespace,
"name": de.Identifier.Name,
"error": de.Error.Error(),
})
}
return nil
}
@ -145,13 +159,3 @@ func (jf *formatter) printEvent(t, eventType string, content map[string]interfac
_, err = fmt.Fprint(jf.ioStreams.Out, string(b)+"\n")
return err
}
func getName(obj runtime.Object) string {
acc, _ := meta.Accessor(obj)
return acc.GetName()
}
func getNamespace(obj runtime.Object) string {
acc, _ := meta.Accessor(obj)
return acc.GetNamespace()
}

View File

@ -5,12 +5,10 @@ package json
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"sigs.k8s.io/cli-utils/pkg/apply/event"
@ -32,9 +30,9 @@ func TestFormatter_FormatApplyEvent(t *testing.T) {
"resource created without dryrun": {
previewStrategy: common.DryRunNone,
event: event.ApplyEvent{
Operation: event.Created,
Type: event.ApplyEventResourceUpdate,
Object: createObject("apps", "Deployment", "default", "my-dep"),
Operation: event.Created,
Type: event.ApplyEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
},
expected: []map[string]interface{}{
{
@ -52,9 +50,9 @@ func TestFormatter_FormatApplyEvent(t *testing.T) {
"resource updated with client dryrun": {
previewStrategy: common.DryRunClient,
event: event.ApplyEvent{
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Object: createObject("apps", "Deployment", "", "my-dep"),
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
},
expected: []map[string]interface{}{
{
@ -72,9 +70,9 @@ func TestFormatter_FormatApplyEvent(t *testing.T) {
"resource updated with server dryrun": {
previewStrategy: common.DryRunServer,
event: event.ApplyEvent{
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Object: createObject("batch", "CronJob", "foo", "my-cron"),
Operation: event.Configured,
Type: event.ApplyEventResourceUpdate,
Identifier: createIdentifier("batch", "CronJob", "foo", "my-cron"),
},
expected: []map[string]interface{}{
{
@ -120,6 +118,7 @@ func TestFormatter_FormatApplyEvent(t *testing.T) {
"count": 1,
"createdCount": 0,
"eventType": "completed",
"failedCount": 0,
"serverSideCount": 1,
"type": "apply",
"unchangedCount": 0,
@ -219,9 +218,9 @@ func TestFormatter_FormatPruneEvent(t *testing.T) {
"resource pruned without dryrun": {
previewStrategy: common.DryRunNone,
event: event.PruneEvent{
Operation: event.Pruned,
Type: event.PruneEventResourceUpdate,
Object: createObject("apps", "Deployment", "default", "my-dep"),
Operation: event.Pruned,
Type: event.PruneEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
},
expected: map[string]interface{}{
"eventType": "resourcePruned",
@ -237,9 +236,9 @@ func TestFormatter_FormatPruneEvent(t *testing.T) {
"resource skipped with client dryrun": {
previewStrategy: common.DryRunClient,
event: event.PruneEvent{
Operation: event.PruneSkipped,
Type: event.PruneEventResourceUpdate,
Object: createObject("apps", "Deployment", "", "my-dep"),
Operation: event.PruneSkipped,
Type: event.PruneEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
},
expected: map[string]interface{}{
"eventType": "resourcePruned",
@ -294,9 +293,9 @@ func TestFormatter_FormatDeleteEvent(t *testing.T) {
"resource deleted without no dryrun": {
previewStrategy: common.DryRunNone,
event: event.DeleteEvent{
Operation: event.Deleted,
Type: event.DeleteEventResourceUpdate,
Object: createObject("apps", "Deployment", "default", "my-dep"),
Operation: event.Deleted,
Type: event.DeleteEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"),
},
expected: map[string]interface{}{
"eventType": "resourceDeleted",
@ -312,9 +311,9 @@ func TestFormatter_FormatDeleteEvent(t *testing.T) {
"resource skipped with client dryrun": {
previewStrategy: common.DryRunClient,
event: event.DeleteEvent{
Operation: event.DeleteSkipped,
Type: event.DeleteEventResourceUpdate,
Object: createObject("apps", "Deployment", "", "my-dep"),
Operation: event.DeleteSkipped,
Type: event.DeleteEventResourceUpdate,
Identifier: createIdentifier("apps", "Deployment", "", "my-dep"),
},
expected: map[string]interface{}{
"eventType": "resourceDeleted",
@ -385,15 +384,13 @@ func assertOutput(t *testing.T, expectedMap map[string]interface{}, actual strin
return assert.Equal(t, expectedMap, m)
}
func createObject(group, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": fmt.Sprintf("%s/v1", group),
"kind": kind,
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
},
func createIdentifier(group, kind, namespace, name string) object.ObjMetadata {
return object.ObjMetadata{
Namespace: namespace,
Name: name,
GroupKind: schema.GroupKind{
Group: group,
Kind: kind,
},
}
}

View File

@ -7,8 +7,6 @@ import (
"sort"
"sync"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/cli-utils/pkg/apply/event"
pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
@ -193,7 +191,7 @@ func (r *ResourceStateCollector) processStatusEvent(e event.StatusEvent) {
// processApplyEvent handles events relating to apply operations
func (r *ResourceStateCollector) processApplyEvent(e event.ApplyEvent) {
if e.Type == event.ApplyEventResourceUpdate {
identifier := toIdentifier(e.Object)
identifier := e.Identifier
previous, found := r.resourceInfos[identifier]
if !found {
return
@ -205,7 +203,7 @@ func (r *ResourceStateCollector) processApplyEvent(e event.ApplyEvent) {
// processPruneEvent handles event related to prune operations.
func (r *ResourceStateCollector) processPruneEvent(e event.PruneEvent) {
if e.Type == event.PruneEventResourceUpdate {
identifier := toIdentifier(e.Object)
identifier := e.Identifier
previous, found := r.resourceInfos[identifier]
if !found {
return
@ -214,17 +212,6 @@ func (r *ResourceStateCollector) processPruneEvent(e event.PruneEvent) {
}
}
// toIdentifier extracts the identifying information from an
// object.
func toIdentifier(o runtime.Object) object.ObjMetadata {
accessor, _ := meta.Accessor(o)
return object.ObjMetadata{
GroupKind: o.GetObjectKind().GroupVersionKind().GroupKind(),
Namespace: accessor.GetNamespace(),
Name: accessor.GetName(),
}
}
// ResourceState contains the latest state for all the resources.
type ResourceState struct {
resourceInfos ResourceInfos

View File

@ -163,11 +163,11 @@ Run preview to check which commands will be executed
```
kapply preview $BASE > $OUTPUT/status
expectedOutputLine "3 resource(s) applied. 3 created, 0 unchanged, 0 configured (preview)"
expectedOutputLine "3 resource(s) applied. 3 created, 0 unchanged, 0 configured, 0 failed (preview)"
kapply preview $BASE --server-side > $OUTPUT/status
expectedOutputLine "3 resource(s) applied. 0 created, 0 unchanged, 0 configured, 3 serverside applied (preview-server)"
expectedOutputLine "3 resource(s) applied. 0 created, 0 unchanged, 0 configured, 0 failed, 3 serverside applied (preview-server)"
# Verify that preview didn't create any resources.
kubectl get all -n hellospace > $OUTPUT/status 2>&1

2
go.sum
View File

@ -706,8 +706,6 @@ sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvO
sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/kustomize/kyaml v0.9.4 h1:DDuzZtjIzFqp2IPy4DTyCI69Cl3bDgcJODjI6sjF9NY=
sigs.k8s.io/kustomize/kyaml v0.9.4/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw=
sigs.k8s.io/kustomize/kyaml v0.10.3 h1:ARSJUMN/c3k31DYxRfZ+vp/UepUQjg9zCwny7Oj908I=
sigs.k8s.io/kustomize/kyaml v0.10.3/go.mod h1:RA+iCHA2wPCOfv6uG6TfXXWhYsHpgErq/AljxWKuxtg=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=

View File

@ -15,11 +15,12 @@ func _() {
_ = x[Created-1]
_ = x[Unchanged-2]
_ = x[Configured-3]
_ = x[Failed-4]
}
const _ApplyEventOperation_name = "ServersideAppliedCreatedUnchangedConfigured"
const _ApplyEventOperation_name = "ServersideAppliedCreatedUnchangedConfiguredFailed"
var _ApplyEventOperation_index = [...]uint8{0, 17, 24, 33, 43}
var _ApplyEventOperation_index = [...]uint8{0, 17, 24, 33, 43, 49}
func (i ApplyEventOperation) String() string {
if i < 0 || i >= ApplyEventOperation(len(_ApplyEventOperation_index)-1) {

View File

@ -91,6 +91,7 @@ const (
Created
Unchanged
Configured
Failed
)
type ApplyEvent struct {

View File

@ -113,7 +113,7 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) {
info, err := a.InfoHelper.BuildInfo(obj)
if err != nil {
taskContext.EventChannel() <- createApplyEvent(
object.UnstructuredToObjMeta(obj), event.Unchanged, applyerror.NewUnknownTypeError(err))
object.UnstructuredToObjMeta(obj), event.Failed, applyerror.NewUnknownTypeError(err))
continue
}
infos = append(infos, info)
@ -121,7 +121,7 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) {
err = ao.Run()
if err != nil {
taskContext.EventChannel() <- createApplyEvent(
object.UnstructuredToObjMeta(obj), event.Unchanged, applyerror.NewApplyRunError(err))
object.UnstructuredToObjMeta(obj), event.Failed, applyerror.NewApplyRunError(err))
}
}
@ -296,6 +296,6 @@ func createApplyEvent(id object.ObjMetadata, operation event.ApplyEventOperation
func sendBatchApplyEvents(taskContext *taskrunner.TaskContext, objects []*unstructured.Unstructured, err error) {
for _, obj := range objects {
taskContext.EventChannel() <- createApplyEvent(
object.UnstructuredToObjMeta(obj), event.Unchanged, applyerror.NewInitializeApplyOptionError(err))
object.UnstructuredToObjMeta(obj), event.Failed, applyerror.NewInitializeApplyOptionError(err))
}
}

View File

@ -33,6 +33,7 @@ type ApplyStats struct {
Created int
Unchanged int
Configured int
Failed int
}
func (a *ApplyStats) inc(op event.ApplyEventOperation) {
@ -45,18 +46,21 @@ func (a *ApplyStats) inc(op event.ApplyEventOperation) {
a.Unchanged++
case event.Configured:
a.Configured++
case event.Failed:
a.Failed++
default:
panic(fmt.Errorf("unknown apply operation %s", op.String()))
}
}
func (a *ApplyStats) Sum() int {
return a.ServersideApplied + a.Configured + a.Unchanged + a.Created
return a.ServersideApplied + a.Configured + a.Unchanged + a.Created + a.Failed
}
type PruneStats struct {
Pruned int
Skipped int
Failed int
}
func (p *PruneStats) incPruned() {
@ -67,9 +71,14 @@ func (p *PruneStats) incSkipped() {
p.Skipped++
}
func (p *PruneStats) incFailed() {
p.Failed++
}
type DeleteStats struct {
Deleted int
Skipped int
Failed int
}
func (d *DeleteStats) incDeleted() {
@ -80,6 +89,10 @@ func (d *DeleteStats) incSkipped() {
d.Skipped++
}
func (d *DeleteStats) incFailed() {
d.Failed++
}
type Collector interface {
LatestStatus() map[object.ObjMetadata]event.StatusEvent
}
@ -142,6 +155,9 @@ func (b *BaseListPrinter) Print(ch <-chan event.Event, previewStrategy common.Dr
pruneStats.incSkipped()
}
}
if e.PruneEvent.Type == event.PruneEventFailed {
pruneStats.incFailed()
}
if err := formatter.FormatPruneEvent(e.PruneEvent, pruneStats); err != nil {
return err
}
@ -154,6 +170,9 @@ func (b *BaseListPrinter) Print(ch <-chan event.Event, previewStrategy common.Dr
deleteStats.incSkipped()
}
}
if e.DeleteEvent.Type == event.DeleteEventFailed {
deleteStats.incFailed()
}
if err := formatter.FormatDeleteEvent(e.DeleteEvent, deleteStats); err != nil {
return err
}