Clean up output format for status events command

This commit is contained in:
Morten Torkildsen
2020-01-22 19:49:57 -08:00
parent 53432ba4bb
commit 0f5256d952
7 changed files with 113 additions and 73 deletions

View File

@@ -54,7 +54,7 @@ type EventsRunner struct {
func (r *EventsRunner) runE(c *cobra.Command, args []string) error { func (r *EventsRunner) runE(c *cobra.Command, args []string) error {
ctx := context.Background() ctx := context.Background()
resolver, err := r.newResolverFunc(r.Interval) resolver, mapper, err := r.newResolverFunc(r.Interval)
if err != nil { if err != nil {
return errors.Wrap(err, "error creating resolver") 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 // Set up a CaptureIdentifierFilter and run all inputs through the
// filter with the pipeline to capture the inventory of resources // filter with the pipeline to capture the inventory of resources
// which we are interested in. // which we are interested in.
captureFilter := &CaptureIdentifiersFilter{} captureFilter := &CaptureIdentifiersFilter{
Mapper: mapper,
}
filters := []kio.Filter{captureFilter} filters := []kio.Filter{captureFilter}
var inputs []kio.Reader var inputs []kio.Reader

View File

@@ -200,7 +200,11 @@ func (e *EventOutput) allResources() []ResourceIdentifier {
if !event.isResourceUpdateEvent() { if !event.isResourceUpdateEvent() {
continue continue
} }
r := event.identifier r := ResourceIdentifier{
namespace: event.namespace,
name: event.name,
kind: event.kind,
}
if _, found := seenResources[r]; !found { if _, found := seenResources[r]; !found {
resources = append(resources, r) resources = append(resources, r)
seenResources[r] = true seenResources[r] = true
@@ -215,7 +219,12 @@ func (e *EventOutput) statusesForResource(resource ResourceIdentifier) []status.
if !event.isResourceUpdateEvent() { if !event.isResourceUpdateEvent() {
continue 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) statuses = append(statuses, event.status)
} }
} }
@@ -223,32 +232,36 @@ func (e *EventOutput) statusesForResource(resource ResourceIdentifier) []status.
} }
type EventOutputLine struct { type EventOutputLine struct {
eventType string eventType wait.EventType
aggStatus status.Status aggStatus status.Status
identifier ResourceIdentifier kind string
status status.Status namespace string
message string name string
status status.Status
message string
} }
func (e *EventOutputLine) isResourceUpdateEvent() bool { func (e *EventOutputLine) isResourceUpdateEvent() bool {
return e.eventType == string(wait.ResourceUpdate) return e.eventType == wait.ResourceUpdate
} }
var ( var (
eventRegex = regexp.MustCompile(`^\s*` + eventRegex = regexp.MustCompile(`^\s*` +
`(?P<eventType>\S+)\s+` + `(?P<aggStatus>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<aggStatus>\S+)\s+` + `(?P<message>.*\S)` +
`((?P<resourceType>\S+)\s+` + `\s*$`)
resourceEventRegex = regexp.MustCompile(`^\s*` +
`(?P<namespace>\S+)\s+` + `(?P<namespace>\S+)\s+` +
`(?P<aggStatus>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<resourceType>\S+)\s+` +
`(?P<name>\S+)\s+` + `(?P<name>\S+)\s+` +
`(?P<status>\S+)\s+` + `(?P<status>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<message>.*\S)){0,1}` + `(?P<message>.*\S)` +
`\s*$`) `\s*$`)
eventHeaderRegex = regexp.MustCompile(`^\s*` + eventHeaderRegex = regexp.MustCompile(`^\s*` +
`EVENT TYPE\s+` + `NAMESPACE\s+` +
`AGG STATUS\s+` + `AGG STATUS\s+` +
`TYPE\s+` + `TYPE\s+` +
`NAMESPACE\s+` +
`NAME\s+` + `NAME\s+` +
`STATUS\s+` + `STATUS\s+` +
`MESSAGE` + `MESSAGE` +
@@ -262,40 +275,43 @@ func parseEventOutput(_ *testing.T, output string) EventOutput {
if len(line) == 0 { if len(line) == 0 {
continue // Ignore empty lines continue // Ignore empty lines
} }
match := eventHeaderRegex.FindStringSubmatch(line) match := eventHeaderRegex.FindStringSubmatch(line)
if match != nil { if match != nil {
continue // Ignore headers continue // Ignore headers
} }
match = eventRegex.FindStringSubmatch(line) 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 { if match == nil {
eventOutput.unknownLines = append(eventOutput.unknownLines, line) eventOutput.unknownLines = append(eventOutput.unknownLines, line)
continue continue
} }
eventOutputLine := EventOutputLine{ eventOutput.events = append(eventOutput.events, EventOutputLine{
eventType: match[1], eventType: wait.ResourceUpdate,
aggStatus: status.FromStringOrDie(match[2]), aggStatus: status.FromStringOrDie(match[2]),
} kind: match[3],
namespace: match[1],
if eventOutputLine.eventType == string(wait.ResourceUpdate) { name: match[4],
resourceType := match[4] status: status.FromStringOrDie(match[5]),
parts := strings.Split(resourceType, "/") message: match[6],
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)
} }
return eventOutput return eventOutput
} }

View File

@@ -50,7 +50,7 @@ type FetchRunner struct {
func (r *FetchRunner) runE(c *cobra.Command, args []string) error { func (r *FetchRunner) runE(c *cobra.Command, args []string) error {
ctx := context.Background() ctx := context.Background()
resolver, err := r.newResolverFunc(time.Minute) resolver, mapper, err := r.newResolverFunc(time.Minute)
if err != nil { if err != nil {
return errors.Wrap(err, "error creating resolver") 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 // Set up a CaptureIdentifierFilter and run all inputs through the
// filter with the pipeline to capture the inventory of resources // filter with the pipeline to capture the inventory of resources
// which we are interested in. // which we are interested in.
captureFilter := &CaptureIdentifiersFilter{} captureFilter := &CaptureIdentifiersFilter{
Mapper: mapper,
}
filters := []kio.Filter{captureFilter} filters := []kio.Filter{captureFilter}
var inputs []kio.Reader var inputs []kio.Reader

View File

@@ -241,7 +241,7 @@ func (f *FakeClient) List(context.Context, runtime.Object, ...client.ListOption)
} }
func fakeResolver(fakeClient client.Reader, mapperTypes ...schema.GroupVersionKind) newResolverFunc { 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 var groupVersions []schema.GroupVersion
for _, gvk := range mapperTypes { for _, gvk := range mapperTypes {
groupVersions = append(groupVersions, gvk.GroupVersion()) groupVersions = append(groupVersions, gvk.GroupVersion())
@@ -251,7 +251,7 @@ func fakeResolver(fakeClient client.Reader, mapperTypes ...schema.GroupVersionKi
mapper.Add(gvk, meta.RESTScopeNamespace) mapper.Add(gvk, meta.RESTScopeNamespace)
} }
return wait.NewResolver(fakeClient, mapper, pollInterval), nil return wait.NewResolver(fakeClient, mapper, pollInterval), mapper, nil
} }
} }

View File

@@ -227,11 +227,11 @@ type eventColumnInfo struct {
var ( var (
eventColumns = []eventColumnInfo{ eventColumns = []eventColumnInfo{
{ {
header: "EVENT TYPE", header: "NAMESPACE",
width: 15, width: 15,
requireResourceUpdateEvent: false, requireResourceUpdateEvent: true,
contentFunc: func(event wait.Event) string { contentFunc: func(event wait.Event) string {
return string(event.Type) return event.EventResource.ResourceIdentifier.Namespace
}, },
}, },
{ {
@@ -247,16 +247,7 @@ var (
width: 20, width: 20,
requireResourceUpdateEvent: true, requireResourceUpdateEvent: true,
contentFunc: func(event wait.Event) string { contentFunc: func(event wait.Event) string {
return fmt.Sprintf("%s/%s", event.EventResource.ResourceIdentifier.GroupKind.Group, return event.EventResource.ResourceIdentifier.GroupKind.Kind
event.EventResource.ResourceIdentifier.GroupKind.Kind)
},
},
{
header: "NAMESPACE",
width: 15,
requireResourceUpdateEvent: true,
contentFunc: func(event wait.Event) string {
return event.EventResource.ResourceIdentifier.Namespace
}, },
}, },
{ {
@@ -278,12 +269,20 @@ var (
{ {
header: "MESSAGE", header: "MESSAGE",
width: 50, width: 50,
requireResourceUpdateEvent: true, requireResourceUpdateEvent: false,
contentFunc: func(event wait.Event) string { contentFunc: func(event wait.Event) string {
if event.EventResource.Error != nil { switch event.Type {
return event.EventResource.Error.Error() 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) { func (e *EventPrinter) printEvent(event wait.Event) {
for _, column := range eventColumns { for _, column := range eventColumns {
var text string
if event.Type != wait.ResourceUpdate && column.requireResourceUpdateEvent { if event.Type != wait.ResourceUpdate && column.requireResourceUpdateEvent {
continue text = ""
} else {
text = trimString(column.contentFunc(event), column.width)
} }
format := fmt.Sprintf("%%-%ds ", 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") printOrDie(e.out, "\n")
} }

View File

@@ -6,6 +6,7 @@ package cmd
import ( import (
"time" "time"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
clientgoscheme "k8s.io/client-go/kubernetes/scheme" clientgoscheme "k8s.io/client-go/kubernetes/scheme"
@@ -25,49 +26,64 @@ func init() {
_ = clientgoscheme.AddToScheme(scheme) _ = 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 // newResolver returns a new resolver that can resolve status for resources based
// on polling the cluster. // 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() config := ctrl.GetConfigOrDie()
mapper, err := apiutil.NewDiscoveryRESTMapper(config) mapper, err := apiutil.NewDiscoveryRESTMapper(config)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
c, err := client.New(config, client.Options{Scheme: scheme, Mapper: mapper}) c, err := client.New(config, client.Options{Scheme: scheme, Mapper: mapper})
if err != nil { 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 // CaptureIdentifiersFilter implements the Filter interface in the kio package. It
// captures the identifiers for all resources passed through the pipeline. // captures the identifiers for all resources passed through the pipeline.
type CaptureIdentifiersFilter struct { type CaptureIdentifiersFilter struct {
Identifiers []wait.ResourceIdentifier Identifiers []wait.ResourceIdentifier
Mapper meta.RESTMapper
} }
var _ kio.Filter = &CaptureIdentifiersFilter{} var _ kio.Filter = &CaptureIdentifiersFilter{}
func (f *CaptureIdentifiersFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) { func (f *CaptureIdentifiersFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range slice { for i := range slice {
meta, err := slice[i].GetMeta() objectMeta, err := slice[i].GetMeta()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO(mortent): Update kyaml library // TODO(mortent): Update kyaml library
id := meta.GetIdentifier() id := objectMeta.GetIdentifier()
gv, err := schema.ParseGroupVersion(id.APIVersion) gv, err := schema.ParseGroupVersion(id.APIVersion)
if err != nil { if err != nil {
return nil, err 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) { if IsValidKubernetesResource(id) {
f.Identifiers = append(f.Identifiers, wait.ResourceIdentifier{ f.Identifiers = append(f.Identifiers, wait.ResourceIdentifier{
Name: id.Name, Name: id.Name,
Namespace: id.Namespace, Namespace: namespace,
GroupKind: schema.GroupKind{ GroupKind: schema.GroupKind{
Group: gv.Group, Group: gv.Group,
Kind: id.Kind, Kind: id.Kind,

View File

@@ -60,12 +60,14 @@ type WaitRunner struct {
func (r *WaitRunner) runE(c *cobra.Command, args []string) error { func (r *WaitRunner) runE(c *cobra.Command, args []string) error {
ctx := context.Background() ctx := context.Background()
resolver, err := r.newResolverFunc(r.Interval) resolver, mapper, err := r.newResolverFunc(r.Interval)
if err != nil { if err != nil {
return errors.Wrap(err, "errors creating resolver") return errors.Wrap(err, "errors creating resolver")
} }
captureFilter := &CaptureIdentifiersFilter{} captureFilter := &CaptureIdentifiersFilter{
Mapper: mapper,
}
filters := []kio.Filter{captureFilter} filters := []kio.Filter{captureFilter}
var inputs []kio.Reader var inputs []kio.Reader