diff --git a/build/versionize-docs.sh b/build/versionize-docs.sh index 221ebe6107..601ec07e41 100755 --- a/build/versionize-docs.sh +++ b/build/versionize-docs.sh @@ -100,5 +100,6 @@ done ${KUBE_ROOT}/hack/update-munge-docs.sh ${KUBE_ROOT}/hack/update-generated-swagger-docs.sh ${KUBE_ROOT}/hack/update-swagger-spec.sh +${KUBE_ROOT}/hack/update-openapi-spec.sh ${KUBE_ROOT}/hack/update-generated-protobuf.sh ./hack/update-api-reference-docs.sh diff --git a/docs/devel/api_changes.md b/docs/devel/api_changes.md index afdbaae7be..6488d231d5 100755 --- a/docs/devel/api_changes.md +++ b/docs/devel/api_changes.md @@ -590,10 +590,11 @@ out. Put `grep` or `ack` to good use. If you added functionality, you should consider documenting it and/or writing an example to illustrate your change. -Make sure you update the swagger API spec by running: +Make sure you update the swagger and OpenAPI spec by running: ```sh hack/update-swagger-spec.sh +hack/update-openapi-spec.sh ``` The API spec changes should be in a commit separate from your other changes. diff --git a/hack/lib/util.sh b/hack/lib/util.sh index 46f5f52b7c..0ac651d5cc 100755 --- a/hack/lib/util.sh +++ b/hack/lib/util.sh @@ -387,6 +387,59 @@ kube::util::fetch-swagger-spec() { curl -w "\n" -fs "${SWAGGER_API_PATH}logs" > "${SWAGGER_ROOT_DIR}/logs.json" } +# Takes a group/version and returns the openapi-spec file name. +# default behavior: extensions/v1beta1 -> v1beta1.extensions +# special case for v1: v1 -> v1 +kube::util::gv-to-openapi-name() { + local group_version="$1" + case "${group_version}" in + v1) + echo "v1" + ;; + *) + echo "${group_version#*/}.${group_version%/*}" + ;; + esac +} + + +# Fetches openapi spec from apiserver. +# Assumed vars: +# OPENAPI_API_PATH: Base path for openapi on apiserver. normally APIServer root. i.e., http://localhost:8080/ +# OPENAPI_ROOT_DIR: Root dir where we want to to save the fetched spec. +# VERSIONS: Array of group versions to include in swagger spec. +kube::util::fetch-openapi-spec() { + for ver in ${VERSIONS}; do + if [[ " ${KUBE_NONSERVER_GROUP_VERSIONS} " == *" ${ver} "* ]]; then + continue + fi + # fetch the openapi spec for each group version. + if [[ ${ver} == "v1" ]]; then + SUBPATH="api" + else + SUBPATH="apis" + fi + SUBPATH="${SUBPATH}/${ver}" + OPENAPI_JSON_NAME="$(kube::util::gv-to-openapi-name ${ver}).json" + curl -w "\n" -fs "${OPENAPI_PATH}${SUBPATH}/swagger.json" > "${OPENAPI_ROOT_DIR}/${OPENAPI_JSON_NAME}" + + # fetch the openapi spec for the discovery mechanism at group level. + if [[ ${ver} == "v1" ]]; then + continue + fi + SUBPATH="apis/"${ver%/*} + OPEAN_JSON_NAME="${ver%/*}.json" + curl -w "\n" -fs "${OPENAPI_PATH}${SUBPATH}/swagger.json" > "${OPENAPI_ROOT_DIR}/${OPENAPI_JSON_NAME}" + done + + # fetch openapi specs for other discovery mechanism. + curl -w "\n" -fs "${OPENAPI_PATH}swagger.json" > "${OPENAPI_ROOT_DIR}/root_swagger.json" + curl -w "\n" -fs "${OPENAPI_PATH}version/swagger.json" > "${OPENAPI_ROOT_DIR}/version.json" + curl -w "\n" -fs "${OPENAPI_PATH}api/swagger.json" > "${OPENAPI_ROOT_DIR}/api.json" + curl -w "\n" -fs "${OPENAPI_PATH}apis/swagger.json" > "${OPENAPI_ROOT_DIR}/apis.json" + curl -w "\n" -fs "${OPENAPI_PATH}logs/swagger.json" > "${OPENAPI_ROOT_DIR}/logs.json" +} + # Returns the name of the upstream remote repository name for the local git # repo, e.g. "upstream" or "origin". diff --git a/hack/update-openapi-spec.sh b/hack/update-openapi-spec.sh new file mode 100755 index 0000000000..b379f16159 --- /dev/null +++ b/hack/update-openapi-spec.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# Copyright 2016 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. + +# Script to fetch latest openapi spec. +# Puts the updated spec at api/openapi-spec/ + +set -o errexit +set -o nounset +set -o pipefail + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +OPENAPI_ROOT_DIR="${KUBE_ROOT}/api/openapi-spec" +source "${KUBE_ROOT}/hack/lib/init.sh" + +kube::golang::setup_env + +make -C "${KUBE_ROOT}" WHAT=cmd/kube-apiserver + +function cleanup() +{ + [[ -n ${APISERVER_PID-} ]] && kill ${APISERVER_PID} 1>&2 2>/dev/null + + kube::etcd::cleanup + + kube::log::status "Clean up complete" +} + +trap cleanup EXIT SIGINT + +kube::golang::setup_env + +apiserver=$(kube::util::find-binary "kube-apiserver") + +TMP_DIR=$(mktemp -d /tmp/update-openapi-spec.XXXX) +ETCD_HOST=${ETCD_HOST:-127.0.0.1} +ETCD_PORT=${ETCD_PORT:-2379} +API_PORT=${API_PORT:-8050} +API_HOST=${API_HOST:-127.0.0.1} + +kube::etcd::start + +# Start kube-apiserver +kube::log::status "Starting kube-apiserver" +"${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \ + --insecure-bind-address="${API_HOST}" \ + --bind-address="${API_HOST}" \ + --insecure-port="${API_PORT}" \ + --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" \ + --advertise-address="10.10.10.10" \ + --cert-dir="${TMP_DIR}/certs" \ + --service-cluster-ip-range="10.0.0.0/24" >/tmp/openapi-api-server.log 2>&1 & +APISERVER_PID=$! + +kube::util::wait_for_url "${API_HOST}:${API_PORT}/healthz" "apiserver: " + +kube::log::status "Updating " ${OPENAPI_ROOT_DIR} + +OPENAPI_PATH="${API_HOST}:${API_PORT}/" OPENAPI_ROOT_DIR="${OPENAPI_ROOT_DIR}" VERSIONS="${KUBE_AVAILABLE_GROUP_VERSIONS}" KUBE_NONSERVER_GROUP_VERSIONS="${KUBE_NONSERVER_GROUP_VERSIONS}" kube::util::fetch-openapi-spec + +kube::log::status "SUCCESS" + +# ex: ts=2 sw=2 et filetype=sh diff --git a/hack/verify-openapi-spec.sh b/hack/verify-openapi-spec.sh new file mode 100755 index 0000000000..1c45d19add --- /dev/null +++ b/hack/verify-openapi-spec.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Copyright 2016 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. + +set -o errexit +set -o nounset +set -o pipefail + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +source "${KUBE_ROOT}/hack/lib/init.sh" + +kube::golang::setup_env + +make -C "${KUBE_ROOT}" WHAT=cmd/kube-apiserver + +apiserver=$(kube::util::find-binary "kube-apiserver") + +SPECROOT="${KUBE_ROOT}/api/openapi-spec" +TMP_SPECROOT="${KUBE_ROOT}/_tmp/openapi-spec" +_tmp="${KUBE_ROOT}/_tmp" + +mkdir -p "${_tmp}" +trap "rm -rf ${_tmp}" EXIT SIGINT +cp -a "${SPECROOT}" "${TMP_SPECROOT}" + +"${KUBE_ROOT}/hack/update-openapi-spec.sh" +echo "diffing ${SPECROOT} against freshly generated openapi spec" +ret=0 +diff -Naupr -I 'Auto generated by' "${SPECROOT}" "${TMP_SPECROOT}" || ret=$? +cp -a ${TMP_SPECROOT} "${KUBE_ROOT}/api" +if [[ $ret -eq 0 ]] +then + echo "${SPECROOT} up to date." +else + echo "${SPECROOT} is out of date. Please run hack/update-openapi-spec.sh" + exit 1 +fi + +# ex: ts=2 sw=2 et filetype=sh diff --git a/hooks/pre-commit b/hooks/pre-commit index bd4cbe53bc..0f0138b72c 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -158,6 +158,18 @@ else fi echo "${reset}" +echo -ne "Checking for openapi spec that need updating... " +if ! hack/verify-openapi-spec.sh > /dev/null; then + echo "${red}ERROR!" + echo "Openapi spec needs to be updated." + echo "To regenerate the spec, run:" + echo " hack/update-openapi-spec.sh" + exit_code=1 +else + echo "${green}OK" +fi +echo "${reset}" + if [[ "${exit_code}" != 0 ]]; then echo "${red}Aborting commit${reset}" fi diff --git a/pkg/genericapiserver/openapi/util.go b/pkg/genericapiserver/openapi/util.go index b189727c7e..79a8f9d474 100644 --- a/pkg/genericapiserver/openapi/util.go +++ b/pkg/genericapiserver/openapi/util.go @@ -42,16 +42,12 @@ func sortParameters(p []spec.Parameter) { sort.Sort(byNameIn{p}) } -func groupRoutesByPath(routes []restful.Route) (ret map[string][]restful.Route) { - ret = make(map[string][]restful.Route) +func groupRoutesByPath(routes []restful.Route) map[string][]restful.Route { + pathToRoutes := make(map[string][]restful.Route) for _, r := range routes { - route, exists := ret[r.Path] - if !exists { - route = make([]restful.Route, 0, 1) - } - ret[r.Path] = append(route, r) + pathToRoutes[r.Path] = append(pathToRoutes[r.Path], r) } - return ret + return pathToRoutes } func mapKeyFromParam(param *restful.Parameter) interface{} {