Proposal: Alternate API representations for resources
Supports server side handling of transformation of resources.
This commit is contained in:
parent
483ac937f4
commit
6786b34ef3
|
@ -0,0 +1,433 @@
|
||||||
|
# Alternate representations of API resources
|
||||||
|
|
||||||
|
## Abstract
|
||||||
|
|
||||||
|
Naive clients benefit from allowing the server to returning resource information in a form
|
||||||
|
that is easy to represent or is more efficient when dealing with resources in bulk. It
|
||||||
|
should be possible to ask an API server to return a representation of one or more resources
|
||||||
|
of the same type in a way useful for:
|
||||||
|
|
||||||
|
* Retrieving a subset of object metadata in a list or watch of a resource, such as the
|
||||||
|
metadata needed by the generic Garbage Collector or the Namespace Lifecycle Controller
|
||||||
|
* Dealing with generic operations like `Scale` correctly from a client across multiple API
|
||||||
|
groups, versions, or servers
|
||||||
|
* Return a simple tabular representation of an object or list of objects for naive
|
||||||
|
web or command-line clients to display (for `kubectl get`)
|
||||||
|
* Return a simple description of an object that can be displayed in a wide range of clients
|
||||||
|
(for `kubectl describe`)
|
||||||
|
* Return the object with fields set by the server cleared (as `kubectl export`) which
|
||||||
|
is dependent on the schema, not on user input.
|
||||||
|
|
||||||
|
The server should allow a common mechanism for a client to request a resource be returned
|
||||||
|
in one of a number of possible forms. In general, many of these forms are simply alternate
|
||||||
|
versions of the existing content and are not intended to support arbitrary parameterization.
|
||||||
|
|
||||||
|
Also, the server today contains a number of objects which are common across multiple groups,
|
||||||
|
but which clients must be able to deal with in a generic fashion. These objects - Status,
|
||||||
|
ListMeta, ObjectMeta, List, ListOptions, ExportOptions, and Scale - are embedded into each
|
||||||
|
group version but are actually part of a a shared API group. It must be possible for a naive
|
||||||
|
client to translate the Scale response returned by two different API group versions.
|
||||||
|
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Currently it is difficult for a naive client (dealing only with the list of resources
|
||||||
|
presented by API discovery) to properly handle new and extended API groups, especially
|
||||||
|
as versions of those groups begin to evolve. It must be possible for a naive client to
|
||||||
|
perform a set of common operations across a wide range of groups and versions and leverage
|
||||||
|
a predictable schema.
|
||||||
|
|
||||||
|
We also foresee increasing difficulty in building clients that must deal with extensions -
|
||||||
|
there are at least 6 known web-ui or CLI implementations that need to display some
|
||||||
|
information about third party resources or additional API groups registered with a server
|
||||||
|
without requiring each of them to change. Providing a server side implementation will
|
||||||
|
allow clients to retrieve meaningful information for the `get` and `describe` style
|
||||||
|
operations even for new API groups.
|
||||||
|
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
The HTTP spec and the common REST paradigm provide mechanisms for clients to [negotiate
|
||||||
|
alternative representations of objects (RFC2616 14.1)](http://www.w3.org/Protocols/rfc2616/rfc2616.txt)
|
||||||
|
and for the server to correctly indicate a requested mechanism was chosen via the `Accept`
|
||||||
|
and `Content-Type` headers. This is a standard request response protocol intended to allow
|
||||||
|
clients to request the server choose a representation to return to the client based on the
|
||||||
|
server's capabilities. In RESTful terminology, a representation is simply a known schema that
|
||||||
|
the client is capable of handling - common schemas are HTML, JSON, XML, or protobuf, with the
|
||||||
|
possibility of the client and server further refining the requested output via either query
|
||||||
|
parameters or media type parameters.
|
||||||
|
|
||||||
|
In order to ensure that generic clients can properly deal with many different group versions,
|
||||||
|
we introduce the `meta.k8s.io` group with version `v1` that grandfathers all existing resources
|
||||||
|
currently described as "unversioned". A generic client may request that responses be applied
|
||||||
|
in this version. The contents of a particular API group version would continue to be bound into
|
||||||
|
other group versions (`status.v1.meta.k8s.io` would be bound as `Status` into all existing
|
||||||
|
API groups). We would remove the `unversioned` package and properly home these resources in
|
||||||
|
a real API group.
|
||||||
|
|
||||||
|
|
||||||
|
### Considerations around choosing an implementation
|
||||||
|
|
||||||
|
* We wish to avoid creating new resource *locations* (URLs) for existing resources
|
||||||
|
* New resource locations complicate access control, caching, and proxying
|
||||||
|
* We are still retrieving the same resource, just in an alternate representation,
|
||||||
|
which matches our current use of the protobuf, JSON, and YAML serializations
|
||||||
|
* We do not wish to alter the mechanism for authorization - a user with access
|
||||||
|
to a particular resource in a given namespace should be limited regardless of
|
||||||
|
the representation in use.
|
||||||
|
* Allowing "all namespaces" to be listed would require us to create "fake" resources
|
||||||
|
which would complicate authorization
|
||||||
|
* We wish to support retrieving object representations in multiple schemas - JSON for
|
||||||
|
simple clients and Protobuf for clients concerned with efficiency.
|
||||||
|
* Most clients will wish to retrieve a newer format, but for older servers will desire
|
||||||
|
to fall back to the implict resource represented by the endpoint.
|
||||||
|
* Over time, clients may need to request results in multiple API group versions
|
||||||
|
because of breaking changes (when we introduce v2, clients that know v2 will want
|
||||||
|
to ask for v2, then v1)
|
||||||
|
* The Scale resource is an example - a generic client may know v1 Scale, but when
|
||||||
|
v2 Scale is introduced the generic client will still only request v1 Scale from
|
||||||
|
any given resource, and the server that no longer recognizes v1 Scale must
|
||||||
|
indicate that to the client.
|
||||||
|
* We wish to preserve the greatest possible query parameter space for sub resources
|
||||||
|
and special cases, which encourages us to avoid polluting the API with query
|
||||||
|
parameters that can be otherwise represented as alternate forms.
|
||||||
|
* We do not wish to allow deep orthogonal parameterization - a list of pods is a list
|
||||||
|
of pods regardless of the form, and the parameters passed to the JSON representation
|
||||||
|
should not vary significantly to the tabular representation.
|
||||||
|
* Because we expect not all extensions will implement protobuf, an efficient client
|
||||||
|
must continue to be able to "fall-back" to JSON, such as for third party
|
||||||
|
resources.
|
||||||
|
* We do not wish to create fake content-types like `application/json+kubernetes+v1+meta.k8s.io`
|
||||||
|
because the list of combinations is unbounded and our ability to encode specific values
|
||||||
|
(like slashes) into the value is limited.
|
||||||
|
|
||||||
|
### Client negotiation of response representation
|
||||||
|
|
||||||
|
When a client wishes to request an alternate representation of an object, it should form
|
||||||
|
a valid `Accept` header containing one or more accepted representations, where each
|
||||||
|
representation is represented by a media-type and [media-type parameters](https://tools.ietf.org/html/rfc6838#section-4.3).
|
||||||
|
The server should omit representations that are unrecognized or in error - if no representations
|
||||||
|
are left after omission the server should return a `406 Not Acceptable` HTTP response.
|
||||||
|
|
||||||
|
The supported parameters are:
|
||||||
|
|
||||||
|
| Name | Value | Default | Description |
|
||||||
|
| ---- | ----- | ------- | ----------- |
|
||||||
|
| g | The group name of the desired response | Current group | The group the response is expected in. |
|
||||||
|
| v | The version of the desired response | Current version | The version the response is expected in. Note that this is separate from Group because `/` is not a valid character in Accept headers. |
|
||||||
|
| as | Kind name | None | If specified, transform the resource into the following kind (including the group and version parameters). |
|
||||||
|
| sv | The server group (`meta.k8s.io`) version that should be applied to generic resources returned by this endpoint | Matching server version for the current group and version | If specified, the server should transform generic responses into this version of the server API group. |
|
||||||
|
| export | `1` | None | If specified, transform the resource prior to returning to omit defaulted fields. Additional arguments allowed in the query parameter. For legacy reasons, `?export=1` will continue to be supported on the request |
|
||||||
|
| pretty | `0`/`1` | `1` | If specified, apply formatting to the returned response that makes the serialization readable (for JSON, use indentation) |
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Request a PodList in an alternate form
|
||||||
|
GET /v1/pods
|
||||||
|
Accept: application/json;as=Table;g=meta.k8s.io;v=v1
|
||||||
|
|
||||||
|
# Request a PodList in an alternate form, with pretty JSON formatting
|
||||||
|
GET /v1/pods
|
||||||
|
Accept: application/json;as=Table;g=meta.k8s.io;v=v1;pretty=1
|
||||||
|
|
||||||
|
# Request that status messages be of the form meta.k8s.io/v2 on the response
|
||||||
|
GET /v1/pods
|
||||||
|
Accept: application/json;sv=v2
|
||||||
|
{
|
||||||
|
"kind": "Status",
|
||||||
|
"apiVersion": "meta.k8s.io/v2",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For both export and the more complicated server side `kubectl get` cases, it's likely that
|
||||||
|
more parameters are required and should be specified as query parameters. However, the core
|
||||||
|
behavior is best represented as a variation on content-type. Supporting both is not limiting
|
||||||
|
in the short term as long as we can validate correctly.
|
||||||
|
|
||||||
|
As a simplification for common use, we should create **media-type aliases** which may show up in lists of mime-types supported
|
||||||
|
and simplify use for clients. For example, the following aliases would be reasonable:
|
||||||
|
|
||||||
|
* `application/json+vnd.kubernetes.export` would return the requested object in export form
|
||||||
|
* `application/json+vnd.kubernetes.as+meta.k8s.io+v1+TabularOutput` would return the requested object in a tabular form
|
||||||
|
* `text/csv` would return the requested object in a tabular form in the comma-separated-value (CSV) format
|
||||||
|
|
||||||
|
### Example: Partial metadata retrieval
|
||||||
|
|
||||||
|
The client may request to the server to return the list of namespaces as a
|
||||||
|
`PartialObjectMetadata` kind, which is an object containing only `ObjectMeta` and
|
||||||
|
can be serialized as protobuf or JSON. This is expected to be significantly more
|
||||||
|
performant when controllers like the Garbage collector retrieve multiple objects.
|
||||||
|
|
||||||
|
GET /api/v1/namespaces
|
||||||
|
Accept: application/json;g=meta.k8s.io,v=v1,as=PartialObjectMetadata, application/json
|
||||||
|
|
||||||
|
The server would respond with
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
Content-Type: application/json;g=meta.k8s.io,v=v1,as=PartialObjectMetadata
|
||||||
|
{
|
||||||
|
"apiVersion": "meta.k8s.io/v1",
|
||||||
|
"kind": "PartialObjectMetadataList",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"apiVersion": "meta.k8s.io/v1",
|
||||||
|
"kind": "PartialObjectMetadata",
|
||||||
|
"metadata": {
|
||||||
|
"name": "foo",
|
||||||
|
"resourceVersion": "10",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
In this example PartialObjectMetadata is a real registered type, and each API group
|
||||||
|
provides an efficient transformation from their schema to the partial schema directly.
|
||||||
|
The client upon retrieving this type can act as a generic resource.
|
||||||
|
|
||||||
|
Note that the `as` parameter indicates to the server the Kind of the resource, but
|
||||||
|
the Kubernetes API convention of returning a List with a known schema continues. An older
|
||||||
|
server could ignore the presence of the `as` parameter on the media type and merely return
|
||||||
|
a `NamespaceList` and the client would either use the content-type or the object Kind
|
||||||
|
to distinguish. Because all responses are expected to be self-describing, an existing
|
||||||
|
Kubernetes client would be expected to differentiate on Kind.
|
||||||
|
|
||||||
|
An old server, not recognizing these parameters, would respond with:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "NamespaceList",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Namespace",
|
||||||
|
"metadata": {
|
||||||
|
"name": "foo",
|
||||||
|
"resourceVersion": "10",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### Example: Retrieving a known version of the Scale resource
|
||||||
|
|
||||||
|
Each API group that supports resources that can be scaled must expose a subresource on
|
||||||
|
their object that accepts GET or PUT with a `Scale` kind resource. This subresource acts
|
||||||
|
as a generic interface that a client that knows nothing about the underlying object can
|
||||||
|
use to modify the scale value of that resource. However, clients *must* be able to understand
|
||||||
|
the response the server provides, and over time the response may change and should therefore
|
||||||
|
be versioned. Our current API provides no way for a client to discover whether a `Scale`
|
||||||
|
response returned by `batch/v2alpha1` is the same as the `Scale` resource returned by
|
||||||
|
`autoscaling/v1`.
|
||||||
|
|
||||||
|
Under this proposal, to scale a generic resource a client would perform the following
|
||||||
|
operations:
|
||||||
|
|
||||||
|
GET /api/v1/namespace/example/replicasets/test/scale
|
||||||
|
Accept: application/json;g=meta.k8s.io,v=v1,as=Scale, application/json
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
Content-Type: application/json;g=meta.k8s.io,v=v1,as=Scale
|
||||||
|
{
|
||||||
|
"apiVersion": "meta.k8s.io/v1",
|
||||||
|
"kind": "Scale",
|
||||||
|
"spec": {
|
||||||
|
"replicas": 1
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
The client, seeing that a generic response was returned (`meta.k8s.io/v1`), knows that
|
||||||
|
the server supports accepting that resource as well, and performs a PUT:
|
||||||
|
|
||||||
|
PUT /apis/extensions/v1beta1/namespace/example/replicasets/test/scale
|
||||||
|
Accept: application/json;g=meta.k8s.io,v=v1,as=Scale, application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"apiVersion": "meta.k8s.io/v1",
|
||||||
|
"kind": "Scale",
|
||||||
|
"spec": {
|
||||||
|
"replicas": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
Content-Type: application/json;g=meta.k8s.io,v=v1,as=Scale
|
||||||
|
{
|
||||||
|
"apiVersion": "meta.k8s.io/v1",
|
||||||
|
"kind": "Scale",
|
||||||
|
"spec": {
|
||||||
|
"replicas": 2
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that the client still asks for the common Scale as the response so that it
|
||||||
|
can access the value it wants.
|
||||||
|
|
||||||
|
|
||||||
|
### Example: Retrieving an alternative representation of the resource for use in `kubectl get`
|
||||||
|
|
||||||
|
As new extension groups are added to the server, all clients must implement simple "view" logic
|
||||||
|
for each resource. However, these views are specific to the resource in question, which only
|
||||||
|
the server is aware of. To make clients more tolerant of extension and third party resources,
|
||||||
|
it should be possible for clients to ask the server to present a resource or list of resources
|
||||||
|
in a tabular / descriptive format rather than raw JSON.
|
||||||
|
|
||||||
|
While the design of serverside tabular support is outside the scope of this proposal, a few
|
||||||
|
knows apply. The server must return a structured resource usable by both command line and
|
||||||
|
rich clients (web or IDE), which implies a schema, which implies JSON, and which means the
|
||||||
|
server should return a known Kind. For this example we will call that kind `TabularOutput`
|
||||||
|
to demonstrate the concept.
|
||||||
|
|
||||||
|
A server side resource would implement a transformation from their resource to `TabularOutput`
|
||||||
|
and the API machinery would translate a single item or a list of items (or a watch) into
|
||||||
|
the tabular resource.
|
||||||
|
|
||||||
|
A generic client wishing to display a tabular list for resources of type `v1.ReplicaSets` would
|
||||||
|
make the following call:
|
||||||
|
|
||||||
|
GET /api/v1/namespaces/example/replicasets
|
||||||
|
Accept: application/json;g=meta.k8s.io,v=v1,as=TabularOutput, application/json
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
Content-Type: application/json;g=meta.k8s.io,v=v1,as=TabularOutput
|
||||||
|
{
|
||||||
|
"apiVersion": "meta.k8s.io/v1",
|
||||||
|
"kind": "TabularOutput",
|
||||||
|
"columns": [
|
||||||
|
{"name": "Name", "description": "The name of the resource"},
|
||||||
|
{"name": "Resource Version", "description": "The version of the resource"},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"items": [
|
||||||
|
{"columns": ["name", "10", ...]},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
The client can then present that information as necessary. If the server returns the
|
||||||
|
resource list `v1.ReplicaSetList` the client knows that the server does not support tabular
|
||||||
|
output and so must fall back to a generic output form (perhaps using the existing
|
||||||
|
compiled in listers).
|
||||||
|
|
||||||
|
Note that `kubectl get` supports a number of parameters for modifying the response,
|
||||||
|
including whether to filter resources, whether to show a "wide" list, or whether to
|
||||||
|
turn certain labels into columns. Those options are best represented as query parameters
|
||||||
|
and transformed into a known type.
|
||||||
|
|
||||||
|
|
||||||
|
### Example: Versioning a ListOptions call to a generic API server
|
||||||
|
|
||||||
|
When retrieving lists of resources, the server transforms input query parameters like
|
||||||
|
`labels` and `fields` into a `ListOptions` type. It should be possible for a generic
|
||||||
|
client dealing with the server to be able to specify the version of ListOptions it
|
||||||
|
is sending to detect version skew.
|
||||||
|
|
||||||
|
Since this is an input and list is implemented with GET, it is not possible to send
|
||||||
|
a body and no Content-Type is possible. For this approach, we recommend that the kind
|
||||||
|
and API version be specifiable via the GET call for further clarification:
|
||||||
|
|
||||||
|
New query parameters:
|
||||||
|
|
||||||
|
| Name | Value | Default | Description |
|
||||||
|
| ---- | ----- | ------- | ----------- |
|
||||||
|
| kind | The kind of parameters being sent | `ListOptions` (GET), `DeleteOptions` (DELETE) | The kind of the serialized struct, defaults to ListOptions on GET and DeleteOptions on DELETE. |
|
||||||
|
| queryVersion / apiVersion | The API version of the parameter struct | `meta.k8s.io/v1` | May be altered to match the expected version. Because we have not yet versioned ListOptions, this is safe to alter. |
|
||||||
|
|
||||||
|
To send ListOptions in the v2 future format, where the serialization of `resourceVersion`
|
||||||
|
is changed to `rv`, clients would provide:
|
||||||
|
|
||||||
|
GET /api/v1/namespaces/example/replicasets?apiVersion=meta.k8s.io/v2&rv=10
|
||||||
|
|
||||||
|
Before we introduce a second API group version, we would have to ensure old servers
|
||||||
|
properly reject apiVersions they do not understand.
|
||||||
|
|
||||||
|
|
||||||
|
### Impact on web infrastructure
|
||||||
|
|
||||||
|
In the past, web infrastructure and old browsers have coped poorly with the `Accept`
|
||||||
|
header. However, most modern caching infrastructure properly supports `Vary: Accept`
|
||||||
|
and caching of responses has not been a significant requirement for Kubernetes APIs
|
||||||
|
to this point.
|
||||||
|
|
||||||
|
|
||||||
|
### Considerations for discoverability
|
||||||
|
|
||||||
|
To ensure clients can discover these endpoints, the Swagger and OpenAPI documents
|
||||||
|
should also include a set of example mime-types for each endpoint that are supported.
|
||||||
|
Specifically, the `produces` field on an individual operation can be used to list a
|
||||||
|
set of well known types. The description of the operation can include a stanza about
|
||||||
|
retrieving alternate representations.
|
||||||
|
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
* Implement only with query parameters
|
||||||
|
|
||||||
|
To properly implement alternative resource versions must support multiple version
|
||||||
|
support (ask for v2, then v1). The Accept mechanism already handles this sort of
|
||||||
|
multi-version negotiation, while any approach based on query parameters would
|
||||||
|
have to implement this option as well. In addition, some serializations may not
|
||||||
|
be valid in all content types, so the client asking for TabularOutput in protobuf
|
||||||
|
may also ask for TabularOutput in JSON - if TabularOutput is not valid in protobuf
|
||||||
|
the server call fall back to JSON.
|
||||||
|
|
||||||
|
* Use new resource paths - `/apis/autoscaling/v1/namespaces/example/horizontalpodautoscalermetadata`
|
||||||
|
|
||||||
|
This leads to a proliferation of paths which will confuse automated tools and end
|
||||||
|
users. Authorization, logging, audit may all need a way to map the two resources
|
||||||
|
as equivalent, while clients would need a discovery mechanism that identifies a
|
||||||
|
"same underlying object" relationship that is different from subresources.
|
||||||
|
|
||||||
|
* Use a special HTTP header to denote the alternative representation
|
||||||
|
|
||||||
|
Given the need to support multiple versions, this would be reimplementing Accept
|
||||||
|
in a slightly different way, so we prefer to reuse Accept.
|
||||||
|
|
||||||
|
* For partial object retrieval, support complex field selectors
|
||||||
|
|
||||||
|
From an efficiency perspective, calculating subpaths and filtering out sub fields
|
||||||
|
from the underlying object is complex. In practice, almost all filtering falls into
|
||||||
|
a few limited subsets, and thus retrieving an object into a few known schemas can be made
|
||||||
|
much more efficient. In addition, arbitrary transformation of the object provides
|
||||||
|
opportunities for supporting forward "partial" migration - for instance, returning a
|
||||||
|
ReplicationController as a ReplicaSet to simplify a transition across resource types.
|
||||||
|
While this is not under explicit consideration, allowing a caller to move objects across
|
||||||
|
schemas will eventually be a required behavior when dramatic changes occur in an API
|
||||||
|
schema.
|
||||||
|
|
||||||
|
## Backwards Compatibility
|
||||||
|
|
||||||
|
### Old clients
|
||||||
|
|
||||||
|
Old clients would not be affected by the new Accept path.
|
||||||
|
|
||||||
|
If servers begin returning Status in version `meta.k8s.io/v1`, old clients would likely error
|
||||||
|
as that group has never been used. We would continue to return the group version of the calling
|
||||||
|
API group on server responses unless the `sv` mime-type parameter is set.
|
||||||
|
|
||||||
|
|
||||||
|
### Old servers
|
||||||
|
|
||||||
|
Because old Kubernetes servers are not selective about the content type parameters they
|
||||||
|
accept, we may wish to patch server versions to explicitly bypass content
|
||||||
|
types they do not recognize the parameters to. As a special consideration, this would allow
|
||||||
|
new clients to more strictly handle Accept (so that the server returns errors if the content
|
||||||
|
type is not recognized).
|
||||||
|
|
||||||
|
As part of introducing the new API group `meta.k8s.io`, some opaque calls where we assume the
|
||||||
|
empty API group-version for the resource (GET parameters) could be defaulted to this group.
|
||||||
|
|
||||||
|
|
||||||
|
## Future items
|
||||||
|
|
||||||
|
* ???
|
Loading…
Reference in New Issue