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

View File

@@ -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<eventType>\S+)\s+` +
`(?P<aggStatus>\S+)\s+` +
`((?P<resourceType>\S+)\s+` +
`(?P<aggStatus>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<message>.*\S)` +
`\s*$`)
resourceEventRegex = regexp.MustCompile(`^\s*` +
`(?P<namespace>\S+)\s+` +
`(?P<aggStatus>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<resourceType>\S+)\s+` +
`(?P<name>\S+)\s+` +
`(?P<status>\S+)\s+` +
`(?P<message>.*\S)){0,1}` +
`(?P<status>(?:Current|InProgress|Failed|Terminating|Unknown))\s+` +
`(?P<message>.*\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
}

View File

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

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

View File

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

View File

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

View File

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