Improve the event hierarchy

This commit is contained in:
Morten Torkildsen 2021-03-26 18:27:46 -07:00
parent a051c61f67
commit ae80e561e2
44 changed files with 1145 additions and 1278 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

185
pkg/testutil/events.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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