Unverified Commit 6f9bf5fe authored by Kubernetes Prow Robot's avatar Kubernetes Prow Robot Committed by GitHub

Merge pull request #71548 from smarterclayton/watch_converted

Support Table and PartialObjectMetadata on watch
parents d7103187 3230a0b4
...@@ -22,10 +22,11 @@ import ( ...@@ -22,10 +22,11 @@ import (
fuzz "github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apitesting "k8s.io/apimachinery/pkg/api/apitesting" apitesting "k8s.io/apimachinery/pkg/api/apitesting"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
genericfuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" genericfuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
admissionregistrationfuzzer "k8s.io/kubernetes/pkg/apis/admissionregistration/fuzzer" admissionregistrationfuzzer "k8s.io/kubernetes/pkg/apis/admissionregistration/fuzzer"
...@@ -105,4 +106,5 @@ var FuzzerFuncs = fuzzer.MergeFuzzerFuncs( ...@@ -105,4 +106,5 @@ var FuzzerFuncs = fuzzer.MergeFuzzerFuncs(
auditregistrationfuzzer.Funcs, auditregistrationfuzzer.Funcs,
storagefuzzer.Funcs, storagefuzzer.Funcs,
networkingfuzzer.Funcs, networkingfuzzer.Funcs,
metafuzzer.Funcs,
) )
...@@ -509,7 +509,9 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio ...@@ -509,7 +509,9 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio
return nil, results[1].Interface().(error) return nil, results[1].Interface().(error)
} }
columns := handler.columnDefinitions var columns []metav1beta1.TableColumnDefinition
if !options.NoHeaders {
columns = handler.columnDefinitions
if !options.Wide { if !options.Wide {
columns = make([]metav1beta1.TableColumnDefinition, 0, len(handler.columnDefinitions)) columns = make([]metav1beta1.TableColumnDefinition, 0, len(handler.columnDefinitions))
for i := range handler.columnDefinitions { for i := range handler.columnDefinitions {
...@@ -519,6 +521,7 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio ...@@ -519,6 +521,7 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio
columns = append(columns, handler.columnDefinitions[i]) columns = append(columns, handler.columnDefinitions[i])
} }
} }
}
table := &metav1beta1.Table{ table := &metav1beta1.Table{
ListMeta: metav1.ListMeta{ ListMeta: metav1.ListMeta{
ResourceVersion: "", ResourceVersion: "",
......
...@@ -430,14 +430,14 @@ func AddHandlers(h printers.PrintHandler) { ...@@ -430,14 +430,14 @@ func AddHandlers(h printers.PrintHandler) {
h.TableHandler(controllerRevisionColumnDefinition, printControllerRevision) h.TableHandler(controllerRevisionColumnDefinition, printControllerRevision)
h.TableHandler(controllerRevisionColumnDefinition, printControllerRevisionList) h.TableHandler(controllerRevisionColumnDefinition, printControllerRevisionList)
resorceQuotaColumnDefinitions := []metav1beta1.TableColumnDefinition{ resourceQuotaColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
{Name: "Request", Type: "string", Description: "Request represents a minimum amount of cpu/memory that a container may consume."}, {Name: "Request", Type: "string", Description: "Request represents a minimum amount of cpu/memory that a container may consume."},
{Name: "Limit", Type: "string", Description: "Limits control the maximum amount of cpu/memory that a container may use independent of contention on the node."}, {Name: "Limit", Type: "string", Description: "Limits control the maximum amount of cpu/memory that a container may use independent of contention on the node."},
} }
h.TableHandler(resorceQuotaColumnDefinitions, printResourceQuota) h.TableHandler(resourceQuotaColumnDefinitions, printResourceQuota)
h.TableHandler(resorceQuotaColumnDefinitions, printResourceQuotaList) h.TableHandler(resourceQuotaColumnDefinitions, printResourceQuotaList)
priorityClassColumnDefinitions := []metav1beta1.TableColumnDefinition{ priorityClassColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
...@@ -456,6 +456,17 @@ func AddHandlers(h printers.PrintHandler) { ...@@ -456,6 +456,17 @@ func AddHandlers(h printers.PrintHandler) {
h.TableHandler(runtimeClassColumnDefinitions, printRuntimeClass) h.TableHandler(runtimeClassColumnDefinitions, printRuntimeClass)
h.TableHandler(runtimeClassColumnDefinitions, printRuntimeClassList) h.TableHandler(runtimeClassColumnDefinitions, printRuntimeClassList)
volumeAttachmentColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Attacher", Type: "string", Format: "name", Description: storagev1.VolumeAttachmentSpec{}.SwaggerDoc()["attacher"]},
{Name: "PV", Type: "string", Description: storagev1.VolumeAttachmentSource{}.SwaggerDoc()["persistentVolumeName"]},
{Name: "Node", Type: "string", Description: storagev1.VolumeAttachmentSpec{}.SwaggerDoc()["nodeName"]},
{Name: "Attached", Type: "boolean", Description: storagev1.VolumeAttachmentStatus{}.SwaggerDoc()["attached"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
}
h.TableHandler(volumeAttachmentColumnDefinitions, printVolumeAttachment)
h.TableHandler(volumeAttachmentColumnDefinitions, printVolumeAttachmentList)
AddDefaultHandlers(h) AddDefaultHandlers(h)
} }
...@@ -1996,6 +2007,34 @@ func printRuntimeClassList(list *nodeapi.RuntimeClassList, options printers.Prin ...@@ -1996,6 +2007,34 @@ func printRuntimeClassList(list *nodeapi.RuntimeClassList, options printers.Prin
rows := make([]metav1beta1.TableRow, 0, len(list.Items)) rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items { for i := range list.Items {
r, err := printRuntimeClass(&list.Items[i], options) r, err := printRuntimeClass(&list.Items[i], options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
}
return rows, nil
}
func printVolumeAttachment(obj *storage.VolumeAttachment, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
name := obj.Name
pvName := ""
if obj.Spec.Source.PersistentVolumeName != nil {
pvName = *obj.Spec.Source.PersistentVolumeName
}
row.Cells = append(row.Cells, name, obj.Spec.Attacher, pvName, obj.Spec.NodeName, obj.Status.Attached, translateTimestampSince(obj.CreationTimestamp))
return []metav1beta1.TableRow{row}, nil
}
func printVolumeAttachmentList(list *storage.VolumeAttachmentList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printVolumeAttachment(&list.Items[i], options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -18,6 +18,7 @@ package storage ...@@ -18,6 +18,7 @@ package storage
import ( import (
"context" "context"
"fmt"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
...@@ -29,5 +30,16 @@ type TableConvertor struct { ...@@ -29,5 +30,16 @@ type TableConvertor struct {
} }
func (c TableConvertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) { func (c TableConvertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
return c.TablePrinter.PrintTable(obj, printers.PrintOptions{Wide: true}) noHeaders := false
if tableOptions != nil {
switch t := tableOptions.(type) {
case *metav1beta1.TableOptions:
if t != nil {
noHeaders = t.NoHeaders
}
default:
return nil, fmt.Errorf("unrecognized type %T for table options, can't display tabular output", tableOptions)
}
}
return c.TablePrinter.PrintTable(obj, printers.PrintOptions{Wide: true, NoHeaders: noHeaders})
} }
...@@ -7,6 +7,9 @@ go_library( ...@@ -7,6 +7,9 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//pkg/apis/storage:go_default_library", "//pkg/apis/storage:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library",
"//pkg/printers/storage:go_default_library",
"//pkg/registry/storage/volumeattachment:go_default_library", "//pkg/registry/storage/volumeattachment: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/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
......
...@@ -25,6 +25,9 @@ import ( ...@@ -25,6 +25,9 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
storageapi "k8s.io/kubernetes/pkg/apis/storage" storageapi "k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/storage/volumeattachment" "k8s.io/kubernetes/pkg/registry/storage/volumeattachment"
) )
...@@ -50,6 +53,8 @@ func NewStorage(optsGetter generic.RESTOptionsGetter) *VolumeAttachmentStorage { ...@@ -50,6 +53,8 @@ func NewStorage(optsGetter generic.RESTOptionsGetter) *VolumeAttachmentStorage {
UpdateStrategy: volumeattachment.Strategy, UpdateStrategy: volumeattachment.Strategy,
DeleteStrategy: volumeattachment.Strategy, DeleteStrategy: volumeattachment.Strategy,
ReturnDeletedObject: true, ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TablePrinter: printers.NewTablePrinter().With(printersinternal.AddHandlers)},
} }
options := &generic.StoreOptions{RESTOptions: optsGetter} options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil { if err := store.CompleteWithOptions(options); err != nil {
......
...@@ -1111,6 +1111,10 @@ ...@@ -1111,6 +1111,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion", "ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -36,4 +36,11 @@ go_test( ...@@ -36,4 +36,11 @@ go_test(
name = "go_default_test", name = "go_default_test",
srcs = ["tableconvertor_test.go"], srcs = ["tableconvertor_test.go"],
embed = [":go_default_library"], embed = [":go_default_library"],
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/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/client-go/util/jsonpath:go_default_library",
],
) )
...@@ -76,9 +76,13 @@ type convertor struct { ...@@ -76,9 +76,13 @@ type convertor struct {
} }
func (c *convertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) { func (c *convertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
table := &metav1beta1.Table{ table := &metav1beta1.Table{}
ColumnDefinitions: c.headers, opt, ok := tableOptions.(*metav1beta1.TableOptions)
noHeaders := ok && opt != nil && opt.NoHeaders
if !noHeaders {
table.ColumnDefinitions = c.headers
} }
if m, err := meta.ListAccessor(obj); err == nil { if m, err := meta.ListAccessor(obj); err == nil {
table.ResourceVersion = m.GetResourceVersion() table.ResourceVersion = m.GetResourceVersion()
table.SelfLink = m.GetSelfLink() table.SelfLink = m.GetSelfLink()
......
...@@ -17,10 +17,17 @@ limitations under the License. ...@@ -17,10 +17,17 @@ limitations under the License.
package tableconvertor package tableconvertor
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
"time" "time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/client-go/util/jsonpath"
) )
func Test_cellForJSONValue(t *testing.T) { func Test_cellForJSONValue(t *testing.T) {
...@@ -66,3 +73,149 @@ func Test_cellForJSONValue(t *testing.T) { ...@@ -66,3 +73,149 @@ func Test_cellForJSONValue(t *testing.T) {
}) })
} }
} }
func Test_convertor_ConvertToTable(t *testing.T) {
type fields struct {
headers []metav1beta1.TableColumnDefinition
additionalColumns []*jsonpath.JSONPath
}
type args struct {
ctx context.Context
obj runtime.Object
tableOptions runtime.Object
}
tests := []struct {
name string
fields fields
args args
want *metav1beta1.Table
wantErr bool
}{
{
name: "Return table for object",
fields: fields{
headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
},
args: args{
obj: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
},
tableOptions: nil,
},
want: &metav1beta1.Table{
ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
Rows: []metav1beta1.TableRow{
{
Cells: []interface{}{"blah"},
Object: runtime.RawExtension{
Object: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
},
},
},
},
},
},
{
name: "Return table for list",
fields: fields{
headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
},
args: args{
obj: &metav1beta1.PartialObjectMetadataList{
Items: []*metav1beta1.PartialObjectMetadata{
{ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))}},
{ObjectMeta: metav1.ObjectMeta{Name: "blah-2", CreationTimestamp: metav1.NewTime(time.Unix(2, 0))}},
},
},
tableOptions: nil,
},
want: &metav1beta1.Table{
ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
Rows: []metav1beta1.TableRow{
{
Cells: []interface{}{"blah"},
Object: runtime.RawExtension{
Object: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
},
},
},
{
Cells: []interface{}{"blah-2"},
Object: runtime.RawExtension{
Object: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah-2", CreationTimestamp: metav1.NewTime(time.Unix(2, 0))},
},
},
},
},
},
},
{
name: "Accept TableOptions",
fields: fields{
headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
},
args: args{
obj: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
},
tableOptions: &metav1beta1.TableOptions{},
},
want: &metav1beta1.Table{
ColumnDefinitions: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
Rows: []metav1beta1.TableRow{
{
Cells: []interface{}{"blah"},
Object: runtime.RawExtension{
Object: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
},
},
},
},
},
},
{
name: "Omit headers from TableOptions",
fields: fields{
headers: []metav1beta1.TableColumnDefinition{{Name: "name", Type: "string"}},
},
args: args{
obj: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
},
tableOptions: &metav1beta1.TableOptions{NoHeaders: true},
},
want: &metav1beta1.Table{
Rows: []metav1beta1.TableRow{
{
Cells: []interface{}{"blah"},
Object: runtime.RawExtension{
Object: &metav1beta1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{Name: "blah", CreationTimestamp: metav1.NewTime(time.Unix(1, 0))},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &convertor{
headers: tt.fields.headers,
additionalColumns: tt.fields.additionalColumns,
}
got, err := c.ConvertToTable(tt.args.ctx, tt.args.obj, tt.args.tableOptions)
if (err != nil) != tt.wantErr {
t.Errorf("convertor.ConvertToTable() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("convertor.ConvertToTable() = %s", diff.ObjectReflectDiff(tt.want, got))
}
})
}
}
...@@ -23,7 +23,7 @@ import ( ...@@ -23,7 +23,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
apitesting "k8s.io/apimachinery/pkg/api/apitesting" apitesting "k8s.io/apimachinery/pkg/api/apitesting"
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
...@@ -282,8 +282,14 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { ...@@ -282,8 +282,14 @@ func v1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
} }
} }
func v1alpha1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { func v1beta1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
return []interface{}{ return []interface{}{
func(r *metav1beta1.TableOptions, c fuzz.Continue) {
c.FuzzNoCustom(r)
// NoHeaders is not serialized to the wire but is allowed within the versioned
// type because we don't use meta internal types in the client and API server.
r.NoHeaders = false
},
func(r *metav1beta1.TableRow, c fuzz.Continue) { func(r *metav1beta1.TableRow, c fuzz.Continue) {
c.Fuzz(&r.Object) c.Fuzz(&r.Object)
c.Fuzz(&r.Conditions) c.Fuzz(&r.Conditions)
...@@ -326,5 +332,5 @@ func v1alpha1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} { ...@@ -326,5 +332,5 @@ func v1alpha1FuzzerFuncs(codecs runtimeserializer.CodecFactory) []interface{} {
var Funcs = fuzzer.MergeFuzzerFuncs( var Funcs = fuzzer.MergeFuzzerFuncs(
genericFuzzerFuncs, genericFuzzerFuncs,
v1FuzzerFuncs, v1FuzzerFuncs,
v1alpha1FuzzerFuncs, v1beta1FuzzerFuncs,
) )
...@@ -34,7 +34,10 @@ filegroup( ...@@ -34,7 +34,10 @@ filegroup(
filegroup( filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [":package-srcs"], srcs = [
":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation:all-srcs",
],
tags = ["automanaged"], tags = ["automanaged"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
...@@ -133,6 +133,10 @@ const ( ...@@ -133,6 +133,10 @@ const (
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TableOptions struct { type TableOptions struct {
v1.TypeMeta `json:",inline"` v1.TypeMeta `json:",inline"`
// NoHeaders is only exposed for internal callers.
NoHeaders bool `json:"-"`
// includeObject decides whether to include each object along with its columnar information. // includeObject decides whether to include each object along with its columnar information.
// Specifying "None" will return no object, specifying "Object" will return the full object contents, and // Specifying "None" will return no object, specifying "Object" will return the full object contents, and
// specifying "Metadata" (the default) will return the object's metadata in the PartialObjectMetadata kind // specifying "Metadata" (the default) will return the object's metadata in the PartialObjectMetadata kind
......
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["validation.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation",
importpath = "k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/util/validation/field"
)
// ValidateTableOptions returns any invalid flags on TableOptions.
func ValidateTableOptions(opts *metav1beta1.TableOptions) field.ErrorList {
var allErrs field.ErrorList
switch opts.IncludeObject {
case metav1beta1.IncludeMetadata, metav1beta1.IncludeNone, metav1beta1.IncludeObject, "":
default:
allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty"))
}
return allErrs
}
...@@ -43,6 +43,8 @@ type TypeMeta struct { ...@@ -43,6 +43,8 @@ type TypeMeta struct {
const ( const (
ContentTypeJSON string = "application/json" ContentTypeJSON string = "application/json"
ContentTypeYAML string = "application/yaml" ContentTypeYAML string = "application/yaml"
ContentTypeProtobuf string = "application/vnd.kubernetes.protobuf"
) )
// RawExtension is used to hold extensions in external versions. // RawExtension is used to hold extensions in external versions.
......
...@@ -1135,6 +1135,10 @@ ...@@ -1135,6 +1135,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion", "ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -46,6 +46,7 @@ go_test( ...@@ -46,6 +46,7 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/audit/policy:go_default_library", "//staging/src/k8s.io/apiserver/pkg/audit/policy:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/filters:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/filters:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/testing:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/testing:go_default_library",
......
...@@ -69,5 +69,5 @@ func (s *APIGroupHandler) handle(req *restful.Request, resp *restful.Response) { ...@@ -69,5 +69,5 @@ func (s *APIGroupHandler) handle(req *restful.Request, resp *restful.Response) {
} }
func (s *APIGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (s *APIGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK, &s.group) responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK, &s.group)
} }
...@@ -72,5 +72,5 @@ func (s *legacyRootAPIHandler) handle(req *restful.Request, resp *restful.Respon ...@@ -72,5 +72,5 @@ func (s *legacyRootAPIHandler) handle(req *restful.Request, resp *restful.Respon
Versions: []string{"v1"}, Versions: []string{"v1"},
} }
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, apiVersions) responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, apiVersions)
} }
...@@ -111,7 +111,7 @@ func (s *rootAPIsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) ...@@ -111,7 +111,7 @@ func (s *rootAPIsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request)
groups[i].ServerAddressByClientCIDRs = serverCIDR groups[i].ServerAddressByClientCIDRs = serverCIDR
} }
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups}) responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups})
} }
func (s *rootAPIsHandler) restfulHandle(req *restful.Request, resp *restful.Response) { func (s *rootAPIsHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
......
...@@ -78,6 +78,6 @@ func (s *APIVersionHandler) handle(req *restful.Request, resp *restful.Response) ...@@ -78,6 +78,6 @@ func (s *APIVersionHandler) handle(req *restful.Request, resp *restful.Response)
} }
func (s *APIVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (s *APIVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK, responsewriters.WriteObjectNegotiated(s.serializer, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK,
&metav1.APIResourceList{GroupVersion: s.groupVersion.String(), APIResources: s.apiResourceLister.ListAPIResources()}) &metav1.APIResourceList{GroupVersion: s.groupVersion.String(), APIResources: s.apiResourceLister.ListAPIResources()})
} }
...@@ -61,6 +61,7 @@ go_library( ...@@ -61,6 +61,7 @@ go_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/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation: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",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
......
...@@ -257,7 +257,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch ...@@ -257,7 +257,7 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch
} }
requestInfo, _ := request.RequestInfoFrom(ctx) requestInfo, _ := request.RequestInfoFrom(ctx)
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() { metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
serveWatch(watcher, scope, req, w, timeout) serveWatch(watcher, scope, outputMediaType, req, w, timeout)
}) })
return return
} }
......
...@@ -47,6 +47,34 @@ func (e errNotAcceptable) Status() metav1.Status { ...@@ -47,6 +47,34 @@ func (e errNotAcceptable) Status() metav1.Status {
} }
} }
// errNotAcceptableConversion indicates Accept negotiation has failed specifically
// for a conversion to a known type.
type errNotAcceptableConversion struct {
target string
accepted []string
}
// NewNotAcceptableConversionError returns an error indicating that the desired
// API transformation to the target group version kind string is not accepted and
// only the listed mime types are allowed. This is temporary while Table does not
// yet support protobuf encoding.
func NewNotAcceptableConversionError(target string, accepted []string) error {
return errNotAcceptableConversion{target, accepted}
}
func (e errNotAcceptableConversion) Error() string {
return fmt.Sprintf("only the following media types are accepted when converting to %s: %v", e.target, strings.Join(e.accepted, ", "))
}
func (e errNotAcceptableConversion) Status() metav1.Status {
return metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusNotAcceptable,
Reason: metav1.StatusReasonNotAcceptable,
Message: e.Error(),
}
}
// errUnsupportedMediaType indicates Content-Type is not recognized // errUnsupportedMediaType indicates Content-Type is not recognized
type errUnsupportedMediaType struct { type errUnsupportedMediaType struct {
accepted []string accepted []string
......
...@@ -56,15 +56,9 @@ func NegotiateOutputMediaType(req *http.Request, ns runtime.NegotiatedSerializer ...@@ -56,15 +56,9 @@ func NegotiateOutputMediaType(req *http.Request, ns runtime.NegotiatedSerializer
return mediaType, info, nil return mediaType, info, nil
} }
// NegotiateOutputSerializer returns a serializer for the output. // NegotiateOutputMediaTypeStream returns a stream serializer for the given request.
func NegotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) { func NegotiateOutputMediaTypeStream(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (runtime.SerializerInfo, error) {
_, info, err := NegotiateOutputMediaType(req, ns, DefaultEndpointRestrictions) mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), AcceptedMediaTypesForEndpoint(ns), restrictions)
return info, err
}
// NegotiateOutputStreamSerializer returns a stream serializer for the given request.
func NegotiateOutputStreamSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), AcceptedMediaTypesForEndpoint(ns), DefaultEndpointRestrictions)
if !ok || mediaType.Accepted.Serializer.StreamSerializer == nil { if !ok || mediaType.Accepted.Serializer.StreamSerializer == nil {
_, supported := MediaTypesForSerializer(ns) _, supported := MediaTypesForSerializer(ns)
return runtime.SerializerInfo{}, NewNotAcceptableError(supported) return runtime.SerializerInfo{}, NewNotAcceptableError(supported)
...@@ -124,7 +118,7 @@ func isPrettyPrint(req *http.Request) bool { ...@@ -124,7 +118,7 @@ func isPrettyPrint(req *http.Request) bool {
type EndpointRestrictions interface { type EndpointRestrictions interface {
// AllowsConversion should return true if the specified group version kind // AllowsConversion should return true if the specified group version kind
// is an allowed target object. // is an allowed target object.
AllowsConversion(schema.GroupVersionKind) bool AllowsConversion(target schema.GroupVersionKind, mimeType, mimeSubType string) bool
// AllowsServerVersion should return true if the specified version is valid // AllowsServerVersion should return true if the specified version is valid
// for the server group. // for the server group.
AllowsServerVersion(version string) bool AllowsServerVersion(version string) bool
...@@ -139,7 +133,9 @@ var DefaultEndpointRestrictions = emptyEndpointRestrictions{} ...@@ -139,7 +133,9 @@ var DefaultEndpointRestrictions = emptyEndpointRestrictions{}
type emptyEndpointRestrictions struct{} type emptyEndpointRestrictions struct{}
func (emptyEndpointRestrictions) AllowsConversion(schema.GroupVersionKind) bool { return false } func (emptyEndpointRestrictions) AllowsConversion(schema.GroupVersionKind, string, string) bool {
return false
}
func (emptyEndpointRestrictions) AllowsServerVersion(string) bool { return false } func (emptyEndpointRestrictions) AllowsServerVersion(string) bool { return false }
func (emptyEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" } func (emptyEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" }
...@@ -240,7 +236,7 @@ func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType ...@@ -240,7 +236,7 @@ func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType
} }
} }
if options.Convert != nil && !endpoint.AllowsConversion(*options.Convert) { if options.Convert != nil && !endpoint.AllowsConversion(*options.Convert, accepts.Type, accepts.SubType) {
return MediaTypeOptions{}, false return MediaTypeOptions{}, false
} }
......
...@@ -231,7 +231,7 @@ func TestNegotiate(t *testing.T) { ...@@ -231,7 +231,7 @@ func TestNegotiate(t *testing.T) {
req = &http.Request{Header: http.Header{}} req = &http.Request{Header: http.Header{}}
req.Header.Set("Accept", test.accept) req.Header.Set("Accept", test.accept)
} }
s, err := NegotiateOutputSerializer(req, test.ns) _, s, err := NegotiateOutputMediaType(req, test.ns, DefaultEndpointRestrictions)
switch { switch {
case err == nil && test.errFn != nil: case err == nil && test.errFn != nil:
t.Errorf("%d: failed: expected error", i) t.Errorf("%d: failed: expected error", i)
......
...@@ -26,86 +26,97 @@ import ( ...@@ -26,86 +26,97 @@ import (
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
) )
// transformResponseObject takes an object loaded from storage and performs any necessary transformations. // transformObject takes the object as returned by storage and ensures it is in
// Will write the complete response object. // the client's desired form, as well as ensuring any API level fields like self-link
func transformResponseObject(ctx context.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, mediaType negotiation.MediaTypeOptions, result runtime.Object) { // are properly set.
// status objects are ignored for transformation func transformObject(ctx context.Context, obj runtime.Object, opts interface{}, mediaType negotiation.MediaTypeOptions, scope RequestScope, req *http.Request) (runtime.Object, error) {
if _, ok := result.(*metav1.Status); ok { if _, ok := obj.(*metav1.Status); ok {
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req) return obj, nil
return
} }
if err := setObjectSelfLink(ctx, obj, req, scope.Namer); err != nil {
// ensure the self link and empty list array are set return nil, err
if err := setObjectSelfLink(ctx, result, req, scope.Namer); err != nil {
scope.err(err, w, req)
return
} }
trace := scope.Trace
// If conversion was allowed by the scope, perform it before writing the response
switch target := mediaType.Convert; { switch target := mediaType.Convert; {
case target == nil: case target == nil:
trace.Step("Writing response") return obj, nil
responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion: case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
partial, err := asV1Beta1PartialObjectMetadata(result) return asV1Beta1PartialObjectMetadata(obj)
if err != nil {
scope.err(err, w, req)
return
}
if err := writeMetaInternalVersion(partial, statusCode, w, req, &scope, target.GroupVersion()); err != nil {
scope.err(err, w, req)
return
}
case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion: case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
trace.Step("Processing list items") return asV1Beta1PartialObjectMetadataList(obj)
partial, err := asV1Beta1PartialObjectMetadataList(result)
if err != nil { case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
scope.err(err, w, req) options, ok := opts.(*metav1beta1.TableOptions)
return if !ok {
return nil, fmt.Errorf("unexpected TableOptions, got %T", opts)
} }
return asV1Beta1Table(ctx, obj, options, scope)
if err := writeMetaInternalVersion(partial, statusCode, w, req, &scope, target.GroupVersion()); err != nil { default:
scope.err(err, w, req) accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
return err := negotiation.NewNotAcceptableError(accepted)
return nil, err
} }
}
// optionsForTransform will load and validate any additional query parameter options for
// a conversion or return an error.
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:
opts := &metav1beta1.TableOptions{} opts := &metav1beta1.TableOptions{}
trace.Step("Decoding parameters")
if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil { if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil {
scope.err(err, w, req) return nil, err
return
} }
switch errs := validation.ValidateTableOptions(opts); len(errs) {
case 0:
return opts, nil
case 1:
return nil, errors.NewBadRequest(fmt.Sprintf("Unable to convert to Table as requested: %v", errs[0].Error()))
default:
return nil, errors.NewBadRequest(fmt.Sprintf("Unable to convert to Table as requested: %v", errs))
}
}
return nil, nil
}
table, err := asV1Beta1Table(ctx, result, opts, scope) // targetEncodingForTransform returns the appropriate serializer for the input media type
if err != nil { func targetEncodingForTransform(scope *RequestScope, mediaType negotiation.MediaTypeOptions, req *http.Request) (schema.GroupVersionKind, runtime.NegotiatedSerializer, bool) {
scope.err(err, w, req) switch target := mediaType.Convert; {
return 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:
return *target, metainternalversion.Codecs, true
} }
return scope.Kind, scope.Serializer, false
}
if err := writeMetaInternalVersion(table, statusCode, w, req, &scope, target.GroupVersion()); err != nil { // transformResponseObject takes an object loaded from storage and performs any necessary transformations.
// Will write the complete response object.
func transformResponseObject(ctx context.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, mediaType negotiation.MediaTypeOptions, result runtime.Object) {
options, err := optionsForTransform(mediaType, req)
if err != nil {
scope.err(err, w, req) scope.err(err, w, req)
return return
} }
obj, err := transformObject(ctx, result, options, mediaType, scope, req)
default: if err != nil {
// this block should only be hit if scope AllowsConversion is incorrect
accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
err := negotiation.NewNotAcceptableError(accepted)
scope.err(err, w, req) scope.err(err, w, req)
return
} }
kind, serializer, _ := targetEncodingForTransform(&scope, mediaType, req)
responsewriters.WriteObjectNegotiated(serializer, &scope, kind.GroupVersion(), w, req, statusCode, obj)
} }
// errNotAcceptable indicates Accept negotiation has failed // errNotAcceptable indicates Accept negotiation has failed
...@@ -131,15 +142,11 @@ func (e errNotAcceptable) Status() metav1.Status { ...@@ -131,15 +142,11 @@ func (e errNotAcceptable) Status() metav1.Status {
} }
func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta1.TableOptions, scope RequestScope) (runtime.Object, error) { func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta1.TableOptions, scope RequestScope) (runtime.Object, error) {
trace := scope.Trace
trace.Step("Converting to table")
table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts) table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
trace.Step("Processing rows")
for i := range table.Rows { for i := range table.Rows {
item := &table.Rows[i] item := &table.Rows[i]
switch opts.IncludeObject { switch opts.IncludeObject {
...@@ -161,7 +168,6 @@ func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta ...@@ -161,7 +168,6 @@ func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta
case metav1beta1.IncludeNone: case metav1beta1.IncludeNone:
item.Object.Object = nil item.Object.Object = nil
default: default:
// TODO: move this to validation on the table options?
err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject)) err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject))
return nil, err return nil, err
} }
...@@ -172,7 +178,6 @@ func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta ...@@ -172,7 +178,6 @@ func asV1Beta1Table(ctx context.Context, result runtime.Object, opts *metav1beta
func asV1Beta1PartialObjectMetadata(result runtime.Object) (runtime.Object, error) { func asV1Beta1PartialObjectMetadata(result runtime.Object) (runtime.Object, error) {
if meta.IsListType(result) { if meta.IsListType(result) {
// TODO: this should be calculated earlier
err := newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result)) err := newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result))
return nil, err return nil, err
} }
...@@ -187,7 +192,6 @@ func asV1Beta1PartialObjectMetadata(result runtime.Object) (runtime.Object, erro ...@@ -187,7 +192,6 @@ func asV1Beta1PartialObjectMetadata(result runtime.Object) (runtime.Object, erro
func asV1Beta1PartialObjectMetadataList(result runtime.Object) (runtime.Object, error) { func asV1Beta1PartialObjectMetadataList(result runtime.Object) (runtime.Object, error) {
if !meta.IsListType(result) { if !meta.IsListType(result) {
// TODO: this should be calculated earlier
return nil, newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result)) return nil, newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result))
} }
list := &metav1beta1.PartialObjectMetadataList{} list := &metav1beta1.PartialObjectMetadataList{}
...@@ -206,14 +210,3 @@ func asV1Beta1PartialObjectMetadataList(result runtime.Object) (runtime.Object, ...@@ -206,14 +210,3 @@ func asV1Beta1PartialObjectMetadataList(result runtime.Object) (runtime.Object,
} }
return list, nil return list, nil
} }
func writeMetaInternalVersion(obj runtime.Object, statusCode int, w http.ResponseWriter, req *http.Request, restrictions negotiation.EndpointRestrictions, target schema.GroupVersion) error {
// renegotiate under the internal version
_, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, restrictions)
if err != nil {
return err
}
encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, target)
responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, obj)
return nil
}
...@@ -55,23 +55,6 @@ func (w httpResponseWriterWithInit) Write(b []byte) (n int, err error) { ...@@ -55,23 +55,6 @@ func (w httpResponseWriterWithInit) Write(b []byte) (n int, err error) {
return w.innerW.Write(b) return w.innerW.Write(b)
} }
// WriteObject renders a returned runtime.Object to the response as a stream or an encoded object. If the object
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
// response. The Accept header and current API version will be passed in, and the output will be copied
// directly to the response body. If content type is returned it is used, otherwise the content type will
// be "application/octet-stream". All other objects are sent to standard JSON serialization.
func WriteObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
stream, ok := object.(rest.ResourceStreamer)
if ok {
requestInfo, _ := request.RequestInfoFrom(req.Context())
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
StreamObject(statusCode, gv, s, stream, w, req)
})
return
}
WriteObjectNegotiated(s, gv, w, req, statusCode, object)
}
// StreamObject performs input stream negotiation from a ResourceStreamer and writes that to the response. // StreamObject performs input stream negotiation from a ResourceStreamer and writes that to the response.
// If the client requests a websocket upgrade, negotiate for a websocket reader protocol (because many // If the client requests a websocket upgrade, negotiate for a websocket reader protocol (because many
// browser clients cannot easily handle binary streaming protocols). // browser clients cannot easily handle binary streaming protocols).
...@@ -123,9 +106,17 @@ func SerializeObject(mediaType string, encoder runtime.Encoder, innerW http.Resp ...@@ -123,9 +106,17 @@ func SerializeObject(mediaType string, encoder runtime.Encoder, innerW http.Resp
} }
// WriteObjectNegotiated renders an object in the content type negotiated by the client. // WriteObjectNegotiated renders an object in the content type negotiated by the client.
// The context is optional and can be nil. func WriteObjectNegotiated(s runtime.NegotiatedSerializer, restrictions negotiation.EndpointRestrictions, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
func WriteObjectNegotiated(s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) { stream, ok := object.(rest.ResourceStreamer)
serializer, err := negotiation.NegotiateOutputSerializer(req, s) if ok {
requestInfo, _ := request.RequestInfoFrom(req.Context())
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
StreamObject(statusCode, gv, s, stream, w, req)
})
return
}
_, serializer, err := negotiation.NegotiateOutputMediaType(req, s, restrictions)
if err != nil { if err != nil {
// if original statusCode was not successful we need to return the original error // if original statusCode was not successful we need to return the original error
// we cannot hide it behind negotiation problems // we cannot hide it behind negotiation problems
...@@ -162,7 +153,7 @@ func ErrorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupV ...@@ -162,7 +153,7 @@ func ErrorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupV
return code return code
} }
WriteObjectNegotiated(s, gv, w, req, code, status) WriteObjectNegotiated(s, negotiation.DefaultEndpointRestrictions, gv, w, req, code, status)
return code return code
} }
......
...@@ -79,12 +79,14 @@ func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Reque ...@@ -79,12 +79,14 @@ func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Reque
responsewriters.ErrorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req) responsewriters.ErrorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req)
} }
func (scope *RequestScope) AllowsConversion(gvk schema.GroupVersionKind) bool { func (scope *RequestScope) AllowsConversion(gvk schema.GroupVersionKind, mimeType, mimeSubType string) bool {
// TODO: this is temporary, replace with an abstraction calculated at endpoint installation time // TODO: this is temporary, replace with an abstraction calculated at endpoint installation time
if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion { if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion {
switch gvk.Kind { switch gvk.Kind {
case "Table": case "Table":
return scope.TableConvertor != nil return scope.TableConvertor != nil &&
mimeType == "application" &&
(mimeSubType == "json" || mimeSubType == "yaml")
case "PartialObjectMetadata", "PartialObjectMetadataList": case "PartialObjectMetadata", "PartialObjectMetadataList":
// TODO: should delineate between lists and non-list endpoints // TODO: should delineate between lists and non-list endpoints
return true return true
...@@ -172,7 +174,7 @@ type responder struct { ...@@ -172,7 +174,7 @@ type responder struct {
} }
func (r *responder) Object(statusCode int, obj runtime.Object) { func (r *responder) Object(statusCode int, obj runtime.Object) {
responsewriters.WriteObject(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.w, r.req) responsewriters.WriteObjectNegotiated(r.scope.Serializer, &r.scope, r.scope.Kind.GroupVersion(), r.w, r.req, statusCode, obj)
} }
func (r *responder) Error(err error) { func (r *responder) Error(err error) {
......
...@@ -25,13 +25,13 @@ import ( ...@@ -25,13 +25,13 @@ 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"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming" "k8s.io/apimachinery/pkg/runtime/serializer/streaming"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation" "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/metrics" "k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server/httplog" "k8s.io/apiserver/pkg/server/httplog"
"k8s.io/apiserver/pkg/util/wsstream" "k8s.io/apiserver/pkg/util/wsstream"
...@@ -61,41 +61,55 @@ func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) { ...@@ -61,41 +61,55 @@ func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) {
return t.C, t.Stop return t.C, t.Stop
} }
// serveWatch handles serving requests to the server // serveWatch will serve a watch response.
// TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled. // TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled.
func serveWatch(watcher watch.Interface, scope RequestScope, req *http.Request, w http.ResponseWriter, timeout time.Duration) { func serveWatch(watcher watch.Interface, scope RequestScope, mediaTypeOptions negotiation.MediaTypeOptions, req *http.Request, w http.ResponseWriter, timeout time.Duration) {
// negotiate for the stream serializer options, err := optionsForTransform(mediaTypeOptions, req)
serializer, err := negotiation.NegotiateOutputStreamSerializer(req, scope.Serializer) if err != nil {
scope.err(err, w, req)
return
}
// negotiate for the stream serializer from the scope's serializer
serializer, err := negotiation.NegotiateOutputMediaTypeStream(req, scope.Serializer, &scope)
if err != nil { if err != nil {
scope.err(err, w, req) scope.err(err, w, req)
return return
} }
framer := serializer.StreamSerializer.Framer framer := serializer.StreamSerializer.Framer
streamSerializer := serializer.StreamSerializer.Serializer streamSerializer := serializer.StreamSerializer.Serializer
embedded := serializer.Serializer encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion())
useTextFraming := serializer.EncodesAsText
if framer == nil { if framer == nil {
scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), w, req) scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), w, req)
return return
} }
encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion())
useTextFraming := serializer.EncodesAsText
// find the embedded serializer matching the media type
embeddedEncoder := scope.Serializer.EncoderForVersion(embedded, scope.Kind.GroupVersion())
// TODO: next step, get back mediaTypeOptions from negotiate and return the exact value here // TODO: next step, get back mediaTypeOptions from negotiate and return the exact value here
mediaType := serializer.MediaType mediaType := serializer.MediaType
if mediaType != runtime.ContentTypeJSON { if mediaType != runtime.ContentTypeJSON {
mediaType += ";stream=watch" mediaType += ";stream=watch"
} }
ctx := req.Context() // locate the appropriate embedded encoder based on the transform
requestInfo, ok := request.RequestInfoFrom(ctx) var embeddedEncoder runtime.Encoder
if !ok { contentKind, contentSerializer, transform := targetEncodingForTransform(&scope, mediaTypeOptions, req)
scope.err(fmt.Errorf("missing requestInfo"), w, req) if transform {
var embedded runtime.Serializer
for _, supported := range contentSerializer.SupportedMediaTypes() {
if supported.MediaType == serializer.MediaType {
embedded = supported.Serializer
}
}
if embedded == nil {
scope.err(fmt.Errorf("no encoder for %q exists in the requested target %#v", serializer.MediaType, contentSerializer), w, req)
return return
} }
embeddedEncoder = contentSerializer.EncoderForVersion(embedded, contentKind.GroupVersion())
} else {
embeddedEncoder = scope.Serializer.EncoderForVersion(serializer.Serializer, contentKind.GroupVersion())
}
ctx := req.Context()
server := &WatchServer{ server := &WatchServer{
Watching: watcher, Watching: watcher,
...@@ -106,10 +120,20 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *http.Request, ...@@ -106,10 +120,20 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *http.Request,
Framer: framer, Framer: framer,
Encoder: encoder, Encoder: encoder,
EmbeddedEncoder: embeddedEncoder, EmbeddedEncoder: embeddedEncoder,
Fixup: func(obj runtime.Object) {
if err := setSelfLink(obj, requestInfo, scope.Namer); err != nil { Fixup: func(obj runtime.Object) runtime.Object {
utilruntime.HandleError(fmt.Errorf("failed to set link for object %v: %v", reflect.TypeOf(obj), err)) result, err := transformObject(ctx, obj, options, mediaTypeOptions, scope, req)
if err != nil {
utilruntime.HandleError(fmt.Errorf("failed to transform object %v: %v", reflect.TypeOf(obj), err))
return obj
}
// When we are transformed to a table, use the table options as the state for whether we
// should print headers - on watch, we only want to print table headers on the first object
// and omit them on subsequent events.
if tableOptions, ok := options.(*metav1beta1.TableOptions); ok {
tableOptions.NoHeaders = true
} }
return result
}, },
TimeoutFactory: &realTimeoutFactory{timeout}, TimeoutFactory: &realTimeoutFactory{timeout},
...@@ -133,7 +157,8 @@ type WatchServer struct { ...@@ -133,7 +157,8 @@ type WatchServer struct {
Encoder runtime.Encoder Encoder runtime.Encoder
// used to encode the nested object in the watch stream // used to encode the nested object in the watch stream
EmbeddedEncoder runtime.Encoder EmbeddedEncoder runtime.Encoder
Fixup func(runtime.Object) // used to correct the object before we send it to the serializer
Fixup func(runtime.Object) runtime.Object
TimeoutFactory TimeoutFactory TimeoutFactory TimeoutFactory
} }
...@@ -205,8 +230,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -205,8 +230,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
obj := event.Object obj := s.Fixup(event.Object)
s.Fixup(obj)
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil { if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
// unexpected error // unexpected error
utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err)) utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err))
...@@ -272,8 +296,7 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) { ...@@ -272,8 +296,7 @@ func (s *WatchServer) HandleWS(ws *websocket.Conn) {
// End of results. // End of results.
return return
} }
obj := event.Object obj := s.Fixup(event.Object)
s.Fixup(obj)
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil { if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
// unexpected error // unexpected error
utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err)) utilruntime.HandleError(fmt.Errorf("unable to encode watch object %T: %v", obj, err))
......
...@@ -586,7 +586,7 @@ func TestWatchHTTPErrors(t *testing.T) { ...@@ -586,7 +586,7 @@ func TestWatchHTTPErrors(t *testing.T) {
Encoder: newCodec, Encoder: newCodec,
EmbeddedEncoder: newCodec, EmbeddedEncoder: newCodec,
Fixup: func(obj runtime.Object) {}, Fixup: func(obj runtime.Object) runtime.Object { return obj },
TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done}, TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done},
} }
...@@ -646,7 +646,7 @@ func TestWatchHTTPDynamicClientErrors(t *testing.T) { ...@@ -646,7 +646,7 @@ func TestWatchHTTPDynamicClientErrors(t *testing.T) {
Encoder: newCodec, Encoder: newCodec,
EmbeddedEncoder: newCodec, EmbeddedEncoder: newCodec,
Fixup: func(obj runtime.Object) {}, Fixup: func(obj runtime.Object) runtime.Object { return obj },
TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done}, TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done},
} }
...@@ -708,7 +708,7 @@ func TestWatchHTTPTimeout(t *testing.T) { ...@@ -708,7 +708,7 @@ func TestWatchHTTPTimeout(t *testing.T) {
Encoder: newCodec, Encoder: newCodec,
EmbeddedEncoder: newCodec, EmbeddedEncoder: newCodec,
Fixup: func(obj runtime.Object) {}, Fixup: func(obj runtime.Object) runtime.Object { return obj },
TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done}, TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done},
} }
......
...@@ -18,6 +18,7 @@ package resttest ...@@ -18,6 +18,7 @@ package resttest
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
...@@ -1460,7 +1461,7 @@ func (t *Tester) testListTableConversion(obj runtime.Object, assignFn AssignFunc ...@@ -1460,7 +1461,7 @@ func (t *Tester) testListTableConversion(obj runtime.Object, assignFn AssignFunc
} }
columns := table.ColumnDefinitions columns := table.ColumnDefinitions
if len(columns) == 0 { if len(columns) == 0 {
t.Errorf("unexpected number of columns: %v", len(columns)) t.Fatalf("unexpected number of columns: %v\n%#v", len(columns), columns)
} }
if !strings.EqualFold(columns[0].Name, "Name") || columns[0].Type != "string" || columns[0].Format != "name" { if !strings.EqualFold(columns[0].Name, "Name") || columns[0].Type != "string" || columns[0].Format != "name" {
t.Errorf("expect column 0 to be the name column: %#v", columns[0]) t.Errorf("expect column 0 to be the name column: %#v", columns[0])
...@@ -1505,8 +1506,11 @@ func (t *Tester) testListTableConversion(obj runtime.Object, assignFn AssignFunc ...@@ -1505,8 +1506,11 @@ func (t *Tester) testListTableConversion(obj runtime.Object, assignFn AssignFunc
} }
} }
if len(row.Cells) != len(table.ColumnDefinitions) { if len(row.Cells) != len(table.ColumnDefinitions) {
t.Fatalf("unmatched row length on row %d: %#v", i, row.Cells)
} }
} }
data, _ := json.MarshalIndent(table, "", " ")
t.Logf("%s", string(data))
} }
// ============================================================================= // =============================================================================
......
...@@ -73,10 +73,12 @@ func (c defaultTableConvertor) ConvertToTable(ctx context.Context, object runtim ...@@ -73,10 +73,12 @@ func (c defaultTableConvertor) ConvertToTable(ctx context.Context, object runtim
table.SelfLink = m.GetSelfLink() table.SelfLink = m.GetSelfLink()
} }
} }
if opt, ok := tableOptions.(*metav1beta1.TableOptions); !ok || !opt.NoHeaders {
table.ColumnDefinitions = []metav1beta1.TableColumnDefinition{ table.ColumnDefinitions = []metav1beta1.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"]},
} }
}
return &table, nil return &table, nil
} }
......
...@@ -36,6 +36,19 @@ type dynamicClient struct { ...@@ -36,6 +36,19 @@ type dynamicClient struct {
var _ Interface = &dynamicClient{} var _ Interface = &dynamicClient{}
// ConfigFor returns a copy of the provided config with the
// appropriate dynamic client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
config := rest.CopyConfig(inConfig)
config.AcceptContentTypes = "application/json"
config.ContentType = "application/json"
config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return config
}
// NewForConfigOrDie creates a new Interface for the given config and // NewForConfigOrDie creates a new Interface for the given config and
// panics if there is an error in the config. // panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface { func NewForConfigOrDie(c *rest.Config) Interface {
...@@ -46,17 +59,12 @@ func NewForConfigOrDie(c *rest.Config) Interface { ...@@ -46,17 +59,12 @@ func NewForConfigOrDie(c *rest.Config) Interface {
return ret return ret
} }
// NewForConfig creates a new dynamic client or returns an error.
func NewForConfig(inConfig *rest.Config) (Interface, error) { func NewForConfig(inConfig *rest.Config) (Interface, error) {
config := rest.CopyConfig(inConfig) config := ConfigFor(inConfig)
// for serializing the options // for serializing the options
config.GroupVersion = &schema.GroupVersion{} config.GroupVersion = &schema.GroupVersion{}
config.APIPath = "/if-you-see-this-search-for-the-break" config.APIPath = "/if-you-see-this-search-for-the-break"
config.AcceptContentTypes = "application/json"
config.ContentType = "application/json"
config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
restClient, err := rest.RESTClientFor(config) restClient, err := rest.RESTClientFor(config)
if err != nil { if err != nil {
......
...@@ -687,6 +687,10 @@ ...@@ -687,6 +687,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion", "ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -54,6 +54,7 @@ go_library( ...@@ -54,6 +54,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library", "//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
......
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
apiregistrationapi "k8s.io/kube-aggregator/pkg/apis/apiregistration" apiregistrationapi "k8s.io/kube-aggregator/pkg/apis/apiregistration"
...@@ -81,7 +82,7 @@ func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -81,7 +82,7 @@ func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
} }
responsewriters.WriteObjectNegotiated(r.codecs, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroupList) responsewriters.WriteObjectNegotiated(r.codecs, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroupList)
} }
// convertToDiscoveryAPIGroup takes apiservices in a single group and returns a discovery compatible object. // convertToDiscoveryAPIGroup takes apiservices in a single group and returns a discovery compatible object.
...@@ -152,5 +153,5 @@ func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { ...@@ -152,5 +153,5 @@ func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
http.Error(w, "", http.StatusNotFound) http.Error(w, "", http.StatusNotFound)
return return
} }
responsewriters.WriteObjectNegotiated(r.codecs, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroup) responsewriters.WriteObjectNegotiated(r.codecs, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroup)
} }
...@@ -671,6 +671,10 @@ ...@@ -671,6 +671,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion", "ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
......
...@@ -39,18 +39,27 @@ go_test( ...@@ -39,18 +39,27 @@ go_test(
"//staging/src/k8s.io/api/scheduling/v1beta1:go_default_library", "//staging/src/k8s.io/api/scheduling/v1beta1:go_default_library",
"//staging/src/k8s.io/api/settings/v1alpha1:go_default_library", "//staging/src/k8s.io/api/settings/v1alpha1:go_default_library",
"//staging/src/k8s.io/api/storage/v1alpha1:go_default_library", "//staging/src/k8s.io/api/storage/v1alpha1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/test/integration/fixtures: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/v1beta1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/features:go_default_library", "//staging/src/k8s.io/apiserver/pkg/features:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/discovery/cached/disk:go_default_library", "//staging/src/k8s.io/client-go/discovery/cached/disk:go_default_library",
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library", "//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment