Improve ignore-not-found behavior (#132542)

* Improve ignore-not-found behavior

* ignore lint errcheck

Kubernetes-commit: a7e8a505c25965c074f2b10b1bf40230eca48a08
This commit is contained in:
Gemma Hou 2025-07-03 01:53:24 +00:00 committed by Kubernetes Publisher
parent f07a946956
commit 76e6818d8d
2 changed files with 151 additions and 2 deletions

View File

@ -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().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.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.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().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.") 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) addServerPrintColumnFlags(cmd, o)
@ -623,6 +623,10 @@ func (o *GetOptions) watch(f cmdutil.Factory, args []string) error {
} }
infos, err := r.Infos() infos, err := r.Infos()
if err != nil { if err != nil {
// Ignore "NotFound" error when ignore-not-found is set to true
if apierrors.IsNotFound(err) && o.IgnoreNotFound {
return nil
}
return err return err
} }
if multipleGVKsRequested(infos) { if multipleGVKsRequested(infos) {

View File

@ -21,6 +21,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"net/http" "net/http"
"reflect" "reflect"
"strings" "strings"
@ -711,7 +712,7 @@ func TestGetEmptyTable(t *testing.T) {
} }
} }
func TestGetObjectIgnoreNotFound(t *testing.T) { func TestGetNonExistObject(t *testing.T) {
cmdtesting.InitTestErrorHandler(t) cmdtesting.InitTestErrorHandler(t)
ns := &corev1.NamespaceList{ 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() streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams) cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOut(buf) cmd.SetOut(buf)
@ -2126,6 +2184,93 @@ foo <unknown>
} }
} }
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) { func TestWatchStatus(t *testing.T) {
pods, events := watchTestData() pods, events := watchTestData()
events = append(events, watch.Event{Type: "ERROR", Object: &metav1.Status{Status: "Failure", Reason: "InternalServerError", Message: "Something happened"}}) events = append(events, watch.Event{Type: "ERROR", Object: &metav1.Status{Status: "Failure", Reason: "InternalServerError", Message: "Something happened"}})