...

Source file src/k8s.io/kubectl/pkg/cmd/events/events.go

Documentation: k8s.io/kubectl/pkg/cmd/events

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package events
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"sort"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/spf13/cobra"
    28  
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/meta"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/fields"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/apimachinery/pkg/util/sets"
    36  	"k8s.io/apimachinery/pkg/watch"
    37  	"k8s.io/cli-runtime/pkg/genericclioptions"
    38  	"k8s.io/cli-runtime/pkg/genericiooptions"
    39  	"k8s.io/cli-runtime/pkg/printers"
    40  	runtimeresource "k8s.io/cli-runtime/pkg/resource"
    41  	"k8s.io/client-go/kubernetes"
    42  	"k8s.io/client-go/kubernetes/scheme"
    43  	watchtools "k8s.io/client-go/tools/watch"
    44  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    45  	"k8s.io/kubectl/pkg/util/i18n"
    46  	"k8s.io/kubectl/pkg/util/interrupt"
    47  	"k8s.io/kubectl/pkg/util/templates"
    48  )
    49  
    50  var (
    51  	eventsLong = templates.LongDesc(i18n.T(`
    52  		Display events.
    53  
    54  		Prints a table of the most important information about events.
    55  		You can request events for a namespace, for all namespace, or
    56  		filtered to only those pertaining to a specified resource.`))
    57  
    58  	eventsExample = templates.Examples(i18n.T(`
    59  	# List recent events in the default namespace
    60  	kubectl events
    61  
    62  	# List recent events in all namespaces
    63  	kubectl events --all-namespaces
    64  
    65  	# List recent events for the specified pod, then wait for more events and list them as they arrive
    66  	kubectl events --for pod/web-pod-13je7 --watch
    67  
    68  	# List recent events in YAML format
    69  	kubectl events -oyaml
    70  
    71  	# List recent only events of type 'Warning' or 'Normal'
    72  	kubectl events --types=Warning,Normal`))
    73  )
    74  
    75  // EventsFlags directly reflect the information that CLI is gathering via flags.  They will be converted to Options, which
    76  // reflect the runtime requirements for the command.  This structure reduces the transformation to wiring and makes
    77  // the logic itself easy to unit test.
    78  type EventsFlags struct {
    79  	RESTClientGetter genericclioptions.RESTClientGetter
    80  	PrintFlags       *genericclioptions.PrintFlags
    81  
    82  	AllNamespaces bool
    83  	Watch         bool
    84  	NoHeaders     bool
    85  	ForObject     string
    86  	FilterTypes   []string
    87  	ChunkSize     int64
    88  	genericiooptions.IOStreams
    89  }
    90  
    91  // NewEventsFlags returns a default EventsFlags
    92  func NewEventsFlags(restClientGetter genericclioptions.RESTClientGetter, streams genericiooptions.IOStreams) *EventsFlags {
    93  	return &EventsFlags{
    94  		RESTClientGetter: restClientGetter,
    95  		PrintFlags:       genericclioptions.NewPrintFlags("events").WithTypeSetter(scheme.Scheme),
    96  		IOStreams:        streams,
    97  		ChunkSize:        cmdutil.DefaultChunkSize,
    98  	}
    99  }
   100  
   101  // EventsOptions is a set of options that allows you to list events.  This is the object reflects the
   102  // runtime needs of an events command, making the logic itself easy to unit test.
   103  type EventsOptions struct {
   104  	Namespace     string
   105  	AllNamespaces bool
   106  	Watch         bool
   107  	FilterTypes   []string
   108  
   109  	forGVK  schema.GroupVersionKind
   110  	forName string
   111  
   112  	client *kubernetes.Clientset
   113  
   114  	PrintObj printers.ResourcePrinterFunc
   115  
   116  	genericiooptions.IOStreams
   117  }
   118  
   119  // NewCmdEvents creates a new events command
   120  func NewCmdEvents(restClientGetter genericclioptions.RESTClientGetter, streams genericiooptions.IOStreams) *cobra.Command {
   121  	flags := NewEventsFlags(restClientGetter, streams)
   122  
   123  	cmd := &cobra.Command{
   124  		Use:                   fmt.Sprintf("events [(-o|--output=)%s] [--for TYPE/NAME] [--watch] [--types=Normal,Warning]", strings.Join(flags.PrintFlags.AllowedFormats(), "|")),
   125  		DisableFlagsInUseLine: true,
   126  		Short:                 i18n.T("List events"),
   127  		Long:                  eventsLong,
   128  		Example:               eventsExample,
   129  		Run: func(cmd *cobra.Command, args []string) {
   130  			o, err := flags.ToOptions()
   131  			cmdutil.CheckErr(err)
   132  			cmdutil.CheckErr(o.Validate())
   133  			cmdutil.CheckErr(o.Run())
   134  		},
   135  	}
   136  	flags.AddFlags(cmd)
   137  	flags.PrintFlags.AddFlags(cmd)
   138  	return cmd
   139  }
   140  
   141  // AddFlags registers flags for a cli.
   142  func (flags *EventsFlags) AddFlags(cmd *cobra.Command) {
   143  	cmd.Flags().BoolVarP(&flags.Watch, "watch", "w", flags.Watch, "After listing the requested events, watch for more events.")
   144  	cmd.Flags().BoolVarP(&flags.AllNamespaces, "all-namespaces", "A", flags.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
   145  	cmd.Flags().StringVar(&flags.ForObject, "for", flags.ForObject, "Filter events to only those pertaining to the specified resource.")
   146  	cmd.Flags().StringSliceVar(&flags.FilterTypes, "types", flags.FilterTypes, "Output only events of given types.")
   147  	cmd.Flags().BoolVar(&flags.NoHeaders, "no-headers", flags.NoHeaders, "When using the default output format, don't print headers.")
   148  	cmdutil.AddChunkSizeFlag(cmd, &flags.ChunkSize)
   149  }
   150  
   151  // ToOptions converts from CLI inputs to runtime inputs.
   152  func (flags *EventsFlags) ToOptions() (*EventsOptions, error) {
   153  	o := &EventsOptions{
   154  		AllNamespaces: flags.AllNamespaces,
   155  		Watch:         flags.Watch,
   156  		FilterTypes:   flags.FilterTypes,
   157  		IOStreams:     flags.IOStreams,
   158  	}
   159  	var err error
   160  	o.Namespace, _, err = flags.RESTClientGetter.ToRawKubeConfigLoader().Namespace()
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	if flags.ForObject != "" {
   166  		mapper, err := flags.RESTClientGetter.ToRESTMapper()
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  		var found bool
   171  		o.forGVK, o.forName, found, err = decodeResourceTypeName(mapper, flags.ForObject)
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  		if !found {
   176  			return nil, fmt.Errorf("--for must be in resource/name form")
   177  		}
   178  	}
   179  
   180  	clientConfig, err := flags.RESTClientGetter.ToRESTConfig()
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	o.client, err = kubernetes.NewForConfig(clientConfig)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	if len(o.FilterTypes) > 0 {
   191  		o.FilterTypes = sets.NewString(o.FilterTypes...).List()
   192  	}
   193  
   194  	var printer printers.ResourcePrinter
   195  	if flags.PrintFlags.OutputFormat != nil && len(*flags.PrintFlags.OutputFormat) > 0 {
   196  		printer, err = flags.PrintFlags.ToPrinter()
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  	} else {
   201  		printer = NewEventPrinter(flags.NoHeaders, flags.AllNamespaces)
   202  	}
   203  
   204  	o.PrintObj = func(object runtime.Object, writer io.Writer) error {
   205  		return printer.PrintObj(object, writer)
   206  	}
   207  
   208  	return o, nil
   209  }
   210  
   211  func (o *EventsOptions) Validate() error {
   212  	for _, val := range o.FilterTypes {
   213  		if !strings.EqualFold(val, "Normal") && !strings.EqualFold(val, "Warning") {
   214  			return fmt.Errorf("valid --types are Normal or Warning")
   215  		}
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  // Run retrieves events
   222  func (o *EventsOptions) Run() error {
   223  	ctx := context.TODO()
   224  	namespace := o.Namespace
   225  	if o.AllNamespaces {
   226  		namespace = ""
   227  	}
   228  	listOptions := metav1.ListOptions{Limit: cmdutil.DefaultChunkSize}
   229  	if o.forName != "" {
   230  		listOptions.FieldSelector = fields.AndSelectors(
   231  			fields.OneTermEqualSelector("involvedObject.kind", o.forGVK.Kind),
   232  			fields.OneTermEqualSelector("involvedObject.apiVersion", o.forGVK.GroupVersion().String()),
   233  			fields.OneTermEqualSelector("involvedObject.name", o.forName)).String()
   234  	}
   235  	if o.Watch {
   236  		return o.runWatch(ctx, namespace, listOptions)
   237  	}
   238  
   239  	e := o.client.CoreV1().Events(namespace)
   240  	el := &corev1.EventList{
   241  		TypeMeta: metav1.TypeMeta{
   242  			Kind:       "EventList",
   243  			APIVersion: "v1",
   244  		},
   245  	}
   246  	err := runtimeresource.FollowContinue(&listOptions,
   247  		func(options metav1.ListOptions) (runtime.Object, error) {
   248  			newEvents, err := e.List(ctx, options)
   249  			if err != nil {
   250  				return nil, runtimeresource.EnhanceListError(err, options, "events")
   251  			}
   252  			el.Items = append(el.Items, newEvents.Items...)
   253  			return newEvents, nil
   254  		})
   255  
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	var filteredEvents []corev1.Event
   261  	for _, e := range el.Items {
   262  		if !o.filteredEventType(e.Type) {
   263  			continue
   264  		}
   265  		if e.GetObjectKind().GroupVersionKind().Empty() {
   266  			e.SetGroupVersionKind(schema.GroupVersionKind{
   267  				Version: "v1",
   268  				Kind:    "Event",
   269  			})
   270  		}
   271  		filteredEvents = append(filteredEvents, e)
   272  	}
   273  
   274  	el.Items = filteredEvents
   275  
   276  	if len(el.Items) == 0 {
   277  		if o.AllNamespaces {
   278  			fmt.Fprintln(o.ErrOut, "No events found.")
   279  		} else {
   280  			fmt.Fprintf(o.ErrOut, "No events found in %s namespace.\n", o.Namespace)
   281  		}
   282  		return nil
   283  	}
   284  
   285  	w := printers.GetNewTabWriter(o.Out)
   286  
   287  	sort.Sort(SortableEvents(el.Items))
   288  
   289  	o.PrintObj(el, w)
   290  	w.Flush()
   291  	return nil
   292  }
   293  
   294  func (o *EventsOptions) runWatch(ctx context.Context, namespace string, listOptions metav1.ListOptions) error {
   295  	eventWatch, err := o.client.CoreV1().Events(namespace).Watch(ctx, listOptions)
   296  	if err != nil {
   297  		return err
   298  	}
   299  	w := printers.GetNewTabWriter(o.Out)
   300  
   301  	cctx, cancel := context.WithCancel(ctx)
   302  	defer cancel()
   303  	intr := interrupt.New(nil, cancel)
   304  	intr.Run(func() error {
   305  		_, err := watchtools.UntilWithoutRetry(cctx, eventWatch, func(e watch.Event) (bool, error) {
   306  			if e.Type == watch.Deleted { // events are deleted after 1 hour; don't print that
   307  				return false, nil
   308  			}
   309  
   310  			if ev, ok := e.Object.(*corev1.Event); !ok || !o.filteredEventType(ev.Type) {
   311  				return false, nil
   312  			}
   313  
   314  			if e.Object.GetObjectKind().GroupVersionKind().Empty() {
   315  				e.Object.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{
   316  					Version: "v1",
   317  					Kind:    "Event",
   318  				})
   319  			}
   320  
   321  			o.PrintObj(e.Object, w)
   322  			w.Flush()
   323  			return false, nil
   324  		})
   325  		return err
   326  	})
   327  
   328  	return nil
   329  }
   330  
   331  // filteredEventType checks given event can be printed
   332  // by comparing it in filtered event flag.
   333  // If --event flag is not set by user, this function allows
   334  // all events to be printed.
   335  func (o *EventsOptions) filteredEventType(et string) bool {
   336  	if len(o.FilterTypes) == 0 {
   337  		return true
   338  	}
   339  
   340  	for _, t := range o.FilterTypes {
   341  		if strings.EqualFold(t, et) {
   342  			return true
   343  		}
   344  	}
   345  
   346  	return false
   347  }
   348  
   349  // SortableEvents implements sort.Interface for []api.Event by time
   350  type SortableEvents []corev1.Event
   351  
   352  func (list SortableEvents) Len() int {
   353  	return len(list)
   354  }
   355  
   356  func (list SortableEvents) Swap(i, j int) {
   357  	list[i], list[j] = list[j], list[i]
   358  }
   359  
   360  func (list SortableEvents) Less(i, j int) bool {
   361  	return eventTime(list[i]).Before(eventTime(list[j]))
   362  }
   363  
   364  // Return the time that should be used for sorting, which can come from
   365  // various places in corev1.Event.
   366  func eventTime(event corev1.Event) time.Time {
   367  	if event.Series != nil {
   368  		return event.Series.LastObservedTime.Time
   369  	}
   370  	if !event.LastTimestamp.Time.IsZero() {
   371  		return event.LastTimestamp.Time
   372  	}
   373  	return event.EventTime.Time
   374  }
   375  
   376  // Inspired by k8s.io/cli-runtime/pkg/resource splitResourceTypeName()
   377  
   378  // decodeResourceTypeName handles type/name resource formats and returns a resource tuple
   379  // (empty or not), whether it successfully found one, and an error
   380  func decodeResourceTypeName(mapper meta.RESTMapper, s string) (gvk schema.GroupVersionKind, name string, found bool, err error) {
   381  	if !strings.Contains(s, "/") {
   382  		return
   383  	}
   384  	seg := strings.Split(s, "/")
   385  	if len(seg) != 2 {
   386  		err = fmt.Errorf("arguments in resource/name form may not have more than one slash")
   387  		return
   388  	}
   389  	resource, name := seg[0], seg[1]
   390  
   391  	fullySpecifiedGVR, groupResource := schema.ParseResourceArg(strings.ToLower(resource))
   392  	gvr := schema.GroupVersionResource{}
   393  	if fullySpecifiedGVR != nil {
   394  		gvr, _ = mapper.ResourceFor(*fullySpecifiedGVR)
   395  	}
   396  	if gvr.Empty() {
   397  		gvr, err = mapper.ResourceFor(groupResource.WithVersion(""))
   398  		if err != nil {
   399  			return
   400  		}
   401  	}
   402  
   403  	gvk, err = mapper.KindFor(gvr)
   404  	if err != nil {
   405  		return
   406  	}
   407  	found = true
   408  
   409  	return
   410  }
   411  

View as plain text