mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 11:28:58 +00:00
Skip events for multi-object kubectl describe
Default ShowEvents=false when describing multiple objects and the user has not explicitly set --show-events. Single-object describe unchanged. Applied in both Run() and DescribeMatchingResources() using a value copy of DescriberSettings. Signed-off-by: Mark Liu <mark@prove.com.au>
This commit is contained in:
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/describe"
|
||||
"k8s.io/kubectl/pkg/util/completion"
|
||||
@@ -72,11 +73,12 @@ var (
|
||||
// DescribeFlags directly reflect the information that CLI is gathering via flags. They will be converted to Options,
|
||||
// which reflect the runtime requirements for the command.
|
||||
type DescribeFlags struct {
|
||||
Factory cmdutil.Factory
|
||||
Selector string
|
||||
AllNamespaces bool
|
||||
FilenameOptions *resource.FilenameOptions
|
||||
DescriberSettings *describe.DescriberSettings
|
||||
Factory cmdutil.Factory
|
||||
Selector string
|
||||
AllNamespaces bool
|
||||
ShowEventsExplicitlySet bool
|
||||
FilenameOptions *resource.FilenameOptions
|
||||
DescriberSettings *describe.DescriberSettings
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
@@ -98,7 +100,11 @@ func (flags *DescribeFlags) AddFlags(cmd *cobra.Command) {
|
||||
cmdutil.AddFilenameOptionFlags(cmd, flags.FilenameOptions, "containing the resource to describe")
|
||||
cmdutil.AddLabelSelectorFlagVar(cmd, &flags.Selector)
|
||||
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().BoolVar(&flags.DescriberSettings.ShowEvents, "show-events", flags.DescriberSettings.ShowEvents, "If true, display events related to the described object.")
|
||||
cmd.Flags().BoolVar(&flags.DescriberSettings.ShowEvents, "show-events", flags.DescriberSettings.ShowEvents, "If true, display events related to the described object. Defaults to true for a single object, false for multiple objects and prefix matching.")
|
||||
|
||||
f := cmd.Flags().Lookup("show-events")
|
||||
f.Value = cliflag.NewTracker(f.Value, &flags.ShowEventsExplicitlySet)
|
||||
|
||||
cmdutil.AddChunkSizeFlag(cmd, &flags.DescriberSettings.ChunkSize)
|
||||
}
|
||||
|
||||
@@ -126,16 +132,17 @@ func (flags *DescribeFlags) ToOptions(parent string, args []string) (*DescribeOp
|
||||
}
|
||||
|
||||
o := &DescribeOptions{
|
||||
Selector: flags.Selector,
|
||||
Namespace: namespace,
|
||||
Describer: describer,
|
||||
NewBuilder: flags.Factory.NewBuilder,
|
||||
BuilderArgs: builderArgs,
|
||||
EnforceNamespace: enforceNamespace,
|
||||
AllNamespaces: flags.AllNamespaces,
|
||||
FilenameOptions: flags.FilenameOptions,
|
||||
DescriberSettings: flags.DescriberSettings,
|
||||
IOStreams: flags.IOStreams,
|
||||
Selector: flags.Selector,
|
||||
Namespace: namespace,
|
||||
Describer: describer,
|
||||
NewBuilder: flags.Factory.NewBuilder,
|
||||
BuilderArgs: builderArgs,
|
||||
EnforceNamespace: enforceNamespace,
|
||||
AllNamespaces: flags.AllNamespaces,
|
||||
ShowEventsExplicitlySet: flags.ShowEventsExplicitlySet,
|
||||
FilenameOptions: flags.FilenameOptions,
|
||||
DescriberSettings: flags.DescriberSettings,
|
||||
IOStreams: flags.IOStreams,
|
||||
}
|
||||
|
||||
return o, nil
|
||||
@@ -186,6 +193,9 @@ func (o *DescribeOptions) Run() error {
|
||||
|
||||
allErrs := []error{}
|
||||
infos, err := r.Infos()
|
||||
if (err != nil || len(infos) > 1) && !o.ShowEventsExplicitlySet {
|
||||
o.DescriberSettings.ShowEvents = false
|
||||
}
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) && len(o.BuilderArgs) == 2 {
|
||||
return o.DescribeMatchingResources(err, o.BuilderArgs[0], o.BuilderArgs[1])
|
||||
@@ -284,8 +294,9 @@ type DescribeOptions struct {
|
||||
|
||||
BuilderArgs []string
|
||||
|
||||
EnforceNamespace bool
|
||||
AllNamespaces bool
|
||||
EnforceNamespace bool
|
||||
AllNamespaces bool
|
||||
ShowEventsExplicitlySet bool
|
||||
|
||||
DescriberSettings *describe.DescriberSettings
|
||||
FilenameOptions *resource.FilenameOptions
|
||||
|
||||
@@ -22,12 +22,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/describe"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
@@ -325,6 +328,382 @@ func TestDescribeNoResourcesFound(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDescribeShowEventsCardinality tests the cardinality-based ShowEvents
|
||||
// default for the standard list and direct-name paths.
|
||||
func TestDescribeShowEventsCardinality(t *testing.T) {
|
||||
onePod := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
},
|
||||
}
|
||||
twoPods := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
flags map[string]string
|
||||
pods *corev1.PodList
|
||||
exactName string // if set, use direct GET client instead of list Resp
|
||||
expectShowEvents bool
|
||||
}{
|
||||
{
|
||||
name: "single object keeps events",
|
||||
args: []string{"pods"},
|
||||
pods: onePod,
|
||||
expectShowEvents: true,
|
||||
},
|
||||
{
|
||||
name: "multiple objects defaults events off",
|
||||
args: []string{"pods"},
|
||||
pods: twoPods,
|
||||
expectShowEvents: false,
|
||||
},
|
||||
{
|
||||
name: "multiple objects explicit --show-events=true",
|
||||
args: []string{"pods"},
|
||||
pods: twoPods,
|
||||
flags: map[string]string{"show-events": "true"},
|
||||
expectShowEvents: true,
|
||||
},
|
||||
{
|
||||
name: "multiple objects explicit --show-events=false",
|
||||
args: []string{"pods"},
|
||||
pods: twoPods,
|
||||
flags: map[string]string{"show-events": "false"},
|
||||
expectShowEvents: false,
|
||||
},
|
||||
{
|
||||
name: "single object by exact name keeps events",
|
||||
args: []string{"pods", "my-pod"},
|
||||
exactName: "my-pod",
|
||||
expectShowEvents: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := describe.DescriberFn
|
||||
defer func() { describe.DescriberFn = oldFn }()
|
||||
describe.DescriberFn = d.describerFor
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
if tc.exactName != "" {
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: tc.exactName, Namespace: "test", ResourceVersion: "10"},
|
||||
Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways},
|
||||
}
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Path == "/namespaces/test/pods/"+tc.exactName && req.Method == http.MethodGet {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pod)}, nil
|
||||
}
|
||||
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
|
||||
return nil, nil
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, tc.pods)},
|
||||
}
|
||||
}
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
|
||||
for k, v := range tc.flags {
|
||||
_ = cmd.Flags().Set(k, v)
|
||||
}
|
||||
cmd.Run(cmd, tc.args)
|
||||
|
||||
if d.Settings.ShowEvents != tc.expectShowEvents {
|
||||
t.Errorf("expected ShowEvents=%v, got %v", tc.expectShowEvents, d.Settings.ShowEvents)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDescribeShowEventsPrefixPath tests the cardinality-based ShowEvents
|
||||
// default for the prefix-match path (kubectl describe TYPE NAME_PREFIX).
|
||||
func TestDescribeShowEventsPrefixPath(t *testing.T) {
|
||||
twoFooOneBar := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo-abc", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo-def", Namespace: "test", ResourceVersion: "11"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "bar-xyz", Namespace: "test", ResourceVersion: "12"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
},
|
||||
}
|
||||
twoPodsFoo := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo-abc", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo-def", Namespace: "test", ResourceVersion: "11"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
},
|
||||
}
|
||||
oneFooOneBar := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo-abc", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "bar-xyz", Namespace: "test", ResourceVersion: "11"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
},
|
||||
}
|
||||
barOnly := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "bar-abc", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
pods *corev1.PodList
|
||||
flags map[string]string
|
||||
expectShowEvents bool
|
||||
expectMatchCount int // 0 when expecting error
|
||||
expectFatal bool
|
||||
}{
|
||||
{
|
||||
name: "multi match defaults events off",
|
||||
pods: twoFooOneBar,
|
||||
expectShowEvents: false,
|
||||
expectMatchCount: 2,
|
||||
},
|
||||
{
|
||||
name: "single match still disables events (prefix path)",
|
||||
pods: oneFooOneBar,
|
||||
expectShowEvents: false,
|
||||
expectMatchCount: 1,
|
||||
},
|
||||
{
|
||||
name: "multi match explicit --show-events=true",
|
||||
pods: twoPodsFoo,
|
||||
flags: map[string]string{"show-events": "true"},
|
||||
expectShowEvents: true,
|
||||
expectMatchCount: 2,
|
||||
},
|
||||
{
|
||||
name: "multi match explicit --show-events=false",
|
||||
pods: twoPodsFoo,
|
||||
flags: map[string]string{"show-events": "false"},
|
||||
expectShowEvents: false,
|
||||
expectMatchCount: 2,
|
||||
},
|
||||
{
|
||||
name: "no match returns original error",
|
||||
pods: barOnly,
|
||||
expectFatal: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := describe.DescriberFn
|
||||
defer func() { describe.DescriberFn = oldFn }()
|
||||
describe.DescriberFn = d.describerFor
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods/foo" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
|
||||
case p == "/namespaces/test/pods" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, tc.pods)}, nil
|
||||
default:
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
var fatalMsg string
|
||||
cmdutil.BehaviorOnFatal(func(msg string, code int) { fatalMsg = msg })
|
||||
defer cmdutil.DefaultBehaviorOnFatal()
|
||||
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
for k, v := range tc.flags {
|
||||
_ = cmd.Flags().Set(k, v)
|
||||
}
|
||||
cmd.Run(cmd, []string{"pods", "foo"})
|
||||
|
||||
if tc.expectFatal {
|
||||
if buf.Len() != 0 {
|
||||
t.Errorf("expected no output, got: %q", buf.String())
|
||||
}
|
||||
if fatalMsg == "" {
|
||||
t.Error("expected fatal error, got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
count := strings.Count(buf.String(), d.Output)
|
||||
if count != tc.expectMatchCount {
|
||||
t.Errorf("expected %d describe outputs, got %d", tc.expectMatchCount, count)
|
||||
}
|
||||
if d.Settings.ShowEvents != tc.expectShowEvents {
|
||||
t.Errorf("expected ShowEvents=%v, got %v", tc.expectShowEvents, d.Settings.ShowEvents)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDescribePartialErrorStillDisablesEvents verifies that when ContinueOnError
|
||||
// produces partial results, the cardinality check applies to successful infos.
|
||||
func TestDescribePartialErrorStillDisablesEvents(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := describe.DescriberFn
|
||||
defer func() { describe.DescriberFn = oldFn }()
|
||||
describe.DescriberFn = d.describerFor
|
||||
|
||||
foo := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}}
|
||||
baz := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods/foo" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, foo)}, nil
|
||||
case p == "/namespaces/test/pods/bar" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusInternalServerError, Header: cmdtesting.DefaultHeader(),
|
||||
Body: cmdtesting.StringBody(`{"kind":"Status","apiVersion":"v1","status":"Failure","message":"internal error"}`)}, nil
|
||||
case p == "/namespaces/test/pods/baz" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, baz)}, nil
|
||||
default:
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
var fatalMsg string
|
||||
cmdutil.BehaviorOnFatal(func(msg string, code int) { fatalMsg = msg })
|
||||
defer cmdutil.DefaultBehaviorOnFatal()
|
||||
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
cmd.Run(cmd, []string{"pods", "foo", "bar", "baz"})
|
||||
|
||||
if count := strings.Count(buf.String(), d.Output); count != 2 {
|
||||
t.Errorf("expected 2 describe outputs for partial success, got %d", count)
|
||||
}
|
||||
if d.Settings.ShowEvents != false {
|
||||
t.Errorf("expected ShowEvents=false for 2 successful infos, got %v", d.Settings.ShowEvents)
|
||||
}
|
||||
if fatalMsg == "" {
|
||||
t.Error("expected partial error to be reported")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDescribeMixedResourceTypesDisablesEvents verifies that the cardinality
|
||||
// check applies across resource types (1 pod + 1 service = events off).
|
||||
func TestDescribeMixedResourceTypesDisablesEvents(t *testing.T) {
|
||||
d := &testDescriber{Output: "test output"}
|
||||
oldFn := describe.DescriberFn
|
||||
defer func() { describe.DescriberFn = oldFn }()
|
||||
describe.DescriberFn = d.describerFor
|
||||
|
||||
pods := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{{ObjectMeta: metav1.ObjectMeta{Name: "my-pod", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}}},
|
||||
}
|
||||
services := &corev1.ServiceList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "16"},
|
||||
Items: []corev1.Service{{ObjectMeta: metav1.ObjectMeta{Name: "my-svc", Namespace: "test", ResourceVersion: "11"}, Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeClusterIP}}},
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/namespaces/test/pods" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)}, nil
|
||||
case p == "/namespaces/test/services" && m == "GET":
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, services)}, nil
|
||||
default:
|
||||
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
cmd := NewCmdDescribe("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
|
||||
cmd.Run(cmd, []string{"pods,services"})
|
||||
if d.Settings.ShowEvents != false {
|
||||
t.Errorf("expected ShowEvents=false for mixed types (2 objects), got %v", d.Settings.ShowEvents)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDescribeDescriberErrorsRetainDisabledEvents verifies that when 3 objects
|
||||
// are fetched (events disabled) but the describer fails for 2, the successful
|
||||
// describe still receives ShowEvents=false.
|
||||
func TestDescribeDescriberErrorsRetainDisabledEvents(t *testing.T) {
|
||||
cd := &conditionalDescriber{
|
||||
Output: "test output",
|
||||
FailNames: map[string]bool{"foo": true, "bar": true},
|
||||
}
|
||||
oldFn := describe.DescriberFn
|
||||
defer func() { describe.DescriberFn = oldFn }()
|
||||
describe.DescriberFn = cd.describerFor
|
||||
|
||||
pods := &corev1.PodList{
|
||||
ListMeta: metav1.ListMeta{ResourceVersion: "15"},
|
||||
Items: []corev1.Pod{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, Spec: corev1.PodSpec{RestartPolicy: corev1.RestartPolicyAlways}},
|
||||
},
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory().WithNamespace("test")
|
||||
defer tf.Cleanup()
|
||||
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
tf.UnstructuredClient = &fake.RESTClient{
|
||||
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, pods)},
|
||||
}
|
||||
|
||||
var fatalMsg string
|
||||
cmdutil.BehaviorOnFatal(func(msg string, code int) { fatalMsg = msg })
|
||||
defer cmdutil.DefaultBehaviorOnFatal()
|
||||
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
cmd := NewCmdDescribe("kubectl", tf, streams)
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
|
||||
if !strings.Contains(buf.String(), cd.Output) {
|
||||
t.Errorf("expected baz's output in result, got: %q", buf.String())
|
||||
}
|
||||
if cd.LastSettings.ShowEvents != false {
|
||||
t.Errorf("expected ShowEvents=false for 3 infos, got %v", cd.LastSettings.ShowEvents)
|
||||
}
|
||||
if fatalMsg == "" {
|
||||
t.Error("expected describer errors to be reported")
|
||||
}
|
||||
}
|
||||
|
||||
type testDescriber struct {
|
||||
Name, Namespace string
|
||||
Settings describe.DescriberSettings
|
||||
@@ -340,3 +719,22 @@ func (t *testDescriber) Describe(namespace, name string, describerSettings descr
|
||||
func (t *testDescriber) describerFor(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (describe.ResourceDescriber, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// conditionalDescriber fails Describe() for names in FailNames, succeeds for others.
|
||||
type conditionalDescriber struct {
|
||||
FailNames map[string]bool
|
||||
Output string
|
||||
LastSettings describe.DescriberSettings
|
||||
}
|
||||
|
||||
func (c *conditionalDescriber) Describe(namespace, name string, settings describe.DescriberSettings) (string, error) {
|
||||
c.LastSettings = settings
|
||||
if c.FailNames[name] {
|
||||
return "", fmt.Errorf("describe failed for %s", name)
|
||||
}
|
||||
return c.Output, nil
|
||||
}
|
||||
|
||||
func (c *conditionalDescriber) describerFor(restClientGetter genericclioptions.RESTClientGetter, mapping *meta.RESTMapping) (describe.ResourceDescriber, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user