Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: v1beta3 API overhaul #1225

Merged
merged 1 commit into from
Sep 30, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
135 changes: 135 additions & 0 deletions docs/api-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
API Conventions
===============

The conventions of the Kubernetes API (and related APIs in the ecosystem) are intended to ease client development and ensure that configuration mechanisms can be implemented that work across a diverse set of use cases consistently.

The general style of the Kubernetes API is RESTful - clients create, update, delete, or retrieve a description of an object via the standard HTTP verbs (POST, PUT, DELETE, and GET) - and those APIs preferentially accept and return JSON. Kubernetes also exposes additional endpoints for non-standard verbs and allows alternative content types.

The following terms are defined:

* **Endpoint** a URL on an HTTP server that modifies, retrieves, or transforms a Resource.
* **Resource** an object manipulated via an HTTP action in an API
* **Kind** a resource has a string that identifies the schema of the JSON used (e.g. a "Car" and a "Dog" would have different attributes and properties)

Types of Resources
------------------

All API resources are either:

1. **Objects** represents a physical or virtual construct in the system
An API object resource is a record of intent - once created, the system will work to ensure that resource exists. All API objects have common metadata intended for client use.
2. **Lists** are collections of **objects** of one or more types
Lists have a limited set of common metadata. All lists use the "items" field to contain the array of objects they return. Each resource kind should have an endpoint that returns the full set of resources, as well as zero or more endpoints that return subsets of the full list.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good place to mention filtering by labels and fields, though labels are also discussed below.

/cc @lavalamp


In addition, all lists that return objects with labels should support label filtering (see [labels.md](labels.md), and most lists should support filtering by fields.

TODO: Describe field filtering below or in a separate doc.

The standard REST verbs (defined below) MUST return singular JSON objects. Some API endpoints may deviate from the strict REST pattern and return resources that are not singular JSON objects, such as streams of JSON objects or unstructured text log data.


### Resources

All singular JSON resources returned by an API MUST have the following fields:

* kind: a string that identifies the schema this object should have
* apiVersion: a string that identifiers the version of the schema the object should have


### Objects

Every object MUST have the following metadata in a nested object field called "metadata":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remind me why we want this in a sub-object rather than embedded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

----- Original Message -----

  • Lists have a limited set of common metadata. All lists use the "items"
    field to contain the array of objects they return. Each resource kind
    should have an endpoint that returns the full set of resources, as well as
    zero or more endpoints that return subsets of the full list.

+The standard REST verbs (defined below) MUST return singular JSON objects.
Some API endpoints may deviate from the strict REST pattern and return
resources that are not singular JSON objects, such as streams of JSON
objects or unstructured text log data.
+
+
+### Resources
+
+All singular JSON resources returned by an API MUST have the following
fields:
+
+* kind: a string that identifies the schema this object should have
+* apiVersion: a string that identifiers the version of the schema the
object should have
+
+
+### Objects
+
+Every object MUST have the following metadata in a nested object field
called "metadata":

Can you remind me why we want this in a sub-object rather than embedded?

To prevent it colliding from other resources or to allow it to grow larger over time without breaking end user names for fields. It's also easier to ignore and blank out when copying resources. I'm halfway in between wanting it embedded (because that's what the vast majority of APIs do and is simpler to parse for many clients) and wanting it to be cleanly isolated. Do we expect namespace collisions as we add metadata in the future? If so, sub object. Otherwise it's probably easier to inline.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devil's advocate: embedding it gives us a disincentive to change this very often - that's probably a GOOD thing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PodTemplate is an example where we may need 2 copies of the metadata, in which case it would be less confusing for both copies to not be inline.

Filtering by field for config would also likely be easier if the metadata weren't inline, since I expect most filtering to be by category: metadata, desired, current.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine either way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere I thought I replied about making it easier to select out this group of fields in queries (GET ...?field=metadata), but I can't find that comment now.

Also, while Go appears to support inlining reasonably well even without Generics, I imagine that dealing with the metadata fields generically across object types without a common subobject may be challenging in some client languages.

However, it's probably not super-critical either way, and I don't want to bikeshed about this forever. If we do inline these fields, then I'm going to insist that we inline nothing else -- all other fields must be in subobjects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the argument about being able to easily (from a client) map the "metadata" field to a specific object type won the argument for me at least. If someone can articulate a new counter argument to "metadata" as a sub object (will regard it as the current argument) please do so in the next few days.

----- Original Message -----

  • Lists have a limited set of common metadata. All lists use the "items"
    field to contain the array of objects they return. Each resource kind
    should have an endpoint that returns the full set of resources, as well as
    zero or more endpoints that return subsets of the full list.

+The standard REST verbs (defined below) MUST return singular JSON objects.
Some API endpoints may deviate from the strict REST pattern and return
resources that are not singular JSON objects, such as streams of JSON
objects or unstructured text log data.
+
+
+### Resources
+
+All singular JSON resources returned by an API MUST have the following
fields:
+
+* kind: a string that identifies the schema this object should have
+* apiVersion: a string that identifiers the version of the schema the
object should have
+
+
+### Objects
+
+Every object MUST have the following metadata in a nested object field
called "metadata":

Somewhere I thought I replied about making it easier to select out this group
of fields in queries (GET ...?field=metadata), but I can't find that comment
now.

Also, while Go appears to support inlining reasonably well even without
Generics, I imagine that dealing with the metadata fields generically across
object types without a common subobject may be challenging in some client
languages.

However, it's probably not super-critical either way, and I don't want to
bikeshed about this forever. If we do inline these fields, then I'm going to
insist that we inline nothing else -- all other fields must be in
subobjects.


Reply to this email directly or view it on GitHub:
https://github.com/GoogleCloudPlatform/kubernetes/pull/1225/files#r18062382


* namespace: a namespace is a DNS compatible subdomain that objects are subdivided into. The default namespace is 'default'. See [namespaces.md](namespaces.md) for more.
* name: a string that uniquely identifies this object within the current namespace (see [identifiers.md](identifiers.md)). This value is used in the path when retrieving an individual object.
* uid: a unique in time and space value (typically an RFC 4122 generated identifier, see [identifiers.md](identifiers.md)) used to distinguish between objects with the same name that have been deleted and recreated

Every object SHOULD have the following metadata in a nested object field called "metadata":

* resourceVersion: a string that identifies the internal version of this object that can be used by clients to determine when objects have changed. This value MUST be treated as opaque by clients and passed unmodified back to the server. Clients should not assume that the resource version has meaning across namespaces, different kinds of resources, or different servers.
* creationTimestamp: a string representing an RFC 3339 date of the date and time an object was created
* labels: a map of string keys and values that can be used to organize and categorize objects (see [labels.md](labels.md))
* annotations: a map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about this object (see [annotations.md](annotations.md))

Labels are intended for organizational purposes by end users (select the pods that match this label query). Annotations enable third party automation and tooling to decorate objects with additional metadata for their own use.

By convention, the Kubernetes API makes a distinction between the specification of a resource (a nested object field called "spec") and the status of the resource at the current time (a nested object field called "status"). The specification is persisted in stable storage with the API object and reflects user input. The status is generated at runtime and summarizes the current effect that the spec has on the system, and is ignored on user input. The PUT and POST verbs will ignore the "status" values.

For example, a pod object has a "spec" field that defines how the pod should be run. The pod also has a "status" field that shows details about what is happening on the host that is running the containers in the pod (if available) and a summarized "status" string that can guide callers as to the overall state of their pod.


### Lists

Every list SHOULD have the following metadata in a nested object field called "metadata":

* resourceVersion: a string that identifies the common version of the objects returned by in a list. This value MUST be treated as opaque by clients and passed unmodified back to the server. A resource version is only valid within a single namespace on a single kind of resource.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment - now I am even more confused.



Special Resources
-----------------

Kubernetes MAY return two resources from any API endpoint in special circumstances. Clients SHOULD handle these types of objects when appropriate.

A "Status" object SHOULD be returned by an API when an operation is not successful (when the server would return a non 2xx HTTP status code). The status object contains fields for humans and machine consumers of the API to determine failures. The information in the status object supplements, but does not override, the HTTP status code's meaning.

An "Operation" object MAY be returned by any non-GET API if the operation may take a significant amount of time. The name of the Operation may be used to retrieve the final result of an operation at a later time.

TODO: Use SelfLink to retrieve operation instead.


Synthetic Resources
-------------------

An API may represent a single object in different ways for different clients, or transform an object after certain transitions in the system occur. In these cases, one request object may have two representations available as different resource kinds. An example is a Pod, which represents the intent of the user to run a container with certain parameters. When Kubernetes schedules the Pod, it creates a Binding object that ties that Pod to a single host in the system. After this occurs, the pod is represented by two distinct resources - under the original Pod resource the user created, as well as in a BoundPods object that the host may query but not update.


Verbs on Resources
------------------

API resources should use the traditional REST pattern:

* GET /<resourceNamePlural> - Retrieve a list of type <resourceName>, e.g. GET /pods returns a list of Pods
* POST /<resourceNamePlural> - Create a new resource from the JSON object provided by the client
* GET /<resourceNamePlural>/<name> - Retrieves a single resource with the given name, e.g. GET /pods/first returns a Pod named 'first'
* DELETE /<resourceNamePlural>/<name> - Delete the single resource with the given name
* PUT /<resourceNamePlural>/<name> - Update or create the resource with the given name with the JSON object provided by the client
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear - we intend to support create by either POST to collection or PUT to named (non-existing) resource?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response

Do we have a reason to support or not support? I like GetOrCreate personally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I guess I am fine with it, I just wanted to be clear that this was the intent. And it's more like CreateOrUpdate() :)


Kubernetes by convention exposes additional verbs as new endpoints with singular names. Examples:

* GET /watch/<resourceNamePlural> - Receive a stream of JSON objects corresponding to changes made to any resource of the given kind over time.
* GET /watch/<resourceNamePlural>/<name> - Receive a stream of JSON objects corresponding to changes made to the named resource of the given kind over time
* GET /redirect/<resourceNamePlural>/<name> - If the named resource can be described by a URL, return an HTTP redirect to that URL instead of a JSON response. For example, a service exposes a port and IP address and a client could invoke the redirect verb to receive an HTTP 307 redirection to that port and IP.

Support of additional verbs is not required for all object types.


Idempotency
-----------

All compatible Kubernetes APIs MUST support "name idempotency" and respond with an HTTP status code 409 when a request is made to POST an object that has the same name as an existing object in the system. See [identifiers.md](identifiers.md) for details.

TODO: name generation

APIs SHOULD set resourceVersion on retrieved resources, and support PUT idempotency by rejecting HTTP requests with a 409 HTTP status code where an HTTP header `If-Match: resourceVersion=` or `?resourceVersion=` query parameter are set and do not match the currently stored version of the resource.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which approach do we support now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither. I can vague this up (or we could spawn an issue to implement it and clarify).

On Sep 26, 2014, at 5:23 PM, bgrant0607 notifications@github.com wrote:

In docs/api-conventions.md:

+* GET /watch/ - Receive a stream of JSON objects corresponding to changes made to any resource of the given kind over time.
+* GET /watch// - Receive a stream of JSON objects corresponding to changes made to the named resource of the given kind over time
+* GET /redirect// - If the named resource can be described by a URL, return an HTTP redirect to that URL instead of a JSON response. For example, a service exposes a port and IP address and a client could invoke the redirect verb to receive an HTTP 307 redirection to that port and IP.
+
+Support of additional verbs is not required for all object types.
+
+
+Idempotency
+-----------
+
+All compatible Kubernetes APIs MUST support "name idempotency" and respond with an HTTP status code 409 when a request is made to POST an object that has the same name as an existing object in the system. See identifiers.md for details.
+
+TODO: name generation
+
+APIs SHOULD set resourceVersion on retrieved resources, and support PUT idempotency by rejecting HTTP requests with a 409 HTTP status code where an HTTP header If-Match: resourceVersion= or ?resourceVersion= query parameter are set and do not match the currently stored version of the resource.
Which approach do we support now?


Reply to this email directly or view it on GitHub.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with the text as-is for now, but please spawn an issue if this is entirely unimplemented.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lavalamp can correct me if I'm wrong, but my understanding is that we use resourceVersion from the object that is PUT as the precondition. It's a little tricky to track down, because I think we rely upon etcd for enforcement: pkg/tools/etcd_tools.go#L240.

I guess that is a little awkward, since we're (presumably) not actually setting the field to that value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+APIs SHOULD set resourceVersion on retrieved resources, and support PUT
idempotency by rejecting HTTP requests with a 409 HTTP status code where
an HTTP header If-Match: resourceVersion= or ?resourceVersion= query
parameter are set and do not match the currently stored version of the
resource.

@lavalamp can correct me if I'm wrong, but my understanding is that we use
resourceVersion from the object that is PUT as the precondition. It's a
little tricky to track down, because I think we rely upon etcd for
enforcement: pkg/tools/etcd_tools.go#L240.

I guess that is a little awkward, since we're (presumably) not actually
setting the field to that value.

I think anywhere we use AtomicUpdate which calls into bodyExtractObj (most updates) we don't explicitly bail out if the value isn't the same.

We should probably open an issue to track codifying this - both the "only continue if my version exactly matches" and the "ensure we're both working from the same original value".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is #656 .


TODO: better syntax?


Serialization Format
--------------------

APIs may return alternative representations of any resource in response to an Accept header or under alternative endpoints, but the default serialization for input and output of API responses MUST be JSON.

All dates should be serialized as RFC3339 strings.


Selecting Fields
----------------

Some APIs may need to identify which field in a JSON object is invalid, or to reference a value to extract from a separate resource. The current recommendation is to use standard JavaScript syntax for accessing that field, assuming the JSON object was transformed into a JavaScript object.

Examples:

* Find the field "current" in the object "state" in the second item in the array "fields": `fields[0].state.current`

TODO: Plugins, extensions, nested kinds, headers