mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
Merge pull request #110007 from ardaguclu/enhancements-alpha-events
Add new flags into alpha events
This commit is contained in:
commit
5856e83573
124
staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer.go
Normal file
124
staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer.go
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
||||
// EventPrinter stores required fields to be used for
|
||||
// default printing for events command.
|
||||
type EventPrinter struct {
|
||||
NoHeaders bool
|
||||
AllNamespaces bool
|
||||
|
||||
headersPrinted bool
|
||||
}
|
||||
|
||||
// PrintObj prints different type of event objects.
|
||||
func (ep *EventPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
|
||||
if !ep.NoHeaders && !ep.headersPrinted {
|
||||
ep.printHeadings(out)
|
||||
ep.headersPrinted = true
|
||||
}
|
||||
|
||||
switch t := obj.(type) {
|
||||
case *corev1.EventList:
|
||||
for _, e := range t.Items {
|
||||
ep.printOneEvent(out, e)
|
||||
}
|
||||
case *corev1.Event:
|
||||
ep.printOneEvent(out, *t)
|
||||
default:
|
||||
return fmt.Errorf("unknown event type %t", t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *EventPrinter) printHeadings(w io.Writer) {
|
||||
if ep.AllNamespaces {
|
||||
fmt.Fprintf(w, "NAMESPACE\t")
|
||||
}
|
||||
fmt.Fprintf(w, "LAST SEEN\tTYPE\tREASON\tOBJECT\tMESSAGE\n")
|
||||
}
|
||||
|
||||
func (ep *EventPrinter) printOneEvent(w io.Writer, e corev1.Event) {
|
||||
interval := getInterval(e)
|
||||
if ep.AllNamespaces {
|
||||
fmt.Fprintf(w, "%v\t", e.Namespace)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s/%s\t%v\n",
|
||||
interval,
|
||||
e.Type,
|
||||
e.Reason,
|
||||
e.InvolvedObject.Kind, e.InvolvedObject.Name,
|
||||
strings.TrimSpace(e.Message),
|
||||
)
|
||||
}
|
||||
|
||||
func getInterval(e corev1.Event) string {
|
||||
var interval string
|
||||
firstTimestampSince := translateMicroTimestampSince(e.EventTime)
|
||||
if e.EventTime.IsZero() {
|
||||
firstTimestampSince = translateTimestampSince(e.FirstTimestamp)
|
||||
}
|
||||
if e.Series != nil {
|
||||
interval = fmt.Sprintf("%s (x%d over %s)", translateMicroTimestampSince(e.Series.LastObservedTime), e.Series.Count, firstTimestampSince)
|
||||
} else if e.Count > 1 {
|
||||
interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, firstTimestampSince)
|
||||
} else {
|
||||
interval = firstTimestampSince
|
||||
}
|
||||
|
||||
return interval
|
||||
}
|
||||
|
||||
// translateMicroTimestampSince returns the elapsed time since timestamp in
|
||||
// human-readable approximation.
|
||||
func translateMicroTimestampSince(timestamp metav1.MicroTime) string {
|
||||
if timestamp.IsZero() {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
return duration.HumanDuration(time.Since(timestamp.Time))
|
||||
}
|
||||
|
||||
// translateTimestampSince returns the elapsed time since timestamp in
|
||||
// human-readable approximation.
|
||||
func translateTimestampSince(timestamp metav1.Time) string {
|
||||
if timestamp.IsZero() {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
return duration.HumanDuration(time.Since(timestamp.Time))
|
||||
}
|
||||
|
||||
func NewEventPrinter(noHeader, allNamespaces bool) *EventPrinter {
|
||||
return &EventPrinter{
|
||||
NoHeaders: noHeader,
|
||||
AllNamespaces: allNamespaces,
|
||||
}
|
||||
}
|
226
staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer_test.go
Normal file
226
staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer_test.go
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPrintObj(t *testing.T) {
|
||||
tests := []struct {
|
||||
printer EventPrinter
|
||||
obj runtime.Object
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
printer: EventPrinter{
|
||||
NoHeaders: false,
|
||||
AllNamespaces: false,
|
||||
},
|
||||
obj: &corev1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-000",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
|
||||
},
|
||||
},
|
||||
expected: `LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
printer: EventPrinter{
|
||||
NoHeaders: false,
|
||||
AllNamespaces: true,
|
||||
},
|
||||
obj: &corev1.EventList{
|
||||
Items: []corev1.Event{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-000",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-001",
|
||||
Namespace: "bar",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar2",
|
||||
Namespace: "foo2",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-15 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-11 * time.Minute)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica set bar-002 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
printer: EventPrinter{
|
||||
NoHeaders: true,
|
||||
AllNamespaces: false,
|
||||
},
|
||||
obj: &corev1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-000",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
|
||||
},
|
||||
},
|
||||
expected: "12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1\n",
|
||||
},
|
||||
{
|
||||
printer: EventPrinter{
|
||||
NoHeaders: false,
|
||||
AllNamespaces: true,
|
||||
},
|
||||
obj: &corev1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-000",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
|
||||
},
|
||||
},
|
||||
expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
printer: EventPrinter{
|
||||
NoHeaders: true,
|
||||
AllNamespaces: true,
|
||||
},
|
||||
obj: &corev1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-000",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
|
||||
},
|
||||
},
|
||||
expected: `foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
buffer := &bytes.Buffer{}
|
||||
if err := test.printer.PrintObj(test.obj, buffer); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if buffer.String() != test.expected {
|
||||
t.Errorf("\nexpected:\n'%s'\nsaw\n'%s'\n", test.expected, buffer.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -32,12 +32,13 @@ import (
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
runtimeresource "k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
@ -45,10 +46,6 @@ import (
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
const (
|
||||
eventsUsageStr = "events [--for TYPE/NAME] [--watch]"
|
||||
)
|
||||
|
||||
var (
|
||||
eventsLong = templates.LongDesc(i18n.T(`
|
||||
Experimental: Display events
|
||||
@ -65,7 +62,13 @@ var (
|
||||
kubectl alpha events --all-namespaces
|
||||
|
||||
# List recent events for the specified pod, then wait for more events and list them as they arrive.
|
||||
kubectl alpha events --for pod/web-pod-13je7 --watch`))
|
||||
kubectl alpha events --for pod/web-pod-13je7 --watch
|
||||
|
||||
# List recent events in given format. Supported ones, apart from default, are json and yaml.
|
||||
kubectl alpha events -oyaml
|
||||
|
||||
# List recent only events in given event types
|
||||
kubectl alpha events --types=Warning,Normal`))
|
||||
)
|
||||
|
||||
// EventsFlags directly reflect the information that CLI is gathering via flags. They will be converted to Options, which
|
||||
@ -73,10 +76,13 @@ var (
|
||||
// the logic itself easy to unit test.
|
||||
type EventsFlags struct {
|
||||
RESTClientGetter genericclioptions.RESTClientGetter
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
|
||||
AllNamespaces bool
|
||||
Watch bool
|
||||
NoHeaders bool
|
||||
ForObject string
|
||||
FilterTypes []string
|
||||
ChunkSize int64
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
@ -85,6 +91,7 @@ type EventsFlags struct {
|
||||
func NewEventsFlags(restClientGetter genericclioptions.RESTClientGetter, streams genericclioptions.IOStreams) *EventsFlags {
|
||||
return &EventsFlags{
|
||||
RESTClientGetter: restClientGetter,
|
||||
PrintFlags: genericclioptions.NewPrintFlags("events").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: streams,
|
||||
ChunkSize: cmdutil.DefaultChunkSize,
|
||||
}
|
||||
@ -96,13 +103,15 @@ type EventsOptions struct {
|
||||
Namespace string
|
||||
AllNamespaces bool
|
||||
Watch bool
|
||||
FilterTypes []string
|
||||
|
||||
forGVK schema.GroupVersionKind
|
||||
forName string
|
||||
|
||||
ctx context.Context
|
||||
client *kubernetes.Clientset
|
||||
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
@ -111,35 +120,39 @@ func NewCmdEvents(restClientGetter genericclioptions.RESTClientGetter, streams g
|
||||
flags := NewEventsFlags(restClientGetter, streams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: eventsUsageStr,
|
||||
Use: fmt.Sprintf("events [(-o|--output=)%s] [--for TYPE/NAME] [--watch] [--event=Normal,Warning]", strings.Join(flags.PrintFlags.AllowedFormats(), "|")),
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Experimental: List events"),
|
||||
Long: eventsLong,
|
||||
Example: eventsExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
o, err := flags.ToOptions(cmd.Context(), args)
|
||||
o, err := flags.ToOptions()
|
||||
cmdutil.CheckErr(err)
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
flags.AddFlags(cmd)
|
||||
flags.PrintFlags.AddFlags(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// AddFlags registers flags for a cli.
|
||||
func (o *EventsFlags) AddFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing the requested events, watch for more events.")
|
||||
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
|
||||
cmd.Flags().StringVar(&o.ForObject, "for", o.ForObject, "Filter events to only those pertaining to the specified resource.")
|
||||
cmdutil.AddChunkSizeFlag(cmd, &o.ChunkSize)
|
||||
func (flags *EventsFlags) AddFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolVarP(&flags.Watch, "watch", "w", flags.Watch, "After listing the requested events, watch for more events.")
|
||||
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.")
|
||||
cmd.Flags().StringVar(&flags.ForObject, "for", flags.ForObject, "Filter events to only those pertaining to the specified resource.")
|
||||
cmd.Flags().StringSliceVar(&flags.FilterTypes, "types", flags.FilterTypes, "Output only events of given types.")
|
||||
cmd.Flags().BoolVar(&flags.NoHeaders, "no-headers", flags.NoHeaders, "When using the default output format, don't print headers.")
|
||||
cmdutil.AddChunkSizeFlag(cmd, &flags.ChunkSize)
|
||||
}
|
||||
|
||||
// ToOptions converts from CLI inputs to runtime inputs.
|
||||
func (flags *EventsFlags) ToOptions(ctx context.Context, args []string) (*EventsOptions, error) {
|
||||
func (flags *EventsFlags) ToOptions() (*EventsOptions, error) {
|
||||
o := &EventsOptions{
|
||||
ctx: ctx,
|
||||
AllNamespaces: flags.AllNamespaces,
|
||||
Watch: flags.Watch,
|
||||
FilterTypes: flags.FilterTypes,
|
||||
IOStreams: flags.IOStreams,
|
||||
}
|
||||
var err error
|
||||
@ -167,16 +180,46 @@ func (flags *EventsFlags) ToOptions(ctx context.Context, args []string) (*Events
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o.client, err = kubernetes.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(o.FilterTypes) > 0 {
|
||||
o.FilterTypes = sets.NewString(o.FilterTypes...).List()
|
||||
}
|
||||
|
||||
var printer printers.ResourcePrinter
|
||||
if flags.PrintFlags.OutputFormat != nil && len(*flags.PrintFlags.OutputFormat) > 0 {
|
||||
printer, err = flags.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
printer = NewEventPrinter(flags.NoHeaders, flags.AllNamespaces)
|
||||
}
|
||||
|
||||
o.PrintObj = func(object runtime.Object, writer io.Writer) error {
|
||||
return printer.PrintObj(object, writer)
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func (o *EventsOptions) Validate() error {
|
||||
for _, val := range o.FilterTypes {
|
||||
if !strings.EqualFold(val, "Normal") && !strings.EqualFold(val, "Warning") {
|
||||
return fmt.Errorf("valid --types are Normal or Warning")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run retrieves events
|
||||
func (o EventsOptions) Run() error {
|
||||
func (o *EventsOptions) Run() error {
|
||||
ctx := context.TODO()
|
||||
namespace := o.Namespace
|
||||
if o.AllNamespaces {
|
||||
namespace = ""
|
||||
@ -188,14 +231,19 @@ func (o EventsOptions) Run() error {
|
||||
fields.OneTermEqualSelector("involvedObject.name", o.forName)).String()
|
||||
}
|
||||
if o.Watch {
|
||||
return o.runWatch(namespace, listOptions)
|
||||
return o.runWatch(ctx, namespace, listOptions)
|
||||
}
|
||||
|
||||
e := o.client.CoreV1().Events(namespace)
|
||||
el := &corev1.EventList{}
|
||||
el := &corev1.EventList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "EventList",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
}
|
||||
err := runtimeresource.FollowContinue(&listOptions,
|
||||
func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
newEvents, err := e.List(o.ctx, options)
|
||||
newEvents, err := e.List(ctx, options)
|
||||
if err != nil {
|
||||
return nil, runtimeresource.EnhanceListError(err, options, "events")
|
||||
}
|
||||
@ -207,6 +255,22 @@ func (o EventsOptions) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var filteredEvents []corev1.Event
|
||||
for _, e := range el.Items {
|
||||
if !o.filteredEventType(e.Type) {
|
||||
continue
|
||||
}
|
||||
if e.GetObjectKind().GroupVersionKind().Empty() {
|
||||
e.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Version: "v1",
|
||||
Kind: "Event",
|
||||
})
|
||||
}
|
||||
filteredEvents = append(filteredEvents, e)
|
||||
}
|
||||
|
||||
el.Items = filteredEvents
|
||||
|
||||
if len(el.Items) == 0 {
|
||||
if o.AllNamespaces {
|
||||
fmt.Fprintln(o.ErrOut, "No events found.")
|
||||
@ -220,36 +284,39 @@ func (o EventsOptions) Run() error {
|
||||
|
||||
sort.Sort(SortableEvents(el.Items))
|
||||
|
||||
printHeadings(w, o.AllNamespaces)
|
||||
for _, e := range el.Items {
|
||||
printOneEvent(w, e, o.AllNamespaces)
|
||||
}
|
||||
o.PrintObj(el, w)
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o EventsOptions) runWatch(namespace string, listOptions metav1.ListOptions) error {
|
||||
eventWatch, err := o.client.CoreV1().Events(namespace).Watch(o.ctx, listOptions)
|
||||
func (o *EventsOptions) runWatch(ctx context.Context, namespace string, listOptions metav1.ListOptions) error {
|
||||
eventWatch, err := o.client.CoreV1().Events(namespace).Watch(ctx, listOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := printers.GetNewTabWriter(o.Out)
|
||||
headingsPrinted := false
|
||||
|
||||
ctx, cancel := context.WithCancel(o.ctx)
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
intr := interrupt.New(nil, cancel)
|
||||
intr.Run(func() error {
|
||||
_, err := watchtools.UntilWithoutRetry(ctx, eventWatch, func(e watch.Event) (bool, error) {
|
||||
_, err := watchtools.UntilWithoutRetry(cctx, eventWatch, func(e watch.Event) (bool, error) {
|
||||
if e.Type == watch.Deleted { // events are deleted after 1 hour; don't print that
|
||||
return false, nil
|
||||
}
|
||||
event := e.Object.(*corev1.Event)
|
||||
if !headingsPrinted {
|
||||
printHeadings(w, o.AllNamespaces)
|
||||
headingsPrinted = true
|
||||
|
||||
if ev, ok := e.Object.(*corev1.Event); !ok || !o.filteredEventType(ev.Type) {
|
||||
return false, nil
|
||||
}
|
||||
printOneEvent(w, *event, o.AllNamespaces)
|
||||
|
||||
if e.Object.GetObjectKind().GroupVersionKind().Empty() {
|
||||
e.Object.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Version: "v1",
|
||||
Kind: "Event",
|
||||
})
|
||||
}
|
||||
|
||||
o.PrintObj(e.Object, w)
|
||||
w.Flush()
|
||||
return false, nil
|
||||
})
|
||||
@ -259,36 +326,22 @@ func (o EventsOptions) runWatch(namespace string, listOptions metav1.ListOptions
|
||||
return nil
|
||||
}
|
||||
|
||||
func printHeadings(w io.Writer, allNamespaces bool) {
|
||||
if allNamespaces {
|
||||
fmt.Fprintf(w, "NAMESPACE\t")
|
||||
}
|
||||
fmt.Fprintf(w, "LAST SEEN\tTYPE\tREASON\tOBJECT\tMESSAGE\n")
|
||||
// filteredEventType checks given event can be printed
|
||||
// by comparing it in filtered event flag.
|
||||
// If --event flag is not set by user, this function allows
|
||||
// all events to be printed.
|
||||
func (o *EventsOptions) filteredEventType(et string) bool {
|
||||
if len(o.FilterTypes) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
func printOneEvent(w io.Writer, e corev1.Event, allNamespaces bool) {
|
||||
var interval string
|
||||
firstTimestampSince := translateMicroTimestampSince(e.EventTime)
|
||||
if e.EventTime.IsZero() {
|
||||
firstTimestampSince = translateTimestampSince(e.FirstTimestamp)
|
||||
for _, t := range o.FilterTypes {
|
||||
if strings.EqualFold(t, et) {
|
||||
return true
|
||||
}
|
||||
if e.Series != nil {
|
||||
interval = fmt.Sprintf("%s (x%d over %s)", translateMicroTimestampSince(e.Series.LastObservedTime), e.Series.Count, firstTimestampSince)
|
||||
} else if e.Count > 1 {
|
||||
interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, firstTimestampSince)
|
||||
} else {
|
||||
interval = firstTimestampSince
|
||||
}
|
||||
if allNamespaces {
|
||||
fmt.Fprintf(w, "%v\t", e.Namespace)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s/%s\t%v\n",
|
||||
interval,
|
||||
e.Type,
|
||||
e.Reason,
|
||||
e.InvolvedObject.Kind, e.InvolvedObject.Name,
|
||||
strings.TrimSpace(e.Message),
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SortableEvents implements sort.Interface for []api.Event by time
|
||||
@ -318,26 +371,6 @@ func eventTime(event corev1.Event) time.Time {
|
||||
return event.EventTime.Time
|
||||
}
|
||||
|
||||
// translateMicroTimestampSince returns the elapsed time since timestamp in
|
||||
// human-readable approximation.
|
||||
func translateMicroTimestampSince(timestamp metav1.MicroTime) string {
|
||||
if timestamp.IsZero() {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
return duration.HumanDuration(time.Since(timestamp.Time))
|
||||
}
|
||||
|
||||
// translateTimestampSince returns the elapsed time since timestamp in
|
||||
// human-readable approximation.
|
||||
func translateTimestampSince(timestamp metav1.Time) string {
|
||||
if timestamp.IsZero() {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
return duration.HumanDuration(time.Since(timestamp.Time))
|
||||
}
|
||||
|
||||
// Inspired by k8s.io/cli-runtime/pkg/resource splitResourceTypeName()
|
||||
|
||||
// decodeResourceTypeName handles type/name resource formats and returns a resource tuple
|
||||
|
219
staging/src/k8s.io/kubectl/pkg/cmd/events/events_test.go
Normal file
219
staging/src/k8s.io/kubectl/pkg/cmd/events/events_test.go
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"io"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
)
|
||||
|
||||
func getFakeEvents() *corev1.EventList {
|
||||
return &corev1.EventList{
|
||||
Items: []corev1.Event{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-000",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-30 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-001",
|
||||
Namespace: "foo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeWarning,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-28 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-18 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar-002",
|
||||
Namespace: "otherfoo",
|
||||
},
|
||||
InvolvedObject: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "bar",
|
||||
Namespace: "otherfoo",
|
||||
UID: "00000000-0000-0000-0000-000000000001",
|
||||
},
|
||||
Type: corev1.EventTypeNormal,
|
||||
Reason: "ScalingReplicaSet",
|
||||
Message: "Scaled up replica set bar-002 to 1",
|
||||
ReportingController: "deployment-controller",
|
||||
EventTime: metav1.NewMicroTime(time.Now().Add(-25 * time.Minute)),
|
||||
Series: &corev1.EventSeries{
|
||||
Count: 3,
|
||||
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-15 * time.Minute)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventIsSorted(t *testing.T) {
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
clientset, err := kubernetes.NewForConfig(cmdtesting.DefaultClientConfig())
|
||||
if err != err {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
clientset.CoreV1().RESTClient().(*restclient.RESTClient).Client = fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, getFakeEvents())}, nil
|
||||
})
|
||||
|
||||
printer := NewEventPrinter(false, true)
|
||||
|
||||
options := &EventsOptions{
|
||||
AllNamespaces: true,
|
||||
client: clientset,
|
||||
PrintObj: func(object runtime.Object, writer io.Writer) error {
|
||||
return printer.PrintObj(object, writer)
|
||||
},
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
err = options.Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
`
|
||||
if e, a := expected, buf.String(); e != a {
|
||||
t.Errorf("expected\n%v\ngot\n%v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventNoHeaders(t *testing.T) {
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
clientset, err := kubernetes.NewForConfig(cmdtesting.DefaultClientConfig())
|
||||
if err != err {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
clientset.CoreV1().RESTClient().(*restclient.RESTClient).Client = fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, getFakeEvents())}, nil
|
||||
})
|
||||
|
||||
printer := NewEventPrinter(true, true)
|
||||
|
||||
options := &EventsOptions{
|
||||
AllNamespaces: true,
|
||||
client: clientset,
|
||||
PrintObj: func(object runtime.Object, writer io.Writer) error {
|
||||
return printer.PrintObj(object, writer)
|
||||
},
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
err = options.Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
`
|
||||
if e, a := expected, buf.String(); e != a {
|
||||
t.Errorf("expected\n%v\ngot\n%v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventFiltered(t *testing.T) {
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
clientset, err := kubernetes.NewForConfig(cmdtesting.DefaultClientConfig())
|
||||
if err != err {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
clientset.CoreV1().RESTClient().(*restclient.RESTClient).Client = fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, getFakeEvents())}, nil
|
||||
})
|
||||
|
||||
printer := NewEventPrinter(false, true)
|
||||
|
||||
options := &EventsOptions{
|
||||
AllNamespaces: true,
|
||||
client: clientset,
|
||||
FilterTypes: []string{"WARNING"},
|
||||
PrintObj: func(object runtime.Object, writer io.Writer) error {
|
||||
return printer.PrintObj(object, writer)
|
||||
},
|
||||
IOStreams: streams,
|
||||
}
|
||||
|
||||
err = options.Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
|
||||
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
|
||||
`
|
||||
if e, a := expected, buf.String(); e != a {
|
||||
t.Errorf("expected\n%v\ngot\n%v", e, a)
|
||||
}
|
||||
}
|
@ -61,6 +61,27 @@ run_kubectl_events_tests() {
|
||||
output_message=$(kubectl alpha events -n test-events --for=Cronjob/pi --watch --request-timeout=1 "${kube_flags[@]:?}" 2>&1)
|
||||
kube::test::if_has_string "${output_message}" "Warning" "InvalidSchedule" "Cronjob/pi"
|
||||
|
||||
# Post-Condition: events returns event for Cronjob/pi when filtered by Warning
|
||||
output_message=$(kubectl alpha events -n test-events --for=Cronjob/pi --types=Warning "${kube_flags[@]:?}" 2>&1)
|
||||
kube::test::if_has_string "${output_message}" "Warning" "InvalidSchedule" "Cronjob/pi"
|
||||
|
||||
# Post-Condition: events not returns event for Cronjob/pi when filtered only by Normal
|
||||
output_message=$(kubectl alpha events -n test-events --for=Cronjob/pi --types=Normal "${kube_flags[@]:?}" 2>&1)
|
||||
kube::test::if_has_not_string "${output_message}" "Warning" "InvalidSchedule" "Cronjob/pi"
|
||||
|
||||
# Post-Condition: events returns event for Cronjob/pi without headers
|
||||
output_message=$(kubectl alpha events -n test-events --for=Cronjob/pi --no-headers "${kube_flags[@]:?}" 2>&1)
|
||||
kube::test::if_has_not_string "${output_message}" "LAST SEEN" "TYPE" "REASON"
|
||||
kube::test::if_has_string "${output_message}" "Warning" "InvalidSchedule" "Cronjob/pi"
|
||||
|
||||
# Post-Condition: events returns event for Cronjob/pi in json format
|
||||
output_message=$(kubectl alpha events -n test-events --for=Cronjob/pi --output=json "${kube_flags[@]:?}" 2>&1)
|
||||
kube::test::if_has_string "${output_message}" "Warning" "InvalidSchedule" "Cronjob/pi"
|
||||
|
||||
# Post-Condition: events returns event for Cronjob/pi in yaml format
|
||||
output_message=$(kubectl alpha events -n test-events --for=Cronjob/pi --output=yaml "${kube_flags[@]:?}" 2>&1)
|
||||
kube::test::if_has_string "${output_message}" "Warning" "InvalidSchedule" "Cronjob/pi"
|
||||
|
||||
#Clean up
|
||||
kubectl delete cronjob pi --namespace=test-events
|
||||
kubectl delete namespace test-events
|
||||
|
Loading…
Reference in New Issue
Block a user