mirror of https://github.com/k3s-io/k3s
Table printers and server generation should always copy ListMeta
Tables should be a mapping from lists, so if the incoming object has these add them to the table. Allows paging over server side tables. Add tests on the generic creater and on the resttest compatibility.pull/6/head
parent
52492e683f
commit
d2a62fd422
|
@ -525,6 +525,16 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio
|
||||||
ColumnDefinitions: columns,
|
ColumnDefinitions: columns,
|
||||||
Rows: results[0].Interface().([]metav1alpha1.TableRow),
|
Rows: results[0].Interface().([]metav1alpha1.TableRow),
|
||||||
}
|
}
|
||||||
|
if m, err := meta.ListAccessor(obj); err == nil {
|
||||||
|
table.ResourceVersion = m.GetResourceVersion()
|
||||||
|
table.SelfLink = m.GetSelfLink()
|
||||||
|
table.Continue = m.GetContinue()
|
||||||
|
} else {
|
||||||
|
if m, err := meta.CommonAccessor(obj); err == nil {
|
||||||
|
table.ResourceVersion = m.GetResourceVersion()
|
||||||
|
table.SelfLink = m.GetSelfLink()
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := DecorateTable(table, options); err != nil {
|
if err := DecorateTable(table, options); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ func ListAccessor(obj interface{}) (List, error) {
|
||||||
}
|
}
|
||||||
return nil, errNotList
|
return nil, errNotList
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("%T does not implement the List interface", obj))
|
return nil, errNotList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -441,6 +441,10 @@ func (storage *SimpleRESTStorage) ConvertToTable(ctx request.Context, obj runtim
|
||||||
func (storage *SimpleRESTStorage) List(ctx request.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
|
func (storage *SimpleRESTStorage) List(ctx request.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||||
storage.checkContext(ctx)
|
storage.checkContext(ctx)
|
||||||
result := &genericapitesting.SimpleList{
|
result := &genericapitesting.SimpleList{
|
||||||
|
ListMeta: metav1.ListMeta{
|
||||||
|
ResourceVersion: "10",
|
||||||
|
SelfLink: "/test/link",
|
||||||
|
},
|
||||||
Items: storage.list,
|
Items: storage.list,
|
||||||
}
|
}
|
||||||
storage.requestedLabelSelector = labels.Everything()
|
storage.requestedLabelSelector = labels.Everything()
|
||||||
|
@ -1832,24 +1836,10 @@ func TestGetPretty(t *testing.T) {
|
||||||
|
|
||||||
func TestGetTable(t *testing.T) {
|
func TestGetTable(t *testing.T) {
|
||||||
now := metav1.Now()
|
now := metav1.Now()
|
||||||
storage := map[string]rest.Storage{}
|
|
||||||
obj := genericapitesting.Simple{
|
obj := genericapitesting.Simple{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")},
|
ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: now, UID: types.UID("abcdef0123")},
|
||||||
Other: "foo",
|
Other: "foo",
|
||||||
}
|
}
|
||||||
simpleStorage := SimpleRESTStorage{
|
|
||||||
item: obj,
|
|
||||||
}
|
|
||||||
selfLinker := &setTestSelfLinker{
|
|
||||||
t: t,
|
|
||||||
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
|
|
||||||
name: "id",
|
|
||||||
namespace: "default",
|
|
||||||
}
|
|
||||||
storage["simple"] = &simpleStorage
|
|
||||||
handler := handleLinker(storage, selfLinker)
|
|
||||||
server := httptest.NewServer(handler)
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
m, err := meta.Accessor(&obj)
|
m, err := meta.Accessor(&obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1872,15 +1862,34 @@ func TestGetTable(t *testing.T) {
|
||||||
pretty bool
|
pretty bool
|
||||||
expected *metav1alpha1.Table
|
expected *metav1alpha1.Table
|
||||||
statusCode int
|
statusCode int
|
||||||
|
item bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io",
|
accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io",
|
||||||
statusCode: http.StatusNotAcceptable,
|
statusCode: http.StatusNotAcceptable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
item: true,
|
||||||
accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io",
|
accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io",
|
||||||
expected: &metav1alpha1.Table{
|
expected: &metav1alpha1.Table{
|
||||||
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
|
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
|
||||||
|
ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
|
||||||
|
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
|
||||||
|
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
|
||||||
|
},
|
||||||
|
Rows: []metav1alpha1.TableRow{
|
||||||
|
{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item: true,
|
||||||
|
accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io",
|
||||||
|
params: url.Values{"includeObject": []string{"Metadata"}},
|
||||||
|
expected: &metav1alpha1.Table{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
|
||||||
|
ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
|
||||||
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
|
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
|
||||||
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
|
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
|
||||||
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
|
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
|
||||||
|
@ -1895,6 +1904,7 @@ func TestGetTable(t *testing.T) {
|
||||||
params: url.Values{"includeObject": []string{"Metadata"}},
|
params: url.Values{"includeObject": []string{"Metadata"}},
|
||||||
expected: &metav1alpha1.Table{
|
expected: &metav1alpha1.Table{
|
||||||
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
|
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
|
||||||
|
ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/test/link"},
|
||||||
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
|
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
|
||||||
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
|
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
|
||||||
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
|
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
|
||||||
|
@ -1906,45 +1916,69 @@ func TestGetTable(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id")
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||||
if err != nil {
|
storage := map[string]rest.Storage{}
|
||||||
t.Fatal(err)
|
simpleStorage := SimpleRESTStorage{
|
||||||
}
|
item: obj,
|
||||||
u.RawQuery = test.params.Encode()
|
list: []genericapitesting.Simple{obj},
|
||||||
req := &http.Request{Method: "GET", URL: u}
|
}
|
||||||
req.Header = http.Header{}
|
selfLinker := &setTestSelfLinker{
|
||||||
req.Header.Set("Accept", test.accept)
|
t: t,
|
||||||
resp, err := http.DefaultClient.Do(req)
|
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple",
|
||||||
if err != nil {
|
namespace: "default",
|
||||||
t.Fatal(err)
|
}
|
||||||
}
|
if test.item {
|
||||||
if test.statusCode != 0 {
|
selfLinker.expectedSet += "/id"
|
||||||
if resp.StatusCode != test.statusCode {
|
selfLinker.name = "id"
|
||||||
|
}
|
||||||
|
storage["simple"] = &simpleStorage
|
||||||
|
handler := handleLinker(storage, selfLinker)
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
var id string
|
||||||
|
if test.item {
|
||||||
|
id = "/id"
|
||||||
|
}
|
||||||
|
u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple" + id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
u.RawQuery = test.params.Encode()
|
||||||
|
req := &http.Request{Method: "GET", URL: u}
|
||||||
|
req.Header = http.Header{}
|
||||||
|
req.Header.Set("Accept", test.accept)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if test.statusCode != 0 {
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("%d: unexpected response: %#v", i, resp)
|
||||||
|
}
|
||||||
|
obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%d: unexpected body read error: %v", err)
|
||||||
|
}
|
||||||
|
gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
|
||||||
|
if obj.GetObjectKind().GroupVersionKind() != gvk {
|
||||||
|
t.Fatalf("%d: unexpected error body: %#v", obj)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
t.Errorf("%d: unexpected response: %#v", i, resp)
|
t.Errorf("%d: unexpected response: %#v", i, resp)
|
||||||
}
|
}
|
||||||
obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
|
var itemOut metav1alpha1.Table
|
||||||
|
body, err := extractBody(resp, &itemOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%d: unexpected body read error: %v", err)
|
t.Fatal(err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
|
if !reflect.DeepEqual(test.expected, &itemOut) {
|
||||||
if obj.GetObjectKind().GroupVersionKind() != gvk {
|
t.Log(body)
|
||||||
t.Errorf("%d: unexpected error body: %#v", obj)
|
t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut))
|
||||||
}
|
}
|
||||||
continue
|
})
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("%d: unexpected response: %#v", i, resp)
|
|
||||||
}
|
|
||||||
var itemOut metav1alpha1.Table
|
|
||||||
body, err := extractBody(resp, &itemOut)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(test.expected, &itemOut) {
|
|
||||||
t.Log(body)
|
|
||||||
t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1291,10 +1291,21 @@ func (t *Tester) testListTableConversion(obj runtime.Object, assignFn AssignFunc
|
||||||
t.Errorf("expected: %#v, got: %#v", objs, items)
|
t.Errorf("expected: %#v, got: %#v", objs, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m, err := meta.ListAccessor(listObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("list should support ListMeta %T: %v", listObj, err)
|
||||||
|
}
|
||||||
|
m.SetContinue("continuetoken")
|
||||||
|
m.SetResourceVersion("11")
|
||||||
|
m.SetSelfLink("/list/link")
|
||||||
|
|
||||||
table, err := t.storage.(rest.TableConvertor).ConvertToTable(ctx, listObj, nil)
|
table, err := t.storage.(rest.TableConvertor).ConvertToTable(ctx, listObj, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
if table.ResourceVersion != "11" || table.SelfLink != "/list/link" || table.Continue != "continuetoken" {
|
||||||
|
t.Errorf("printer lost list meta: %#v", table.ListMeta)
|
||||||
|
}
|
||||||
if len(table.Rows) != len(items) {
|
if len(table.Rows) != len(items) {
|
||||||
t.Errorf("unexpected number of rows: %v", len(table.Rows))
|
t.Errorf("unexpected number of rows: %v", len(table.Rows))
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,16 @@ func (c defaultTableConvertor) ConvertToTable(ctx genericapirequest.Context, obj
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if m, err := meta.ListAccessor(object); err == nil {
|
||||||
|
table.ResourceVersion = m.GetResourceVersion()
|
||||||
|
table.SelfLink = m.GetSelfLink()
|
||||||
|
table.Continue = m.GetContinue()
|
||||||
|
} else {
|
||||||
|
if m, err := meta.CommonAccessor(object); err == nil {
|
||||||
|
table.ResourceVersion = m.GetResourceVersion()
|
||||||
|
table.SelfLink = m.GetSelfLink()
|
||||||
|
}
|
||||||
|
}
|
||||||
table.ColumnDefinitions = []metav1alpha1.TableColumnDefinition{
|
table.ColumnDefinitions = []metav1alpha1.TableColumnDefinition{
|
||||||
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
|
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
|
||||||
{Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]},
|
{Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]},
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||||
|
@ -63,6 +64,56 @@ var _ = SIGDescribe("Servers with support for Table transformation", func() {
|
||||||
framework.Logf("Table:\n%s", out)
|
framework.Logf("Table:\n%s", out)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should return chunks of table results for list calls", func() {
|
||||||
|
ns := f.Namespace.Name
|
||||||
|
c := f.ClientSet
|
||||||
|
client := c.CoreV1().PodTemplates(ns)
|
||||||
|
|
||||||
|
By("creating a large number of resources")
|
||||||
|
workqueue.Parallelize(5, 20, func(i int) {
|
||||||
|
for tries := 3; tries >= 0; tries-- {
|
||||||
|
_, err := client.Create(&v1.PodTemplate{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("template-%04d", i),
|
||||||
|
},
|
||||||
|
Template: v1.PodTemplateSpec{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{Name: "test", Image: "test2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
framework.Logf("Got an error creating template %d: %v", i, err)
|
||||||
|
}
|
||||||
|
Fail("Unable to create template %d, exiting", i)
|
||||||
|
})
|
||||||
|
|
||||||
|
pagedTable := &metav1alpha1.Table{}
|
||||||
|
err := c.CoreV1().RESTClient().Get().Namespace(ns).Resource("podtemplates").
|
||||||
|
VersionedParams(&metav1.ListOptions{Limit: 2}, metav1.ParameterCodec).
|
||||||
|
SetHeader("Accept", "application/json;as=Table;v=v1alpha1;g=meta.k8s.io").
|
||||||
|
Do().Into(pagedTable)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(pagedTable.Rows)).To(Equal(2))
|
||||||
|
Expect(pagedTable.ResourceVersion).ToNot(Equal(""))
|
||||||
|
Expect(pagedTable.SelfLink).ToNot(Equal(""))
|
||||||
|
Expect(pagedTable.Continue).ToNot(Equal(""))
|
||||||
|
Expect(pagedTable.Rows[0].Cells[0]).To(Equal("template-0000"))
|
||||||
|
Expect(pagedTable.Rows[1].Cells[0]).To(Equal("template-0001"))
|
||||||
|
|
||||||
|
err = c.CoreV1().RESTClient().Get().Namespace(ns).Resource("podtemplates").
|
||||||
|
VersionedParams(&metav1.ListOptions{Continue: pagedTable.Continue}, metav1.ParameterCodec).
|
||||||
|
SetHeader("Accept", "application/json;as=Table;v=v1alpha1;g=meta.k8s.io").
|
||||||
|
Do().Into(pagedTable)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(pagedTable.Rows)).To(BeNumerically(">", 0))
|
||||||
|
Expect(pagedTable.Rows[0].Cells[0]).To(Equal("template-0002"))
|
||||||
|
})
|
||||||
|
|
||||||
It("should return generic metadata details across all namespaces for nodes", func() {
|
It("should return generic metadata details across all namespaces for nodes", func() {
|
||||||
c := f.ClientSet
|
c := f.ClientSet
|
||||||
|
|
||||||
|
@ -75,6 +126,8 @@ var _ = SIGDescribe("Servers with support for Table transformation", func() {
|
||||||
Expect(len(table.Rows)).To(BeNumerically(">=", 1))
|
Expect(len(table.Rows)).To(BeNumerically(">=", 1))
|
||||||
Expect(len(table.Rows[0].Cells)).To(Equal(len(table.ColumnDefinitions)))
|
Expect(len(table.Rows[0].Cells)).To(Equal(len(table.ColumnDefinitions)))
|
||||||
Expect(table.ColumnDefinitions[0].Name).To(Equal("Name"))
|
Expect(table.ColumnDefinitions[0].Name).To(Equal("Name"))
|
||||||
|
Expect(table.ResourceVersion).ToNot(Equal(""))
|
||||||
|
Expect(table.SelfLink).ToNot(Equal(""))
|
||||||
|
|
||||||
out := printTable(table)
|
out := printTable(table)
|
||||||
Expect(out).To(MatchRegexp("^NAME\\s"))
|
Expect(out).To(MatchRegexp("^NAME\\s"))
|
||||||
|
|
Loading…
Reference in New Issue