From 0f5256d9529d3eeba2f1282b2d3494fbc79c0b00 Mon Sep 17 00:00:00 2001 From: Morten Torkildsen Date: Wed, 22 Jan 2020 19:49:57 -0800 Subject: [PATCH] Clean up output format for status events command --- .../internal/commands/status/cmd/events.go | 6 +- .../commands/status/cmd/events_test.go | 92 +++++++++++-------- .../internal/commands/status/cmd/fetch.go | 6 +- .../commands/status/cmd/helpers_test.go | 4 +- .../internal/commands/status/cmd/print.go | 40 ++++---- .../internal/commands/status/cmd/util.go | 32 +++++-- .../internal/commands/status/cmd/wait.go | 6 +- 7 files changed, 113 insertions(+), 73 deletions(-) diff --git a/kustomize/internal/commands/status/cmd/events.go b/kustomize/internal/commands/status/cmd/events.go index f89a3fde0..0bf24d2dc 100644 --- a/kustomize/internal/commands/status/cmd/events.go +++ b/kustomize/internal/commands/status/cmd/events.go @@ -54,7 +54,7 @@ type EventsRunner struct { func (r *EventsRunner) runE(c *cobra.Command, args []string) error { ctx := context.Background() - resolver, err := r.newResolverFunc(r.Interval) + resolver, mapper, err := r.newResolverFunc(r.Interval) if err != nil { return errors.Wrap(err, "error creating resolver") } @@ -62,7 +62,9 @@ func (r *EventsRunner) runE(c *cobra.Command, args []string) error { // Set up a CaptureIdentifierFilter and run all inputs through the // filter with the pipeline to capture the inventory of resources // which we are interested in. - captureFilter := &CaptureIdentifiersFilter{} + captureFilter := &CaptureIdentifiersFilter{ + Mapper: mapper, + } filters := []kio.Filter{captureFilter} var inputs []kio.Reader diff --git a/kustomize/internal/commands/status/cmd/events_test.go b/kustomize/internal/commands/status/cmd/events_test.go index b570302df..8752966e1 100644 --- a/kustomize/internal/commands/status/cmd/events_test.go +++ b/kustomize/internal/commands/status/cmd/events_test.go @@ -200,7 +200,11 @@ func (e *EventOutput) allResources() []ResourceIdentifier { if !event.isResourceUpdateEvent() { continue } - r := event.identifier + r := ResourceIdentifier{ + namespace: event.namespace, + name: event.name, + kind: event.kind, + } if _, found := seenResources[r]; !found { resources = append(resources, r) seenResources[r] = true @@ -215,7 +219,12 @@ func (e *EventOutput) statusesForResource(resource ResourceIdentifier) []status. if !event.isResourceUpdateEvent() { continue } - if event.identifier.Equals(resource) { + identifier := ResourceIdentifier{ + namespace: event.namespace, + name: event.name, + kind: event.kind, + } + if identifier.Equals(resource) { statuses = append(statuses, event.status) } } @@ -223,32 +232,36 @@ func (e *EventOutput) statusesForResource(resource ResourceIdentifier) []status. } type EventOutputLine struct { - eventType string - aggStatus status.Status - identifier ResourceIdentifier - status status.Status - message string + eventType wait.EventType + aggStatus status.Status + kind string + namespace string + name string + status status.Status + message string } func (e *EventOutputLine) isResourceUpdateEvent() bool { - return e.eventType == string(wait.ResourceUpdate) + return e.eventType == wait.ResourceUpdate } var ( eventRegex = regexp.MustCompile(`^\s*` + - `(?P\S+)\s+` + - `(?P\S+)\s+` + - `((?P\S+)\s+` + + `(?P(?:Current|InProgress|Failed|Terminating|Unknown))\s+` + + `(?P.*\S)` + + `\s*$`) + resourceEventRegex = regexp.MustCompile(`^\s*` + `(?P\S+)\s+` + + `(?P(?:Current|InProgress|Failed|Terminating|Unknown))\s+` + + `(?P\S+)\s+` + `(?P\S+)\s+` + - `(?P\S+)\s+` + - `(?P.*\S)){0,1}` + + `(?P(?:Current|InProgress|Failed|Terminating|Unknown))\s+` + + `(?P.*\S)` + `\s*$`) eventHeaderRegex = regexp.MustCompile(`^\s*` + - `EVENT TYPE\s+` + + `NAMESPACE\s+` + `AGG STATUS\s+` + `TYPE\s+` + - `NAMESPACE\s+` + `NAME\s+` + `STATUS\s+` + `MESSAGE` + @@ -262,40 +275,43 @@ func parseEventOutput(_ *testing.T, output string) EventOutput { if len(line) == 0 { continue // Ignore empty lines } + match := eventHeaderRegex.FindStringSubmatch(line) if match != nil { continue // Ignore headers } + match = eventRegex.FindStringSubmatch(line) + if match != nil { + aggStatus := status.FromStringOrDie(match[1]) + var eventType wait.EventType + if aggStatus == status.CurrentStatus { + eventType = wait.Completed + } else { + eventType = wait.Aborted + } + eventOutput.events = append(eventOutput.events, EventOutputLine{ + eventType: eventType, + aggStatus: aggStatus, + message: match[2], + }) + } + + match = resourceEventRegex.FindStringSubmatch(line) if match == nil { eventOutput.unknownLines = append(eventOutput.unknownLines, line) continue } - eventOutputLine := EventOutputLine{ - eventType: match[1], + eventOutput.events = append(eventOutput.events, EventOutputLine{ + eventType: wait.ResourceUpdate, aggStatus: status.FromStringOrDie(match[2]), - } - - if eventOutputLine.eventType == string(wait.ResourceUpdate) { - resourceType := match[4] - parts := strings.Split(resourceType, "/") - var identifier ResourceIdentifier - if len(parts) == 2 { - identifier.apiVersion = parts[0] - identifier.kind = parts[1] - } else { - identifier.apiVersion = strings.Join(parts[:2], "/") - identifier.kind = parts[2] - } - identifier.namespace = match[5] - identifier.name = match[6] - eventOutputLine.identifier = identifier - eventOutputLine.status = status.FromStringOrDie(match[7]) - eventOutputLine.message = match[8] - } - - eventOutput.events = append(eventOutput.events, eventOutputLine) + kind: match[3], + namespace: match[1], + name: match[4], + status: status.FromStringOrDie(match[5]), + message: match[6], + }) } return eventOutput } diff --git a/kustomize/internal/commands/status/cmd/fetch.go b/kustomize/internal/commands/status/cmd/fetch.go index 5680a7b69..68546afef 100644 --- a/kustomize/internal/commands/status/cmd/fetch.go +++ b/kustomize/internal/commands/status/cmd/fetch.go @@ -50,7 +50,7 @@ type FetchRunner struct { func (r *FetchRunner) runE(c *cobra.Command, args []string) error { ctx := context.Background() - resolver, err := r.newResolverFunc(time.Minute) + resolver, mapper, err := r.newResolverFunc(time.Minute) if err != nil { return errors.Wrap(err, "error creating resolver") } @@ -58,7 +58,9 @@ func (r *FetchRunner) runE(c *cobra.Command, args []string) error { // Set up a CaptureIdentifierFilter and run all inputs through the // filter with the pipeline to capture the inventory of resources // which we are interested in. - captureFilter := &CaptureIdentifiersFilter{} + captureFilter := &CaptureIdentifiersFilter{ + Mapper: mapper, + } filters := []kio.Filter{captureFilter} var inputs []kio.Reader diff --git a/kustomize/internal/commands/status/cmd/helpers_test.go b/kustomize/internal/commands/status/cmd/helpers_test.go index c6892cb4b..b55df7e2c 100644 --- a/kustomize/internal/commands/status/cmd/helpers_test.go +++ b/kustomize/internal/commands/status/cmd/helpers_test.go @@ -241,7 +241,7 @@ func (f *FakeClient) List(context.Context, runtime.Object, ...client.ListOption) } func fakeResolver(fakeClient client.Reader, mapperTypes ...schema.GroupVersionKind) newResolverFunc { - return func(pollInterval time.Duration) (*wait.Resolver, error) { + return func(pollInterval time.Duration) (*wait.Resolver, meta.RESTMapper, error) { var groupVersions []schema.GroupVersion for _, gvk := range mapperTypes { groupVersions = append(groupVersions, gvk.GroupVersion()) @@ -251,7 +251,7 @@ func fakeResolver(fakeClient client.Reader, mapperTypes ...schema.GroupVersionKi mapper.Add(gvk, meta.RESTScopeNamespace) } - return wait.NewResolver(fakeClient, mapper, pollInterval), nil + return wait.NewResolver(fakeClient, mapper, pollInterval), mapper, nil } } diff --git a/kustomize/internal/commands/status/cmd/print.go b/kustomize/internal/commands/status/cmd/print.go index 8af0aaeb5..3371647a8 100644 --- a/kustomize/internal/commands/status/cmd/print.go +++ b/kustomize/internal/commands/status/cmd/print.go @@ -227,11 +227,11 @@ type eventColumnInfo struct { var ( eventColumns = []eventColumnInfo{ { - header: "EVENT TYPE", + header: "NAMESPACE", width: 15, - requireResourceUpdateEvent: false, + requireResourceUpdateEvent: true, contentFunc: func(event wait.Event) string { - return string(event.Type) + return event.EventResource.ResourceIdentifier.Namespace }, }, { @@ -247,16 +247,7 @@ var ( width: 20, requireResourceUpdateEvent: true, contentFunc: func(event wait.Event) string { - return fmt.Sprintf("%s/%s", event.EventResource.ResourceIdentifier.GroupKind.Group, - event.EventResource.ResourceIdentifier.GroupKind.Kind) - }, - }, - { - header: "NAMESPACE", - width: 15, - requireResourceUpdateEvent: true, - contentFunc: func(event wait.Event) string { - return event.EventResource.ResourceIdentifier.Namespace + return event.EventResource.ResourceIdentifier.GroupKind.Kind }, }, { @@ -278,12 +269,20 @@ var ( { header: "MESSAGE", width: 50, - requireResourceUpdateEvent: true, + requireResourceUpdateEvent: false, contentFunc: func(event wait.Event) string { - if event.EventResource.Error != nil { - return event.EventResource.Error.Error() + switch event.Type { + case wait.ResourceUpdate: + if event.EventResource.Error != nil { + return event.EventResource.Error.Error() + } + return event.EventResource.Message + case wait.Aborted: + return fmt.Sprint("Operation aborted before all resources have become Current") + case wait.Completed: + return fmt.Sprint("All resources have become Current") } - return event.EventResource.Message + return "" }, }, } @@ -308,11 +307,14 @@ func newEventPrinter(out io.Writer, err io.Writer) *EventPrinter { func (e *EventPrinter) printEvent(event wait.Event) { for _, column := range eventColumns { + var text string if event.Type != wait.ResourceUpdate && column.requireResourceUpdateEvent { - continue + text = "" + } else { + text = trimString(column.contentFunc(event), column.width) } format := fmt.Sprintf("%%-%ds ", column.width) - printOrDie(e.out, format, trimString(column.contentFunc(event), column.width)) + printOrDie(e.out, format, text) } printOrDie(e.out, "\n") } diff --git a/kustomize/internal/commands/status/cmd/util.go b/kustomize/internal/commands/status/cmd/util.go index 6071f145d..0388ea0c1 100644 --- a/kustomize/internal/commands/status/cmd/util.go +++ b/kustomize/internal/commands/status/cmd/util.go @@ -6,6 +6,7 @@ package cmd import ( "time" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -25,49 +26,64 @@ func init() { _ = clientgoscheme.AddToScheme(scheme) } -type newResolverFunc func(pollInterval time.Duration) (*wait.Resolver, error) +type newResolverFunc func(pollInterval time.Duration) (*wait.Resolver, meta.RESTMapper, error) // newResolver returns a new resolver that can resolve status for resources based // on polling the cluster. -func newResolver(pollInterval time.Duration) (*wait.Resolver, error) { +func newResolver(pollInterval time.Duration) (*wait.Resolver, meta.RESTMapper, error) { config := ctrl.GetConfigOrDie() mapper, err := apiutil.NewDiscoveryRESTMapper(config) if err != nil { - return nil, err + return nil, nil, err } c, err := client.New(config, client.Options{Scheme: scheme, Mapper: mapper}) if err != nil { - return nil, err + return nil, nil, err } - return wait.NewResolver(c, mapper, pollInterval), nil + return wait.NewResolver(c, mapper, pollInterval), mapper, nil } // CaptureIdentifiersFilter implements the Filter interface in the kio package. It // captures the identifiers for all resources passed through the pipeline. type CaptureIdentifiersFilter struct { Identifiers []wait.ResourceIdentifier + Mapper meta.RESTMapper } var _ kio.Filter = &CaptureIdentifiersFilter{} func (f *CaptureIdentifiersFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) { for i := range slice { - meta, err := slice[i].GetMeta() + objectMeta, err := slice[i].GetMeta() if err != nil { return nil, err } // TODO(mortent): Update kyaml library - id := meta.GetIdentifier() + id := objectMeta.GetIdentifier() gv, err := schema.ParseGroupVersion(id.APIVersion) if err != nil { return nil, err } + gk := schema.GroupKind{ + Group: gv.Group, + Kind: id.Kind, + } + mapping, err := f.Mapper.RESTMapping(gk) + if err != nil { + return nil, err + } + var namespace string + if mapping.Scope.Name() == meta.RESTScopeNameNamespace && id.Namespace == "" { + namespace = "default" + } else { + namespace = id.Namespace + } if IsValidKubernetesResource(id) { f.Identifiers = append(f.Identifiers, wait.ResourceIdentifier{ Name: id.Name, - Namespace: id.Namespace, + Namespace: namespace, GroupKind: schema.GroupKind{ Group: gv.Group, Kind: id.Kind, diff --git a/kustomize/internal/commands/status/cmd/wait.go b/kustomize/internal/commands/status/cmd/wait.go index 60a319696..53665b6ce 100644 --- a/kustomize/internal/commands/status/cmd/wait.go +++ b/kustomize/internal/commands/status/cmd/wait.go @@ -60,12 +60,14 @@ type WaitRunner struct { func (r *WaitRunner) runE(c *cobra.Command, args []string) error { ctx := context.Background() - resolver, err := r.newResolverFunc(r.Interval) + resolver, mapper, err := r.newResolverFunc(r.Interval) if err != nil { return errors.Wrap(err, "errors creating resolver") } - captureFilter := &CaptureIdentifiersFilter{} + captureFilter := &CaptureIdentifiersFilter{ + Mapper: mapper, + } filters := []kio.Filter{captureFilter} var inputs []kio.Reader