Chef Server API Versioning

The purpose of this RFC is to specify the means by which Chef Server will support a range of API versions in a given release.

Each server API version indicates the behavior of the server API as a whole, at the given version level. For comparison, consider the way a git commit SHA is representative of the state of the entire repository as of that commit.

Supporting a range of API versions (minimum and maximum) in any given release will allow API behavior to change, while providing reasonable deprecation expectations for the older behaviors. This also allows the Server to remain focused on supporting a finite range of behaviors, rather than needing to support old behaviors indefinitely.

Any given release of the Chef Server will include documentation of the current minimum and maximum supported API versions, as well as behavior changes between any newly introduced version and the one prior to it. It will also include, if appropriate, which version has been deprecated, and which version has been retired.

An important note: API Versioning is determined independently of product versioning. Product support lifecycle will determine which range of API versions are supported in a given product release.

Arbitrary examples with no significance:

This means that if you are using Chef Server 12.1, any supported client will behave as it did with Chef Server 12.0, though API v4 will be available to those clients who can use it.
However when you upgrade Chef Server 13.0 you will need to upgrade the client to one that supports the behaviors introduced or changes in API Version 1 and 2 at minimum.

Motivation

As a consumer of the Chef Server API,
I want to be able to consume significant changes at my own pace
so that they don't break my usage of the API.

As a user of chef-client at a supported version level,
I want to ensure that changes to the server API don't affect me until I'm ready to upgrade
so that all my nodes continue to converge even as Chef Server API continues to mature.

As a developer of the Chef Server API,
I want to be able to add potentially breaking features without breaking customers on supported versions
so that the state of the Server API can continue to advance.

Specification

The Chef Server API Version (Version from this point forward) is indicated as a whole number, starting at zero. Zero indicates the behavior of the API at the time of this RFC's acceptance.

If a client does not indicate a version preference, it will be assumed to request API version zero. Once zero is not supported, clients must request a specific API version, or the server will not process the request and return a 406.

If a particular endpoint does not implement versioning due to remaining unchanged, it MUST behave in a forward-compatible way through the range of currently supported version. In documentation these will be specified as having compatibility with versions X+ where X is the first version in which the API was introduced (or zero if existing).

If a particular endpoint does implement versioning and remains unchanged, it MUST behave in a forward-compatible way through the range of currently supported versions. In documentation these will be specified as N+, where N is the version at which it was last modified.

For this section, "API" refers to any combination of a method and endpoint, eg GET /users, POST /organizations/$org/clients, et al.

Versioning is Global

No restrictions or requirements are placed on the number of APIs that can be changed in a given API Version. There is no guarantee of a 1:1 relationship between API changes and server versions. For example, Server API Version 1 may change the GET /nodes endpoint behavior, while Version 2 may change the behaviors of GET /roles, PUT /users/:name/keys and DELETE /users/:name

Changes grouped into a single version are not guaranteed to be related, except that the changes were created near the same points in time.

Example Usage

As an example scenario, we'll assume a case where the field username is being replaced with name in the body response in the users endpoint. For this case, we'll state that:

For demonstration, the example request is GET /users/bob with...

To continue the example, let's say we've moved forward a year and are now shipping Chef Server 13. At that time we announce that our new supported API Range is 12-20, and the API Versions 12-14 are considered deprecated.

See Response below for full understanding of the X-Ops-Server-API-Version response header content. For now, just note that request_version is "-1" when an invalid API version was sent (non-integer) and and responseversion is "-1" when the `requestversion` is not supported, indicating that the request was not responded to.

The GET request from the examples above would generate the following responses:

Moving forward another year and we have released Chef Server 14. We announce that API Versions 12-14 are retired and that our new supported API Version Range is 15-22.

Request

A client MAY request version compatibility via custom header as follows:

X-Ops-Server-API-Version: $version

$version MUST be one of the desired version number, indicated by a whole integer.

Response

If a client does include this header and $version is...

Response Header

In the response header, X-Ops-Server-API-Version will return json of the format:

{
  "min_version":"<non-negative integer>",
  "max_version":"<non-negative integer>",
  "request_version":"<integer>",
  "response_version":"<integer>"
}

Where: + min_version is the minimum API version the server supports. + max_version is the maximum API version the server supports. + request_version is an integer representing the desired API version from the client passed via X-Ops-Server-API-Version defaulting to 0 if X-Ops-Server-API-Version was not sent by the client. If X-Ops-Server-API-Version sent by the client contained an invalid value (not an integer), then -1 is returned in request_version. + response_version is an integer representing the API version used by the server to process the request. It either matches what the client requested in X-Ops-Server-API-Version or is -1 if a 406 occurred (which happens when X-Ops-Server-API-Version sent by the client was invalid or not in the supported range of the server).

X-Ops-Server-API-Version is needed to implement client-side support for the multiple versioned API and to distinguish between servers that have implemented this RFC and servers that do not support multiple versioning.

The Vary header is included in the event it becomes desirable to support caching of specific responses.

406 Response Body

When the server provides a 406 response due to unsupported API version, the response body will be of type application/json and MUST look as follows:

{ "error"           : "invalid-x-ops-server-api-version",
  "message"         : "Specified version $version not supported",
  "min_api_version" : $x,
  "max_api_version" : $y }

$version indicates the requested version. $x and $y are the current minimum and maximum supported versions, respectively.

Server Implementation

Minimum and maximum version are set in code, and MUST NOT be configurable.

Minimum and maximum values in effect MUST be logged at server startup. Further, current API support levels MUST be exposed through a new endpoint, GET /server_api_version as specified below.

The server MUST resolve and validate the version at the start of each request, and retain it in request state for resources to make use of.

The implementation MAY require addition of version support callbacks in erchef webmachine resources.

Given no X-Ops-Server-API-Version header or a null value of version, it MUST resolve to the minimum supported version.

Given a value of version, it MUST be resolved as follows:

Effects on Unmodified APIs:

APIs that are not modified are not required to support Version. They are assumed to be compatible and unchanged through the current range of min/max supported versions.

Effects on Modified APIs:

Any API modified as follows MUST use api_version to determine which behavior to present to the client:

Any API modified as follows MAY use api_version to determine which behavior to present to the client:

New API Implementations

Any new API MAY implement checking of api_version to determine its availability.

Deprecated API Behaviors

Any API behavior that is to be deprecated MUST have the version for both its deprecation and its removal documented in release notes and in API documentation.

After deprecated behavior has been removed, last supported API version MUST be updated in accordance with the published documentation.

Documentation

When an API is modified in a way that requires support of Version, release notes and API documentation MUST reflect this. API documentation MUST further specify minimum/maximum versions for each endpoint that implements support of versioning.

New Endpoint: /server_api_version

This is a new server endpoint that MUST be implemented, and which supports only the GET method. Any other method MUST respond with a 405.

The GET response body MUST look as follows:

{ "min_api_version" : $x,
  "max_api_version" : $y }

$x and $y refer to the current minimum and maximum supported versions, respectively.

New Endpoint: /server_api_version/extended

This is a new server endpoint that MAY be implemented, and which supports only the GET method. Any other method MUST respond with a 405.

If this endpoint is implemented, the GET response body contains supported API version data for each endpoint that has implemented versioning, and MUST look as follows:

{ "endpoints" : [ { "name" : "$relative_url",
                    "versions" :
                      [ { "method" : $method,
                          "version" : $version
                          "status" : $status }, ... ] } ] }

Sample output:

{ "endpoints" : [ { "name" : "/organizations/:orgname/clients/:client",
                    "versions" :
                      [ { "method" : "GET", "version" : 0, "status" : "deprecated" },
                        { "method" : "GET", "version" : 1, "status" : "active"},
                        { "method" : "GET", "version" : "next", "status" : "unstable" }] },
                  { "name" : "/users/:user",
                    "versions" :
                      [ { "method" : "GET", "version" : 0, "status" : "deprecated"},
                        { "method" : "GET", "version" : 1, "status" : "active" } ] }
                 ] }

This endpoint MAY be extended to include information about endpoints that do not explicitly implement versioning. The response MUST be in the same form.

The server MAY rate-limit this endpoint due to the potential cost associated with it.

Finally, the server MAY further extend this API to allow clients to query version information for a specific method + endpoint, in which case version information for that specific endpoint is returned, or 404 if there is no such endpoint.

To continue the example above, the request:

GET /server_api_version/extended/GET/organizations/:orgname/clients/:client

Would receive the response:

 { "name" : "/organizations/:orgname/clients/:client",
   "versions" : [ { "method" : "GET", "version" : 0, "status" : "deprecated" },
                  { "method" : "GET", "version" : 1, "status" : "active" },
                  { "method" : "GET", "version" : "next", "status" : "unstable"}] },

Rationale

As the capabilities of the Chef Server API expand, it becomes necessary to deprecate and de-support old API behaviors. However, consumers of the API such as chef-client may not yet be able to support changed API behaviors. The version indicator is used by a client to indicate which level of API behavior it can support.

We have decided on the use of a single custom header to indicate version (instead of url-based or Accept header-based) for several reasons:

Copyright

This work is in the public domain. In jurisdictions that do not allow for this, this work is available under CC0. To the extent possible under law, the person who associated CC0 with this work has waived all copyright and related or neighboring rights to this work.