mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
Merge pull request #79345 from liggitt/kubectl-printing
Add meta Table tests, fix --watch-only with single item
This commit is contained in:
commit
4745946525
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
48
pkg/kubectl/cmd/get/skip_printer.go
Normal file
48
pkg/kubectl/cmd/get/skip_printer.go
Normal 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)
|
||||
}
|
@ -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{}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user