From ae80e561e212983f82784da945e7652f561030a5 Mon Sep 17 00:00:00 2001 From: Morten Torkildsen Date: Fri, 26 Mar 2021 18:27:46 -0700 Subject: [PATCH] Improve the event hierarchy --- cmd/printers/events/formatter.go | 145 ++++----- cmd/printers/events/formatter_test.go | 102 ++---- cmd/printers/json/formatter.go | 168 +++++----- cmd/printers/json/formatter_test.go | 121 +------ cmd/printers/table/collector.go | 59 ++-- cmd/printers/table/collector_test.go | 39 +-- cmd/printers/table/printer.go | 6 +- cmd/printers/table/printer_test.go | 6 +- examples/alphaTestExamples/crds.md | 2 - pkg/apply/applier.go | 13 +- pkg/apply/applier_test.go | 296 ++++++++++-------- pkg/apply/destroyer.go | 26 +- .../event/actiongroupeventtype_string.go | 24 ++ pkg/apply/event/applyeventoperation_string.go | 17 +- pkg/apply/event/applyeventtype_string.go | 27 -- .../event/deleteeventoperation_string.go | 12 +- pkg/apply/event/deleteeventtype_string.go | 28 -- pkg/apply/event/event.go | 80 +++-- pkg/apply/event/pruneeventoperation_string.go | 12 +- pkg/apply/event/pruneeventtype_string.go | 28 -- pkg/apply/event/resourceaction_string.go | 9 +- pkg/apply/event/statuseventtype_string.go | 27 -- pkg/apply/event/type_string.go | 16 +- pkg/apply/prune/prune.go | 2 - pkg/apply/solver/solver.go | 98 +++--- pkg/apply/solver/solver_test.go | 83 +++-- pkg/apply/task/apply_task.go | 53 +++- pkg/apply/task/printer_adapter.go | 5 +- pkg/apply/task/printer_adapter_test.go | 2 +- pkg/apply/task/prune_task.go | 16 + pkg/apply/taskrunner/collector.go | 6 +- pkg/apply/taskrunner/runner.go | 25 +- pkg/apply/taskrunner/runner_test.go | 98 ++++-- pkg/apply/taskrunner/task.go | 81 ++++- pkg/apply/taskrunner/task_test.go | 10 +- pkg/kstatus/polling/event/eventtype_string.go | 3 - pkg/object/objmetadata.go | 13 + pkg/print/list/base.go | 109 ++++--- pkg/testutil/events.go | 185 +++++++++++ test/e2e/apply_and_destroy_test.go | 20 +- test/e2e/common_test.go | 183 ----------- test/e2e/continue_on_error_test.go | 41 +-- test/e2e/crd_test.go | 70 ++--- test/e2e/inventory_policy_test.go | 57 ++-- 44 files changed, 1145 insertions(+), 1278 deletions(-) create mode 100644 pkg/apply/event/actiongroupeventtype_string.go delete mode 100644 pkg/apply/event/applyeventtype_string.go delete mode 100644 pkg/apply/event/deleteeventtype_string.go delete mode 100644 pkg/apply/event/pruneeventtype_string.go delete mode 100644 pkg/apply/event/statuseventtype_string.go create mode 100644 pkg/testutil/events.go diff --git a/cmd/printers/events/formatter.go b/cmd/printers/events/formatter.go index f24fc60..90b6050 100644 --- a/cmd/printers/events/formatter.go +++ b/cmd/printers/events/formatter.go @@ -8,8 +8,6 @@ import ( "io" "strings" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/genericclioptions" "sigs.k8s.io/cli-utils/pkg/apply/event" @@ -29,78 +27,57 @@ type formatter struct { print printFunc } -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, %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 { - output += fmt.Sprintf(", %d serverside applied", as.ServersideApplied) - } - ef.print(output) - for id, se := range c.LatestStatus() { - ef.printResourceStatus(id, se) - } - case event.ApplyEventResourceUpdate: - gk := ae.Identifier.GroupKind - name := ae.Identifier.Name - if ae.Error != nil { - ef.print("%s failed: %s", resourceIDToString(gk, name), - ae.Error.Error()) - } else { - ef.print("%s %s", resourceIDToString(gk, name), - strings.ToLower(ae.Operation.String())) - } +func (ef *formatter) FormatApplyEvent(ae event.ApplyEvent) error { + gk := ae.Identifier.GroupKind + name := ae.Identifier.Name + if ae.Error != nil { + ef.print("%s apply failed: %s", resourceIDToString(gk, name), + ae.Error.Error()) + } else { + ef.print("%s %s", resourceIDToString(gk, name), + strings.ToLower(ae.Operation.String())) } return nil } -func (ef *formatter) FormatStatusEvent(se event.StatusEvent, _ list.Collector) error { - if se.Type == event.StatusEventResourceUpdate { - id := se.Resource.Identifier - ef.printResourceStatus(id, se) - } +func (ef *formatter) FormatStatusEvent(se event.StatusEvent) error { + id := se.Identifier + ef.printResourceStatus(id, se) return nil } -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, %d failed", ps.Pruned, ps.Skipped, ps.Failed) - case event.PruneEventResourceUpdate: - gk := pe.Identifier.GroupKind - switch pe.Operation { - case event.Pruned: - ef.print("%s pruned", resourceIDToString(gk, pe.Identifier.Name)) - case event.PruneSkipped: - ef.print("%s prune skipped", resourceIDToString(gk, pe.Identifier.Name)) - } - case event.PruneEventFailed: - ef.print("%s prune failed: %s", resourceIDToString(pe.Identifier.GroupKind, pe.Identifier.Name), +func (ef *formatter) FormatPruneEvent(pe event.PruneEvent) error { + gk := pe.Identifier.GroupKind + if pe.Error != nil { + ef.print("%s prune failed: %s", resourceIDToString(gk, pe.Identifier.Name), pe.Error.Error()) + return nil + } + + switch pe.Operation { + case event.Pruned: + ef.print("%s pruned", resourceIDToString(gk, pe.Identifier.Name)) + case event.PruneSkipped: + ef.print("%s prune skipped", resourceIDToString(gk, pe.Identifier.Name)) } return nil } -func (ef *formatter) FormatDeleteEvent(de event.DeleteEvent, ds *list.DeleteStats) error { - switch de.Type { - case event.DeleteEventCompleted: - ef.print("%d resource(s) deleted, %d skipped", ds.Deleted, ds.Skipped) - case event.DeleteEventResourceUpdate: - obj := de.Object - gvk := obj.GetObjectKind().GroupVersionKind() - name := getName(obj) - switch de.Operation { - case event.Deleted: - ef.print("%s deleted", resourceIDToString(gvk.GroupKind(), name)) - case event.DeleteSkipped: - ef.print("%s delete skipped", resourceIDToString(gvk.GroupKind(), name)) - } - case event.DeleteEventFailed: - ef.print("%s deletion failed: %s", resourceIDToString(de.Identifier.GroupKind, de.Identifier.Name), +func (ef *formatter) FormatDeleteEvent(de event.DeleteEvent) error { + gk := de.Identifier.GroupKind + name := de.Identifier.Name + + if de.Error != nil { + ef.print("%s deletion failed: %s", resourceIDToString(gk, name), de.Error.Error()) + return nil + } + + switch de.Operation { + case event.Deleted: + ef.print("%s deleted", resourceIDToString(gk, name)) + case event.DeleteSkipped: + ef.print("%s delete skipped", resourceIDToString(gk, name)) } return nil } @@ -109,18 +86,46 @@ func (ef *formatter) FormatErrorEvent(_ event.ErrorEvent) error { return nil } -func (ef *formatter) printResourceStatus(id object.ObjMetadata, se event.StatusEvent) { - ef.print("%s is %s: %s", resourceIDToString(id.GroupKind, id.Name), - se.Resource.Status.String(), se.Resource.Message) -} +func (ef *formatter) FormatActionGroupEvent(age event.ActionGroupEvent, ags []event.ActionGroup, + as *list.ApplyStats, ps *list.PruneStats, ds *list.DeleteStats, c list.Collector) error { + if age.Action == event.ApplyAction && age.Type == event.Finished { + 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 { + output += fmt.Sprintf(", %d serverside applied", as.ServersideApplied) + } + ef.print(output) + } -func getName(obj runtime.Object) string { - if acc, err := meta.Accessor(obj); err == nil { - if n := acc.GetName(); len(n) > 0 { - return n + if age.Action == event.PruneAction && age.Type == event.Finished { + ef.print("%d resource(s) pruned, %d skipped, %d failed", ps.Pruned, ps.Skipped, ps.Failed) + } + + if age.Action == event.DeleteAction && age.Type == event.Finished { + ef.print("%d resource(s) deleted, %d skipped", ds.Deleted, ds.Skipped) + } + + if age.Action == event.WaitAction && age.Type == event.Started { + ag, found := list.ActionGroupByName(age.GroupName, ags) + if !found { + panic(fmt.Errorf("unknown action group name %q", age.GroupName)) + } + for id, se := range c.LatestStatus() { + // Only print information about objects that we actually care about + // for this wait task. + if found := object.ObjMetas(ag.Identifiers).Contains(id); found { + ef.printResourceStatus(id, se) + } } } - return "" + return nil +} + +func (ef *formatter) printResourceStatus(id object.ObjMetadata, se event.StatusEvent) { + ef.print("%s is %s: %s", resourceIDToString(id.GroupKind, id.Name), + se.PollResourceInfo.Status.String(), se.PollResourceInfo.Message) } // resourceIDToString returns the string representation of a GroupKind and a resource name. diff --git a/cmd/printers/events/formatter_test.go b/cmd/printers/events/formatter_test.go index f3b8be9..8f31f78 100644 --- a/cmd/printers/events/formatter_test.go +++ b/cmd/printers/events/formatter_test.go @@ -32,7 +32,6 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { previewStrategy: common.DryRunNone, event: event.ApplyEvent{ Operation: event.Created, - Type: event.ApplyEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"), }, expected: "deployment.apps/my-dep created", @@ -41,7 +40,6 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { previewStrategy: common.DryRunClient, event: event.ApplyEvent{ Operation: event.Configured, - Type: event.ApplyEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), }, expected: "deployment.apps/my-dep configured (preview)", @@ -50,7 +48,6 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { previewStrategy: common.DryRunServer, event: event.ApplyEvent{ Operation: event.Configured, - Type: event.ApplyEventResourceUpdate, Identifier: createIdentifier("batch", "CronJob", "foo", "my-cron"), }, expected: "cronjob.batch/my-cron configured (preview-server)", @@ -58,42 +55,10 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { "apply event with error should display the error": { previewStrategy: common.DryRunServer, event: event.ApplyEvent{ - Operation: event.Failed, - Type: event.ApplyEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), Error: fmt.Errorf("this is a test error"), }, - expected: "deployment.apps/my-dep failed: this is a test error (preview-server)", - }, - "completed event": { - previewStrategy: common.DryRunNone, - event: event.ApplyEvent{ - Type: event.ApplyEventCompleted, - }, - applyStats: &list.ApplyStats{ - ServersideApplied: 1, - }, - statusCollector: &fakeCollector{ - m: map[object.ObjMetadata]event.StatusEvent{ - object.ObjMetadata{ //nolint:gofmt - GroupKind: schema.GroupKind{ - Group: "apps", - Kind: "Deployment", - }, - Namespace: "foo", - Name: "my-dep", - }: { - Resource: &pollevent.ResourceStatus{ - Status: status.CurrentStatus, - Message: "Resource is Current", - }, - }, - }, - }, - expected: ` -1 resource(s) applied. 0 created, 0 unchanged, 0 configured, 0 failed, 1 serverside applied -deployment.apps/my-dep is Current: Resource is Current -`, + expected: "deployment.apps/my-dep apply failed: this is a test error (preview-server)", }, } @@ -101,7 +66,7 @@ deployment.apps/my-dep is Current: Resource is Current t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatApplyEvent(tc.event, tc.applyStats, tc.statusCollector) + err := formatter.FormatApplyEvent(tc.event) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String())) @@ -119,8 +84,15 @@ func TestFormatter_FormatStatusEvent(t *testing.T) { "resource update with Current status": { previewStrategy: common.DryRunNone, event: event.StatusEvent{ - Type: event.StatusEventResourceUpdate, - Resource: &pollevent.ResourceStatus{ + Identifier: object.ObjMetadata{ + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: "foo", + Name: "bar", + }, + PollResourceInfo: &pollevent.ResourceStatus{ Identifier: object.ObjMetadata{ GroupKind: schema.GroupKind{ Group: "apps", @@ -141,7 +113,7 @@ func TestFormatter_FormatStatusEvent(t *testing.T) { t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatStatusEvent(tc.event, tc.statusCollector) + err := formatter.FormatStatusEvent(tc.event) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String())) @@ -160,7 +132,6 @@ func TestFormatter_FormatPruneEvent(t *testing.T) { previewStrategy: common.DryRunNone, event: event.PruneEvent{ Operation: event.Pruned, - Type: event.PruneEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"), }, expected: "deployment.apps/my-dep pruned", @@ -169,7 +140,6 @@ func TestFormatter_FormatPruneEvent(t *testing.T) { previewStrategy: common.DryRunClient, event: event.PruneEvent{ Operation: event.PruneSkipped, - Type: event.PruneEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), }, expected: "deployment.apps/my-dep prune skipped (preview)", @@ -177,30 +147,18 @@ func TestFormatter_FormatPruneEvent(t *testing.T) { "resource with prune error": { previewStrategy: common.DryRunNone, event: event.PruneEvent{ - Type: event.PruneEventFailed, Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), Error: fmt.Errorf("this is a test"), }, expected: "deployment.apps/my-dep prune failed: this is a test", }, - "prune event with completed status": { - previewStrategy: common.DryRunNone, - event: event.PruneEvent{ - Type: event.PruneEventCompleted, - }, - pruneStats: &list.PruneStats{ - Pruned: 1, - Skipped: 2, - }, - expected: "1 resource(s) pruned, 2 skipped, 0 failed", - }, } for tn, tc := range testCases { t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatPruneEvent(tc.event, tc.pruneStats) + err := formatter.FormatPruneEvent(tc.event) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String())) @@ -219,49 +177,37 @@ 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, + Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"), + Object: createObject("apps", "Deployment", "default", "my-dep"), }, expected: "deployment.apps/my-dep deleted", }, "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, + Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), + Object: createObject("apps", "Deployment", "", "my-dep"), }, expected: "deployment.apps/my-dep delete skipped (preview)", }, "resource with delete error": { previewStrategy: common.DryRunServer, event: event.DeleteEvent{ - Type: event.DeleteEventFailed, Object: createObject("apps", "Deployment", "", "my-dep"), Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), Error: fmt.Errorf("this is a test"), }, expected: "deployment.apps/my-dep deletion failed: this is a test (preview-server)", }, - "delete event with completed status": { - previewStrategy: common.DryRunNone, - event: event.DeleteEvent{ - Type: event.DeleteEventCompleted, - }, - deleteStats: &list.DeleteStats{ - Deleted: 1, - Skipped: 2, - }, - expected: "1 resource(s) deleted, 2 skipped", - }, } for tn, tc := range testCases { t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatDeleteEvent(tc.event, tc.deleteStats) + err := formatter.FormatDeleteEvent(tc.event) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(out.String())) @@ -292,11 +238,3 @@ func createIdentifier(group, kind, namespace, name string) object.ObjMetadata { }, } } - -type fakeCollector struct { - m map[object.ObjMetadata]event.StatusEvent -} - -func (f *fakeCollector) LatestStatus() map[object.ObjMetadata]event.StatusEvent { - return f.m -} diff --git a/cmd/printers/json/formatter.go b/cmd/printers/json/formatter.go index d527af8..064d2ff 100644 --- a/cmd/printers/json/formatter.go +++ b/cmd/printers/json/formatter.go @@ -28,9 +28,56 @@ type formatter struct { ioStreams genericclioptions.IOStreams } -func (jf *formatter) FormatApplyEvent(ae event.ApplyEvent, as *list.ApplyStats, c list.Collector) error { - switch ae.Type { - case event.ApplyEventCompleted: +func (jf *formatter) FormatApplyEvent(ae event.ApplyEvent) error { + eventInfo := jf.baseResourceEvent(ae.Identifier) + if ae.Error != nil { + eventInfo["error"] = ae.Error.Error() + return jf.printEvent("apply", "resourceFailed", eventInfo) + } + eventInfo["operation"] = ae.Operation.String() + return jf.printEvent("apply", "resourceApplied", eventInfo) +} + +func (jf *formatter) FormatStatusEvent(se event.StatusEvent) error { + return jf.printResourceStatus(se) +} + +func (jf *formatter) printResourceStatus(se event.StatusEvent) error { + eventInfo := jf.baseResourceEvent(se.Identifier) + eventInfo["status"] = se.PollResourceInfo.Status.String() + eventInfo["message"] = se.PollResourceInfo.Message + return jf.printEvent("status", "resourceStatus", eventInfo) +} + +func (jf *formatter) FormatPruneEvent(pe event.PruneEvent) error { + eventInfo := jf.baseResourceEvent(pe.Identifier) + if pe.Error != nil { + eventInfo["error"] = pe.Error.Error() + return jf.printEvent("prune", "resourceFailed", eventInfo) + } + eventInfo["operation"] = pe.Operation.String() + return jf.printEvent("prune", "resourcePruned", eventInfo) +} + +func (jf *formatter) FormatDeleteEvent(de event.DeleteEvent) error { + eventInfo := jf.baseResourceEvent(de.Identifier) + if de.Error != nil { + eventInfo["error"] = de.Error.Error() + return jf.printEvent("delete", "resourceFailed", eventInfo) + } + eventInfo["operation"] = de.Operation.String() + return jf.printEvent("delete", "resourceDeleted", eventInfo) +} + +func (jf *formatter) FormatErrorEvent(ee event.ErrorEvent) error { + return jf.printEvent("error", "error", map[string]interface{}{ + "error": ee.Err.Error(), + }) +} + +func (jf *formatter) FormatActionGroupEvent(age event.ActionGroupEvent, ags []event.ActionGroup, + as *list.ApplyStats, ps *list.PruneStats, ds *list.DeleteStats, c list.Collector) error { + if age.Action == event.ApplyAction && age.Type == event.Finished { if err := jf.printEvent("apply", "completed", map[string]interface{}{ "count": as.Sum(), "createdCount": as.Created, @@ -41,112 +88,47 @@ func (jf *formatter) FormatApplyEvent(ae event.ApplyEvent, as *list.ApplyStats, }); err != nil { return err } - - for id, se := range c.LatestStatus() { - if err := jf.printResourceStatus(id, se); err != nil { - return err - } - } - case event.ApplyEventResourceUpdate: - gk := ae.Identifier.GroupKind - eventInfo := map[string]interface{}{ - "group": gk.Group, - "kind": gk.Kind, - "namespace": ae.Identifier.Namespace, - "name": ae.Identifier.Name, - "operation": ae.Operation.String(), - } - if ae.Error != nil { - eventInfo["error"] = ae.Error.Error() - } - - return jf.printEvent("apply", "resourceApplied", eventInfo) } - return nil -} -func (jf *formatter) FormatStatusEvent(se event.StatusEvent, _ list.Collector) error { - if se.Type == event.StatusEventResourceUpdate { - id := se.Resource.Identifier - return jf.printResourceStatus(id, se) - } - return nil -} - -func (jf *formatter) printResourceStatus(id object.ObjMetadata, se event.StatusEvent) error { - return jf.printEvent("status", "resourceStatus", - map[string]interface{}{ - "group": id.GroupKind.Group, - "kind": id.GroupKind.Kind, - "namespace": id.Namespace, - "name": id.Name, - "status": se.Resource.Status.String(), - "message": se.Resource.Message, - }) -} - -func (jf *formatter) FormatPruneEvent(pe event.PruneEvent, ps *list.PruneStats) error { - switch pe.Type { - case event.PruneEventCompleted: + if age.Action == event.PruneAction && age.Type == event.Finished { return jf.printEvent("prune", "completed", map[string]interface{}{ "pruned": ps.Pruned, "skipped": ps.Skipped, }) - case event.PruneEventResourceUpdate: - gk := pe.Identifier.GroupKind - return jf.printEvent("prune", "resourcePruned", map[string]interface{}{ - "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 -} -func (jf *formatter) FormatDeleteEvent(de event.DeleteEvent, ds *list.DeleteStats) error { - switch de.Type { - case event.DeleteEventCompleted: + if age.Action == event.DeleteAction && age.Type == event.Finished { return jf.printEvent("delete", "completed", map[string]interface{}{ "deleted": ds.Deleted, "skipped": ds.Skipped, }) - case event.DeleteEventResourceUpdate: - gk := de.Identifier.GroupKind - return jf.printEvent("delete", "resourceDeleted", map[string]interface{}{ - "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(), - }) + } + + if age.Action == event.WaitAction && age.Type == event.Started { + ag, found := list.ActionGroupByName(age.GroupName, ags) + if !found { + panic(fmt.Errorf("unknown action group name %q", age.GroupName)) + } + for id, se := range c.LatestStatus() { + // Only print information about objects that we actually care about + // for this wait task. + if found := object.ObjMetas(ag.Identifiers).Contains(id); found { + if err := jf.printResourceStatus(se); err != nil { + return err + } + } + } } return nil } -func (jf *formatter) FormatErrorEvent(ee event.ErrorEvent) error { - return jf.printEvent("error", "error", map[string]interface{}{ - "error": ee.Err.Error(), - }) +func (jf *formatter) baseResourceEvent(identifier object.ObjMetadata) map[string]interface{} { + return map[string]interface{}{ + "group": identifier.GroupKind.Group, + "kind": identifier.GroupKind.Kind, + "namespace": identifier.Namespace, + "name": identifier.Name, + } } func (jf *formatter) printEvent(t, eventType string, content map[string]interface{}) error { diff --git a/cmd/printers/json/formatter_test.go b/cmd/printers/json/formatter_test.go index 80110e9..7da7a94 100644 --- a/cmd/printers/json/formatter_test.go +++ b/cmd/printers/json/formatter_test.go @@ -23,15 +23,12 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { testCases := map[string]struct { previewStrategy common.DryRunStrategy event event.ApplyEvent - applyStats *list.ApplyStats - statusCollector list.Collector expected []map[string]interface{} }{ "resource created without dryrun": { previewStrategy: common.DryRunNone, event: event.ApplyEvent{ Operation: event.Created, - Type: event.ApplyEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"), }, expected: []map[string]interface{}{ @@ -51,7 +48,6 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { previewStrategy: common.DryRunClient, event: event.ApplyEvent{ Operation: event.Configured, - Type: event.ApplyEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), }, expected: []map[string]interface{}{ @@ -71,7 +67,6 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { previewStrategy: common.DryRunServer, event: event.ApplyEvent{ Operation: event.Configured, - Type: event.ApplyEventResourceUpdate, Identifier: createIdentifier("batch", "CronJob", "foo", "my-cron"), }, expected: []map[string]interface{}{ @@ -87,63 +82,13 @@ func TestFormatter_FormatApplyEvent(t *testing.T) { }, }, }, - "completed event": { - previewStrategy: common.DryRunNone, - event: event.ApplyEvent{ - Type: event.ApplyEventCompleted, - }, - applyStats: &list.ApplyStats{ - ServersideApplied: 1, - }, - statusCollector: &fakeCollector{ - m: map[object.ObjMetadata]event.StatusEvent{ - object.ObjMetadata{ //nolint:gofmt - GroupKind: schema.GroupKind{ - Group: "apps", - Kind: "Deployment", - }, - Namespace: "foo", - Name: "my-dep", - }: { - Resource: &pollevent.ResourceStatus{ - Status: status.CurrentStatus, - Message: "Resource is Current", - }, - }, - }, - }, - expected: []map[string]interface{}{ - { - "configuredCount": 0, - "count": 1, - "createdCount": 0, - "eventType": "completed", - "failedCount": 0, - "serverSideCount": 1, - "type": "apply", - "unchangedCount": 0, - "timestamp": "", - }, - { - "eventType": "resourceStatus", - "group": "apps", - "kind": "Deployment", - "message": "Resource is Current", - "name": "my-dep", - "namespace": "foo", - "status": "Current", - "timestamp": "", - "type": "status", - }, - }, - }, } for tn, tc := range testCases { t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatApplyEvent(tc.event, tc.applyStats, tc.statusCollector) + err := formatter.FormatApplyEvent(tc.event) assert.NoError(t, err) objects := strings.Split(strings.TrimSpace(out.String()), "\n") @@ -162,14 +107,20 @@ func TestFormatter_FormatStatusEvent(t *testing.T) { testCases := map[string]struct { previewStrategy common.DryRunStrategy event event.StatusEvent - statusCollector list.Collector expected map[string]interface{} }{ "resource update with Current status": { previewStrategy: common.DryRunNone, event: event.StatusEvent{ - Type: event.StatusEventResourceUpdate, - Resource: &pollevent.ResourceStatus{ + Identifier: object.ObjMetadata{ + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: "foo", + Name: "bar", + }, + PollResourceInfo: &pollevent.ResourceStatus{ Identifier: object.ObjMetadata{ GroupKind: schema.GroupKind{ Group: "apps", @@ -200,7 +151,7 @@ func TestFormatter_FormatStatusEvent(t *testing.T) { t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatStatusEvent(tc.event, tc.statusCollector) + err := formatter.FormatStatusEvent(tc.event) assert.NoError(t, err) assertOutput(t, tc.expected, out.String()) @@ -219,7 +170,6 @@ func TestFormatter_FormatPruneEvent(t *testing.T) { previewStrategy: common.DryRunNone, event: event.PruneEvent{ Operation: event.Pruned, - Type: event.PruneEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"), }, expected: map[string]interface{}{ @@ -237,7 +187,6 @@ func TestFormatter_FormatPruneEvent(t *testing.T) { previewStrategy: common.DryRunClient, event: event.PruneEvent{ Operation: event.PruneSkipped, - Type: event.PruneEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), }, expected: map[string]interface{}{ @@ -251,30 +200,13 @@ func TestFormatter_FormatPruneEvent(t *testing.T) { "type": "prune", }, }, - "prune event with completed status": { - previewStrategy: common.DryRunNone, - event: event.PruneEvent{ - Type: event.PruneEventCompleted, - }, - pruneStats: &list.PruneStats{ - Pruned: 1, - Skipped: 2, - }, - expected: map[string]interface{}{ - "eventType": "completed", - "skipped": 2, - "pruned": 1, - "timestamp": "", - "type": "prune", - }, - }, } for tn, tc := range testCases { t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatPruneEvent(tc.event, tc.pruneStats) + err := formatter.FormatPruneEvent(tc.event) assert.NoError(t, err) assertOutput(t, tc.expected, out.String()) @@ -294,7 +226,6 @@ func TestFormatter_FormatDeleteEvent(t *testing.T) { previewStrategy: common.DryRunNone, event: event.DeleteEvent{ Operation: event.Deleted, - Type: event.DeleteEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "default", "my-dep"), }, expected: map[string]interface{}{ @@ -312,7 +243,6 @@ func TestFormatter_FormatDeleteEvent(t *testing.T) { previewStrategy: common.DryRunClient, event: event.DeleteEvent{ Operation: event.DeleteSkipped, - Type: event.DeleteEventResourceUpdate, Identifier: createIdentifier("apps", "Deployment", "", "my-dep"), }, expected: map[string]interface{}{ @@ -326,30 +256,13 @@ func TestFormatter_FormatDeleteEvent(t *testing.T) { "type": "delete", }, }, - "delete event with completed status": { - previewStrategy: common.DryRunNone, - event: event.DeleteEvent{ - Type: event.DeleteEventCompleted, - }, - deleteStats: &list.DeleteStats{ - Deleted: 1, - Skipped: 2, - }, - expected: map[string]interface{}{ - "deleted": 1, - "eventType": "completed", - "skipped": 2, - "timestamp": "", - "type": "delete", - }, - }, } for tn, tc := range testCases { t.Run(tn, func(t *testing.T) { ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled formatter := NewFormatter(ioStreams, tc.previewStrategy) - err := formatter.FormatDeleteEvent(tc.event, tc.deleteStats) + err := formatter.FormatDeleteEvent(tc.event) assert.NoError(t, err) assertOutput(t, tc.expected, out.String()) @@ -394,11 +307,3 @@ func createIdentifier(group, kind, namespace, name string) object.ObjMetadata { }, } } - -type fakeCollector struct { - m map[object.ObjMetadata]event.StatusEvent -} - -func (f *fakeCollector) LatestStatus() map[object.ObjMetadata]event.StatusEvent { - return f.m -} diff --git a/cmd/printers/table/collector.go b/cmd/printers/table/collector.go index 24ddcad..b4ae5d2 100644 --- a/cmd/printers/table/collector.go +++ b/cmd/printers/table/collector.go @@ -15,7 +15,7 @@ import ( "sigs.k8s.io/cli-utils/pkg/print/table" ) -func newResourceStateCollector(resourceGroups []event.ResourceGroup) *ResourceStateCollector { +func newResourceStateCollector(resourceGroups []event.ActionGroup) *ResourceStateCollector { resourceInfos := make(map[object.ObjMetadata]*ResourceInfo) for _, group := range resourceGroups { action := group.Action @@ -70,15 +70,15 @@ type ResourceInfo struct { // ApplyOpResult contains the result after // a resource has been applied to the cluster. - ApplyOpResult *event.ApplyEventOperation + ApplyOpResult event.ApplyEventOperation // PruneOpResult contains the result after // a prune operation on a resource - PruneOpResult *event.PruneEventOperation + PruneOpResult event.PruneEventOperation // DeleteOpResult contains the result after // a delete operation on a resource - DeleteOpResult *event.DeleteEventOperation + DeleteOpResult event.DeleteEventOperation } // Identifier returns the identifier for the given resource. @@ -182,48 +182,37 @@ func (r *ResourceStateCollector) processEvent(ev event.Event) error { // processStatusEvent handles events pertaining to a status // update for a resource. func (r *ResourceStateCollector) processStatusEvent(e event.StatusEvent) { - if e.Type == event.StatusEventResourceUpdate { - klog.V(7).Infoln("processing status event") - resource := e.Resource - if resource == nil { - klog.V(4).Infoln("status event missing Resource field; no processing") - return - } - previous, found := r.resourceInfos[resource.Identifier] - if !found { - klog.V(4).Infof("%s status event not found in ResourceInfos; no processing", resource.Identifier) - return - } - previous.resourceStatus = e.Resource + klog.V(7).Infoln("processing status event") + previous, found := r.resourceInfos[e.Identifier] + if !found { + klog.V(4).Infof("%s status event not found in ResourceInfos; no processing", e.Identifier) + return } + previous.resourceStatus = e.PollResourceInfo } // processApplyEvent handles events relating to apply operations func (r *ResourceStateCollector) processApplyEvent(e event.ApplyEvent) { - if e.Type == event.ApplyEventResourceUpdate { - identifier := e.Identifier - klog.V(7).Infof("processing apply event for %s", identifier) - previous, found := r.resourceInfos[identifier] - if !found { - klog.V(4).Infof("%s apply event not found in ResourceInfos; no processing", identifier) - return - } - previous.ApplyOpResult = &e.Operation + identifier := e.Identifier + klog.V(7).Infof("processing apply event for %s", identifier) + previous, found := r.resourceInfos[identifier] + if !found { + klog.V(4).Infof("%s apply event not found in ResourceInfos; no processing", identifier) + 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 := e.Identifier - klog.V(7).Infof("processing prune event for %s", identifier) - previous, found := r.resourceInfos[identifier] - if !found { - klog.V(4).Infof("%s prune event not found in ResourceInfos; no processing", identifier) - return - } - previous.PruneOpResult = &e.Operation + identifier := e.Identifier + klog.V(7).Infof("processing prune event for %s", identifier) + previous, found := r.resourceInfos[identifier] + if !found { + klog.V(4).Infof("%s prune event not found in ResourceInfos; no processing", identifier) + return } + previous.PruneOpResult = e.Operation } // ResourceState contains the latest state for all the resources. diff --git a/cmd/printers/table/collector_test.go b/cmd/printers/table/collector_test.go index 2a4511e..2a56182 100644 --- a/cmd/printers/table/collector_test.go +++ b/cmd/printers/table/collector_test.go @@ -35,15 +35,15 @@ const testMessage = "test message for ResourceStatus" func TestResourceStateCollector_New(t *testing.T) { testCases := map[string]struct { - resourceGroups []event.ResourceGroup + resourceGroups []event.ActionGroup resourceInfos map[object.ObjMetadata]*ResourceInfo }{ "no resources": { - resourceGroups: []event.ResourceGroup{}, + resourceGroups: []event.ActionGroup{}, resourceInfos: map[object.ObjMetadata]*ResourceInfo{}, }, "several resources for apply": { - resourceGroups: []event.ResourceGroup{ + resourceGroups: []event.ActionGroup{ { Action: event.ApplyAction, Identifiers: []object.ObjMetadata{ @@ -61,7 +61,7 @@ func TestResourceStateCollector_New(t *testing.T) { }, }, "several resources for prune": { - resourceGroups: []event.ResourceGroup{ + resourceGroups: []event.ActionGroup{ { Action: event.ApplyAction, Identifiers: []object.ObjMetadata{ @@ -104,39 +104,30 @@ func TestResourceStateCollector_New(t *testing.T) { func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) { testCases := map[string]struct { - resourceGroups []event.ResourceGroup + resourceGroups []event.ActionGroup statusEvent event.StatusEvent }{ "nil StatusEvent.Resource does not crash": { - resourceGroups: []event.ResourceGroup{}, + resourceGroups: []event.ActionGroup{}, statusEvent: event.StatusEvent{ - Type: event.StatusEventResourceUpdate, - Resource: nil, - }, - }, - "type StatusEventCompleted does nothing": { - resourceGroups: []event.ResourceGroup{}, - statusEvent: event.StatusEvent{ - Type: event.StatusEventCompleted, Resource: nil, }, }, "unfound Resource identifier does not crash": { - resourceGroups: []event.ResourceGroup{ + resourceGroups: []event.ActionGroup{ { Action: event.ApplyAction, Identifiers: []object.ObjMetadata{depID}, }, }, statusEvent: event.StatusEvent{ - Type: event.StatusEventResourceUpdate, - Resource: &pe.ResourceStatus{ + PollResourceInfo: &pe.ResourceStatus{ Identifier: customID, // Does not match identifier in resourceGroups }, }, }, "basic status event for applying two resources updates resourceStatus": { - resourceGroups: []event.ResourceGroup{ + resourceGroups: []event.ActionGroup{ { Action: event.ApplyAction, Identifiers: []object.ObjMetadata{ @@ -145,15 +136,14 @@ func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) { }, }, statusEvent: event.StatusEvent{ - Type: event.StatusEventResourceUpdate, - Resource: &pe.ResourceStatus{ + PollResourceInfo: &pe.ResourceStatus{ Identifier: depID, Message: testMessage, }, }, }, "several resources for prune": { - resourceGroups: []event.ResourceGroup{ + resourceGroups: []event.ActionGroup{ { Action: event.ApplyAction, Identifiers: []object.ObjMetadata{ @@ -168,8 +158,7 @@ func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) { }, }, statusEvent: event.StatusEvent{ - Type: event.StatusEventResourceUpdate, - Resource: &pe.ResourceStatus{ + PollResourceInfo: &pe.ResourceStatus{ Identifier: depID, Message: testMessage, }, @@ -186,7 +175,7 @@ func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) { resourceInfo, found := rsc.resourceInfos[id] if found { // Validate the ResourceStatus was set from StatusEvent - if resourceInfo.resourceStatus != tc.statusEvent.Resource { + if resourceInfo.resourceStatus != tc.statusEvent.PollResourceInfo { t.Errorf("status event not processed for %s", id) } } @@ -199,5 +188,5 @@ func getID(e event.StatusEvent) (object.ObjMetadata, bool) { if e.Resource == nil { return object.ObjMetadata{}, false } - return e.Resource.Identifier, true + return e.Identifier, true } diff --git a/cmd/printers/table/printer.go b/cmd/printers/table/printer.go index bfd9b28..45f536a 100644 --- a/cmd/printers/table/printer.go +++ b/cmd/printers/table/printer.go @@ -35,7 +35,7 @@ func (t *Printer) Print(ch <-chan event.Event, _ common.DryRunStrategy) error { } // Create a new collector and initialize it with the resources // we are interested in. - coll := newResourceStateCollector(initEvent.ResourceGroups) + coll := newResourceStateCollector(initEvent.ActionGroups) stop := make(chan struct{}) @@ -89,11 +89,11 @@ var ( var text string switch resInfo.ResourceAction { case event.ApplyAction: - if resInfo.ApplyOpResult != nil { + if resInfo.ApplyOpResult != event.ApplyUnspecified { text = resInfo.ApplyOpResult.String() } case event.PruneAction: - if resInfo.PruneOpResult != nil { + if resInfo.PruneOpResult != event.PruneUnspecified { text = resInfo.PruneOpResult.String() } } diff --git a/cmd/printers/table/printer_test.go b/cmd/printers/table/printer_test.go index ca6a4f2..7af0b5b 100644 --- a/cmd/printers/table/printer_test.go +++ b/cmd/printers/table/printer_test.go @@ -35,7 +35,7 @@ func TestActionColumnDef(t *testing.T) { "applied": { resource: &ResourceInfo{ ResourceAction: event.ApplyAction, - ApplyOpResult: &createdOpResult, + ApplyOpResult: createdOpResult, }, columnWidth: 15, expectedOutput: "Created", @@ -43,7 +43,7 @@ func TestActionColumnDef(t *testing.T) { "pruned": { resource: &ResourceInfo{ ResourceAction: event.PruneAction, - PruneOpResult: &prunedOpResult, + PruneOpResult: prunedOpResult, }, columnWidth: 15, expectedOutput: "Pruned", @@ -51,7 +51,7 @@ func TestActionColumnDef(t *testing.T) { "trimmed output": { resource: &ResourceInfo{ ResourceAction: event.ApplyAction, - ApplyOpResult: &createdOpResult, + ApplyOpResult: createdOpResult, }, columnWidth: 5, expectedOutput: "Creat", diff --git a/examples/alphaTestExamples/crds.md b/examples/alphaTestExamples/crds.md index ea7b0c4..93858a0 100644 --- a/examples/alphaTestExamples/crds.md +++ b/examples/alphaTestExamples/crds.md @@ -124,8 +124,6 @@ Use the `kapply` binary in `MYGOBIN` to apply both the CRD and the CR. ``` kapply apply $BASE --reconcile-timeout=1m > $OUTPUT/status -expectedOutputLine "customresourcedefinition.apiextensions.k8s.io/foos.custom.io is Current: CRD is established" - expectedOutputLine "foo.custom.io/example-foo is Current: Resource is current" kubectl get crd --no-headers | awk '{print $1}' > $OUTPUT/status diff --git a/pkg/apply/applier.go b/pkg/apply/applier.go index a0b4212..796ab14 100644 --- a/pkg/apply/applier.go +++ b/pkg/apply/applier.go @@ -259,16 +259,7 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.InventoryInfo, obje eventChannel <- event.Event{ Type: event.InitType, InitEvent: event.InitEvent{ - ResourceGroups: []event.ResourceGroup{ - { - Action: event.ApplyAction, - Identifiers: resourceObjects.IdsForApply(), - }, - { - Action: event.PruneAction, - Identifiers: resourceObjects.IdsForPrune(), - }, - }, + ActionGroups: taskQueue.ToActionGroups(), }, } @@ -276,7 +267,7 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.InventoryInfo, obje klog.V(4).Infoln("applier building TaskStatusRunner...") runner := taskrunner.NewTaskStatusRunner(resourceObjects.AllIds(), a.StatusPoller) klog.V(4).Infoln("applier running TaskStatusRunner...") - err = runner.Run(ctx, taskQueue, eventChannel, taskrunner.Options{ + err = runner.Run(ctx, taskQueue.ToChannel(), eventChannel, taskrunner.Options{ PollInterval: options.PollInterval, UseCache: true, EmitStatusEvents: options.EmitStatusEvents, diff --git a/pkg/apply/applier_test.go b/pkg/apply/applier_test.go index 8163599..0cbf205 100644 --- a/pkg/apply/applier_test.go +++ b/pkg/apply/applier_test.go @@ -9,7 +9,6 @@ import ( "fmt" "io/ioutil" "net/http" - "reflect" "regexp" "testing" "time" @@ -35,6 +34,7 @@ import ( "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/provider" + "sigs.k8s.io/cli-utils/pkg/testutil" ) var ( @@ -138,30 +138,18 @@ func (i inventoryInfo) toWrapped() inventory.InventoryInfo { return inventory.WrapInventoryInfoObj(inv) } -type expectedEvent struct { - eventType event.Type - - applyEventType event.ApplyEventType - statusEventType event.StatusEventType - pruneEventType event.PruneEventType - deleteEventType event.DeleteEventType - - applyErrorType error - pruneEventOp event.PruneEventOperation -} - func TestApplier(t *testing.T) { testCases := map[string]struct { - namespace string - resources []*unstructured.Unstructured - invInfo inventoryInfo - clusterObjs []*unstructured.Unstructured - handlers []handler - reconcileTimeout time.Duration - prune bool - inventoryPolicy inventory.InventoryPolicy - statusEvents []pollevent.Event - expectedEventTypes []expectedEvent + namespace string + resources []*unstructured.Unstructured + invInfo inventoryInfo + clusterObjs []*unstructured.Unstructured + handlers []handler + reconcileTimeout time.Duration + prune bool + inventoryPolicy inventory.InventoryPolicy + statusEvents []pollevent.Event + expectedEvents []testutil.ExpEvent }{ "initial apply without status or prune": { namespace: "default", @@ -177,17 +165,18 @@ func TestApplier(t *testing.T) { reconcileTimeout: time.Duration(0), prune: false, inventoryPolicy: inventory.InventoryPolicyMustMatch, - expectedEventTypes: []expectedEvent{ + expectedEvents: []testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventResourceUpdate, + EventType: event.ActionGroupType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventCompleted, + EventType: event.ApplyType, + }, + { + EventType: event.ActionGroupType, }, }, }, @@ -232,41 +221,42 @@ func TestApplier(t *testing.T) { }, }, }, - expectedEventTypes: []expectedEvent{ + expectedEvents: []testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventResourceUpdate, + EventType: event.ActionGroupType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventResourceUpdate, + EventType: event.ApplyType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventCompleted, + EventType: event.ApplyType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventResourceUpdate, + EventType: event.ActionGroupType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventResourceUpdate, + EventType: event.ActionGroupType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventResourceUpdate, + EventType: event.StatusType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventCompleted, + EventType: event.StatusType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventCompleted, + EventType: event.StatusType, + }, + { + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, }, }, }, @@ -310,37 +300,39 @@ func TestApplier(t *testing.T) { }, }, }, - expectedEventTypes: []expectedEvent{ + expectedEvents: []testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventResourceUpdate, + EventType: event.ActionGroupType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventResourceUpdate, + EventType: event.ApplyType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventCompleted, + EventType: event.ApplyType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventResourceUpdate, + EventType: event.ActionGroupType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventResourceUpdate, + EventType: event.ActionGroupType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventCompleted, + EventType: event.StatusType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventCompleted, + EventType: event.StatusType, + }, + { + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, }, }, }, @@ -368,31 +360,39 @@ func TestApplier(t *testing.T) { prune: true, inventoryPolicy: inventory.InventoryPolicyMustMatch, statusEvents: []pollevent.Event{}, - expectedEventTypes: []expectedEvent{ + expectedEvents: []testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventCompleted, + EventType: event.ActionGroupType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventCompleted, + EventType: event.ActionGroupType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventResourceUpdate, - pruneEventOp: event.Pruned, + EventType: event.ActionGroupType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventResourceUpdate, - pruneEventOp: event.Pruned, + EventType: event.ActionGroupType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventCompleted, + EventType: event.ActionGroupType, + }, + { + EventType: event.PruneType, + PruneEvent: &testutil.ExpPruneEvent{ + Operation: event.Pruned, + }, + }, + { + EventType: event.PruneType, + PruneEvent: &testutil.ExpPruneEvent{ + Operation: event.Pruned, + }, + }, + { + EventType: event.ActionGroupType, }, }, }, @@ -412,27 +412,49 @@ func TestApplier(t *testing.T) { reconcileTimeout: time.Minute, prune: true, inventoryPolicy: inventory.InventoryPolicyMustMatch, - statusEvents: []pollevent.Event{}, - expectedEventTypes: []expectedEvent{ + statusEvents: []pollevent.Event{ { - eventType: event.InitType, + EventType: pollevent.ResourceUpdateEvent, + Resource: &pollevent.ResourceStatus{ + Identifier: toIdentifier(t, resources["deployment"]), + Status: status.InProgressStatus, + }, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventResourceUpdate, - applyErrorType: inventory.NewInventoryOverlapError(fmt.Errorf("")), + EventType: pollevent.ResourceUpdateEvent, + Resource: &pollevent.ResourceStatus{ + Identifier: toIdentifier(t, resources["deployment"]), + Status: status.CurrentStatus, + }, + }, + }, + expectedEvents: []testutil.ExpEvent{ + { + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventCompleted, + EventType: event.ActionGroupType, }, { - eventType: event.StatusType, - statusEventType: event.StatusEventCompleted, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Error: inventory.NewInventoryOverlapError(fmt.Errorf("")), + }, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventCompleted, + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, + }, + { + EventType: event.ActionGroupType, }, }, }, @@ -455,22 +477,27 @@ func TestApplier(t *testing.T) { reconcileTimeout: 0, prune: true, inventoryPolicy: inventory.InventoryPolicyMustMatch, - expectedEventTypes: []expectedEvent{ + expectedEvents: []testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventCompleted, + EventType: event.ActionGroupType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventResourceUpdate, - pruneEventOp: event.PruneSkipped, + EventType: event.ActionGroupType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventCompleted, + EventType: event.ActionGroupType, + }, + { + EventType: event.PruneType, + PruneEvent: &testutil.ExpPruneEvent{ + Operation: event.PruneSkipped, + }, + }, + { + EventType: event.ActionGroupType, }, }, }, @@ -493,22 +520,27 @@ func TestApplier(t *testing.T) { reconcileTimeout: 0, prune: true, inventoryPolicy: inventory.InventoryPolicyMustMatch, - expectedEventTypes: []expectedEvent{ + expectedEvents: []testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEventType: event.ApplyEventCompleted, + EventType: event.ActionGroupType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventResourceUpdate, - pruneEventOp: event.Pruned, + EventType: event.ActionGroupType, }, { - eventType: event.PruneType, - pruneEventType: event.PruneEventCompleted, + EventType: event.ActionGroupType, + }, + { + EventType: event.PruneType, + PruneEvent: &testutil.ExpPruneEvent{ + Operation: event.Pruned, + }, + }, + { + EventType: event.ActionGroupType, }, }, }, @@ -590,39 +622,29 @@ func TestApplier(t *testing.T) { }) var events []event.Event - for e := range eventChannel { - if e.Type == event.ApplyType && e.ApplyEvent.Type == event.ApplyEventCompleted { - close(poller.start) - } - events = append(events, e) - } + timer := time.NewTimer(30 * time.Second) - if !assert.Equal(t, len(tc.expectedEventTypes), len(events)) { - t.FailNow() - } - - for i, e := range events { - expected := tc.expectedEventTypes[i] - if !assert.Equal(t, expected.eventType.String(), e.Type.String()) { - t.FailNow() - } - - switch expected.eventType { - case event.InitType: - case event.ApplyType: - assert.Equal(t, expected.applyEventType.String(), e.ApplyEvent.Type.String()) - assert.Equal(t, reflect.TypeOf(expected.applyErrorType), reflect.TypeOf(e.ApplyEvent.Error)) - case event.StatusType: - assert.Equal(t, expected.statusEventType.String(), e.StatusEvent.Type.String()) - case event.PruneType: - assert.Equal(t, expected.pruneEventType.String(), e.PruneEvent.Type.String()) - assert.Equal(t, expected.pruneEventOp.String(), e.PruneEvent.Operation.String()) - case event.DeleteType: - assert.Equal(t, expected.deleteEventType.String(), e.DeleteEvent.Type.String()) - default: - assert.Fail(t, "unexpected event type %s", expected.eventType.String()) + loop: + for { + select { + case e, ok := <-eventChannel: + if !ok { + break loop + } + if e.Type == event.ActionGroupType && + e.ActionGroupEvent.Action == event.ApplyAction && + e.ActionGroupEvent.Type == event.Finished { + close(poller.start) + } + events = append(events, e) + case <-timer.C: + t.Errorf("timeout") + break loop } } + + err = testutil.VerifyEvents(tc.expectedEvents, events) + assert.NoError(t, err) }) } } diff --git a/pkg/apply/destroyer.go b/pkg/apply/destroyer.go index 470d870..5f5f964 100644 --- a/pkg/apply/destroyer.go +++ b/pkg/apply/destroyer.go @@ -110,9 +110,11 @@ func (d *Destroyer) Run(inv inventory.InventoryInfo, option *DestroyerOption) <- // events and shut down before we continue. <-completedChannel ch <- event.Event{ - Type: event.DeleteType, - DeleteEvent: event.DeleteEvent{ - Type: event.DeleteEventCompleted, + Type: event.ActionGroupType, + ActionGroupEvent: event.ActionGroupEvent{ + GroupName: "destroyer", + Type: event.Finished, + Action: event.DeleteAction, }, } }() @@ -135,19 +137,9 @@ func runPruneEventTransformer(eventChannel chan event.Event) (chan event.Event, if msg.Type != event.PruneType { eventChannel <- msg } else { - var deleteEventType event.DeleteEventType - switch msg.PruneEvent.Type { - case event.PruneEventFailed: - deleteEventType = event.DeleteEventFailed - case event.PruneEventCompleted: - deleteEventType = event.DeleteEventCompleted - case event.PruneEventResourceUpdate: - deleteEventType = event.DeleteEventResourceUpdate - } eventChannel <- event.Event{ Type: event.DeleteType, DeleteEvent: event.DeleteEvent{ - Type: deleteEventType, Operation: transformPruneOperation(msg.PruneEvent.Operation), Object: msg.PruneEvent.Object, Identifier: msg.PruneEvent.Identifier, @@ -161,12 +153,16 @@ func runPruneEventTransformer(eventChannel chan event.Event) (chan event.Event, } func transformPruneOperation(pruneOp event.PruneEventOperation) event.DeleteEventOperation { + var deleteOp event.DeleteEventOperation switch pruneOp { case event.PruneSkipped: - return event.DeleteSkipped + deleteOp = event.DeleteSkipped case event.Pruned: - return event.Deleted + deleteOp = event.Deleted + case event.PruneUnspecified: + deleteOp = event.DeleteUnspecified default: panic(fmt.Errorf("unknown prune operation %s", pruneOp.String())) } + return deleteOp } diff --git a/pkg/apply/event/actiongroupeventtype_string.go b/pkg/apply/event/actiongroupeventtype_string.go new file mode 100644 index 0000000..ac610a2 --- /dev/null +++ b/pkg/apply/event/actiongroupeventtype_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=ActionGroupEventType"; DO NOT EDIT. + +package event + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Started-0] + _ = x[Finished-1] +} + +const _ActionGroupEventType_name = "StartedFinished" + +var _ActionGroupEventType_index = [...]uint8{0, 7, 15} + +func (i ActionGroupEventType) String() string { + if i < 0 || i >= ActionGroupEventType(len(_ActionGroupEventType_index)-1) { + return "ActionGroupEventType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ActionGroupEventType_name[_ActionGroupEventType_index[i]:_ActionGroupEventType_index[i+1]] +} diff --git a/pkg/apply/event/applyeventoperation_string.go b/pkg/apply/event/applyeventoperation_string.go index 58642b4..edfbad0 100644 --- a/pkg/apply/event/applyeventoperation_string.go +++ b/pkg/apply/event/applyeventoperation_string.go @@ -1,6 +1,3 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - // Code generated by "stringer -type=ApplyEventOperation"; DO NOT EDIT. package event @@ -11,16 +8,16 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[ServersideApplied-0] - _ = x[Created-1] - _ = x[Unchanged-2] - _ = x[Configured-3] - _ = x[Failed-4] + _ = x[ApplyUnspecified-0] + _ = x[ServersideApplied-1] + _ = x[Created-2] + _ = x[Unchanged-3] + _ = x[Configured-4] } -const _ApplyEventOperation_name = "ServersideAppliedCreatedUnchangedConfiguredFailed" +const _ApplyEventOperation_name = "ApplyUnspecifiedServersideAppliedCreatedUnchangedConfigured" -var _ApplyEventOperation_index = [...]uint8{0, 17, 24, 33, 43, 49} +var _ApplyEventOperation_index = [...]uint8{0, 16, 33, 40, 49, 59} func (i ApplyEventOperation) String() string { if i < 0 || i >= ApplyEventOperation(len(_ApplyEventOperation_index)-1) { diff --git a/pkg/apply/event/applyeventtype_string.go b/pkg/apply/event/applyeventtype_string.go deleted file mode 100644 index eb94385..0000000 --- a/pkg/apply/event/applyeventtype_string.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by "stringer -type=ApplyEventType"; DO NOT EDIT. - -package event - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[ApplyEventResourceUpdate-0] - _ = x[ApplyEventCompleted-1] -} - -const _ApplyEventType_name = "ApplyEventResourceUpdateApplyEventCompleted" - -var _ApplyEventType_index = [...]uint8{0, 24, 43} - -func (i ApplyEventType) String() string { - if i < 0 || i >= ApplyEventType(len(_ApplyEventType_index)-1) { - return "ApplyEventType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _ApplyEventType_name[_ApplyEventType_index[i]:_ApplyEventType_index[i+1]] -} diff --git a/pkg/apply/event/deleteeventoperation_string.go b/pkg/apply/event/deleteeventoperation_string.go index 11fdc41..90bc851 100644 --- a/pkg/apply/event/deleteeventoperation_string.go +++ b/pkg/apply/event/deleteeventoperation_string.go @@ -1,6 +1,3 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - // Code generated by "stringer -type=DeleteEventOperation"; DO NOT EDIT. package event @@ -11,13 +8,14 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[Deleted-0] - _ = x[DeleteSkipped-1] + _ = x[DeleteUnspecified-0] + _ = x[Deleted-1] + _ = x[DeleteSkipped-2] } -const _DeleteEventOperation_name = "DeletedDeleteSkipped" +const _DeleteEventOperation_name = "DeleteUnspecifiedDeletedDeleteSkipped" -var _DeleteEventOperation_index = [...]uint8{0, 7, 20} +var _DeleteEventOperation_index = [...]uint8{0, 17, 24, 37} func (i DeleteEventOperation) String() string { if i < 0 || i >= DeleteEventOperation(len(_DeleteEventOperation_index)-1) { diff --git a/pkg/apply/event/deleteeventtype_string.go b/pkg/apply/event/deleteeventtype_string.go deleted file mode 100644 index ad5f0e5..0000000 --- a/pkg/apply/event/deleteeventtype_string.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by "stringer -type=DeleteEventType"; DO NOT EDIT. - -package event - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[DeleteEventResourceUpdate-0] - _ = x[DeleteEventCompleted-1] - _ = x[DeleteEventFailed-2] -} - -const _DeleteEventType_name = "DeleteEventResourceUpdateDeleteEventCompletedDeleteEventFailed" - -var _DeleteEventType_index = [...]uint8{0, 25, 45, 62} - -func (i DeleteEventType) String() string { - if i < 0 || i >= DeleteEventType(len(_DeleteEventType_index)-1) { - return "DeleteEventType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _DeleteEventType_name[_DeleteEventType_index[i]:_DeleteEventType_index[i+1]] -} diff --git a/pkg/apply/event/event.go b/pkg/apply/event/event.go index 29ff720..f57676d 100644 --- a/pkg/apply/event/event.go +++ b/pkg/apply/event/event.go @@ -16,6 +16,7 @@ type Type int const ( InitType Type = iota ErrorType + ActionGroupType ApplyType StatusType PruneType @@ -37,6 +38,11 @@ type Event struct { // ErrorEvent contains information about any errors encountered. ErrorEvent ErrorEvent + // ActionGroupEvent contains information about the progression of tasks + // to apply, prune, and destroy resources, and tasks that involves waiting + // for a set of resources to reach a specific state. + ActionGroupEvent ActionGroupEvent + // ApplyEvent contains information about progress pertaining to // applying a resource to the cluster. ApplyEvent ApplyEvent @@ -55,7 +61,7 @@ type Event struct { } type InitEvent struct { - ResourceGroups []ResourceGroup + ActionGroups []ActionGroup } //go:generate stringer -type=ResourceAction @@ -64,9 +70,12 @@ type ResourceAction int const ( ApplyAction ResourceAction = iota PruneAction + DeleteAction + WaitAction ) -type ResourceGroup struct { +type ActionGroup struct { + Name string Action ResourceAction Identifiers []object.ObjMetadata } @@ -75,92 +84,73 @@ type ErrorEvent struct { Err error } -//go:generate stringer -type=ApplyEventType -type ApplyEventType int +//go:generate stringer -type=ActionGroupEventType +type ActionGroupEventType int const ( - ApplyEventResourceUpdate ApplyEventType = iota - ApplyEventCompleted + Started ActionGroupEventType = iota + Finished ) +type ActionGroupEvent struct { + GroupName string + Action ResourceAction + Type ActionGroupEventType +} + //go:generate stringer -type=ApplyEventOperation type ApplyEventOperation int const ( - ServersideApplied ApplyEventOperation = iota + ApplyUnspecified ApplyEventOperation = iota + ServersideApplied Created Unchanged Configured - Failed ) type ApplyEvent struct { - Type ApplyEventType - Operation ApplyEventOperation - Object *unstructured.Unstructured Identifier object.ObjMetadata + Operation ApplyEventOperation + Resource *unstructured.Unstructured Error error } -//go:generate stringer -type=StatusEventType -type StatusEventType int - -const ( - StatusEventResourceUpdate StatusEventType = iota - StatusEventCompleted -) - type StatusEvent struct { - Type StatusEventType - Resource *pollevent.ResourceStatus + Identifier object.ObjMetadata + PollResourceInfo *pollevent.ResourceStatus + Resource *unstructured.Unstructured + Error error } -//go:generate stringer -type=PruneEventType -type PruneEventType int - -const ( - PruneEventResourceUpdate PruneEventType = iota - PruneEventCompleted - PruneEventFailed -) - //go:generate stringer -type=PruneEventOperation type PruneEventOperation int const ( - Pruned PruneEventOperation = iota + PruneUnspecified PruneEventOperation = iota + Pruned PruneSkipped ) type PruneEvent struct { - Type PruneEventType + Identifier object.ObjMetadata Operation PruneEventOperation Object *unstructured.Unstructured - Identifier object.ObjMetadata Error error } -//go:generate stringer -type=DeleteEventType -type DeleteEventType int - -const ( - DeleteEventResourceUpdate DeleteEventType = iota - DeleteEventCompleted - DeleteEventFailed -) - //go:generate stringer -type=DeleteEventOperation type DeleteEventOperation int const ( - Deleted DeleteEventOperation = iota + DeleteUnspecified DeleteEventOperation = iota + Deleted DeleteSkipped ) type DeleteEvent struct { - Type DeleteEventType + Identifier object.ObjMetadata Operation DeleteEventOperation Object *unstructured.Unstructured - Identifier object.ObjMetadata Error error } diff --git a/pkg/apply/event/pruneeventoperation_string.go b/pkg/apply/event/pruneeventoperation_string.go index 992cc64..8fbee71 100644 --- a/pkg/apply/event/pruneeventoperation_string.go +++ b/pkg/apply/event/pruneeventoperation_string.go @@ -1,6 +1,3 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - // Code generated by "stringer -type=PruneEventOperation"; DO NOT EDIT. package event @@ -11,13 +8,14 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[Pruned-0] - _ = x[PruneSkipped-1] + _ = x[PruneUnspecified-0] + _ = x[Pruned-1] + _ = x[PruneSkipped-2] } -const _PruneEventOperation_name = "PrunedPruneSkipped" +const _PruneEventOperation_name = "PruneUnspecifiedPrunedPruneSkipped" -var _PruneEventOperation_index = [...]uint8{0, 6, 18} +var _PruneEventOperation_index = [...]uint8{0, 16, 22, 34} func (i PruneEventOperation) String() string { if i < 0 || i >= PruneEventOperation(len(_PruneEventOperation_index)-1) { diff --git a/pkg/apply/event/pruneeventtype_string.go b/pkg/apply/event/pruneeventtype_string.go deleted file mode 100644 index 9629f55..0000000 --- a/pkg/apply/event/pruneeventtype_string.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by "stringer -type=PruneEventType"; DO NOT EDIT. - -package event - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[PruneEventResourceUpdate-0] - _ = x[PruneEventCompleted-1] - _ = x[PruneEventFailed-2] -} - -const _PruneEventType_name = "PruneEventResourceUpdatePruneEventCompletedPruneEventFailed" - -var _PruneEventType_index = [...]uint8{0, 24, 43, 59} - -func (i PruneEventType) String() string { - if i < 0 || i >= PruneEventType(len(_PruneEventType_index)-1) { - return "PruneEventType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _PruneEventType_name[_PruneEventType_index[i]:_PruneEventType_index[i+1]] -} diff --git a/pkg/apply/event/resourceaction_string.go b/pkg/apply/event/resourceaction_string.go index 8ea3627..ffa7e96 100644 --- a/pkg/apply/event/resourceaction_string.go +++ b/pkg/apply/event/resourceaction_string.go @@ -1,6 +1,3 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - // Code generated by "stringer -type=ResourceAction"; DO NOT EDIT. package event @@ -13,11 +10,13 @@ func _() { var x [1]struct{} _ = x[ApplyAction-0] _ = x[PruneAction-1] + _ = x[DeleteAction-2] + _ = x[WaitAction-3] } -const _ResourceAction_name = "ApplyActionPruneAction" +const _ResourceAction_name = "ApplyActionPruneActionDeleteActionWaitAction" -var _ResourceAction_index = [...]uint8{0, 11, 22} +var _ResourceAction_index = [...]uint8{0, 11, 22, 34, 44} func (i ResourceAction) String() string { if i < 0 || i >= ResourceAction(len(_ResourceAction_index)-1) { diff --git a/pkg/apply/event/statuseventtype_string.go b/pkg/apply/event/statuseventtype_string.go deleted file mode 100644 index fc35d53..0000000 --- a/pkg/apply/event/statuseventtype_string.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by "stringer -type=StatusEventType"; DO NOT EDIT. - -package event - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[StatusEventResourceUpdate-0] - _ = x[StatusEventCompleted-1] -} - -const _StatusEventType_name = "StatusEventResourceUpdateStatusEventCompleted" - -var _StatusEventType_index = [...]uint8{0, 25, 45} - -func (i StatusEventType) String() string { - if i < 0 || i >= StatusEventType(len(_StatusEventType_index)-1) { - return "StatusEventType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _StatusEventType_name[_StatusEventType_index[i]:_StatusEventType_index[i+1]] -} diff --git a/pkg/apply/event/type_string.go b/pkg/apply/event/type_string.go index 79e82a7..b37aa8b 100644 --- a/pkg/apply/event/type_string.go +++ b/pkg/apply/event/type_string.go @@ -1,6 +1,3 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - // Code generated by "stringer -type=Type"; DO NOT EDIT. package event @@ -13,15 +10,16 @@ func _() { var x [1]struct{} _ = x[InitType-0] _ = x[ErrorType-1] - _ = x[ApplyType-2] - _ = x[StatusType-3] - _ = x[PruneType-4] - _ = x[DeleteType-5] + _ = x[ActionGroupType-2] + _ = x[ApplyType-3] + _ = x[StatusType-4] + _ = x[PruneType-5] + _ = x[DeleteType-6] } -const _Type_name = "InitTypeErrorTypeApplyTypeStatusTypePruneTypeDeleteType" +const _Type_name = "InitTypeErrorTypeActionGroupTypeApplyTypeStatusTypePruneTypeDeleteType" -var _Type_index = [...]uint8{0, 8, 17, 26, 36, 45, 55} +var _Type_index = [...]uint8{0, 8, 17, 32, 41, 51, 60, 70} func (i Type) String() string { if i < 0 || i >= Type(len(_Type_index)-1) { diff --git a/pkg/apply/prune/prune.go b/pkg/apply/prune/prune.go index 0e9bb3d..187e349 100644 --- a/pkg/apply/prune/prune.go +++ b/pkg/apply/prune/prune.go @@ -250,7 +250,6 @@ func createPruneEvent(id object.ObjMetadata, obj *unstructured.Unstructured, op return event.Event{ Type: event.PruneType, PruneEvent: event.PruneEvent{ - Type: event.PruneEventResourceUpdate, Operation: op, Object: obj, Identifier: id, @@ -263,7 +262,6 @@ func createPruneFailedEvent(objMeta object.ObjMetadata, err error) event.Event { return event.Event{ Type: event.PruneType, PruneEvent: event.PruneEvent{ - Type: event.PruneEventFailed, Identifier: objMeta, Error: err, }, diff --git a/pkg/apply/solver/solver.go b/pkg/apply/solver/solver.go index 996f457..2bd2873 100644 --- a/pkg/apply/solver/solver.go +++ b/pkg/apply/solver/solver.go @@ -15,6 +15,7 @@ package solver import ( + "fmt" "time" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -41,6 +42,31 @@ type TaskQueueSolver struct { Mapper meta.RESTMapper } +type TaskQueue struct { + tasks []taskrunner.Task +} + +func (tq *TaskQueue) ToChannel() chan taskrunner.Task { + taskQueue := make(chan taskrunner.Task, len(tq.tasks)) + for _, t := range tq.tasks { + taskQueue <- t + } + return taskQueue +} + +func (tq *TaskQueue) ToActionGroups() []event.ActionGroup { + var ags []event.ActionGroup + + for _, t := range tq.tasks { + ags = append(ags, event.ActionGroup{ + Name: t.Name(), + Action: t.Action(), + Identifiers: t.Identifiers(), + }) + } + return ags +} + type Options struct { ServerSideOptions common.ServerSideOptions ReconcileTimeout time.Duration @@ -63,7 +89,11 @@ type resourceObjects interface { // and constructs the task queue. The options parameter allows // customization of how the task queue are built. func (t *TaskQueueSolver) BuildTaskQueue(ro resourceObjects, - o Options) chan taskrunner.Task { + o Options) *TaskQueue { + var applyCounter int + var pruneCounter int + var waitCounter int + var tasks []taskrunner.Task remainingInfos := ro.ObjsForApply() // Convert slice of previous inventory objects into a map. @@ -76,6 +106,7 @@ func (t *TaskQueueSolver) BuildTaskQueue(ro resourceObjects, crdSplitRes, hasCRDs := splitAfterCRDs(remainingInfos) if hasCRDs { tasks = append(tasks, &task.ApplyTask{ + TaskName: fmt.Sprintf("apply-%d", applyCounter), Objects: append(crdSplitRes.before, crdSplitRes.crds...), CRDs: crdSplitRes.crds, PrevInventory: prevInventory, @@ -87,21 +118,24 @@ func (t *TaskQueueSolver) BuildTaskQueue(ro resourceObjects, InventoryPolicy: o.InventoryPolicy, InvInfo: ro.Inventory(), }) + applyCounter += 1 if !o.DryRunStrategy.ClientOrServerDryRun() { objs := object.UnstructuredsToObjMetas(crdSplitRes.crds) tasks = append(tasks, taskrunner.NewWaitTask( + fmt.Sprintf("wait-%d", waitCounter), objs, taskrunner.AllCurrent, - 1*time.Minute), - &task.ResetRESTMapperTask{ - Mapper: t.Mapper, - }) + 1*time.Minute, + t.Mapper), + ) + waitCounter += 1 } remainingInfos = crdSplitRes.after } tasks = append(tasks, &task.ApplyTask{ + TaskName: fmt.Sprintf("apply-%d", applyCounter), Objects: remainingInfos, CRDs: crdSplitRes.crds, PrevInventory: prevInventory, @@ -113,36 +147,24 @@ func (t *TaskQueueSolver) BuildTaskQueue(ro resourceObjects, InventoryPolicy: o.InventoryPolicy, InvInfo: ro.Inventory(), }, - &task.SendEventTask{ - Event: event.Event{ - Type: event.ApplyType, - ApplyEvent: event.ApplyEvent{ - Type: event.ApplyEventCompleted, - }, - }, - }, ) if !o.DryRunStrategy.ClientOrServerDryRun() && o.ReconcileTimeout != time.Duration(0) { tasks = append(tasks, taskrunner.NewWaitTask( - ro.IdsForApply(), + fmt.Sprintf("wait-%d", waitCounter), + object.UnstructuredsToObjMetas(remainingInfos), taskrunner.AllCurrent, - o.ReconcileTimeout), - &task.SendEventTask{ - Event: event.Event{ - Type: event.StatusType, - StatusEvent: event.StatusEvent{ - Type: event.StatusEventCompleted, - }, - }, - }, + o.ReconcileTimeout, + t.Mapper), ) + waitCounter += 1 } if o.Prune { tasks = append(tasks, &task.PruneTask{ + TaskName: fmt.Sprintf("prune-%d", pruneCounter), Objects: ro.ObjsForApply(), InventoryObject: ro.Inventory(), PruneOptions: t.PruneOptions, @@ -150,43 +172,23 @@ func (t *TaskQueueSolver) BuildTaskQueue(ro resourceObjects, DryRunStrategy: o.DryRunStrategy, InventoryPolicy: o.InventoryPolicy, }, - &task.SendEventTask{ - Event: event.Event{ - Type: event.PruneType, - PruneEvent: event.PruneEvent{ - Type: event.PruneEventCompleted, - }, - }, - }, ) if !o.DryRunStrategy.ClientOrServerDryRun() && o.PruneTimeout != time.Duration(0) { tasks = append(tasks, taskrunner.NewWaitTask( + fmt.Sprintf("wait-%d", waitCounter), ro.IdsForPrune(), taskrunner.AllNotFound, - o.PruneTimeout), - &task.SendEventTask{ - Event: event.Event{ - Type: event.StatusType, - StatusEvent: event.StatusEvent{ - Type: event.StatusEventCompleted, - }, - }, - }, + o.PruneTimeout, + t.Mapper), ) } } - return tasksToQueue(tasks) -} - -func tasksToQueue(tasks []taskrunner.Task) chan taskrunner.Task { - taskQueue := make(chan taskrunner.Task, len(tasks)) - for _, t := range tasks { - taskQueue <- t + return &TaskQueue{ + tasks: tasks, } - return taskQueue } type crdSplitResult struct { diff --git a/pkg/apply/solver/solver_test.go b/pkg/apply/solver/solver_test.go index 82b7ea1..577ed3d 100644 --- a/pkg/apply/solver/solver_test.go +++ b/pkg/apply/solver/solver_test.go @@ -38,8 +38,10 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { objs: []*unstructured.Unstructured{}, options: Options{}, expectedTasks: []taskrunner.Task{ - &task.ApplyTask{}, - &task.SendEventTask{}, + &task.ApplyTask{ + TaskName: "apply-0", + Objects: []*unstructured.Unstructured{}, + }, }, }, "single resource": { @@ -49,11 +51,11 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { options: Options{}, expectedTasks: []taskrunner.Task{ &task.ApplyTask{ + TaskName: "apply-0", Objects: []*unstructured.Unstructured{ depInfo, }, }, - &task.SendEventTask{}, }, }, "multiple resources with wait": { @@ -66,19 +68,20 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { }, expectedTasks: []taskrunner.Task{ &task.ApplyTask{ + TaskName: "apply-0", Objects: []*unstructured.Unstructured{ depInfo, customInfo, }, }, - &task.SendEventTask{}, taskrunner.NewWaitTask( + "wait-0", []object.ObjMetadata{ ignoreErrInfoToObjMeta(depInfo), ignoreErrInfoToObjMeta(customInfo), }, - taskrunner.AllCurrent, 1*time.Second), - &task.SendEventTask{}, + taskrunner.AllCurrent, 1*time.Second, + testutil.NewFakeRESTMapper()), }, }, "multiple resources with wait and prune": { @@ -92,21 +95,23 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { }, expectedTasks: []taskrunner.Task{ &task.ApplyTask{ + TaskName: "apply-0", Objects: []*unstructured.Unstructured{ depInfo, customInfo, }, }, - &task.SendEventTask{}, taskrunner.NewWaitTask( + "wait-0", []object.ObjMetadata{ ignoreErrInfoToObjMeta(depInfo), ignoreErrInfoToObjMeta(customInfo), }, - taskrunner.AllCurrent, 1*time.Second), - &task.SendEventTask{}, - &task.PruneTask{}, - &task.SendEventTask{}, + taskrunner.AllCurrent, 1*time.Second, + testutil.NewFakeRESTMapper()), + &task.PruneTask{ + TaskName: "prune-0", + }, }, }, "multiple resources with wait, prune and dryrun": { @@ -121,14 +126,15 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { }, expectedTasks: []taskrunner.Task{ &task.ApplyTask{ + TaskName: "apply-0", Objects: []*unstructured.Unstructured{ depInfo, customInfo, }, }, - &task.SendEventTask{}, - &task.PruneTask{}, - &task.SendEventTask{}, + &task.PruneTask{ + TaskName: "prune-0", + }, }, }, "multiple resources with wait, prune and server-dryrun": { @@ -143,14 +149,15 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { }, expectedTasks: []taskrunner.Task{ &task.ApplyTask{ + TaskName: "apply-0", Objects: []*unstructured.Unstructured{ depInfo, customInfo, }, }, - &task.SendEventTask{}, - &task.PruneTask{}, - &task.SendEventTask{}, + &task.PruneTask{ + TaskName: "prune-0", + }, }, }, "multiple resources including CRD": { @@ -163,29 +170,31 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { }, expectedTasks: []taskrunner.Task{ &task.ApplyTask{ + TaskName: "apply-0", Objects: []*unstructured.Unstructured{ crdInfo, }, }, taskrunner.NewWaitTask( + "wait-0", []object.ObjMetadata{ ignoreErrInfoToObjMeta(crdInfo), }, - taskrunner.AllCurrent, 1*time.Second), - &task.ResetRESTMapperTask{}, + taskrunner.AllCurrent, 1*time.Second, + testutil.NewFakeRESTMapper()), &task.ApplyTask{ + TaskName: "apply-1", Objects: []*unstructured.Unstructured{ depInfo, }, }, - &task.SendEventTask{}, taskrunner.NewWaitTask( + "wait-1", []object.ObjMetadata{ - ignoreErrInfoToObjMeta(crdInfo), ignoreErrInfoToObjMeta(depInfo), }, - taskrunner.AllCurrent, 1*time.Second), - &task.SendEventTask{}, + taskrunner.AllCurrent, 1*time.Second, + testutil.NewFakeRESTMapper()), }, }, "no wait with CRDs if it is a dryrun": { @@ -199,16 +208,17 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { }, expectedTasks: []taskrunner.Task{ &task.ApplyTask{ + TaskName: "apply-0", Objects: []*unstructured.Unstructured{ crdInfo, }, }, &task.ApplyTask{ + TaskName: "apply-1", Objects: []*unstructured.Unstructured{ depInfo, }, }, - &task.SendEventTask{}, }, }, } @@ -227,12 +237,11 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { idsForPrune: nil, }, tc.options) - tasks := queueToSlice(tq) - - assert.Equal(t, len(tc.expectedTasks), len(tasks)) + assert.Equal(t, len(tc.expectedTasks), len(tq.tasks)) for i, expTask := range tc.expectedTasks { - actualTask := tasks[i] + actualTask := tq.tasks[i] assert.Equal(t, getType(expTask), getType(actualTask)) + assert.Equal(t, expTask.Name(), actualTask.Name()) switch expTsk := expTask.(type) { case *task.ApplyTask: @@ -244,9 +253,9 @@ func TestTaskQueueSolver_BuildTaskQueue(t *testing.T) { } case *taskrunner.WaitTask: actWaitTask := toWaitTask(t, actualTask) - assert.Equal(t, len(expTsk.Identifiers), len(actWaitTask.Identifiers)) - for j, id := range expTsk.Identifiers { - actID := actWaitTask.Identifiers[j] + assert.Equal(t, len(expTsk.Ids), len(actWaitTask.Ids)) + for j, id := range expTsk.Ids { + actID := actWaitTask.Ids[j] assert.Equal(t, id, actID) } } @@ -292,18 +301,6 @@ func createInfo(apiVersion, kind, name, namespace string) *resource.Info { } } -func queueToSlice(tq chan taskrunner.Task) []taskrunner.Task { - var tasks []taskrunner.Task - for { - select { - case t := <-tq: - tasks = append(tasks, t) - default: - return tasks - } - } -} - func getType(task taskrunner.Task) reflect.Type { return reflect.TypeOf(task) } diff --git a/pkg/apply/task/apply_task.go b/pkg/apply/task/apply_task.go index 29519fc..678fbce 100644 --- a/pkg/apply/task/apply_task.go +++ b/pkg/apply/task/apply_task.go @@ -46,6 +46,8 @@ type applyOptions interface { // ApplyTask applies the given Objects to the cluster // by using the ApplyOptions. type ApplyTask struct { + TaskName string + Factory util.Factory InfoHelper info.InfoHelper Mapper meta.RESTMapper @@ -66,6 +68,18 @@ var applyOptionsFactoryFunc = newApplyOptions // getClusterObj gets the cluster object. Used for allow unit testing. var getClusterObj = getClusterObject +func (a *ApplyTask) Name() string { + return a.TaskName +} + +func (a *ApplyTask) Action() event.ResourceAction { + return event.ApplyAction +} + +func (a *ApplyTask) Identifiers() []object.ObjMetadata { + return object.UnstructuredsToObjMetas(a.Objects) +} + // Start creates a new goroutine that will invoke // the Run function on the ApplyOptions to update // the cluster. It will push a TaskResult on the taskChannel @@ -140,8 +154,8 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) { klog.Errorf("unable to convert obj to info for %s/%s (%s)--continue", obj.GetNamespace(), obj.GetName(), err) } - taskContext.EventChannel() <- createApplyEvent( - id, event.Failed, applyerror.NewUnknownTypeError(err)) + taskContext.EventChannel() <- createApplyFailedEvent(id, + applyerror.NewUnknownTypeError(err)) taskContext.CaptureResourceFailure(id) continue } @@ -153,15 +167,13 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) { klog.Errorf("error (%s) retrieving %s/%s from cluster--continue", err, info.Namespace, info.Name) } - op := event.Failed if a.objInCluster(id) { // Object in cluster stays in the inventory. klog.V(4).Infof("%s/%s apply retrieval failure, but in cluster--keep in inventory", info.Namespace, info.Name) invInfos[id] = info - op = event.Unchanged } - taskContext.EventChannel() <- createApplyEvent(id, op, err) + taskContext.EventChannel() <- createApplyFailedEvent(id, err) taskContext.CaptureResourceFailure(id) continue } @@ -173,10 +185,12 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) { if !canApply { klog.V(5).Infof("can not apply %s/%s--continue", clusterObj.GetNamespace(), clusterObj.GetName()) - taskContext.EventChannel() <- createApplyEvent( - id, - event.Unchanged, - err) + if err != nil { + taskContext.EventChannel() <- createApplyFailedEvent(id, err) + } else { + taskContext.EventChannel() <- createApplyEvent(id, + event.Unchanged, clusterObj) + } taskContext.CaptureResourceFailure(id) continue } @@ -202,8 +216,8 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) { info.Namespace, info.Name) delete(invInfos, id) } - taskContext.EventChannel() <- createApplyEvent( - id, event.Failed, applyerror.NewApplyRunError(err)) + taskContext.EventChannel() <- createApplyFailedEvent(id, + applyerror.NewApplyRunError(err)) taskContext.CaptureResourceFailure(id) } } @@ -373,12 +387,21 @@ func buildCRDsInfo(crds []*unstructured.Unstructured) *crdsInfo { func (a *ApplyTask) ClearTimeout() {} // createApplyEvent is a helper function to package an apply event for a single resource. -func createApplyEvent(id object.ObjMetadata, operation event.ApplyEventOperation, err error) event.Event { +func createApplyEvent(id object.ObjMetadata, operation event.ApplyEventOperation, resource *unstructured.Unstructured) event.Event { return event.Event{ Type: event.ApplyType, ApplyEvent: event.ApplyEvent{ - Type: event.ApplyEventResourceUpdate, + Identifier: id, Operation: operation, + Resource: resource, + }, + } +} + +func createApplyFailedEvent(id object.ObjMetadata, err error) event.Event { + return event.Event{ + Type: event.ApplyType, + ApplyEvent: event.ApplyEvent{ Identifier: id, Error: err, }, @@ -390,8 +413,8 @@ func createApplyEvent(id object.ObjMetadata, operation event.ApplyEventOperation func sendBatchApplyEvents(taskContext *taskrunner.TaskContext, objects []*unstructured.Unstructured, err error) { for _, obj := range objects { id := object.UnstructuredToObjMeta(obj) - taskContext.EventChannel() <- createApplyEvent( - id, event.Failed, applyerror.NewInitializeApplyOptionError(err)) + taskContext.EventChannel() <- createApplyFailedEvent(id, + applyerror.NewInitializeApplyOptionError(err)) taskContext.CaptureResourceFailure(id) } } diff --git a/pkg/apply/task/printer_adapter.go b/pkg/apply/task/printer_adapter.go index 19182bc..955a011 100644 --- a/pkg/apply/task/printer_adapter.go +++ b/pkg/apply/task/printer_adapter.go @@ -36,10 +36,9 @@ func (r *resourcePrinterImpl) PrintObj(obj runtime.Object, _ io.Writer) error { r.ch <- event.Event{ Type: event.ApplyType, ApplyEvent: event.ApplyEvent{ - Type: event.ApplyEventResourceUpdate, - Operation: r.applyOperation, - Object: obj.(*unstructured.Unstructured), Identifier: object.RuntimeToObjMeta(obj), + Operation: r.applyOperation, + Resource: obj.(*unstructured.Unstructured), }, } return nil diff --git a/pkg/apply/task/printer_adapter_test.go b/pkg/apply/task/printer_adapter_test.go index 15876a3..657a201 100644 --- a/pkg/apply/task/printer_adapter_test.go +++ b/pkg/apply/task/printer_adapter_test.go @@ -50,5 +50,5 @@ func TestKubectlPrinterAdapter(t *testing.T) { assert.NoError(t, err) assert.Equal(t, event.ServersideApplied, msg.ApplyEvent.Operation) - assert.Equal(t, deployment, msg.ApplyEvent.Object) + assert.Equal(t, deployment, msg.ApplyEvent.Resource) } diff --git a/pkg/apply/task/prune_task.go b/pkg/apply/task/prune_task.go index 1a6caf6..43bc632 100644 --- a/pkg/apply/task/prune_task.go +++ b/pkg/apply/task/prune_task.go @@ -6,16 +6,20 @@ package task import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/apply/prune" "sigs.k8s.io/cli-utils/pkg/apply/taskrunner" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/inventory" + "sigs.k8s.io/cli-utils/pkg/object" ) // PruneTask prunes objects from the cluster // by using the PruneOptions. The provided Objects is the // set of resources that have just been applied. type PruneTask struct { + TaskName string + PruneOptions *prune.PruneOptions InventoryObject inventory.InventoryInfo Objects []*unstructured.Unstructured @@ -24,6 +28,18 @@ type PruneTask struct { InventoryPolicy inventory.InventoryPolicy } +func (p *PruneTask) Name() string { + return p.TaskName +} + +func (p *PruneTask) Action() event.ResourceAction { + return event.PruneAction +} + +func (p *PruneTask) Identifiers() []object.ObjMetadata { + return object.UnstructuredsToObjMetas(p.Objects) +} + // Start creates a new goroutine that will invoke // the Run function on the PruneOptions to update // the cluster. It will push a TaskResult on the taskChannel diff --git a/pkg/apply/taskrunner/collector.go b/pkg/apply/taskrunner/collector.go index 8b300bf..f608f91 100644 --- a/pkg/apply/taskrunner/collector.go +++ b/pkg/apply/taskrunner/collector.go @@ -62,7 +62,7 @@ func getGeneration(r *event.ResourceStatus) int64 { } // conditionMet tests whether the provided Condition holds true for -// all resources given by the list of Identifiers. +// all resources given by the list of Ids. func (a *resourceStatusCollector) conditionMet(rwd []resourceWaitData, c Condition) bool { switch c { case AllCurrent: @@ -75,7 +75,7 @@ func (a *resourceStatusCollector) conditionMet(rwd []resourceWaitData, c Conditi } // allMatchStatus checks whether all resources given by the -// Identifiers parameter has the provided status. +// Ids parameter has the provided status. func (a *resourceStatusCollector) allMatchStatus(rwd []resourceWaitData, s status.Status) bool { for _, wd := range rwd { ri, found := a.resourceMap[wd.identifier] @@ -90,7 +90,7 @@ func (a *resourceStatusCollector) allMatchStatus(rwd []resourceWaitData, s statu } // noneMatchStatus checks whether none of the resources given -// by the Identifiers parameters has the provided status. +// by the Ids parameters has the provided status. func (a *resourceStatusCollector) noneMatchStatus(rwd []resourceWaitData, s status.Status) bool { for _, wd := range rwd { ri, found := a.resourceMap[wd.identifier] diff --git a/pkg/apply/taskrunner/runner.go b/pkg/apply/taskrunner/runner.go index 854ef44..803e760 100644 --- a/pkg/apply/taskrunner/runner.go +++ b/pkg/apply/taskrunner/runner.go @@ -159,7 +159,7 @@ func (b *baseRunner) run(ctx context.Context, taskQueue chan Task, // If the statusChannel has closed or we are preparing // to abort the task processing, we just ignore all // statusEvents. - // TODO(mortent): Check if a losed statusChannel might + // TODO(mortent): Check if a closed statusChannel might // create a busy loop here. if !ok || abort { continue @@ -183,8 +183,10 @@ func (b *baseRunner) run(ctx context.Context, taskQueue chan Task, eventChannel <- event.Event{ Type: event.StatusType, StatusEvent: event.StatusEvent{ - Type: event.StatusEventResourceUpdate, - Resource: statusEvent.Resource, + Identifier: statusEvent.Resource.Identifier, + PollResourceInfo: statusEvent.Resource, + Resource: statusEvent.Resource.Resource, + Error: statusEvent.Error, }, } } @@ -208,6 +210,14 @@ func (b *baseRunner) run(ctx context.Context, taskQueue chan Task, // If everything is ok, we fetch and start the next task. case msg := <-taskContext.TaskChannel(): currentTask.ClearTimeout() + taskContext.EventChannel() <- event.Event{ + Type: event.ActionGroupType, + ActionGroupEvent: event.ActionGroupEvent{ + GroupName: currentTask.Name(), + Action: currentTask.Action(), + Type: event.Finished, + }, + } if msg.Err != nil { b.amendTimeoutError(msg.Err) return msg.Err @@ -277,6 +287,15 @@ func (b *baseRunner) nextTask(taskQueue chan Task, return nil, true } + taskContext.EventChannel() <- event.Event{ + Type: event.ActionGroupType, + ActionGroupEvent: event.ActionGroupEvent{ + GroupName: tsk.Name(), + Action: tsk.Action(), + Type: event.Started, + }, + } + switch st := tsk.(type) { case *WaitTask: // The wait tasks need to be handled specifically here. Before diff --git a/pkg/apply/taskrunner/runner_test.go b/pkg/apply/taskrunner/runner_test.go index 67e7d3f..dc1fc10 100644 --- a/pkg/apply/taskrunner/runner_test.go +++ b/pkg/apply/taskrunner/runner_test.go @@ -16,6 +16,7 @@ import ( pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/cli-utils/pkg/testutil" ) var ( @@ -50,15 +51,15 @@ func TestBaseRunner(t *testing.T) { "wait task runs until condition is met": { identifiers: []object.ObjMetadata{depID, cmID}, tasks: []Task{ - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.ApplyType, }, duration: 3 * time.Second, }, - NewWaitTask([]object.ObjMetadata{depID, cmID}, AllCurrent, - 1*time.Minute), - &busyTask{ + NewWaitTask("wait", []object.ObjMetadata{depID, cmID}, AllCurrent, + 1*time.Minute, testutil.NewFakeRESTMapper()), + &fakeApplyTask{ resultEvent: event.Event{ Type: event.PruneType, }, @@ -83,17 +84,23 @@ func TestBaseRunner(t *testing.T) { }, }, expectedEventTypes: []event.Type{ + event.ActionGroupType, event.ApplyType, + event.ActionGroupType, + event.ActionGroupType, event.StatusType, event.StatusType, + event.ActionGroupType, + event.ActionGroupType, event.PruneType, + event.ActionGroupType, }, }, "wait task times out eventually": { identifiers: []object.ObjMetadata{depID, cmID}, tasks: []Task{ - NewWaitTask([]object.ObjMetadata{depID, cmID}, AllCurrent, - 2*time.Second), + NewWaitTask("wait", []object.ObjMetadata{depID, cmID}, AllCurrent, + 2*time.Second, testutil.NewFakeRESTMapper()), }, statusEventsDelay: time.Second, statusEvents: []pollevent.Event{ @@ -119,25 +126,25 @@ func TestBaseRunner(t *testing.T) { "tasks run in order": { identifiers: []object.ObjMetadata{}, tasks: []Task{ - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.ApplyType, }, duration: 1 * time.Second, }, - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.PruneType, }, duration: 1 * time.Second, }, - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.ApplyType, }, duration: 1 * time.Second, }, - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.PruneType, }, @@ -147,10 +154,18 @@ func TestBaseRunner(t *testing.T) { statusEventsDelay: 1 * time.Second, statusEvents: []pollevent.Event{}, expectedEventTypes: []event.Type{ + event.ActionGroupType, event.ApplyType, + event.ActionGroupType, + event.ActionGroupType, event.PruneType, + event.ActionGroupType, + event.ActionGroupType, event.ApplyType, + event.ActionGroupType, + event.ActionGroupType, event.PruneType, + event.ActionGroupType, }, }, } @@ -235,13 +250,13 @@ func TestBaseRunnerCancellation(t *testing.T) { "cancellation while custom task is running": { identifiers: []object.ObjMetadata{depID}, tasks: []Task{ - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.ApplyType, }, duration: 4 * time.Second, }, - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.PruneType, }, @@ -250,34 +265,40 @@ func TestBaseRunnerCancellation(t *testing.T) { }, contextTimeout: 2 * time.Second, expectedEventTypes: []event.Type{ + event.ActionGroupType, event.ApplyType, + event.ActionGroupType, }, }, "cancellation while wait task is running": { identifiers: []object.ObjMetadata{depID}, tasks: []Task{ - NewWaitTask([]object.ObjMetadata{depID}, AllCurrent, 20*time.Second), - &busyTask{ + NewWaitTask("wait", []object.ObjMetadata{depID}, AllCurrent, + 20*time.Second, testutil.NewFakeRESTMapper()), + &fakeApplyTask{ resultEvent: event.Event{ Type: event.PruneType, }, duration: 2 * time.Second, }, }, - contextTimeout: 2 * time.Second, - expectedEventTypes: []event.Type{}, + contextTimeout: 2 * time.Second, + expectedEventTypes: []event.Type{ + event.ActionGroupType, + event.ActionGroupType, + }, }, "error while custom task is running": { identifiers: []object.ObjMetadata{depID}, tasks: []Task{ - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.ApplyType, }, duration: 2 * time.Second, err: testError, }, - &busyTask{ + &fakeApplyTask{ resultEvent: event.Event{ Type: event.PruneType, }, @@ -287,14 +308,17 @@ func TestBaseRunnerCancellation(t *testing.T) { contextTimeout: 30 * time.Second, expectedError: testError, expectedEventTypes: []event.Type{ + event.ActionGroupType, event.ApplyType, + event.ActionGroupType, }, }, "error from status poller while wait task is running": { identifiers: []object.ObjMetadata{depID}, tasks: []Task{ - NewWaitTask([]object.ObjMetadata{depID}, AllCurrent, 20*time.Second), - &busyTask{ + NewWaitTask("wait", []object.ObjMetadata{depID}, AllCurrent, + 20*time.Second, testutil.NewFakeRESTMapper()), + &fakeApplyTask{ resultEvent: event.Event{ Type: event.PruneType, }, @@ -308,9 +332,12 @@ func TestBaseRunnerCancellation(t *testing.T) { Error: testError, }, }, - contextTimeout: 30 * time.Second, - expectedError: testError, - expectedEventTypes: []event.Type{}, + contextTimeout: 30 * time.Second, + expectedError: testError, + expectedEventTypes: []event.Type{ + event.ActionGroupType, + event.ActionGroupType, + }, }, } @@ -379,20 +406,33 @@ func TestBaseRunnerCancellation(t *testing.T) { } } -type busyTask struct { +type fakeApplyTask struct { + name string resultEvent event.Event duration time.Duration err error } -func (b *busyTask) Start(taskContext *TaskContext) { +func (f *fakeApplyTask) Name() string { + return f.name +} + +func (f *fakeApplyTask) Action() event.ResourceAction { + return event.ApplyAction +} + +func (f *fakeApplyTask) Identifiers() []object.ObjMetadata { + return []object.ObjMetadata{} +} + +func (f *fakeApplyTask) Start(taskContext *TaskContext) { go func() { - <-time.NewTimer(b.duration).C - taskContext.EventChannel() <- b.resultEvent + <-time.NewTimer(f.duration).C + taskContext.EventChannel() <- f.resultEvent taskContext.TaskChannel() <- TaskResult{ - Err: b.err, + Err: f.err, } }() } -func (b *busyTask) ClearTimeout() {} +func (f *fakeApplyTask) ClearTimeout() {} diff --git a/pkg/apply/taskrunner/task.go b/pkg/apply/taskrunner/task.go index f08ba72..6bd2979 100644 --- a/pkg/apply/taskrunner/task.go +++ b/pkg/apply/taskrunner/task.go @@ -4,8 +4,15 @@ package taskrunner import ( + "fmt" + "reflect" "time" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/restmapper" + "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object" ) @@ -13,23 +20,28 @@ import ( // Task is the interface that must be implemented by // all tasks that will be executed by the taskrunner. type Task interface { + Name() string + Action() event.ResourceAction + Identifiers() []object.ObjMetadata Start(taskContext *TaskContext) ClearTimeout() } // NewWaitTask creates a new wait task where we will wait until // the resources specifies by ids all meet the specified condition. -func NewWaitTask(ids []object.ObjMetadata, cond Condition, timeout time.Duration) *WaitTask { +func NewWaitTask(name string, ids []object.ObjMetadata, cond Condition, timeout time.Duration, mapper meta.RESTMapper) *WaitTask { // Create the token channel and only add one item. tokenChannel := make(chan struct{}, 1) tokenChannel <- struct{}{} return &WaitTask{ - Identifiers: ids, - Condition: cond, - Timeout: timeout, + name: name, + Ids: ids, + Condition: cond, + Timeout: timeout, - token: tokenChannel, + mapper: mapper, + token: tokenChannel, } } @@ -41,14 +53,18 @@ func NewWaitTask(ids []object.ObjMetadata, cond Condition, timeout time.Duration // is handled in a special way to the taskrunner and is a part of the core // package. type WaitTask struct { - // Identifiers is the list of resources that we are waiting for. - Identifiers []object.ObjMetadata + // name allows providing a name for the task. + name string + // Ids is the list of resources that we are waiting for. + Ids []object.ObjMetadata // Condition defines the status we want all resources to reach Condition Condition // Timeout defines how long we are willing to wait for the condition // to be met. Timeout time.Duration + mapper meta.RESTMapper + // cancelFunc is a function that will cancel the timeout timer // on the task. cancelFunc func() @@ -62,6 +78,18 @@ type WaitTask struct { token chan struct{} } +func (w *WaitTask) Name() string { + return w.name +} + +func (w *WaitTask) Action() event.ResourceAction { + return event.WaitAction +} + +func (w *WaitTask) Identifiers() []object.ObjMetadata { + return w.Ids +} + // Start kicks off the task. For the wait task, this just means // setting up the timeout timer. func (w *WaitTask) Start(taskContext *TaskContext) { @@ -84,7 +112,7 @@ func (w *WaitTask) setTimer(taskContext *TaskContext) { case <-w.token: taskContext.TaskChannel() <- TaskResult{ Err: &TimeoutError{ - Identifiers: w.Identifiers, + Identifiers: w.Ids, Timeout: w.Timeout, Condition: w.Condition, }, @@ -111,7 +139,7 @@ func (w *WaitTask) checkCondition(taskContext *TaskContext, coll *resourceStatus // was applied. func (w *WaitTask) computeResourceWaitData(taskContext *TaskContext) []resourceWaitData { var rwd []resourceWaitData - for _, id := range w.Identifiers { + for _, id := range w.Ids { if taskContext.ResourceFailed(id) { continue } @@ -137,11 +165,27 @@ func (w *WaitTask) startAndComplete(taskContext *TaskContext) { // for the task has been met, or something has failed so the task // need to be stopped. func (w *WaitTask) complete(taskContext *TaskContext) { + var err error + for _, obj := range w.Ids { + if (obj.GroupKind.Group == v1.SchemeGroupVersion.Group || + obj.GroupKind.Group == v1beta1.SchemeGroupVersion.Group) && + obj.GroupKind.Kind == "CustomResourceDefinition" { + ddRESTMapper, err := extractDeferredDiscoveryRESTMapper(w.mapper) + if err == nil { + ddRESTMapper.Reset() + // We only need to reset once. + break + } + continue + } + } select { // Only do something if we can get the token. case <-w.token: go func() { - taskContext.TaskChannel() <- TaskResult{} + taskContext.TaskChannel() <- TaskResult{ + Err: err, + } }() default: return @@ -185,3 +229,20 @@ func (c Condition) Meets(s status.Status) bool { return false } } + +// extractDeferredDiscoveryRESTMapper unwraps the provided RESTMapper +// interface to get access to the underlying DeferredDiscoveryRESTMapper +// that can be reset. +func extractDeferredDiscoveryRESTMapper(mapper meta.RESTMapper) (*restmapper.DeferredDiscoveryRESTMapper, + error) { + val := reflect.ValueOf(mapper) + if val.Type().Kind() != reflect.Struct { + return nil, fmt.Errorf("unexpected RESTMapper type: %s", val.Type().String()) + } + fv := val.FieldByName("RESTMapper") + ddRESTMapper, ok := fv.Interface().(*restmapper.DeferredDiscoveryRESTMapper) + if !ok { + return nil, fmt.Errorf("unexpected RESTMapper type") + } + return ddRESTMapper, nil +} diff --git a/pkg/apply/taskrunner/task_test.go b/pkg/apply/taskrunner/task_test.go index a992368..bdde850 100644 --- a/pkg/apply/taskrunner/task_test.go +++ b/pkg/apply/taskrunner/task_test.go @@ -10,10 +10,12 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/cli-utils/pkg/testutil" ) func TestWaitTask_TimeoutTriggered(t *testing.T) { - task := NewWaitTask([]object.ObjMetadata{}, AllCurrent, 2*time.Second) + task := NewWaitTask("wait", []object.ObjMetadata{}, AllCurrent, + 2*time.Second, testutil.NewFakeRESTMapper()) eventChannel := make(chan event.Event) taskContext := NewTaskContext(eventChannel) @@ -35,7 +37,8 @@ func TestWaitTask_TimeoutTriggered(t *testing.T) { } func TestWaitTask_TimeoutCancelled(t *testing.T) { - task := NewWaitTask([]object.ObjMetadata{}, AllCurrent, 2*time.Second) + task := NewWaitTask("wait", []object.ObjMetadata{}, AllCurrent, + 2*time.Second, testutil.NewFakeRESTMapper()) eventChannel := make(chan event.Event) taskContext := NewTaskContext(eventChannel) @@ -54,7 +57,8 @@ func TestWaitTask_TimeoutCancelled(t *testing.T) { } func TestWaitTask_SingleTaskResult(t *testing.T) { - task := NewWaitTask([]object.ObjMetadata{}, AllCurrent, 2*time.Second) + task := NewWaitTask("wait", []object.ObjMetadata{}, AllCurrent, + 2*time.Second, testutil.NewFakeRESTMapper()) eventChannel := make(chan event.Event) taskContext := NewTaskContext(eventChannel) diff --git a/pkg/kstatus/polling/event/eventtype_string.go b/pkg/kstatus/polling/event/eventtype_string.go index 49cb627..ba39dc7 100644 --- a/pkg/kstatus/polling/event/eventtype_string.go +++ b/pkg/kstatus/polling/event/eventtype_string.go @@ -1,6 +1,3 @@ -// Copyright 2020 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - // Code generated by "stringer -type=EventType"; DO NOT EDIT. package event diff --git a/pkg/object/objmetadata.go b/pkg/object/objmetadata.go index 484ff96..8f30df0 100644 --- a/pkg/object/objmetadata.go +++ b/pkg/object/objmetadata.go @@ -62,6 +62,19 @@ type ObjMetadata struct { GroupKind schema.GroupKind } +// ObjMetas is a slice of ObjMetadata. +type ObjMetas []ObjMetadata + +// Contains checks if the provided ObjMetadata exists in the ObjMetas slice. +func (oms ObjMetas) Contains(id ObjMetadata) bool { + for _, om := range oms { + if om == id { + return true + } + } + return false +} + // CreateObjMetadata returns a pointer to an ObjMetadata struct filled // with the passed values. This function normalizes and validates the // passed fields and returns an error for bad parameters. diff --git a/pkg/print/list/base.go b/pkg/print/list/base.go index 5ae529c..9604b0e 100644 --- a/pkg/print/list/base.go +++ b/pkg/print/list/base.go @@ -13,11 +13,12 @@ import ( ) type Formatter interface { - FormatApplyEvent(ae event.ApplyEvent, as *ApplyStats, c Collector) error - FormatStatusEvent(se event.StatusEvent, sc Collector) error - FormatPruneEvent(pe event.PruneEvent, ps *PruneStats) error - FormatDeleteEvent(de event.DeleteEvent, ds *DeleteStats) error + FormatApplyEvent(ae event.ApplyEvent) error + FormatStatusEvent(se event.StatusEvent) error + FormatPruneEvent(pe event.PruneEvent) error + FormatDeleteEvent(de event.DeleteEvent) error FormatErrorEvent(ee event.ErrorEvent) error + FormatActionGroupEvent(age event.ActionGroupEvent, ags []event.ActionGroup, as *ApplyStats, ps *PruneStats, ds *DeleteStats, c Collector) error } type FormatterFactory func(ioStreams genericclioptions.IOStreams, @@ -38,6 +39,7 @@ type ApplyStats struct { func (a *ApplyStats) inc(op event.ApplyEventOperation) { switch op { + case event.ApplyUnspecified: case event.ServersideApplied: a.ServersideApplied++ case event.Created: @@ -46,13 +48,15 @@ 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) incFailed() { + a.Failed++ +} + func (a *ApplyStats) Sum() int { return a.ServersideApplied + a.Configured + a.Unchanged + a.Created + a.Failed } @@ -113,69 +117,89 @@ func (sc *StatusCollector) LatestStatus() map[object.ObjMetadata]event.StatusEve // format on StdOut. As we support other printer implementations // this should probably be an interface. // This function will block until the channel is closed. +//nolint:gocyclo func (b *BaseListPrinter) Print(ch <-chan event.Event, previewStrategy common.DryRunStrategy) error { + var actionGroups []event.ActionGroup applyStats := &ApplyStats{} + pruneStats := &PruneStats{} + deleteStats := &DeleteStats{} statusCollector := &StatusCollector{ latestStatus: make(map[object.ObjMetadata]event.StatusEvent), } printStatus := false - pruneStats := &PruneStats{} - deleteStats := &DeleteStats{} formatter := b.FormatterFactory(b.IOStreams, previewStrategy) for e := range ch { switch e.Type { + case event.InitType: + actionGroups = e.InitEvent.ActionGroups case event.ErrorType: _ = formatter.FormatErrorEvent(e.ErrorEvent) return e.ErrorEvent.Err case event.ApplyType: - if e.ApplyEvent.Type == event.ApplyEventResourceUpdate { - applyStats.inc(e.ApplyEvent.Operation) + applyStats.inc(e.ApplyEvent.Operation) + if e.ApplyEvent.Error != nil { + applyStats.incFailed() } - if e.ApplyEvent.Type == event.ApplyEventCompleted { - printStatus = true - } - if err := formatter.FormatApplyEvent(e.ApplyEvent, applyStats, statusCollector); err != nil { + if err := formatter.FormatApplyEvent(e.ApplyEvent); err != nil { return err } case event.StatusType: - if se := e.StatusEvent; se.Type == event.StatusEventResourceUpdate { - statusCollector.updateStatus(e.StatusEvent.Resource.Identifier, e.StatusEvent) - if printStatus { - if err := formatter.FormatStatusEvent(e.StatusEvent, statusCollector); err != nil { - return err - } + statusCollector.updateStatus(e.StatusEvent.Identifier, e.StatusEvent) + if printStatus { + if err := formatter.FormatStatusEvent(e.StatusEvent); err != nil { + return err } } case event.PruneType: - if e.PruneEvent.Type == event.PruneEventResourceUpdate { - switch e.PruneEvent.Operation { - case event.Pruned: - pruneStats.incPruned() - case event.PruneSkipped: - pruneStats.incSkipped() - } + switch e.PruneEvent.Operation { + case event.Pruned: + pruneStats.incPruned() + case event.PruneSkipped: + pruneStats.incSkipped() } - if e.PruneEvent.Type == event.PruneEventFailed { + if e.PruneEvent.Error != nil { pruneStats.incFailed() } - if err := formatter.FormatPruneEvent(e.PruneEvent, pruneStats); err != nil { + if err := formatter.FormatPruneEvent(e.PruneEvent); err != nil { return err } case event.DeleteType: - if e.DeleteEvent.Type == event.DeleteEventResourceUpdate { - switch e.DeleteEvent.Operation { - case event.Deleted: - deleteStats.incDeleted() - case event.DeleteSkipped: - deleteStats.incSkipped() - } + switch e.DeleteEvent.Operation { + case event.Deleted: + deleteStats.incDeleted() + case event.DeleteSkipped: + deleteStats.incSkipped() } - if e.DeleteEvent.Type == event.DeleteEventFailed { + if e.DeleteEvent.Error != nil { deleteStats.incFailed() } - if err := formatter.FormatDeleteEvent(e.DeleteEvent, deleteStats); err != nil { + if err := formatter.FormatDeleteEvent(e.DeleteEvent); err != nil { return err } + case event.ActionGroupType: + if err := formatter.FormatActionGroupEvent(e.ActionGroupEvent, actionGroups, applyStats, + pruneStats, deleteStats, statusCollector); err != nil { + return err + } + + switch e.ActionGroupEvent.Action { + case event.ApplyAction: + if e.ActionGroupEvent.Type == event.Started { + applyStats = &ApplyStats{} + } + case event.PruneAction: + if e.ActionGroupEvent.Type == event.Started { + pruneStats = &PruneStats{} + } + case event.DeleteAction: + if e.ActionGroupEvent.Type == event.Started { + deleteStats = &DeleteStats{} + } + case event.WaitAction: + if e.ActionGroupEvent.Type == event.Started { + printStatus = true + } + } } } failedSum := applyStats.Failed + pruneStats.Failed + deleteStats.Failed @@ -184,3 +208,12 @@ func (b *BaseListPrinter) Print(ch <-chan event.Event, previewStrategy common.Dr } return nil } + +func ActionGroupByName(name string, ags []event.ActionGroup) (event.ActionGroup, bool) { + for _, ag := range ags { + if ag.Name == name { + return ag, true + } + } + return event.ActionGroup{}, false +} diff --git a/pkg/testutil/events.go b/pkg/testutil/events.go new file mode 100644 index 0000000..7112be7 --- /dev/null +++ b/pkg/testutil/events.go @@ -0,0 +1,185 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package testutil + +import ( + "fmt" + + "sigs.k8s.io/cli-utils/pkg/apply/event" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" + "sigs.k8s.io/cli-utils/pkg/object" +) + +type ExpEvent struct { + EventType event.Type + + ActionGroupEvent *ExpActionGroupEvent + ApplyEvent *ExpApplyEvent + StatusEvent *ExpStatusEvent + PruneEvent *ExpPruneEvent + DeleteEvent *ExpDeleteEvent +} + +type ExpActionGroupEvent struct { + Name string + Action event.ResourceAction +} + +type ExpApplyEvent struct { + Operation event.ApplyEventOperation + Identifier object.ObjMetadata + Error error +} + +type ExpStatusEvent struct { + Identifier object.ObjMetadata + Status status.Status + Error error +} + +type ExpPruneEvent struct { + Operation event.PruneEventOperation + Identifier object.ObjMetadata + Error error +} + +type ExpDeleteEvent struct { + Operation event.DeleteEventOperation + Identifier object.ObjMetadata + Error error +} + +func VerifyEvents(expEvents []ExpEvent, events []event.Event) error { + expEventIndex := 0 + for i := range events { + e := events[i] + ee := expEvents[expEventIndex] + if isMatch(ee, e) { + expEventIndex += 1 + if expEventIndex >= len(expEvents) { + return nil + } + } + } + return fmt.Errorf("event %s not found", expEvents[expEventIndex].EventType) +} + +var nilIdentifier = object.ObjMetadata{} + +// nolint:gocyclo +// TODO(mortent): This function is pretty complex and with quite a bit of +// duplication. We should see if there is a better way to provide a flexible +// way to verify that we go the expected events. +func isMatch(ee ExpEvent, e event.Event) bool { + if ee.EventType != e.Type { + return false + } + + // nolint:gocritic + switch e.Type { + case event.ActionGroupType: + agee := ee.ActionGroupEvent + + if agee == nil { + return true + } + + age := e.ActionGroupEvent + + if agee.Name != age.GroupName { + return false + } + + if agee.Action != age.Action { + return false + } + case event.ApplyType: + aee := ee.ApplyEvent + // If no more information is specified, we consider it a match. + if aee == nil { + return true + } + ae := e.ApplyEvent + + if aee.Identifier != nilIdentifier { + if aee.Identifier != ae.Identifier { + return false + } + } + + if aee.Operation != ae.Operation { + return false + } + + if aee.Error != nil { + return ae.Error != nil + } + return ae.Error == nil + + case event.StatusType: + see := ee.StatusEvent + if see == nil { + return true + } + se := e.StatusEvent + + if see.Identifier != se.Identifier { + return false + } + + if see.Status != se.PollResourceInfo.Status { + return false + } + + if see.Error != nil { + return se.Error != nil + } + return se.Error == nil + + case event.PruneType: + pee := ee.PruneEvent + if pee == nil { + return true + } + pe := e.PruneEvent + + if pee.Identifier != nilIdentifier { + if pee.Identifier != pe.Identifier { + return false + } + } + + if pee.Operation != pe.Operation { + return false + } + + if pee.Error != nil { + return pe.Error != nil + } + return pe.Error == nil + + case event.DeleteType: + dee := ee.DeleteEvent + if dee == nil { + return true + } + de := e.DeleteEvent + + if dee.Identifier != nilIdentifier { + if dee.Identifier != de.Identifier { + return false + } + } + + if dee.Operation != de.Operation { + return false + } + + if dee.Error != nil { + return de.Error != nil + } + return de.Error == nil + } + return true +} diff --git a/test/e2e/apply_and_destroy_test.go b/test/e2e/apply_and_destroy_test.go index 3dc1358..97efeea 100644 --- a/test/e2e/apply_and_destroy_test.go +++ b/test/e2e/apply_and_destroy_test.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply" "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/inventory" + "sigs.k8s.io/cli-utils/pkg/testutil" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -38,18 +39,12 @@ func applyAndDestroyTest(c client.Client, invConfig InventoryConfig, inventoryNa Expect(e.Type).NotTo(Equal(event.ErrorType)) applierEvents = append(applierEvents, e) } - err := verifyEvents([]expEvent{ + err := testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - }, - { - eventType: event.ApplyType, - }, - { - eventType: event.PruneType, + EventType: event.ApplyType, }, }, applierEvents) Expect(err).ToNot(HaveOccurred()) @@ -63,12 +58,9 @@ func applyAndDestroyTest(c client.Client, invConfig InventoryConfig, inventoryNa destroyInv := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) option := &apply.DestroyerOption{InventoryPolicy: inventory.AdoptIfNoInventory} destroyerEvents := runCollectNoErr(destroyer.Run(destroyInv, option)) - err = verifyEvents([]expEvent{ + err = testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.DeleteType, - }, - { - eventType: event.DeleteType, + EventType: event.DeleteType, }, }, destroyerEvents) Expect(err).ToNot(HaveOccurred()) diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 3468008..a80bfb9 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -16,8 +16,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/common" - "sigs.k8s.io/cli-utils/pkg/kstatus/status" - "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -180,184 +178,3 @@ func updateReplicas(u *unstructured.Unstructured, replicas int) *unstructured.Un } return u } - -type expEvent struct { - eventType event.Type - - applyEvent *expApplyEvent - statusEvent *expStatusEvent - pruneEvent *expPruneEvent - deleteEvent *expDeleteEvent -} - -type expApplyEvent struct { - applyEventType event.ApplyEventType - operation event.ApplyEventOperation - identifier object.ObjMetadata - error error -} - -type expStatusEvent struct { - statusEventType event.StatusEventType - identifier object.ObjMetadata - status status.Status - error error -} - -type expPruneEvent struct { - pruneEventType event.PruneEventType - operation event.PruneEventOperation - identifier object.ObjMetadata - error error -} - -type expDeleteEvent struct { - deleteEventType event.DeleteEventType - operation event.DeleteEventOperation - identifier object.ObjMetadata - error error -} - -func verifyEvents(expEvents []expEvent, events []event.Event) error { - expEventIndex := 0 - for i := range events { - e := events[i] - ee := expEvents[expEventIndex] - if isMatch(ee, e) { - expEventIndex += 1 - if expEventIndex >= len(expEvents) { - return nil - } - } - } - return fmt.Errorf("event %s not found", expEvents[expEventIndex].eventType) -} - -var nilIdentifier = object.ObjMetadata{} - -// nolint:gocyclo -// TODO(mortent): This function is pretty complex and with quite a bit of -// duplication. We should see if there is a better way to provide a flexible -// way to verify that we go the expected events. -func isMatch(ee expEvent, e event.Event) bool { - if ee.eventType != e.Type { - return false - } - - // nolint:gocritic - switch e.Type { - case event.ApplyType: - aee := ee.applyEvent - // If no more information is specified, we consider it a match. - if aee == nil { - return true - } - ae := e.ApplyEvent - - if aee.applyEventType != ae.Type { - return false - } - - if aee.applyEventType == event.ApplyEventResourceUpdate { - if aee.identifier != nilIdentifier { - if aee.identifier != ae.Identifier { - return false - } - } - - if aee.operation != ae.Operation { - return false - } - } - - if aee.error != nil { - return ae.Error != nil - } - return ae.Error == nil - - case event.StatusType: - see := ee.statusEvent - if see == nil { - return true - } - se := e.StatusEvent - - if see.statusEventType != se.Type { - return false - } - - if see.statusEventType == event.StatusEventResourceUpdate { - if see.identifier != nilIdentifier { - if see.identifier != se.Resource.Identifier { - return false - } - } - - if see.status != se.Resource.Status { - return false - } - - if see.error != nil { - return se.Resource.Error != nil - } - return se.Resource.Error == nil - } - - case event.PruneType: - pee := ee.pruneEvent - if pee == nil { - return true - } - pe := e.PruneEvent - - if pee.pruneEventType != pe.Type { - return false - } - - if pee.pruneEventType == event.PruneEventResourceUpdate { - if pee.identifier != nilIdentifier { - if pee.identifier != pe.Identifier { - return false - } - } - - if pee.operation != pe.Operation { - return false - } - } - - if pee.error != nil { - return pe.Error != nil - } - return pe.Error == nil - - case event.DeleteType: - dee := ee.deleteEvent - if dee == nil { - return true - } - de := e.DeleteEvent - - if dee.deleteEventType != de.Type { - return false - } - - if dee.deleteEventType == event.DeleteEventResourceUpdate { - if dee.identifier != nilIdentifier { - if dee.identifier != de.Identifier { - return false - } - } - - if dee.operation != de.Operation { - return false - } - } - - if dee.error != nil { - return de.Error != nil - } - return de.Error == nil - } - return true -} diff --git a/test/e2e/continue_on_error_test.go b/test/e2e/continue_on_error_test.go index f68cf09..0d5820d 100644 --- a/test/e2e/continue_on_error_test.go +++ b/test/e2e/continue_on_error_test.go @@ -15,6 +15,7 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/cli-utils/pkg/testutil" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -36,34 +37,23 @@ func continueOnErrorTest(_ client.Client, invConfig InventoryConfig, inventoryNa Expect(e.Type).NotTo(Equal(event.ErrorType)) applierEvents = append(applierEvents, e) } - err := verifyEvents([]expEvent{ + err := testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.InitType, + EventType: event.InitType, }, { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Failed, - identifier: object.UnstructuredToObjMeta(manifestToUnstructured(invalidCrd)), - error: fmt.Errorf("failed to apply"), + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Identifier: object.UnstructuredToObjMeta(manifestToUnstructured(invalidCrd)), + Error: fmt.Errorf("failed to apply"), }, }, { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Created, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Operation: event.Created, }, }, - { - // complete - eventType: event.ApplyType, - }, - { - // complete - eventType: event.PruneType, - }, }, applierEvents) Expect(err).ToNot(HaveOccurred()) @@ -71,13 +61,12 @@ func continueOnErrorTest(_ client.Client, invConfig InventoryConfig, inventoryNa destroyer := invConfig.DestroyerFactoryFunc() option := &apply.DestroyerOption{InventoryPolicy: inventory.AdoptIfNoInventory} destroyerEvents := runCollectNoErr(destroyer.Run(inv, option)) - err = verifyEvents([]expEvent{ + err = testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.DeleteType, - deleteEvent: &expDeleteEvent{ - deleteEventType: event.DeleteEventResourceUpdate, - operation: event.Deleted, - error: nil, + EventType: event.DeleteType, + DeleteEvent: &testutil.ExpDeleteEvent{ + Operation: event.Deleted, + Error: nil, }, }, }, destroyerEvents) diff --git a/test/e2e/crd_test.go b/test/e2e/crd_test.go index fab7b72..9a18212 100644 --- a/test/e2e/crd_test.go +++ b/test/e2e/crd_test.go @@ -16,6 +16,7 @@ import ( "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/cli-utils/pkg/testutil" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -41,37 +42,33 @@ func crdTest(_ client.Client, invConfig InventoryConfig, inventoryName, namespac Expect(e.Type).NotTo(Equal(event.ErrorType)) applierEvents = append(applierEvents, e) } - err := verifyEvents([]expEvent{ + err := testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Created, - identifier: object.UnstructuredToObjMeta(manifestToUnstructured(crd)), - error: nil, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Operation: event.Created, + Identifier: object.UnstructuredToObjMeta(manifestToUnstructured(crd)), + Error: nil, }, }, { - eventType: event.StatusType, - statusEvent: &expStatusEvent{ - statusEventType: event.StatusEventResourceUpdate, - identifier: object.UnstructuredToObjMeta(manifestToUnstructured(crd)), - status: status.CurrentStatus, - error: nil, + EventType: event.StatusType, + StatusEvent: &testutil.ExpStatusEvent{ + Identifier: object.UnstructuredToObjMeta(manifestToUnstructured(crd)), + Status: status.CurrentStatus, + Error: nil, }, }, { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Created, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Operation: event.Created, }, }, { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Created, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Operation: event.Created, }, }, }, applierEvents) @@ -81,30 +78,27 @@ func crdTest(_ client.Client, invConfig InventoryConfig, inventoryName, namespac destroyer := invConfig.DestroyerFactoryFunc() option := &apply.DestroyerOption{InventoryPolicy: inventory.AdoptIfNoInventory} destroyerEvents := runCollectNoErr(destroyer.Run(inv, option)) - err = verifyEvents([]expEvent{ + err = testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.DeleteType, - deleteEvent: &expDeleteEvent{ - deleteEventType: event.DeleteEventResourceUpdate, - operation: event.Deleted, - error: nil, + EventType: event.DeleteType, + DeleteEvent: &testutil.ExpDeleteEvent{ + Operation: event.Deleted, + Error: nil, }, }, { - eventType: event.DeleteType, - deleteEvent: &expDeleteEvent{ - deleteEventType: event.DeleteEventResourceUpdate, - operation: event.Deleted, - error: nil, + EventType: event.DeleteType, + DeleteEvent: &testutil.ExpDeleteEvent{ + Operation: event.Deleted, + Error: nil, }, }, { - eventType: event.DeleteType, - deleteEvent: &expDeleteEvent{ - deleteEventType: event.DeleteEventResourceUpdate, - operation: event.Deleted, - identifier: object.UnstructuredToObjMeta(manifestToUnstructured(crd)), - error: nil, + EventType: event.DeleteType, + DeleteEvent: &testutil.ExpDeleteEvent{ + Operation: event.Deleted, + Identifier: object.UnstructuredToObjMeta(manifestToUnstructured(crd)), + Error: nil, }, }, }, destroyerEvents) diff --git a/test/e2e/inventory_policy_test.go b/test/e2e/inventory_policy_test.go index e01c5b6..4cebd22 100644 --- a/test/e2e/inventory_policy_test.go +++ b/test/e2e/inventory_policy_test.go @@ -17,6 +17,7 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/cli-utils/pkg/testutil" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -54,20 +55,12 @@ func inventoryPolicyMustMatchTest(c client.Client, invConfig InventoryConfig, na } By("Verify the events") - err := verifyEvents([]expEvent{ + err := testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Unchanged, - identifier: object.UnstructuredToObjMeta(deploymentManifest(namespaceName)), - error: inventory.NewInventoryOverlapError(fmt.Errorf("test")), - }, - }, - { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventCompleted, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Identifier: object.UnstructuredToObjMeta(deploymentManifest(namespaceName)), + Error: inventory.NewInventoryOverlapError(fmt.Errorf("test")), }, }, }, events) @@ -111,20 +104,13 @@ func inventoryPolicyAdoptIfNoInventoryTest(c client.Client, invConfig InventoryC } By("Verify the events") - err = verifyEvents([]expEvent{ + err = testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Configured, - identifier: object.UnstructuredToObjMeta(deploymentManifest(namespaceName)), - error: nil, - }, - }, - { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventCompleted, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Operation: event.Configured, + Identifier: object.UnstructuredToObjMeta(deploymentManifest(namespaceName)), + Error: nil, }, }, }, events) @@ -178,20 +164,13 @@ func inventoryPolicyAdoptAllTest(c client.Client, invConfig InventoryConfig, nam } By("Verify the events") - err := verifyEvents([]expEvent{ + err := testutil.VerifyEvents([]testutil.ExpEvent{ { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventResourceUpdate, - operation: event.Configured, - identifier: object.UnstructuredToObjMeta(deploymentManifest(namespaceName)), - error: nil, - }, - }, - { - eventType: event.ApplyType, - applyEvent: &expApplyEvent{ - applyEventType: event.ApplyEventCompleted, + EventType: event.ApplyType, + ApplyEvent: &testutil.ExpApplyEvent{ + Operation: event.Configured, + Identifier: object.UnstructuredToObjMeta(deploymentManifest(namespaceName)), + Error: nil, }, }, }, events)