mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 21:17:23 +00:00
Request and handle server-side printing when watching with kubectl
This commit is contained in:
parent
34e9d80b87
commit
1c3adedf1c
@ -41,6 +41,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
kapierrors "k8s.io/apimachinery/pkg/api/errors"
|
kapierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||||
@ -622,6 +623,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
|
|||||||
ResourceTypeOrNameArgs(true, args...).
|
ResourceTypeOrNameArgs(true, args...).
|
||||||
SingleResourceType().
|
SingleResourceType().
|
||||||
Latest().
|
Latest().
|
||||||
|
TransformRequests(o.transformRequests).
|
||||||
Do()
|
Do()
|
||||||
if err := r.Err(); err != nil {
|
if err := r.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -662,6 +664,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
|
|||||||
|
|
||||||
writer := utilprinters.GetNewTabWriter(o.Out)
|
writer := utilprinters.GetNewTabWriter(o.Out)
|
||||||
|
|
||||||
|
tableGK := metainternal.SchemeGroupVersion.WithKind("Table").GroupKind()
|
||||||
|
|
||||||
// print the current object
|
// print the current object
|
||||||
if !o.WatchOnly {
|
if !o.WatchOnly {
|
||||||
var objsToPrint []runtime.Object
|
var objsToPrint []runtime.Object
|
||||||
@ -672,8 +676,8 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
|
|||||||
objsToPrint = append(objsToPrint, obj)
|
objsToPrint = append(objsToPrint, obj)
|
||||||
}
|
}
|
||||||
for _, objToPrint := range objsToPrint {
|
for _, objToPrint := range objsToPrint {
|
||||||
if o.IsHumanReadablePrinter {
|
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
|
||||||
// printing always takes the internal version, but the watch event uses externals
|
// printing anything other than tables always takes the internal version, but the watch event uses externals
|
||||||
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
|
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
|
||||||
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
|
objToPrint = attemptToConvertToInternal(objToPrint, legacyscheme.Scheme, internalGV)
|
||||||
}
|
}
|
||||||
@ -705,7 +709,7 @@ func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string)
|
|||||||
// printing always takes the internal version, but the watch event uses externals
|
// printing always takes the internal version, but the watch event uses externals
|
||||||
// TODO fix printing to use server-side or be version agnostic
|
// TODO fix printing to use server-side or be version agnostic
|
||||||
objToPrint := e.Object
|
objToPrint := e.Object
|
||||||
if o.IsHumanReadablePrinter {
|
if o.IsHumanReadablePrinter && objToPrint.GetObjectKind().GroupVersionKind().GroupKind() != tableGK {
|
||||||
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
|
internalGV := mapping.GroupVersionKind.GroupKind().WithVersion(runtime.APIVersionInternal).GroupVersion()
|
||||||
objToPrint = attemptToConvertToInternal(e.Object, legacyscheme.Scheme, internalGV)
|
objToPrint = attemptToConvertToInternal(e.Object, legacyscheme.Scheme, internalGV)
|
||||||
}
|
}
|
||||||
|
@ -1403,6 +1403,113 @@ foo 0/0 0 <unknown>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWatchResourceTable(t *testing.T) {
|
||||||
|
columns := []metav1beta1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name", Description: "the name", Priority: 0},
|
||||||
|
{Name: "Active", Type: "boolean", Description: "active", Priority: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
listTable := &metav1beta1.Table{
|
||||||
|
TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "Table"},
|
||||||
|
ColumnDefinitions: columns,
|
||||||
|
Rows: []metav1beta1.TableRow{
|
||||||
|
{
|
||||||
|
Cells: []interface{}{"a", true},
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "10"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cells: []interface{}{"b", true},
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "20"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
events := []watch.Event{
|
||||||
|
{
|
||||||
|
Type: watch.Added,
|
||||||
|
Object: &metav1beta1.Table{
|
||||||
|
TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1beta1", Kind: "Table"},
|
||||||
|
ColumnDefinitions: columns, // first event includes the columns
|
||||||
|
Rows: []metav1beta1.TableRow{{
|
||||||
|
Cells: []interface{}{"a", false},
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "30"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: watch.Deleted,
|
||||||
|
Object: &metav1beta1.Table{
|
||||||
|
ColumnDefinitions: []metav1beta1.TableColumnDefinition{},
|
||||||
|
Rows: []metav1beta1.TableRow{{
|
||||||
|
Cells: []interface{}{"b", false},
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "40"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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":
|
||||||
|
if req.URL.Query().Get("watch") != "true" && req.URL.Query().Get("fieldSelector") == "" {
|
||||||
|
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, listTable)}, nil
|
||||||
|
}
|
||||||
|
if req.URL.Query().Get("watch") == "true" && req.URL.Query().Get("fieldSelector") == "" {
|
||||||
|
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: watchBody(codec, events)}, 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"})
|
||||||
|
|
||||||
|
expected := `NAME ACTIVE
|
||||||
|
a true
|
||||||
|
b true
|
||||||
|
a false
|
||||||
|
b false
|
||||||
|
`
|
||||||
|
if e, a := expected, buf.String(); e != a {
|
||||||
|
t.Errorf("expected\n%v\ngot\n%v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWatchResourceIdentifiedByFile(t *testing.T) {
|
func TestWatchResourceIdentifiedByFile(t *testing.T) {
|
||||||
pods, events := watchTestData()
|
pods, events := watchTestData()
|
||||||
|
|
||||||
@ -1538,7 +1645,9 @@ func watchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser {
|
|||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
enc := restclientwatch.NewEncoder(streaming.NewEncoder(buf, codec), codec)
|
enc := restclientwatch.NewEncoder(streaming.NewEncoder(buf, codec), codec)
|
||||||
for i := range events {
|
for i := range events {
|
||||||
enc.Encode(&events[i])
|
if err := enc.Encode(&events[i]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return json.Framer.NewFrameReader(ioutil.NopCloser(buf))
|
return json.Framer.NewFrameReader(ioutil.NopCloser(buf))
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ type HumanReadablePrinter struct {
|
|||||||
defaultHandler *handlerEntry
|
defaultHandler *handlerEntry
|
||||||
options PrintOptions
|
options PrintOptions
|
||||||
lastType interface{}
|
lastType interface{}
|
||||||
|
lastColumns []metav1beta1.TableColumnDefinition
|
||||||
skipTabWriter bool
|
skipTabWriter bool
|
||||||
encoder runtime.Encoder
|
encoder runtime.Encoder
|
||||||
decoder runtime.Decoder
|
decoder runtime.Decoder
|
||||||
@ -289,10 +290,26 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
|
|||||||
|
|
||||||
// display tables following the rules of options
|
// display tables following the rules of options
|
||||||
if table, ok := obj.(*metav1beta1.Table); ok {
|
if table, ok := obj.(*metav1beta1.Table); ok {
|
||||||
if err := DecorateTable(table, h.options); err != nil {
|
// 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) {
|
||||||
|
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 {
|
||||||
|
// If this table has column definitions, remember them for future use.
|
||||||
|
h.lastColumns = table.ColumnDefinitions
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := DecorateTable(table, localOptions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return PrintTable(table, output, h.options)
|
return PrintTable(table, output, localOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before
|
// check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before
|
||||||
|
Loading…
Reference in New Issue
Block a user