diff --git a/pkg/kubectl/scheme/install.go b/pkg/kubectl/scheme/install.go index fd25c730e6..76a0578dc1 100644 --- a/pkg/kubectl/scheme/install.go +++ b/pkg/kubectl/scheme/install.go @@ -57,7 +57,8 @@ import ( func init() { // Register external types for Scheme metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(metav1beta1.AddToScheme(Scheme)) + utilruntime.Must(metav1beta1.AddMetaToScheme(Scheme)) + utilruntime.Must(metav1.AddMetaToScheme(Scheme)) utilruntime.Must(scheme.AddToScheme(Scheme)) utilruntime.Must(Scheme.SetVersionPriority(corev1.SchemeGroupVersion)) diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index dbb81ca8d6..cc50fb9547 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -539,12 +539,12 @@ func translateTimestampUntil(timestamp metav1.Time) string { } var ( - podSuccessConditions = []metav1beta1.TableRowCondition{{Type: metav1beta1.RowCompleted, Status: metav1beta1.ConditionTrue, Reason: string(api.PodSucceeded), Message: "The pod has completed successfully."}} - podFailedConditions = []metav1beta1.TableRowCondition{{Type: metav1beta1.RowCompleted, Status: metav1beta1.ConditionTrue, Reason: string(api.PodFailed), Message: "The pod failed."}} + podSuccessConditions = []metav1.TableRowCondition{{Type: metav1.RowCompleted, Status: metav1.ConditionTrue, Reason: string(api.PodSucceeded), Message: "The pod has completed successfully."}} + podFailedConditions = []metav1.TableRowCondition{{Type: metav1.RowCompleted, Status: metav1.ConditionTrue, Reason: string(api.PodFailed), Message: "The pod failed."}} ) -func printPodList(podList *api.PodList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { - rows := make([]metav1beta1.TableRow, 0, len(podList.Items)) +func printPodList(podList *api.PodList, options printers.PrintOptions) ([]metav1.TableRow, error) { + rows := make([]metav1.TableRow, 0, len(podList.Items)) for i := range podList.Items { r, err := printPod(&podList.Items[i], options) if err != nil { @@ -555,7 +555,7 @@ func printPodList(podList *api.PodList, options printers.PrintOptions) ([]metav1 return rows, nil } -func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { +func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1.TableRow, error) { restarts := 0 totalContainers := len(pod.Spec.Containers) readyContainers := 0 @@ -565,7 +565,7 @@ func printPod(pod *api.Pod, options printers.PrintOptions) ([]metav1beta1.TableR reason = pod.Status.Reason } - row := metav1beta1.TableRow{ + row := metav1.TableRow{ Object: runtime.RawExtension{Object: pod}, } diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go index 14645504ac..f2aa2c6a77 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/table_test.go @@ -148,8 +148,10 @@ func TestTableGet(t *testing.T) { codecs := serializer.NewCodecFactory(scheme) parameterCodec := runtime.NewParameterCodec(scheme) metav1.AddToGroupVersion(scheme, gv) - scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) + scheme.AddKnownTypes(gv, &metav1beta1.TableOptions{}) + scheme.AddKnownTypes(gv, &metav1.TableOptions{}) scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) + scheme.AddKnownTypes(metav1.SchemeGroupVersion, &metav1.Table{}, &metav1.TableOptions{}) crConfig := *config crConfig.GroupVersion = &gv diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD index 4ced3ce566..a424f4839d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD @@ -42,7 +42,6 @@ go_library( importpath = "k8s.io/apimachinery/pkg/api/meta", deps = [ "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go index b50337e13f..086bce04b0 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/meta.go @@ -21,7 +21,6 @@ import ( "reflect" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -114,12 +113,12 @@ func Accessor(obj interface{}) (metav1.Object, error) { // AsPartialObjectMetadata takes the metav1 interface and returns a partial object. // TODO: consider making this solely a conversion action. -func AsPartialObjectMetadata(m metav1.Object) *metav1beta1.PartialObjectMetadata { +func AsPartialObjectMetadata(m metav1.Object) *metav1.PartialObjectMetadata { switch t := m.(type) { case *metav1.ObjectMeta: - return &metav1beta1.PartialObjectMetadata{ObjectMeta: *t} + return &metav1.PartialObjectMetadata{ObjectMeta: *t} default: - return &metav1beta1.PartialObjectMetadata{ + return &metav1.PartialObjectMetadata{ ObjectMeta: metav1.ObjectMeta{ Name: m.GetName(), GenerateName: m.GetGenerateName(), diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go index 24fc134150..368efe1efd 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/register.go @@ -113,7 +113,4 @@ func AddMetaToScheme(scheme *runtime.Scheme) error { return scheme.AddConversionFuncs( Convert_Slice_string_To_v1_IncludeObjectPolicy, ) - - // register manually. This usually goes through the SchemeBuilder, which we cannot use here. - //scheme.AddGeneratedDeepCopyFuncs(GetGeneratedDeepCopyFuncs()...) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go index 61583fab34..594576ba98 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go @@ -1749,14 +1749,14 @@ func TestGetPretty(t *testing.T) { pretty bool }{ {accept: runtime.ContentTypeJSON}, - {accept: runtime.ContentTypeJSON + ";pretty=0"}, + {accept: "application/json;pretty=0"}, {accept: runtime.ContentTypeJSON, userAgent: "kubectl"}, {accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"0"}}}, {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "curl"}, {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Mozilla/5.0"}, {pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Wget"}, - {pretty: true, accept: runtime.ContentTypeJSON + ";pretty=1"}, + {pretty: true, accept: "application/json;pretty=1"}, {pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"1"}}}, {pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"true"}}}, } @@ -1818,14 +1818,28 @@ func TestGetTable(t *testing.T) { if err != nil { t.Fatal(err) } - partial := meta.AsPartialObjectMetadata(m) - partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) - encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial) - if err != nil { - t.Fatal(err) + var encodedV1Beta1Body []byte + { + partial := meta.AsPartialObjectMetadata(m) + partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) + encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1beta1.SchemeGroupVersion), partial) + if err != nil { + t.Fatal(err) + } + // the codec includes a trailing newline that is not present during decode + encodedV1Beta1Body = bytes.TrimSpace(encodedBody) + } + var encodedV1Body []byte + { + partial := meta.AsPartialObjectMetadata(m) + partial.GetObjectKind().SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) + encodedBody, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial) + if err != nil { + t.Fatal(err) + } + // the codec includes a trailing newline that is not present during decode + encodedV1Body = bytes.TrimSpace(encodedBody) } - // the codec includes a trailing newline that is not present during decode - encodedBody = bytes.TrimSpace(encodedBody) metaDoc := metav1.ObjectMeta{}.SwaggerDoc() @@ -1838,16 +1852,36 @@ func TestGetTable(t *testing.T) { item bool }{ { - accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io", + accept: "application/json;as=Table;v=v1alpha1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, { accept: runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, + { + accept: runtime.ContentTypeProtobuf + ";as=Table;v=v1;g=meta.k8s.io", + statusCode: http.StatusNotAcceptable, + }, + { item: true, - accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=Table;v=v1;g=meta.k8s.io", + expected: &metav1.Table{ + TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, + ColumnDefinitions: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, + {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, + }, + Rows: []metav1.TableRow{ + {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Body}}, + }, + }, + }, + { + item: true, + accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", expected: &metav1beta1.Table{ TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, @@ -1856,7 +1890,7 @@ func TestGetTable(t *testing.T) { {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, }, Rows: []metav1beta1.TableRow{ - {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, + {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, }, }, }, @@ -1864,7 +1898,7 @@ func TestGetTable(t *testing.T) { item: true, accept: strings.Join([]string{ runtime.ContentTypeProtobuf + ";as=Table;v=v1beta1;g=meta.k8s.io", - runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", + "application/json;as=Table;v=v1beta1;g=meta.k8s.io", }, ","), expected: &metav1beta1.Table{ TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, @@ -1874,13 +1908,13 @@ func TestGetTable(t *testing.T) { {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, }, Rows: []metav1beta1.TableRow{ - {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, + {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, }, }, }, { item: true, - accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", params: url.Values{"includeObject": []string{"Metadata"}}, expected: &metav1beta1.Table{ TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, @@ -1890,12 +1924,12 @@ func TestGetTable(t *testing.T) { {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, }, Rows: []metav1beta1.TableRow{ - {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, + {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, }, }, }, { - accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", params: url.Values{"includeObject": []string{"Metadata"}}, expected: &metav1beta1.Table{ TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1beta1"}, @@ -1905,7 +1939,7 @@ func TestGetTable(t *testing.T) { {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, }, Rows: []metav1beta1.TableRow{ - {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}}, + {Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedV1Beta1Body}}, }, }, }, @@ -1996,6 +2030,13 @@ func TestWatchTable(t *testing.T) { // the codec includes a trailing newline that is not present during decode encodedBody = bytes.TrimSpace(encodedBody) + encodedBodyV1, err := runtime.Encode(metainternalversion.Codecs.LegacyCodec(metav1.SchemeGroupVersion), partial) + if err != nil { + t.Fatal(err) + } + // the codec includes a trailing newline that is not present during decode + encodedBodyV1 = bytes.TrimSpace(encodedBodyV1) + metaDoc := metav1.ObjectMeta{}.SwaggerDoc() s := metainternalversion.Codecs.SupportedMediaTypes()[0].Serializer @@ -2011,11 +2052,11 @@ func TestWatchTable(t *testing.T) { item bool }{ { - accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io", + accept: "application/json;as=Table;v=v1alpha1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, { - accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", send: func(w *watch.FakeWatcher) { w.Add(&obj) }, @@ -2039,7 +2080,7 @@ func TestWatchTable(t *testing.T) { }, }, { - accept: runtime.ContentTypeJSON + ";as=Table;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=Table;v=v1beta1;g=meta.k8s.io", send: func(w *watch.FakeWatcher) { w.Add(&obj) w.Modify(&obj) @@ -2075,6 +2116,43 @@ func TestWatchTable(t *testing.T) { }, }, }, + { + accept: "application/json;as=Table;v=v1;g=meta.k8s.io", + send: func(w *watch.FakeWatcher) { + w.Add(&obj) + w.Modify(&obj) + }, + expected: []*metav1.WatchEvent{ + { + Type: "ADDED", + Object: runtime.RawExtension{ + Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{ + TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, + ColumnDefinitions: []metav1beta1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, + {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, + }, + Rows: []metav1.TableRow{ + {Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}}, + }, + }))), + }, + }, + { + Type: "MODIFIED", + Object: runtime.RawExtension{ + Raw: []byte(strings.TrimSpace(runtime.EncodeOrDie(s, &metav1.Table{ + TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1"}, + ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"}, + Rows: []metav1.TableRow{ + {Cells: []interface{}{"foo1", time.Unix(1, 0).UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBodyV1}}, + }, + }))), + }, + }, + }, + }, } for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { @@ -2122,6 +2200,7 @@ func TestWatchTable(t *testing.T) { if err != nil { t.Fatal(err) } + defer resp.Body.Close() if test.statusCode != 0 { if resp.StatusCode != test.statusCode { t.Fatalf("%d: unexpected response: %#v", i, resp) @@ -2228,46 +2307,72 @@ func TestGetPartialObjectMetadata(t *testing.T) { statusCode int }{ { - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io", + accept: "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, { - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", + accept: "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json", expectKind: schema.GroupVersionKind{Kind: "Simple", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, }, { - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json", + accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json", expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, }, { list: true, - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, + + // verify preferred version overrides supported version + { + accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", + expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, + }, + { + accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json", + expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"}, + }, + { + accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io", + expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, + }, + { + accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", + expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"}, + }, + { list: true, - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1;g=meta.k8s.io, application/json", + accept: "application/json;as=PartialObjectMetadata;v=v1alpha1;g=meta.k8s.io, application/json", expectKind: schema.GroupVersionKind{Kind: "SimpleList", Group: testGroupVersion.Group, Version: testGroupVersion.Version}, }, { list: true, - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io, application/json", + accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io, application/json", expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadataList", Group: "meta.k8s.io", Version: "v1beta1"}, }, { - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", statusCode: http.StatusNotAcceptable, }, { - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=PartialObjectMetadata;v=v1beta1;g=meta.k8s.io", expected: &metav1beta1.PartialObjectMetadata{ ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, }, expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1beta1"}, }, + { + accept: "application/json;as=PartialObjectMetadata;v=v1;g=meta.k8s.io", + expected: &metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, + }, + expectKind: schema.GroupVersionKind{Kind: "PartialObjectMetadata", Group: "meta.k8s.io", Version: "v1"}, + }, { list: true, - accept: runtime.ContentTypeJSON + ";as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", + accept: "application/json;as=PartialObjectMetadataList;v=v1beta1;g=meta.k8s.io", expected: &metav1beta1.PartialObjectMetadataList{ ListMeta: metav1.ListMeta{ ResourceVersion: "10", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/response.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/response.go index 214dbc1e7d..0fe8a71c72 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/response.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/response.go @@ -49,18 +49,18 @@ func transformObject(ctx context.Context, obj runtime.Object, opts interface{}, case target == nil: return obj, nil - case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion: - return asV1Beta1PartialObjectMetadata(obj) + case target.Kind == "PartialObjectMetadata": + return asPartialObjectMetadata(obj, target.GroupVersion()) - case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion: - return asV1Beta1PartialObjectMetadataList(obj) + case target.Kind == "PartialObjectMetadataList": + return asPartialObjectMetadataList(obj, target.GroupVersion()) - case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion: + case target.Kind == "Table": options, ok := opts.(*metav1beta1.TableOptions) if !ok { return nil, fmt.Errorf("unexpected TableOptions, got %T", opts) } - return asV1Beta1Table(ctx, obj, options, scope) + return asTable(ctx, obj, options, scope, target.GroupVersion()) default: accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs) @@ -74,7 +74,7 @@ func transformObject(ctx context.Context, obj runtime.Object, opts interface{}, func optionsForTransform(mediaType negotiation.MediaTypeOptions, req *http.Request) (interface{}, error) { switch target := mediaType.Convert; { case target == nil: - case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion: + case target.Kind == "Table" && (target.GroupVersion() == metav1beta1.SchemeGroupVersion || target.GroupVersion() == metav1.SchemeGroupVersion): opts := &metav1beta1.TableOptions{} if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil { return nil, err @@ -95,9 +95,8 @@ func optionsForTransform(mediaType negotiation.MediaTypeOptions, req *http.Reque func targetEncodingForTransform(scope *RequestScope, mediaType negotiation.MediaTypeOptions, req *http.Request) (schema.GroupVersionKind, runtime.NegotiatedSerializer, bool) { switch target := mediaType.Convert; { case target == nil: - case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion, - target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion, - target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion: + case (target.Kind == "PartialObjectMetadata" || target.Kind == "PartialObjectMetadataList" || target.Kind == "Table") && + (target.GroupVersion() == metav1beta1.SchemeGroupVersion || target.GroupVersion() == metav1.SchemeGroupVersion): return *target, metainternalversion.Codecs, true } return scope.Kind, scope.Serializer, false @@ -142,31 +141,39 @@ func (e errNotAcceptable) Status() metav1.Status { } } -func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta1.TableOptions, scope *RequestScope) (runtime.Object, error) { - table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts) +func asTable(ctx context.Context, result runtime.Object, opts *metav1beta1.TableOptions, scope *RequestScope, groupVersion schema.GroupVersion) (runtime.Object, error) { + switch groupVersion { + case metav1beta1.SchemeGroupVersion, metav1.SchemeGroupVersion: + default: + return nil, newNotAcceptableError(fmt.Sprintf("no Table exists in group version %s", groupVersion)) + } + + obj, err := scope.TableConvertor.ConvertToTable(ctx, result, opts) if err != nil { return nil, err } + table := (*metav1.Table)(obj) + for i := range table.Rows { item := &table.Rows[i] switch opts.IncludeObject { - case metav1beta1.IncludeObject: + case metav1.IncludeObject: item.Object.Object, err = scope.Convertor.ConvertToVersion(item.Object.Object, scope.Kind.GroupVersion()) if err != nil { return nil, err } // TODO: rely on defaulting for the value here? - case metav1beta1.IncludeMetadata, "": + case metav1.IncludeMetadata, "": m, err := meta.Accessor(item.Object.Object) if err != nil { return nil, err } // TODO: turn this into an internal type and do conversion in order to get object kind automatically set? partial := meta.AsPartialObjectMetadata(m) - partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) + partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata")) item.Object.Object = partial - case metav1beta1.IncludeNone: + case metav1.IncludeNone: item.Object.Object = nil default: err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject)) @@ -177,42 +184,74 @@ func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta return table, nil } -func asV1Beta1PartialObjectMetadata(result runtime.Object) (runtime.Object, error) { +func asPartialObjectMetadata(result runtime.Object, groupVersion schema.GroupVersion) (runtime.Object, error) { if meta.IsListType(result) { err := newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result)) return nil, err } + switch groupVersion { + case metav1beta1.SchemeGroupVersion, metav1.SchemeGroupVersion: + default: + return nil, newNotAcceptableError(fmt.Sprintf("no PartialObjectMetadataList exists in group version %s", groupVersion)) + } m, err := meta.Accessor(result) if err != nil { return nil, err } partial := meta.AsPartialObjectMetadata(m) - partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) + partial.GetObjectKind().SetGroupVersionKind(groupVersion.WithKind("PartialObjectMetadata")) return partial, nil } -func asV1Beta1PartialObjectMetadataList(result runtime.Object) (runtime.Object, error) { - if !meta.IsListType(result) { +func asPartialObjectMetadataList(result runtime.Object, groupVersion schema.GroupVersion) (runtime.Object, error) { + li, ok := result.(metav1.ListInterface) + if !ok { return nil, newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result)) } - list := &metav1beta1.PartialObjectMetadataList{} - if li, ok := result.(metav1.ListInterface); ok { + + gvk := groupVersion.WithKind("PartialObjectMetadata") + switch { + case groupVersion == metav1beta1.SchemeGroupVersion: + list := &metav1beta1.PartialObjectMetadataList{} + err := meta.EachListItem(result, func(obj runtime.Object) error { + m, err := meta.Accessor(obj) + if err != nil { + return err + } + partial := meta.AsPartialObjectMetadata(m) + partial.GetObjectKind().SetGroupVersionKind(gvk) + list.Items = append(list.Items, partial) + return nil + }) + if err != nil { + return nil, err + } list.SelfLink = li.GetSelfLink() list.ResourceVersion = li.GetResourceVersion() list.Continue = li.GetContinue() - } - err := meta.EachListItem(result, func(obj runtime.Object) error { - m, err := meta.Accessor(obj) + return list, nil + + case groupVersion == metav1.SchemeGroupVersion: + list := &metav1.PartialObjectMetadataList{} + err := meta.EachListItem(result, func(obj runtime.Object) error { + m, err := meta.Accessor(obj) + if err != nil { + return err + } + partial := meta.AsPartialObjectMetadata(m) + partial.GetObjectKind().SetGroupVersionKind(gvk) + list.Items = append(list.Items, partial) + return nil + }) if err != nil { - return err + return nil, err } - partial := meta.AsPartialObjectMetadata(m) - partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata")) - list.Items = append(list.Items, partial) - return nil - }) - if err != nil { - return nil, err + list.SelfLink = li.GetSelfLink() + list.ResourceVersion = li.GetResourceVersion() + list.Continue = li.GetContinue() + return list, nil + + default: + return nil, newNotAcceptableError(fmt.Sprintf("no PartialObjectMetadataList exists in group version %s", groupVersion)) } - return list, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go index 6adbf84955..4db0c06762 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go @@ -78,7 +78,7 @@ func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Reque func (scope *RequestScope) AllowsConversion(gvk schema.GroupVersionKind, mimeType, mimeSubType string) bool { // TODO: this is temporary, replace with an abstraction calculated at endpoint installation time - if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion { + if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion || gvk.GroupVersion() == metav1.SchemeGroupVersion { switch gvk.Kind { case "Table": return scope.TableConvertor != nil && diff --git a/test/integration/apiserver/apiserver_test.go b/test/integration/apiserver/apiserver_test.go index 22ae8ca317..d62a725e69 100644 --- a/test/integration/apiserver/apiserver_test.go +++ b/test/integration/apiserver/apiserver_test.go @@ -548,7 +548,7 @@ func TestAPICRDProtobuf(t *testing.T) { } } -func TestTransformOnWatch(t *testing.T) { +func TestTransform(t *testing.T) { tearDown, config, _, err := fixtures.StartDefaultServer(t) if err != nil { t.Fatal(err) @@ -592,23 +592,23 @@ func TestTransformOnWatch(t *testing.T) { testcases := []struct { name string accept string - includeObject metav1beta1.IncludeObjectPolicy + includeObject metav1.IncludeObjectPolicy object func(*testing.T) (metav1.Object, string, string) wantErr func(*testing.T, error) wantBody func(*testing.T, io.Reader) }{ { - name: "verify columns on cluster scoped resources", + name: "v1beta1 verify columns on cluster scoped resources", accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces" }, wantBody: func(t *testing.T, w io.Reader) { - expectTableWatchEvents(t, 1, 3, metav1beta1.IncludeMetadata, json.NewDecoder(w)) + expectTableWatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w)) }, }, { - name: "verify columns on CRDs in json", + name: "v1beta1 verify columns on CRDs in json", accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-1"}}}, metav1.CreateOptions{}) @@ -621,11 +621,11 @@ func TestTransformOnWatch(t *testing.T) { return cr, crdGVR.Group, "foos" }, wantBody: func(t *testing.T, w io.Reader) { - expectTableWatchEvents(t, 2, 2, metav1beta1.IncludeMetadata, json.NewDecoder(w)) + expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w)) }, }, { - name: "verify columns on CRDs in json;stream=watch", + name: "v1beta1 verify columns on CRDs in json;stream=watch", accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-2"}}}, metav1.CreateOptions{}) @@ -638,11 +638,11 @@ func TestTransformOnWatch(t *testing.T) { return cr, crdGVR.Group, "foos" }, wantBody: func(t *testing.T, w io.Reader) { - expectTableWatchEvents(t, 2, 2, metav1beta1.IncludeMetadata, json.NewDecoder(w)) + expectTableWatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w)) }, }, { - name: "verify columns on CRDs in yaml", + name: "v1beta1 verify columns on CRDs in yaml", accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-3"}}}, metav1.CreateOptions{}) @@ -665,7 +665,7 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify columns on services", + name: "v1beta1 verify columns on services", accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { ns := "default" @@ -679,13 +679,13 @@ func TestTransformOnWatch(t *testing.T) { return svc, "", "services" }, wantBody: func(t *testing.T, w io.Reader) { - expectTableWatchEvents(t, 2, 7, metav1beta1.IncludeMetadata, json.NewDecoder(w)) + expectTableWatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w)) }, }, { - name: "verify columns on services with no object", + name: "v1beta1 verify columns on services with no object", accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1", - includeObject: metav1beta1.IncludeNone, + includeObject: metav1.IncludeNone, object: func(t *testing.T) (metav1.Object, string, string) { ns := "default" obj, err := clientset.CoreV1().Services(ns).Create(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-2"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}) @@ -698,13 +698,13 @@ func TestTransformOnWatch(t *testing.T) { return obj, "", "services" }, wantBody: func(t *testing.T, w io.Reader) { - expectTableWatchEvents(t, 2, 7, metav1beta1.IncludeNone, json.NewDecoder(w)) + expectTableWatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w)) }, }, { - name: "verify columns on services with full object", + name: "v1beta1 verify columns on services with full object", accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1", - includeObject: metav1beta1.IncludeObject, + includeObject: metav1.IncludeObject, object: func(t *testing.T) (metav1.Object, string, string) { ns := "default" obj, err := clientset.CoreV1().Services(ns).Create(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-3"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}) @@ -717,7 +717,7 @@ func TestTransformOnWatch(t *testing.T) { return obj, "", "services" }, wantBody: func(t *testing.T, w io.Reader) { - objects := expectTableWatchEvents(t, 2, 7, metav1beta1.IncludeObject, json.NewDecoder(w)) + objects := expectTableWatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w)) var svc v1.Service if err := json.Unmarshal(objects[1], &svc); err != nil { t.Fatal(err) @@ -728,7 +728,7 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify partial metadata object on config maps", + name: "v1beta1 verify partial metadata object on config maps", accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { ns := "default" @@ -746,7 +746,7 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify partial metadata object on config maps in protobuf", + name: "v1beta1 verify partial metadata object on config maps in protobuf", accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { ns := "default" @@ -764,7 +764,7 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify error on unsupported mimetype protobuf for table conversion", + name: "v1beta1 verify error on unsupported mimetype protobuf for table conversion", accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" @@ -781,7 +781,7 @@ func TestTransformOnWatch(t *testing.T) { }, { name: "verify error on invalid mimetype - bad version", - accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1", + accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1alpha1", object: func(t *testing.T) (metav1.Object, string, string) { return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" }, @@ -792,7 +792,7 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify error on invalid mimetype - bad group", + name: "v1beta1 verify error on invalid mimetype - bad group", accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" @@ -804,7 +804,7 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify error on invalid mimetype - bad kind", + name: "v1beta1 verify error on invalid mimetype - bad kind", accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" @@ -816,7 +816,7 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify error on invalid mimetype - missing kind", + name: "v1beta1 verify error on invalid mimetype - missing kind", accept: "application/json;g=meta.k8s.io;v=v1beta1", object: func(t *testing.T) (metav1.Object, string, string) { return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" @@ -828,9 +828,241 @@ func TestTransformOnWatch(t *testing.T) { }, }, { - name: "verify error on invalid transform parameter", + name: "v1beta1 verify error on invalid transform parameter", accept: "application/json;as=Table;g=meta.k8s.io;v=v1beta1", - includeObject: metav1beta1.IncludeObjectPolicy("unrecognized"), + includeObject: metav1.IncludeObjectPolicy("unrecognized"), + object: func(t *testing.T) (metav1.Object, string, string) { + return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" + }, + wantErr: func(t *testing.T, err error) { + if !apierrors.IsBadRequest(err) || !strings.Contains(err.Error(), `Invalid value: "unrecognized": must be 'Metadata', 'Object', 'None', or empty`) { + t.Fatal(err) + } + }, + }, + + { + name: "v1 verify columns on cluster scoped resources", + accept: "application/json;as=Table;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + return &metav1.ObjectMeta{Name: "default", Namespace: ""}, "", "namespaces" + }, + wantBody: func(t *testing.T, w io.Reader) { + expectTableV1WatchEvents(t, 1, 3, metav1.IncludeMetadata, json.NewDecoder(w)) + }, + }, + { + name: "v1 verify columns on CRDs in json", + accept: "application/json;as=Table;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-4"}}}, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("unable to create cr: %v", err) + } + if _, err := crclient.Patch("test-4", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil { + t.Fatalf("unable to patch cr: %v", err) + } + return cr, crdGVR.Group, "foos" + }, + wantBody: func(t *testing.T, w io.Reader) { + expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w)) + }, + }, + { + name: "v1 verify columns on CRDs in json;stream=watch", + accept: "application/json;stream=watch;as=Table;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-5"}}}, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("unable to create cr: %v", err) + } + if _, err := crclient.Patch("test-5", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil { + t.Fatalf("unable to patch cr: %v", err) + } + return cr, crdGVR.Group, "foos" + }, + wantBody: func(t *testing.T, w io.Reader) { + expectTableV1WatchEvents(t, 2, 2, metav1.IncludeMetadata, json.NewDecoder(w)) + }, + }, + { + name: "v1 verify columns on CRDs in yaml", + accept: "application/yaml;as=Table;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + cr, err := crclient.Create(&unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "cr.bar.com/v1", "kind": "Foo", "metadata": map[string]interface{}{"name": "test-6"}}}, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("unable to create cr: %v", err) + } + if _, err := crclient.Patch("test-6", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{}); err != nil { + t.Fatalf("unable to patch cr: %v", err) + } + return cr, crdGVR.Group, "foos" + }, + wantErr: func(t *testing.T, err error) { + if !apierrors.IsNotAcceptable(err) { + t.Fatal(err) + } + // TODO: this should be a more specific error + if err.Error() != "only the following media types are accepted: application/json;stream=watch" { + t.Fatal(err) + } + }, + }, + { + name: "v1 verify columns on services", + accept: "application/json;as=Table;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + ns := "default" + svc, err := clientset.CoreV1().Services(ns).Create(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-4"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}) + if err != nil { + t.Fatalf("unable to create service: %v", err) + } + if _, err := clientset.CoreV1().Services(ns).Patch(svc.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`)); err != nil { + t.Fatalf("unable to update service: %v", err) + } + return svc, "", "services" + }, + wantBody: func(t *testing.T, w io.Reader) { + expectTableV1WatchEvents(t, 2, 7, metav1.IncludeMetadata, json.NewDecoder(w)) + }, + }, + { + name: "v1 verify columns on services with no object", + accept: "application/json;as=Table;g=meta.k8s.io;v=v1", + includeObject: metav1.IncludeNone, + object: func(t *testing.T) (metav1.Object, string, string) { + ns := "default" + obj, err := clientset.CoreV1().Services(ns).Create(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-5"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + if _, err := clientset.CoreV1().Services(ns).Patch(obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`)); err != nil { + t.Fatalf("unable to update object: %v", err) + } + return obj, "", "services" + }, + wantBody: func(t *testing.T, w io.Reader) { + expectTableV1WatchEvents(t, 2, 7, metav1.IncludeNone, json.NewDecoder(w)) + }, + }, + { + name: "v1 verify columns on services with full object", + accept: "application/json;as=Table;g=meta.k8s.io;v=v1", + includeObject: metav1.IncludeObject, + object: func(t *testing.T) (metav1.Object, string, string) { + ns := "default" + obj, err := clientset.CoreV1().Services(ns).Create(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-6"}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}}) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + if _, err := clientset.CoreV1().Services(ns).Patch(obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`)); err != nil { + t.Fatalf("unable to update object: %v", err) + } + return obj, "", "services" + }, + wantBody: func(t *testing.T, w io.Reader) { + objects := expectTableV1WatchEvents(t, 2, 7, metav1.IncludeObject, json.NewDecoder(w)) + var svc v1.Service + if err := json.Unmarshal(objects[1], &svc); err != nil { + t.Fatal(err) + } + if svc.Annotations["test"] != "1" || svc.Spec.Ports[0].Port != 1000 { + t.Fatalf("unexpected object: %#v", svc) + } + }, + }, + { + name: "v1 verify partial metadata object on config maps", + accept: "application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + ns := "default" + obj, err := clientset.CoreV1().ConfigMaps(ns).Create(&v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-3", Annotations: map[string]string{"test": "0"}}}) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + if _, err := clientset.CoreV1().ConfigMaps(ns).Patch(obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`)); err != nil { + t.Fatalf("unable to update object: %v", err) + } + return obj, "", "configmaps" + }, + wantBody: func(t *testing.T, w io.Reader) { + expectPartialObjectMetaV1Events(t, json.NewDecoder(w), "0", "1") + }, + }, + { + name: "v1 verify partial metadata object on config maps in protobuf", + accept: "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + ns := "default" + obj, err := clientset.CoreV1().ConfigMaps(ns).Create(&v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-4", Annotations: map[string]string{"test": "0"}}}) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + if _, err := clientset.CoreV1().ConfigMaps(ns).Patch(obj.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`)); err != nil { + t.Fatalf("unable to update object: %v", err) + } + return obj, "", "configmaps" + }, + wantBody: func(t *testing.T, w io.Reader) { + expectPartialObjectMetaV1EventsProtobuf(t, w, "0", "1") + }, + }, + { + name: "v1 verify error on unsupported mimetype protobuf for table conversion", + accept: "application/vnd.kubernetes.protobuf;as=Table;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" + }, + wantErr: func(t *testing.T, err error) { + if !apierrors.IsNotAcceptable(err) { + t.Fatal(err) + } + // TODO: this should be a more specific error + if err.Error() != "only the following media types are accepted: application/json, application/yaml, application/vnd.kubernetes.protobuf" { + t.Fatal(err) + } + }, + }, + { + name: "v1 verify error on invalid mimetype - bad group", + accept: "application/json;as=PartialObjectMetadata;g=k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" + }, + wantErr: func(t *testing.T, err error) { + if !apierrors.IsNotAcceptable(err) { + t.Fatal(err) + } + }, + }, + { + name: "v1 verify error on invalid mimetype - bad kind", + accept: "application/json;as=PartialObject;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" + }, + wantErr: func(t *testing.T, err error) { + if !apierrors.IsNotAcceptable(err) { + t.Fatal(err) + } + }, + }, + { + name: "v1 verify error on invalid mimetype - missing kind", + accept: "application/json;g=meta.k8s.io;v=v1", + object: func(t *testing.T) (metav1.Object, string, string) { + return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" + }, + wantErr: func(t *testing.T, err error) { + if !apierrors.IsNotAcceptable(err) { + t.Fatal(err) + } + }, + }, + { + name: "v1 verify error on invalid transform parameter", + accept: "application/json;as=Table;g=meta.k8s.io;v=v1", + includeObject: metav1.IncludeObjectPolicy("unrecognized"), object: func(t *testing.T) (metav1.Object, string, string) { return &metav1.ObjectMeta{Name: "kubernetes", Namespace: "default"}, "", "services" }, @@ -892,7 +1124,7 @@ func TestTransformOnWatch(t *testing.T) { } } -func expectTableWatchEvents(t *testing.T, count, columns int, policy metav1beta1.IncludeObjectPolicy, d *json.Decoder) [][]byte { +func expectTableWatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte { t.Helper() var objects [][]byte @@ -923,7 +1155,7 @@ func expectTableWatchEvents(t *testing.T, count, columns int, policy metav1beta1 t.Fatalf("Invalid row width: %#v", row.Cells) } switch policy { - case metav1beta1.IncludeMetadata: + case metav1.IncludeMetadata: var meta metav1beta1.PartialObjectMetadata if err := json.Unmarshal(row.Object.Raw, &meta); err != nil { t.Fatalf("expected partial object: %v", err) @@ -932,11 +1164,11 @@ func expectTableWatchEvents(t *testing.T, count, columns int, policy metav1beta1 if meta.TypeMeta != partialObj { t.Fatalf("expected partial object: %#v", meta) } - case metav1beta1.IncludeNone: + case metav1.IncludeNone: if len(row.Object.Raw) != 0 { t.Fatalf("Expected no object: %s", string(row.Object.Raw)) } - case metav1beta1.IncludeObject: + case metav1.IncludeObject: if len(row.Object.Raw) == 0 { t.Fatalf("Expected object: %s", string(row.Object.Raw)) } @@ -1000,3 +1232,112 @@ func expectPartialObjectMetaEventsProtobuf(t *testing.T, r io.Reader, values ... } } } + +func expectTableV1WatchEvents(t *testing.T, count, columns int, policy metav1.IncludeObjectPolicy, d *json.Decoder) [][]byte { + t.Helper() + + var objects [][]byte + + for i := 0; i < count; i++ { + var evt metav1.WatchEvent + if err := d.Decode(&evt); err != nil { + t.Fatal(err) + } + var table metav1.Table + if err := json.Unmarshal(evt.Object.Raw, &table); err != nil { + t.Fatal(err) + } + if i == 0 { + if len(table.ColumnDefinitions) != columns { + t.Fatalf("Got unexpected columns on first watch event: %d vs %#v", columns, table.ColumnDefinitions) + } + } else { + if len(table.ColumnDefinitions) != 0 { + t.Fatalf("Expected no columns on second watch event: %#v", table.ColumnDefinitions) + } + } + if len(table.Rows) != 1 { + t.Fatalf("Invalid rows: %#v", table.Rows) + } + row := table.Rows[0] + if len(row.Cells) != columns { + t.Fatalf("Invalid row width: %#v", row.Cells) + } + switch policy { + case metav1.IncludeMetadata: + var meta metav1.PartialObjectMetadata + if err := json.Unmarshal(row.Object.Raw, &meta); err != nil { + t.Fatalf("expected partial object: %v", err) + } + partialObj := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"} + if meta.TypeMeta != partialObj { + t.Fatalf("expected partial object: %#v", meta) + } + case metav1.IncludeNone: + if len(row.Object.Raw) != 0 { + t.Fatalf("Expected no object: %s", string(row.Object.Raw)) + } + case metav1.IncludeObject: + if len(row.Object.Raw) == 0 { + t.Fatalf("Expected object: %s", string(row.Object.Raw)) + } + objects = append(objects, row.Object.Raw) + } + } + return objects +} + +func expectPartialObjectMetaV1Events(t *testing.T, d *json.Decoder, values ...string) { + t.Helper() + + for i, value := range values { + var evt metav1.WatchEvent + if err := d.Decode(&evt); err != nil { + t.Fatal(err) + } + var meta metav1.PartialObjectMetadata + if err := json.Unmarshal(evt.Object.Raw, &meta); err != nil { + t.Fatal(err) + } + typeMeta := metav1.TypeMeta{Kind: "PartialObjectMetadata", APIVersion: "meta.k8s.io/v1"} + if meta.TypeMeta != typeMeta { + t.Fatalf("expected partial object: %#v", meta) + } + if meta.Annotations["test"] != value { + t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"]) + } + } +} + +func expectPartialObjectMetaV1EventsProtobuf(t *testing.T, r io.Reader, values ...string) { + scheme := runtime.NewScheme() + metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + rs := protobuf.NewRawSerializer(scheme, scheme) + d := streaming.NewDecoder( + protobuf.LengthDelimitedFramer.NewFrameReader(ioutil.NopCloser(r)), + rs, + ) + ds := metainternalversion.Codecs.UniversalDeserializer() + + for i, value := range values { + var evt metav1.WatchEvent + if _, _, err := d.Decode(nil, &evt); err != nil { + t.Fatal(err) + } + obj, gvk, err := ds.Decode(evt.Object.Raw, nil, nil) + if err != nil { + t.Fatal(err) + } + meta, ok := obj.(*metav1.PartialObjectMetadata) + if !ok { + t.Fatalf("unexpected watch object %T", obj) + } + expected := &schema.GroupVersionKind{Kind: "PartialObjectMetadata", Version: "v1", Group: "meta.k8s.io"} + if !reflect.DeepEqual(expected, gvk) { + t.Fatalf("expected partial object: %#v", meta) + } + if meta.Annotations["test"] != value { + t.Fatalf("expected event %d to have value %q instead of %q", i+1, value, meta.Annotations["test"]) + } + } +}