1
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
76
77
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
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
102
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
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
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
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
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 {
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
332
333
334
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
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
365
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
377
378
379
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