Skip to content

Commit

Permalink
Support content-type negotiation in the API server
Browse files Browse the repository at this point in the history
A NegotiatedSerializer is passed into the API installer (and
ParameterCodec, which abstracts conversion of query params) that can be
used to negotiate client/server request/response serialization. All
error paths are now negotiation aware, and are at least minimally
version aware.

Watch is specially coded to only allow application/json - a follow up
change will convert it to use negotiation.

Ensure the swagger scheme will include supported serializations - this
now includes application/yaml as a negotiated option.
  • Loading branch information
smarterclayton committed Jan 22, 2016
1 parent 6b2f70d commit 125ef6f
Show file tree
Hide file tree
Showing 15 changed files with 971 additions and 366 deletions.
1 change: 0 additions & 1 deletion cmd/kube-apiserver/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
apiutil "k8s.io/kubernetes/pkg/api/util"
"k8s.io/kubernetes/pkg/apimachinery/registered"
Expand Down
54 changes: 27 additions & 27 deletions pkg/apiserver/api_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ var errEmptyName = errors.NewBadRequest("name must be provided")
func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversioned.APIResource, errors []error) {
errors = make([]error, 0)

proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.Storage, a.group.Codec, a.group.Context, a.info})
proxyHandler := (&ProxyHandler{
prefix: a.prefix + "/proxy/",
storage: a.group.Storage,
serializer: a.group.Serializer,
context: a.group.Context,
requestInfoResolver: a.info,
})

// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
paths := make([]string, len(a.group.Storage))
Expand Down Expand Up @@ -93,9 +99,11 @@ func (a *APIInstaller) NewWebService() *restful.WebService {
ws.Path(a.prefix)
// a.prefix contains "prefix/group/version"
ws.Doc("API at " + a.prefix)
// TODO: change to restful.MIME_JSON when we set content type in client
// Backwards compatibilty, we accepted objects with empty content-type at V1.
// If we stop using go-restful, we can default empty content-type to application/json on an
// endpoint by endpoint basis
ws.Consumes("*/*")
ws.Produces(restful.MIME_JSON)
ws.Produces(a.group.Serializer.SupportedMediaTypes()...)
ws.ApiVersion(a.group.GroupVersion.String())

return ws
Expand Down Expand Up @@ -262,19 +270,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
getOptions runtime.Object
versionedGetOptions runtime.Object
getOptionsInternalKind unversioned.GroupVersionKind
getOptionsExternalKind unversioned.GroupVersionKind
getSubpath bool
getSubpathKey string
)
if isGetterWithOptions {
getOptions, getSubpath, getSubpathKey = getterWithOptions.NewGetOptions()
getOptions, getSubpath, _ = getterWithOptions.NewGetOptions()
getOptionsInternalKind, err = a.group.Typer.ObjectKind(getOptions)
if err != nil {
return nil, err
}
// TODO this should be a list of all the different external versions we can coerce into the internalKind
getOptionsExternalKind = optionsExternalVersion.WithKind(getOptionsInternalKind.Kind)

versionedGetOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(getOptionsInternalKind.Kind))
if err != nil {
return nil, err
Expand All @@ -286,19 +289,15 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
connectOptions runtime.Object
versionedConnectOptions runtime.Object
connectOptionsInternalKind unversioned.GroupVersionKind
connectOptionsExternalKind unversioned.GroupVersionKind
connectSubpath bool
connectSubpathKey string
)
if isConnecter {
connectOptions, connectSubpath, connectSubpathKey = connecter.NewConnectOptions()
connectOptions, connectSubpath, _ = connecter.NewConnectOptions()
if connectOptions != nil {
connectOptionsInternalKind, err = a.group.Typer.ObjectKind(connectOptions)
if err != nil {
return nil, err
}
// TODO this should be a list of all the different external versions we can coerce into the internalKind
connectOptionsExternalKind = optionsExternalVersion.WithKind(connectOptionsInternalKind.Kind)

versionedConnectOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(connectOptionsInternalKind.Kind))
}
Expand Down Expand Up @@ -434,10 +433,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
// test/integration/auth_test.go is currently the most comprehensive status code test

reqScope := RequestScope{
ContextFunc: ctxFn,
Creater: a.group.Creater,
Convertor: a.group.Convertor,
Codec: mapping.Codec,
ContextFunc: ctxFn,
Serializer: a.group.Serializer,
ParameterCodec: a.group.ParameterCodec,
Creater: a.group.Creater,
Convertor: a.group.Convertor,

Resource: a.group.GroupVersion.WithResource(resource),
Subresource: subresource,
Expand All @@ -454,7 +454,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
case "GET": // Get a resource.
var handler restful.RouteFunction
if isGetterWithOptions {
handler = GetResourceWithOptions(getterWithOptions, exporter, reqScope, getOptionsInternalKind, getOptionsExternalKind, getSubpath, getSubpathKey)
handler = GetResourceWithOptions(getterWithOptions, reqScope)
} else {
handler = GetResource(getter, exporter, reqScope)
}
Expand All @@ -467,7 +467,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("read"+namespaced+kind+strings.Title(subresource)).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Returns(http.StatusOK, "OK", versionedObject).
Writes(versionedObject)
if isGetterWithOptions {
Expand All @@ -492,7 +492,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("list"+namespaced+kind+strings.Title(subresource)).
Produces("application/json").
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Returns(http.StatusOK, "OK", versionedList).
Writes(versionedList)
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
Expand Down Expand Up @@ -524,7 +524,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("replace"+namespaced+kind+strings.Title(subresource)).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Returns(http.StatusOK, "OK", versionedObject).
Reads(versionedObject).
Writes(versionedObject)
Expand All @@ -541,7 +541,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Consumes(string(api.JSONPatchType), string(api.MergePatchType), string(api.StrategicMergePatchType)).
Operation("patch"+namespaced+kind+strings.Title(subresource)).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Returns(http.StatusOK, "OK", versionedObject).
Reads(unversioned.Patch{}).
Writes(versionedObject)
Expand All @@ -563,7 +563,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("create"+namespaced+kind+strings.Title(subresource)).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Returns(http.StatusOK, "OK", versionedObject).
Reads(versionedObject).
Writes(versionedObject)
Expand All @@ -579,7 +579,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("delete"+namespaced+kind+strings.Title(subresource)).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Writes(versionedStatus).
Returns(http.StatusOK, "OK", versionedStatus)
if isGracefulDeleter {
Expand All @@ -597,7 +597,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("deletecollection"+namespaced+kind+strings.Title(subresource)).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Writes(versionedStatus).
Returns(http.StatusOK, "OK", versionedStatus)
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
Expand Down Expand Up @@ -658,7 +658,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
doc = "connect " + method + " requests to " + subresource + " of " + kind
}
route := ws.Method(method).Path(action.Path).
To(ConnectResource(connecter, reqScope, admit, connectOptionsInternalKind, connectOptionsExternalKind, path, connectSubpath, connectSubpathKey)).
To(ConnectResource(connecter, reqScope, admit, path)).
Filter(m).
Doc(doc).
Operation("connect" + strings.Title(strings.ToLower(method)) + namespaced + kind + strings.Title(subresource)).
Expand Down
Loading

0 comments on commit 125ef6f

Please sign in to comment.