Merge pull request #87527 from brianpursley/kubectl-796

Added 'No resources found' message to describe <type> and top pod commands

Kubernetes-commit: 38acec9bbc955a33c3366dc6082df90d18229b6f
This commit is contained in:
Kubernetes Publisher 2020-02-11 01:20:02 -08:00
commit b5db31b032
7 changed files with 175 additions and 4 deletions

2
Godeps/Godeps.json generated
View File

@ -580,7 +580,7 @@
}, },
{ {
"ImportPath": "k8s.io/client-go", "ImportPath": "k8s.io/client-go",
"Rev": "af50d22222d3" "Rev": "0ff5a65499e6"
}, },
{ {
"ImportPath": "k8s.io/code-generator", "ImportPath": "k8s.io/code-generator",

4
go.mod
View File

@ -39,7 +39,7 @@ require (
k8s.io/api v0.0.0-20200209065837-d5ab0ddd8b73 k8s.io/api v0.0.0-20200209065837-d5ab0ddd8b73
k8s.io/apimachinery v0.0.0-20200209065656-fb45917aae8b k8s.io/apimachinery v0.0.0-20200209065656-fb45917aae8b
k8s.io/cli-runtime v0.0.0-20200209071941-a3380550112a k8s.io/cli-runtime v0.0.0-20200209071941-a3380550112a
k8s.io/client-go v0.0.0-20200209070101-af50d22222d3 k8s.io/client-go v0.0.0-20200210225353-0ff5a65499e6
k8s.io/component-base v0.0.0-20200209110224-e34e74d72164 k8s.io/component-base v0.0.0-20200209110224-e34e74d72164
k8s.io/klog v1.0.0 k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c
@ -56,7 +56,7 @@ replace (
k8s.io/api => k8s.io/api v0.0.0-20200209065837-d5ab0ddd8b73 k8s.io/api => k8s.io/api v0.0.0-20200209065837-d5ab0ddd8b73
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200209065656-fb45917aae8b k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200209065656-fb45917aae8b
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20200209071941-a3380550112a k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20200209071941-a3380550112a
k8s.io/client-go => k8s.io/client-go v0.0.0-20200209070101-af50d22222d3 k8s.io/client-go => k8s.io/client-go v0.0.0-20200210225353-0ff5a65499e6
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200209044353-396603da40fa k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200209044353-396603da40fa
k8s.io/component-base => k8s.io/component-base v0.0.0-20200209110224-e34e74d72164 k8s.io/component-base => k8s.io/component-base v0.0.0-20200209110224-e34e74d72164
k8s.io/metrics => k8s.io/metrics v0.0.0-20200209071824-48c4e2ddad0b k8s.io/metrics => k8s.io/metrics v0.0.0-20200209071824-48c4e2ddad0b

2
go.sum
View File

@ -315,7 +315,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
k8s.io/api v0.0.0-20200209065837-d5ab0ddd8b73/go.mod h1:p/XFTiuxjo+0OlHcql2jDLBO6mxwx4J9xy7Sq9vRtnY= k8s.io/api v0.0.0-20200209065837-d5ab0ddd8b73/go.mod h1:p/XFTiuxjo+0OlHcql2jDLBO6mxwx4J9xy7Sq9vRtnY=
k8s.io/apimachinery v0.0.0-20200209065656-fb45917aae8b/go.mod h1:5X8oEhnd931nEg6/Nkumo00nT6ZsCLp2h7Xwd7Ym6P4= k8s.io/apimachinery v0.0.0-20200209065656-fb45917aae8b/go.mod h1:5X8oEhnd931nEg6/Nkumo00nT6ZsCLp2h7Xwd7Ym6P4=
k8s.io/cli-runtime v0.0.0-20200209071941-a3380550112a/go.mod h1:STmitnuF1Wn7OiGEN67PnaK8WXHx76SPGZgZKsIdtr8= k8s.io/cli-runtime v0.0.0-20200209071941-a3380550112a/go.mod h1:STmitnuF1Wn7OiGEN67PnaK8WXHx76SPGZgZKsIdtr8=
k8s.io/client-go v0.0.0-20200209070101-af50d22222d3/go.mod h1:lrXl4vvqm+n6jlzJDxwcfMx2obCLSV1hYD1HuaL7WEc= k8s.io/client-go v0.0.0-20200210225353-0ff5a65499e6/go.mod h1:lrXl4vvqm+n6jlzJDxwcfMx2obCLSV1hYD1HuaL7WEc=
k8s.io/code-generator v0.0.0-20200209044353-396603da40fa/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.0.0-20200209044353-396603da40fa/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/component-base v0.0.0-20200209110224-e34e74d72164/go.mod h1:D0o0vo4rsOSZ/RBsvwDaBwyF8JBQwfUEhO8nnx89q9Y= k8s.io/component-base v0.0.0-20200209110224-e34e74d72164/go.mod h1:D0o0vo4rsOSZ/RBsvwDaBwyF8JBQwfUEhO8nnx89q9Y=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=

View File

@ -203,6 +203,15 @@ func (o *DescribeOptions) Run() error {
} }
} }
if len(infos) == 0 && len(allErrs) == 0 {
// if we wrote no output, and had no errors, be sure we output something.
if o.AllNamespaces {
fmt.Fprintln(o.ErrOut, "No resources found")
} else {
fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace)
}
}
return utilerrors.NewAggregate(allErrs) return utilerrors.NewAggregate(allErrs)
} }

View File

@ -245,6 +245,59 @@ func TestDescribeHelpMessage(t *testing.T) {
} }
} }
func TestDescribeNoResourcesFound(t *testing.T) {
testNS := "testns"
testCases := []struct {
name string
flags map[string]string
namespace string
expectedOutput string
expectedErr string
}{
{
name: "all namespaces",
flags: map[string]string{"all-namespaces": "true"},
expectedOutput: "",
expectedErr: "No resources found\n",
},
{
name: "all in namespace",
namespace: testNS,
expectedOutput: "",
expectedErr: "No resources found in " + testNS + " namespace.\n",
},
}
cmdtesting.InitTestErrorHandler(t)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
pods, _, _ := cmdtesting.EmptyTestData()
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
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)},
}
streams, _, buf, errbuf := genericclioptions.NewTestIOStreams()
cmd := NewCmdDescribe("kubectl", tf, streams)
for name, value := range testCase.flags {
_ = cmd.Flags().Set(name, value)
}
cmd.Run(cmd, []string{"pods"})
if e, a := testCase.expectedOutput, buf.String(); e != a {
t.Errorf("Unexpected output:\nExpected:\n%v\nActual:\n%v", e, a)
}
if e, a := testCase.expectedErr, errbuf.String(); e != a {
t.Errorf("Unexpected error:\nExpected:\n%v\nActual:\n%v", e, a)
}
})
}
}
type testDescriber struct { type testDescriber struct {
Name, Namespace string Name, Namespace string
Settings describe.DescriberSettings Settings describe.DescriberSettings

View File

@ -197,6 +197,13 @@ func (o TopPodOptions) RunTopPod() error {
if e != nil { if e != nil {
return e return e
} }
// if we had no errors, be sure we output something.
if o.AllNamespaces {
fmt.Fprintln(o.ErrOut, "No resources found")
} else {
fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace)
}
} }
if err != nil { if err != nil {
return err return err

View File

@ -449,6 +449,108 @@ func TestTopPodWithMetricsServer(t *testing.T) {
} }
} }
func TestTopPodNoResourcesFound(t *testing.T) {
testNS := "testns"
testCases := []struct {
name string
options *TopPodOptions
namespace string
expectedOutput string
expectedErr string
expectedPath string
}{
{
name: "all namespaces",
options: &TopPodOptions{AllNamespaces: true},
expectedOutput: "",
expectedErr: "No resources found\n",
expectedPath: topMetricsAPIPathPrefix + "/pods",
},
{
name: "all in namespace",
namespace: testNS,
expectedOutput: "",
expectedErr: "No resources found in " + testNS + " namespace.\n",
expectedPath: topMetricsAPIPathPrefix + "/namespaces/" + testNS + "/pods",
},
}
cmdtesting.InitTestErrorHandler(t)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
fakemetricsClientset := &metricsfake.Clientset{}
fakemetricsClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
res := &metricsv1beta1api.PodMetricsList{
ListMeta: metav1.ListMeta{
ResourceVersion: "2",
},
Items: nil, // No metrics found
}
return true, res, nil
})
tf := cmdtesting.NewTestFactory().WithNamespace(testNS)
defer tf.Cleanup()
ns := scheme.Codecs.WithoutConversion()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p := req.URL.Path; {
case p == "/api":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
case p == "/apis":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: ioutil.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
case p == "/api/v1/namespaces/"+testNS+"/pods":
// Top Pod calls this endpoint to check if there are pods whenever it gets no metrics,
// so we need to return no pods for this test scenario
body, _ := marshallBody(metricsv1alpha1api.PodMetricsList{
ListMeta: metav1.ListMeta{
ResourceVersion: "2",
},
Items: nil, // No pods found
})
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
default:
t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v",
testCase.name, req, req.URL)
return nil, nil
}
}),
}
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
streams, _, buf, errbuf := genericclioptions.NewTestIOStreams()
cmd := NewCmdTopPod(tf, nil, streams)
var cmdOptions *TopPodOptions
if testCase.options != nil {
cmdOptions = testCase.options
} else {
cmdOptions = &TopPodOptions{}
}
cmdOptions.IOStreams = streams
if err := cmdOptions.Complete(tf, cmd, nil); err != nil {
t.Fatal(err)
}
cmdOptions.MetricsClient = fakemetricsClientset
if err := cmdOptions.Validate(); err != nil {
t.Fatal(err)
}
if err := cmdOptions.RunTopPod(); err != nil {
t.Fatal(err)
}
if e, a := testCase.expectedOutput, buf.String(); e != a {
t.Errorf("Unexpected output:\nExpected:\n%v\nActual:\n%v", e, a)
}
if e, a := testCase.expectedErr, errbuf.String(); e != a {
t.Errorf("Unexpected error:\nExpected:\n%v\nActual:\n%v", e, a)
}
})
}
}
type fakeDiscovery struct{} type fakeDiscovery struct{}
// ServerGroups returns the supported groups, with information like supported versions and the // ServerGroups returns the supported groups, with information like supported versions and the