Skip to content

Commit 1c3aded

Browse files
committed
Request and handle server-side printing when watching with kubectl
1 parent 34e9d80 commit 1c3aded

File tree

4 files changed

+137
-6
lines changed

4 files changed

+137
-6
lines changed

pkg/kubectl/cmd/get/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ go_library(
4141
"//staging/src/k8s.io/api/core/v1:go_default_library",
4242
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
4343
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
44+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
4445
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
4546
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
4647
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",

pkg/kubectl/cmd/get/get.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
corev1 "k8s.io/api/core/v1"
3030
kapierrors "k8s.io/apimachinery/pkg/api/errors"
3131
"k8s.io/apimachinery/pkg/api/meta"
32+
metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
3233
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3334
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3435
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
@@ -622,6 +623,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
622623
ResourceTypeOrNameArgs(true, args...).
623624
SingleResourceType().
624625
Latest().
626+
TransformRequests(o.transformRequests).
625627
Do()
626628
if err := r.Err(); err != nil {
627629
return err
@@ -662,6 +664,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
662664

663665
writer := utilprinters.GetNewTabWriter(o.Out)
664666

667+
tableGK := metainternal.SchemeGroupVersion.WithKind("Table").GroupKind()
668+
665669
// print the current object
666670
if !o.WatchOnly {
667671
var objsToPrint []runtime.Object
@@ -672,8 +676,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
672676
objsToPrint = append(objsToPrint, obj)
673677
}
674678
for _, objToPrint := range objsToPrint {
675-
if o.IsHumanReadablePrinter {
676-
// printing always takes the internal version, but the watch event uses externals
679+
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
680+
// printing anything other than tables always takes the internal version, but the watch event uses externals
677681
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
678682
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
679683
}
@@ -705,7 +709,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
705709
// printing always takes the internal version, but the watch event uses externals
706710
// TODO fix printing to use server-side or be version agnostic
707711
objToPrint := e.Object
708-
if o.IsHumanReadablePrinter {
712+
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
709713
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
710714
objToPrint = attemptToConvertToInternal(e.Object, legacyscheme.Scheme, internalGV)
711715
}

pkg/kubectl/cmd/get/get_test.go

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,113 @@ foo 0/0 0 <unknown>
14031403
}
14041404
}
14051405

1406+
func TestWatchResourceTable(t *testing.T) {
1407+
columns := []metav1beta1.TableColumnDefinition{
1408+
{Name: "Name", Type: "string", Format: "name", Description: "the name", Priority: 0},
1409+
{Name: "Active", Type: "boolean", Description: "active", Priority: 0},
1410+
}
1411+
1412+
listTable := &metav1beta1.Table{
1413+
TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "Table"},
1414+
ColumnDefinitions: columns,
1415+
Rows: []metav1beta1.TableRow{
1416+
{
1417+
Cells: []interface{}{"a", true},
1418+
Object: runtime.RawExtension{
1419+
Object: &corev1.Pod{
1420+
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
1421+
ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "10"},
1422+
},
1423+
},
1424+
},
1425+
{
1426+
Cells: []interface{}{"b", true},
1427+
Object: runtime.RawExtension{
1428+
Object: &corev1.Pod{
1429+
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
1430+
ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "20"},
1431+
},
1432+
},
1433+
},
1434+
},
1435+
}
1436+
1437+
events := []watch.Event{
1438+
{
1439+
Type: watch.Added,
1440+
Object: &metav1beta1.Table{
1441+
TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "Table"},
1442+
ColumnDefinitions: columns, // first event includes the columns
1443+
Rows: []metav1beta1.TableRow{{
1444+
Cells: []interface{}{"a", false},
1445+
Object: runtime.RawExtension{
1446+
Object: &corev1.Pod{
1447+
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
1448+
ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "30"},
1449+
},
1450+
},
1451+
}},
1452+
},
1453+
},
1454+
{
1455+
Type: watch.Deleted,
1456+
Object: &metav1beta1.Table{
1457+
ColumnDefinitions: []metav1beta1.TableColumnDefinition{},
1458+
Rows: []metav1beta1.TableRow{{
1459+
Cells: []interface{}{"b", false},
1460+
Object: runtime.RawExtension{
1461+
Object: &corev1.Pod{
1462+
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
1463+
ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "40"},
1464+
},
1465+
},
1466+
}},
1467+
},
1468+
},
1469+
}
1470+
1471+
tf := cmdtesting.NewTestFactory().WithNamespace("test")
1472+
defer tf.Cleanup()
1473+
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
1474+
1475+
tf.UnstructuredClient = &fake.RESTClient{
1476+
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
1477+
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
1478+
switch req.URL.Path {
1479+
case "/namespaces/test/pods":
1480+
if req.URL.Query().Get("watch") != "true" && req.URL.Query().Get("fieldSelector") == "" {
1481+
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, listTable)}, nil
1482+
}
1483+
if req.URL.Query().Get("watch") == "true" && req.URL.Query().Get("fieldSelector") == "" {
1484+
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: watchBody(codec, events)}, nil
1485+
}
1486+
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
1487+
return nil, nil
1488+
default:
1489+
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
1490+
return nil, nil
1491+
}
1492+
}),
1493+
}
1494+
1495+
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
1496+
cmd := NewCmdGet("kubectl", tf, streams)
1497+
cmd.SetOutput(buf)
1498+
1499+
cmd.Flags().Set("watch", "true")
1500+
cmd.Run(cmd, []string{"pods"})
1501+
1502+
expected := `NAME ACTIVE
1503+
a true
1504+
b true
1505+
a false
1506+
b false
1507+
`
1508+
if e, a := expected, buf.String(); e != a {
1509+
t.Errorf("expected\n%v\ngot\n%v", e, a)
1510+
}
1511+
}
1512+
14061513
func TestWatchResourceIdentifiedByFile(t *testing.T) {
14071514
pods, events := watchTestData()
14081515

@@ -1538,7 +1645,9 @@ func watchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser {
15381645
buf := bytes.NewBuffer([]byte{})
15391646
enc := restclientwatch.NewEncoder(streaming.NewEncoder(buf, codec), codec)
15401647
for i := range events {
1541-
enc.Encode(&events[i])
1648+
if err := enc.Encode(&events[i]); err != nil {
1649+
panic(err)
1650+
}
15421651
}
15431652
return json.Framer.NewFrameReader(ioutil.NopCloser(buf))
15441653
}

pkg/printers/humanreadable.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type HumanReadablePrinter struct {
6363
defaultHandler *handlerEntry
6464
options PrintOptions
6565
lastType interface{}
66+
lastColumns []metav1beta1.TableColumnDefinition
6667
skipTabWriter bool
6768
encoder runtime.Encoder
6869
decoder runtime.Decoder
@@ -289,10 +290,26 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
289290

290291
// display tables following the rules of options
291292
if table, ok := obj.(*metav1beta1.Table); ok {
292-
if err := DecorateTable(table, h.options); err != nil {
293+
// Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
294+
localOptions := h.options
295+
if len(table.ColumnDefinitions) == 0 || reflect.DeepEqual(table.ColumnDefinitions, h.lastColumns) {
296+
localOptions.NoHeaders = true
297+
}
298+
299+
if len(table.ColumnDefinitions) == 0 {
300+
// If this table has no column definitions, use the columns from the last table we printed for decoration and layout.
301+
// This is done when receiving tables in watch events to save bandwidth.
302+
localOptions.NoHeaders = true
303+
table.ColumnDefinitions = h.lastColumns
304+
} else {
305+
// If this table has column definitions, remember them for future use.
306+
h.lastColumns = table.ColumnDefinitions
307+
}
308+
309+
if err := DecorateTable(table, localOptions); err != nil {
293310
return err
294311
}
295-
return PrintTable(table, output, h.options)
312+
return PrintTable(table, output, localOptions)
296313
}
297314

298315
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before

0 commit comments

Comments
 (0)