From 76e6818d8df502c62ccb13de5d135aa57639ba3f Mon Sep 17 00:00:00 2001 From: Gemma Hou Date: Thu, 3 Jul 2025 01:53:24 +0000 Subject: [PATCH] Improve ignore-not-found behavior (#132542) * Improve ignore-not-found behavior * ignore lint errcheck Kubernetes-commit: a7e8a505c25965c074f2b10b1bf40230eca48a08 --- pkg/cmd/get/get.go | 6 +- pkg/cmd/get/get_test.go | 147 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/get/get.go b/pkg/cmd/get/get.go index d84ed3d3e..5509158a7 100644 --- a/pkg/cmd/get/get.go +++ b/pkg/cmd/get/get.go @@ -182,7 +182,7 @@ func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStre cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes.") cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.") cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.") - cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.") + cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If set to true, suppresses NotFound error for specific objects that do not exist. Using this flag with commands that query for collections of resources has no effect when no resources are found.") cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") 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.") addServerPrintColumnFlags(cmd, o) @@ -623,6 +623,10 @@ func (o *GetOptions) watch(f cmdutil.Factory, args []string) error { } infos, err := r.Infos() if err != nil { + // Ignore "NotFound" error when ignore-not-found is set to true + if apierrors.IsNotFound(err) && o.IgnoreNotFound { + return nil + } return err } if multipleGVKsRequested(infos) { diff --git a/pkg/cmd/get/get_test.go b/pkg/cmd/get/get_test.go index 95abf0197..b2822921c 100644 --- a/pkg/cmd/get/get_test.go +++ b/pkg/cmd/get/get_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "io" + cmdutil "k8s.io/kubectl/pkg/cmd/util" "net/http" "reflect" "strings" @@ -711,7 +712,7 @@ func TestGetEmptyTable(t *testing.T) { } } -func TestGetObjectIgnoreNotFound(t *testing.T) { +func TestGetNonExistObject(t *testing.T) { cmdtesting.InitTestErrorHandler(t) ns := &corev1.NamespaceList{ @@ -745,6 +746,63 @@ func TestGetObjectIgnoreNotFound(t *testing.T) { }), } + cmdutil.BehaviorOnFatal(func(str string, code int) { + expectedErr := "Error from server (NotFound): the server could not find the requested resource (get pods nonexistentpod)" + if str != expectedErr { + t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr) + } + }) + + // Get nonexistentpod fails with above error message + streams, _, buf, _ := genericiooptions.NewTestIOStreams() + cmd := NewCmdGet("kubectl", tf, streams) + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.Run(cmd, []string{"pods", "nonexistentpod"}) +} + +func TestGetNonExistObjectIgnoreNotFound(t *testing.T) { + cmdtesting.InitTestErrorHandler(t) + + ns := &corev1.NamespaceList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "1", + }, + Items: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{Name: "testns", Namespace: "test", ResourceVersion: "11"}, + Spec: corev1.NamespaceSpec{}, + }, + }, + } + + 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/nonexistentpod" && m == "GET": + return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil + case p == "/api/v1/namespaces/test" && m == "GET": + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &ns.Items[0])}, nil + default: + t.Fatalf("request url: %#v,and request: %#v", req.URL, req) + return nil, nil + } + }), + } + + cmdutil.BehaviorOnFatal(func(str string, code int) { + expectedErr := "" + if str != expectedErr { + t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr) + } + }) + + // Get nonexistentpod passes without error when setting ignore-not-found to true streams, _, buf, _ := genericiooptions.NewTestIOStreams() cmd := NewCmdGet("kubectl", tf, streams) cmd.SetOut(buf) @@ -2126,6 +2184,93 @@ foo } } +func TestWatchNonExistObject(t *testing.T) { + pods, _ := watchTestData() + + 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/nonexistentpod" && m == "GET": + return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil + case p == "/api/v1/namespaces/test" && m == "GET": + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods[1])}, nil + default: + t.Fatalf("request url: %#v,and request: %#v", req.URL, req) + return nil, nil + } + }), + } + + cmdutil.BehaviorOnFatal(func(str string, code int) { + expectedErr := "Error from server (NotFound): the server could not find the requested resource (get pods nonexistentpod)" + if str != expectedErr { + t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr) + } + }) + + // Get nonexistentpod fails with above error message + streams, _, buf, _ := genericiooptions.NewTestIOStreams() + cmd := NewCmdGet("kubectl", tf, streams) + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.Flags().Set("watch", "true") //nolint:errcheck + cmd.Flags().Set("output", "yaml") //nolint:errcheck + cmd.Run(cmd, []string{"pods", "nonexistentpod"}) + + if buf.String() != "" { + t.Errorf("unexpected output: %s", buf.String()) + } +} + +func TestWatchNonExistObjectIgnoreNotFound(t *testing.T) { + pods, _ := watchTestData() + + 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/nonexistentpod" && m == "GET": + return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil + case p == "/api/v1/namespaces/test" && m == "GET": + return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods[1])}, nil + default: + t.Fatalf("request url: %#v,and request: %#v", req.URL, req) + return nil, nil + } + }), + } + + cmdutil.BehaviorOnFatal(func(str string, code int) { + expectedErr := "" + if str != expectedErr { + t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr) + } + }) + + // Get nonexistentpod passes without error when setting ignore-not-found to true + streams, _, buf, _ := genericiooptions.NewTestIOStreams() + cmd := NewCmdGet("kubectl", tf, streams) + cmd.SetOut(buf) + cmd.SetErr(buf) + cmd.Flags().Set("ignore-not-found", "true") //nolint:errcheck + cmd.Flags().Set("watch", "true") //nolint:errcheck + cmd.Flags().Set("output", "yaml") //nolint:errcheck + cmd.Run(cmd, []string{"pods", "nonexistentpod"}) + + if buf.String() != "" { + t.Errorf("unexpected output: %s", buf.String()) + } +} + func TestWatchStatus(t *testing.T) { pods, events := watchTestData() events = append(events, watch.Event{Type: "ERROR", Object: &metav1.Status{Status: "Failure", Reason: "InternalServerError", Message: "Something happened"}})