Merge pull request #79345 from liggitt/kubectl-printing

Add meta Table tests, fix --watch-only with single item
This commit is contained in:
Kubernetes Prow Robot 2019-06-27 20:09:33 -07:00 committed by GitHub
commit 4745946525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 866 additions and 47 deletions

View File

@ -22,6 +22,7 @@ go_library(
"get.go",
"get_flags.go",
"humanreadable_flags.go",
"skip_printer.go",
"sorter.go",
"table_printer.go",
],
@ -60,6 +61,7 @@ go_library(
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/integer:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
"//vendor/vbom.ml/util/sortorder:go_default_library",
],
)
@ -97,7 +99,6 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",

View File

@ -49,12 +49,13 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
utilpointer "k8s.io/utils/pointer"
)
// GetOptions contains the input to the get command.
type GetOptions struct {
PrintFlags *PrintFlags
ToPrinter func(*meta.RESTMapping, bool, bool) (printers.ResourcePrinterFunc, error)
ToPrinter func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error)
IsHumanReadablePrinter bool
PrintWithOpenAPICols bool
@ -230,7 +231,7 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
o.IsHumanReadablePrinter = true
}
o.ToPrinter = func(mapping *meta.RESTMapping, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
o.ToPrinter = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
// make a new copy of current flags / opts before mutating
printFlags := o.PrintFlags.Copy()
@ -257,6 +258,9 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
if o.Sort {
printer = &SortingPrinter{Delegate: printer, SortField: sortBy}
}
if outputObjects != nil {
printer = &skipPrinter{delegate: printer, output: outputObjects}
}
if o.ServerPrint {
printer = &TablePrinter{Delegate: printer}
}
@ -539,7 +543,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
fmt.Fprintln(o.ErrOut)
}
printer, err = o.ToPrinter(mapping, printWithNamespace, printWithKind)
printer, err = o.ToPrinter(mapping, nil, printWithNamespace, printWithKind)
if err != nil {
if !errs.Has(err.Error()) {
errs.Insert(err.Error())
@ -635,7 +639,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
info := infos[0]
mapping := info.ResourceMapping()
printer, err := o.ToPrinter(mapping, o.AllNamespaces, false)
outputObjects := utilpointer.BoolPtr(!o.WatchOnly)
printer, err := o.ToPrinter(mapping, outputObjects, o.AllNamespaces, false)
if err != nil {
return err
}
@ -664,25 +669,29 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
tableGK := metainternal.SchemeGroupVersion.WithKind("Table").GroupKind()
// print the current object
if !o.WatchOnly {
var objsToPrint []runtime.Object
if isList {
objsToPrint, _ = meta.ExtractList(obj)
} else {
objsToPrint = append(objsToPrint, obj)
var objsToPrint []runtime.Object
if isList {
objsToPrint, _ = meta.ExtractList(obj)
} else {
objsToPrint = append(objsToPrint, obj)
}
for _, objToPrint := range objsToPrint {
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
// printing anything other than tables always takes the internal version, but the watch event uses externals
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
}
for _, objToPrint := range objsToPrint {
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
// printing anything other than tables always takes the internal version, but the watch event uses externals
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
}
if err := printer.PrintObj(objToPrint, writer); err != nil {
return fmt.Errorf("unable to output the provided object: %v", err)
}
if err := printer.PrintObj(objToPrint, writer); err != nil {
return fmt.Errorf("unable to output the provided object: %v", err)
}
writer.Flush()
}
writer.Flush()
if isList {
// we can start outputting objects now, watches started from lists don't emit synthetic added events
*outputObjects = true
} else {
// suppress output, since watches started for individual items emit a synthetic ADDED event first
*outputObjects = false
}
// print watched changes
@ -691,18 +700,11 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
return err
}
first := true
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
intr := interrupt.New(nil, cancel)
intr.Run(func() error {
_, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
if !isList && first {
// drop the initial watch event in the single resource case
first = false
return false, nil
}
// printing always takes the internal version, but the watch event uses externals
// TODO fix printing to use server-side or be version agnostic
objToPrint := e.Object
@ -714,6 +716,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
return false, err
}
writer.Flush()
// after processing at least one event, start outputting objects
*outputObjects = true
return false, nil
})
return err
@ -750,7 +754,7 @@ func (o *GetOptions) printGeneric(r *resource.Result) error {
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
}
printer, err := o.ToPrinter(nil, false, false)
printer, err := o.ToPrinter(nil, nil, false, false)
if err != nil {
return err
}

View File

@ -38,7 +38,6 @@ import (
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/watch"
@ -244,6 +243,31 @@ foo 0/0 0 <unknown>
}
}
func TestGetTableObjects(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
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: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items[0])},
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetObjectsShowKind(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
@ -270,6 +294,32 @@ pod/foo 0/0 0 <unknown>
}
}
func TestGetTableObjectsShowKind(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
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: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items[0])},
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("show-kind", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetMultipleResourceTypesShowKinds(t *testing.T) {
pods, svcs, _ := cmdtesting.TestData()
@ -325,6 +375,61 @@ service/baz ClusterIP <none> <none> <none> <unknown>
}
}
func TestGetMultipleTableResourceTypesShowKinds(t *testing.T) {
pods, svcs, _ := cmdtesting.TestData()
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: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items...)}, nil
case p == "/namespaces/test/replicationcontrollers" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &corev1.ReplicationControllerList{})}, nil
case p == "/namespaces/test/services" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: serviceTableObjBody(codec, svcs.Items...)}, nil
case p == "/namespaces/test/statefulsets" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &appsv1.StatefulSetList{})}, nil
case p == "/namespaces/test/horizontalpodautoscalers" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &autoscalingv1.HorizontalPodAutoscalerList{})}, nil
case p == "/namespaces/test/jobs" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &batchv1.JobList{})}, nil
case p == "/namespaces/test/cronjobs" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &batchv1beta1.CronJobList{})}, nil
case p == "/namespaces/test/daemonsets" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &appsv1.DaemonSetList{})}, nil
case p == "/namespaces/test/deployments" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &extensionsv1beta1.DeploymentList{})}, nil
case p == "/namespaces/test/replicasets" && m == "GET":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &extensionsv1beta1.ReplicaSetList{})}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"all"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetObjectsShowLabels(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
@ -351,6 +456,32 @@ foo 0/0 0 <unknown> <none>
}
}
func TestGetTableObjectsShowLabels(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
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: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items[0])},
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("show-labels", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE LABELS
foo 0/0 0 <unknown> <none>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetEmptyTable(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -769,6 +900,32 @@ foo 0/0 0 <unknown>
}
}
func TestGetTableObjectsIdentifiedByFile(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
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: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items[0])},
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("filename", "../../../../test/e2e/testing-manifests/statefulset/cassandra/controller.yaml")
cmd.Run(cmd, []string{})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetListObjects(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
@ -795,6 +952,32 @@ bar 0/0 0 <unknown>
}
}
func TestGetListTableObjects(t *testing.T) {
pods, _, _ := cmdtesting.TestData()
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: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items...)},
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
bar 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetListComponentStatus(t *testing.T) {
statuses := testComponentStatusData()
@ -922,6 +1105,44 @@ service/baz ClusterIP <none> <none> <none> <unknown>
}
}
func TestGetMultipleTypeTableObjects(t *testing.T) {
pods, svc, _ := cmdtesting.TestData()
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 req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items...)}, nil
case "/namespaces/test/services":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: serviceTableObjBody(codec, svc.Items...)}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"pods,services"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetMultipleTypeObjectsAsList(t *testing.T) {
pods, svc, _ := cmdtesting.TestData()
@ -1066,6 +1287,49 @@ service/baz ClusterIP <none> <none> <none> <unknown>
}
}
func TestGetMultipleTypeTableObjectsWithLabelSelector(t *testing.T) {
pods, svc, _ := cmdtesting.TestData()
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) {
if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
}
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items...)}, nil
case "/namespaces/test/services":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: serviceTableObjBody(codec, svc.Items...)}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetMultipleTypeObjectsWithFieldSelector(t *testing.T) {
pods, svc, _ := cmdtesting.TestData()
@ -1109,6 +1373,49 @@ service/baz ClusterIP <none> <none> <none> <unknown>
}
}
func TestGetMultipleTypeTableObjectsWithFieldSelector(t *testing.T) {
pods, svc, _ := cmdtesting.TestData()
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) {
if req.URL.Query().Get(metav1.FieldSelectorQueryParam("v1")) != "a=b" {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
}
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods.Items...)}, nil
case "/namespaces/test/services":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: serviceTableObjBody(codec, svc.Items...)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("field-selector", "a=b")
cmd.Run(cmd, []string{"pods,services"})
expected := `NAME READY STATUS RESTARTS AGE
pod/foo 0/0 0 <unknown>
pod/bar 0/0 0 <unknown>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
_, svc, _ := cmdtesting.TestData()
node := &corev1.Node{
@ -1152,6 +1459,49 @@ node/foo Unknown <none> <unknown>
}
}
func TestGetMultipleTypeTableObjectsWithDirectReference(t *testing.T) {
_, svc, _ := cmdtesting.TestData()
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
}
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 req.URL.Path {
case "/nodes/foo":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: nodeTableObjBody(codec, *node)}, nil
case "/namespaces/test/services/bar":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: serviceTableObjBody(codec, svc.Items[0])}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"services/bar", "node/foo"})
expected := `NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/baz ClusterIP <none> <none> <none> <unknown>
NAME STATUS ROLES AGE VERSION
node/foo Unknown <none> <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func watchTestData() ([]corev1.Pod, []watch.Event) {
pods := []corev1.Pod{
{
@ -1309,6 +1659,57 @@ foo 0/0 0 <unknown>
}
}
func TestWatchTableLabelSelector(t *testing.T) {
pods, events := watchTestData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
podList := &corev1.PodList{
Items: pods,
ListMeta: metav1.ListMeta{
ResourceVersion: "10",
},
}
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if req.URL.Query().Get(metav1.LabelSelectorQueryParam("v1")) != "a=b" {
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
}
switch req.URL.Path {
case "/namespaces/test/pods":
if req.URL.Query().Get("watch") == "true" {
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableWatchBody(codec, events[2:])}, nil
}
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, podList.Items...)}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("watch", "true")
cmd.Flags().Set("selector", "a=b")
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
bar 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestWatchFieldSelector(t *testing.T) {
pods, events := watchTestData()
@ -1360,6 +1761,57 @@ foo 0/0 0 <unknown>
}
}
func TestWatchTableFieldSelector(t *testing.T) {
pods, events := watchTestData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
podList := &corev1.PodList{
Items: pods,
ListMeta: metav1.ListMeta{
ResourceVersion: "10",
},
}
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if req.URL.Query().Get(metav1.FieldSelectorQueryParam("v1")) != "a=b" {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
}
switch req.URL.Path {
case "/namespaces/test/pods":
if req.URL.Query().Get("watch") == "true" {
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableWatchBody(codec, events[2:])}, nil
}
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, podList.Items...)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("watch", "true")
cmd.Flags().Set("field-selector", "a=b")
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
bar 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestWatchResource(t *testing.T) {
pods, events := watchTestData()
@ -1403,6 +1855,49 @@ foo 0/0 0 <unknown>
}
}
func TestWatchTableResource(t *testing.T) {
pods, events := 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 req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods[1])}, nil
case "/namespaces/test/pods":
if req.URL.Query().Get("watch") == "true" && req.URL.Query().Get("fieldSelector") == "metadata.name=foo" {
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableWatchBody(codec, events[1:])}, nil
}
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("watch", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestWatchResourceTable(t *testing.T) {
columns := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: "the name", Priority: 0},
@ -1596,6 +2091,48 @@ foo 0/0 0 <unknown>
}
}
func TestWatchOnlyTableResource(t *testing.T) {
pods, events := 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 req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, pods[1])}, nil
case "/namespaces/test/pods":
if req.URL.Query().Get("watch") == "true" && req.URL.Query().Get("fieldSelector") == "metadata.name=foo" {
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableWatchBody(codec, events[1:])}, nil
}
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("watch-only", "true")
cmd.Run(cmd, []string{"pods", "foo"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func TestWatchOnlyList(t *testing.T) {
pods, events := watchTestData()
@ -1641,6 +2178,51 @@ foo 0/0 0 <unknown>
}
}
func TestWatchOnlyTableList(t *testing.T) {
pods, events := watchTestData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
podList := &corev1.PodList{
Items: pods,
ListMeta: metav1.ListMeta{
ResourceVersion: "10",
},
}
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/namespaces/test/pods":
if req.URL.Query().Get("watch") == "true" {
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableWatchBody(codec, events[2:])}, nil
}
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: podTableObjBody(codec, podList.Items...)}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOutput(buf)
cmd.Flags().Set("watch-only", "true")
cmd.Run(cmd, []string{"pods"})
expected := `NAME READY STATUS RESTARTS AGE
foo 0/0 0 <unknown>
foo 0/0 0 <unknown>
`
if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a)
}
}
func watchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser {
buf := bytes.NewBuffer([]byte{})
enc := restclientwatch.NewEncoder(streaming.NewEncoder(buf, codec), codec)
@ -1649,5 +2231,101 @@ func watchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser {
panic(err)
}
}
return json.Framer.NewFrameReader(ioutil.NopCloser(buf))
return ioutil.NopCloser(buf)
}
var podColumns = []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Ready", Type: "string", Format: ""},
{Name: "Status", Type: "string", Format: ""},
{Name: "Restarts", Type: "integer", Format: ""},
{Name: "Age", Type: "string", Format: ""},
{Name: "IP", Type: "string", Format: "", Priority: 1},
{Name: "Node", Type: "string", Format: "", Priority: 1},
{Name: "Nominated Node", Type: "string", Format: "", Priority: 1},
{Name: "Readiness Gates", Type: "string", Format: "", Priority: 1},
}
// build a meta table response from a pod list
func podTableObjBody(codec runtime.Codec, pods ...corev1.Pod) io.ReadCloser {
table := &metav1.Table{
ColumnDefinitions: podColumns,
}
for i := range pods {
b := bytes.NewBuffer(nil)
codec.Encode(&pods[i], b)
table.Rows = append(table.Rows, metav1.TableRow{
Object: runtime.RawExtension{Raw: b.Bytes()},
Cells: []interface{}{pods[i].Name, "0/0", "", int64(0), "<unknown>", "<none>", "<none>", "<none>", "<none>"},
})
}
return cmdtesting.ObjBody(codec, table)
}
// build meta table watch events from pod watch events
func podTableWatchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser {
tableEvents := []watch.Event{}
for i, e := range events {
b := bytes.NewBuffer(nil)
codec.Encode(e.Object, b)
var columns []metav1.TableColumnDefinition
if i == 0 {
columns = podColumns
}
tableEvents = append(tableEvents, watch.Event{
Type: e.Type,
Object: &metav1.Table{
ColumnDefinitions: columns,
Rows: []metav1.TableRow{{
Object: runtime.RawExtension{Raw: b.Bytes()},
Cells: []interface{}{e.Object.(*corev1.Pod).Name, "0/0", "", int64(0), "<unknown>", "<none>", "<none>", "<none>", "<none>"},
}}},
})
}
return watchBody(codec, tableEvents)
}
// build a meta table response from a service list
func serviceTableObjBody(codec runtime.Codec, services ...corev1.Service) io.ReadCloser {
table := &metav1.Table{
ColumnDefinitions: []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Type", Type: "string", Format: ""},
{Name: "Cluster-IP", Type: "string", Format: ""},
{Name: "External-IP", Type: "string", Format: ""},
{Name: "Port(s)", Type: "string", Format: ""},
{Name: "Age", Type: "string", Format: ""},
},
}
for i := range services {
b := bytes.NewBuffer(nil)
codec.Encode(&services[i], b)
table.Rows = append(table.Rows, metav1.TableRow{
Object: runtime.RawExtension{Raw: b.Bytes()},
Cells: []interface{}{services[i].Name, "ClusterIP", "<none>", "<none>", "<none>", "<unknown>"},
})
}
return cmdtesting.ObjBody(codec, table)
}
// build a meta table response from a node list
func nodeTableObjBody(codec runtime.Codec, nodes ...corev1.Node) io.ReadCloser {
table := &metav1.Table{
ColumnDefinitions: []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Status", Type: "string", Format: ""},
{Name: "Roles", Type: "string", Format: ""},
{Name: "Age", Type: "string", Format: ""},
{Name: "Version", Type: "string", Format: ""},
},
}
for i := range nodes {
b := bytes.NewBuffer(nil)
codec.Encode(&nodes[i], b)
table.Rows = append(table.Rows, metav1.TableRow{
Object: runtime.RawExtension{Raw: b.Bytes()},
Cells: []interface{}{nodes[i].Name, "Unknown", "<none>", "<unknown>", ""},
})
}
return cmdtesting.ObjBody(codec, table)
}

View File

@ -18,26 +18,58 @@ package get
import (
"bytes"
"fmt"
"regexp"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
api "k8s.io/kubernetes/pkg/apis/core"
)
func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
testObject := &api.Pod{ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"l1": "value",
testTable := &metav1.Table{
ColumnDefinitions: []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Ready", Type: "string", Format: ""},
{Name: "Status", Type: "string", Format: ""},
{Name: "Restarts", Type: "integer", Format: ""},
{Name: "Age", Type: "string", Format: ""},
{Name: "IP", Type: "string", Format: "", Priority: 1},
{Name: "Node", Type: "string", Format: "", Priority: 1},
{Name: "Nominated Node", Type: "string", Format: "", Priority: 1},
{Name: "Readiness Gates", Type: "string", Format: "", Priority: 1},
},
}}
Rows: []metav1.TableRow{{
Object: runtime.RawExtension{
Object: &corev1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{"l1": "value"}},
},
},
Cells: []interface{}{"foo", "0/0", "", int64(0), "<unknown>", "<none>", "<none>", "<none>", "<none>"},
}},
}
testPod := &api.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"l1": "value",
},
},
}
testCases := []struct {
name string
testObject runtime.Object
showKind bool
showLabels bool
@ -56,53 +88,104 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
}{
{
name: "empty output format matches a humanreadable printer",
testObject: testPod.DeepCopy(),
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\nfoo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "empty output format matches a humanreadable printer",
testObject: testTable.DeepCopy(),
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\nfoo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "\"wide\" output format prints",
testObject: testPod.DeepCopy(),
outputFormat: "wide",
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +IP\\ +NODE\\ +NOMINATED NODE\\ +READINESS GATES\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
},
{
name: "\"wide\" output format prints",
testObject: testTable.DeepCopy(),
outputFormat: "wide",
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +IP\\ +NODE\\ +NOMINATED NODE\\ +READINESS GATES\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
},
{
name: "no-headers prints output with no headers",
testObject: testPod.DeepCopy(),
noHeaders: true,
expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "no-headers prints output with no headers",
testObject: testTable.DeepCopy(),
noHeaders: true,
expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "no-headers and a \"wide\" output format prints output with no headers and additional columns",
testObject: testPod.DeepCopy(),
outputFormat: "wide",
noHeaders: true,
expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
},
{
name: "no-headers and a \"wide\" output format prints output with no headers and additional columns",
testObject: testTable.DeepCopy(),
outputFormat: "wide",
noHeaders: true,
expectedOutput: "foo\\ +0/0\\ +0\\ +<unknown>\\ +<none>\\ +<none>\\ +<none>\\ +<none>\n",
},
{
name: "show-kind displays the resource's kind, even when printing a single type of resource",
testObject: testPod.DeepCopy(),
showKind: true,
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\npod/foo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "show-kind displays the resource's kind, even when printing a single type of resource",
testObject: testTable.DeepCopy(),
showKind: true,
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\npod/foo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "label-columns prints specified label values in new column",
testObject: testPod.DeepCopy(),
columnLabels: []string{"l1"},
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +L1\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +value\n",
},
{
name: "label-columns prints specified label values in new column",
testObject: testTable.DeepCopy(),
columnLabels: []string{"l1"},
expectedOutput: "NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\\ +L1\nfoo\\ +0/0\\ +0\\ +<unknown>\\ +value\n",
},
{
name: "withNamespace displays an additional NAMESPACE column",
testObject: testPod.DeepCopy(),
withNamespace: true,
expectedOutput: "NAMESPACE\\ +NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\n\\ +foo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "withNamespace displays an additional NAMESPACE column",
testObject: testTable.DeepCopy(),
withNamespace: true,
expectedOutput: "NAMESPACE\\ +NAME\\ +READY\\ +STATUS\\ +RESTARTS\\ +AGE\n\\ +foo\\ +0/0\\ +0\\ +<unknown>\n",
},
{
name: "no printer is matched on an invalid outputFormat",
testObject: testPod.DeepCopy(),
outputFormat: "invalid",
expectNoMatch: true,
},
{
name: "printer should not match on any other format supported by another printer",
testObject: testPod.DeepCopy(),
outputFormat: "go-template",
expectNoMatch: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run(fmt.Sprintf("%s %T", tc.name, tc.testObject), func(t *testing.T) {
printFlags := HumanPrintFlags{
ShowKind: &tc.showKind,
ShowLabels: &tc.showLabels,
@ -139,7 +222,7 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
}
out := bytes.NewBuffer([]byte{})
err = p.PrintObj(testObject, out)
err = p.PrintObj(tc.testObject, out)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -149,7 +232,7 @@ func TestHumanReadablePrinterSupportsExpectedOptions(t *testing.T) {
t.Errorf("unexpected error: %v", err)
}
if !match {
t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String())
t.Errorf("unexpected output: expecting\n%s\ngot\n%s", tc.expectedOutput, out.String())
}
})
}

View File

@ -0,0 +1,48 @@
/*
Copyright 2019 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 get
import (
"io"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
)
// skipPrinter allows conditionally suppressing object output via the output field.
// table objects are suppressed by setting their Rows to nil (allowing column definitions to propagate to the delegate).
// non-table objects are suppressed by not calling the delegate at all.
type skipPrinter struct {
delegate printers.ResourcePrinter
output *bool
}
func (p *skipPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
if *p.output {
return p.delegate.PrintObj(obj, writer)
}
table, isTable := obj.(*metav1beta1.Table)
if !isTable {
return nil
}
table = table.DeepCopy()
table.Rows = nil
return p.delegate.PrintObj(table, writer)
}

View File

@ -48,10 +48,11 @@ type handlerEntry struct {
// will only be printed if the object type changes. This makes it useful for printing items
// received from watches.
type HumanReadablePrinter struct {
handlerMap map[reflect.Type]*handlerEntry
options PrintOptions
lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition
handlerMap map[reflect.Type]*handlerEntry
options PrintOptions
lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition
printedHeaders bool
}
var _ TableGenerator = &HumanReadablePrinter{}

View File

@ -80,18 +80,22 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
if table, ok := obj.(*metav1beta1.Table); ok {
// Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
localOptions := h.options
if len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
if h.printedHeaders && (len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns)) {
localOptions.NoHeaders = true
}
if len(table.ColumnDefinitions) == 0 {
// If this table has no column definitions, use the columns from the last table we printed for decoration and layout.
// This is done when receiving tables in watch events to save bandwidth.
localOptions.NoHeaders = true
table.ColumnDefinitions = h.lastColumns
} else {
} else if !reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
// If this table has column definitions, remember them for future use.
h.lastColumns = table.ColumnDefinitions
h.printedHeaders = false
}
if len(table.Rows) > 0 {
h.printedHeaders = true
}
if err := decorateTable(table, localOptions); err != nil {