Switch to github.com/golang/dep for vendoring
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
parent
d6ab91be27
commit
8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions
135
vendor/k8s.io/kubernetes/pkg/genericapiserver/BUILD
generated
vendored
Normal file
135
vendor/k8s.io/kubernetes/pkg/genericapiserver/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"config_selfclient.go",
|
||||
"discovery.go",
|
||||
"doc.go",
|
||||
"genericapiserver.go",
|
||||
"healthz.go",
|
||||
"hooks.go",
|
||||
"resource_config.go",
|
||||
"resource_encoding_config.go",
|
||||
"serve.go",
|
||||
"storage_factory.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/auth/handlers:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/genericapiserver/api:go_default_library",
|
||||
"//pkg/genericapiserver/api/filters:go_default_library",
|
||||
"//pkg/genericapiserver/api/openapi:go_default_library",
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//pkg/genericapiserver/authenticator:go_default_library",
|
||||
"//pkg/genericapiserver/authorizer:go_default_library",
|
||||
"//pkg/genericapiserver/filters:go_default_library",
|
||||
"//pkg/genericapiserver/mux:go_default_library",
|
||||
"//pkg/genericapiserver/options:go_default_library",
|
||||
"//pkg/genericapiserver/routes:go_default_library",
|
||||
"//pkg/storage/storagebackend:go_default_library",
|
||||
"//pkg/util/cert:go_default_library",
|
||||
"//pkg/version:go_default_library",
|
||||
"//vendor:github.com/coreos/go-systemd/daemon",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/emicklei/go-restful/swagger",
|
||||
"//vendor:github.com/go-openapi/spec",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/pborman/uuid",
|
||||
"//vendor:github.com/pkg/errors",
|
||||
"//vendor:gopkg.in/natefinch/lumberjack.v2",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apimachinery",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/openapi",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/net",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/validation",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/authenticator",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/request/union",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/union",
|
||||
"//vendor:k8s.io/apiserver/pkg/healthz",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"genericapiserver_test.go",
|
||||
"resource_config_test.go",
|
||||
"serve_test.go",
|
||||
"server_run_options_test.go",
|
||||
"storage_factory_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/autoscaling:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//pkg/generated/openapi:go_default_library",
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//pkg/genericapiserver/options:go_default_library",
|
||||
"//pkg/storage/etcd/testing:go_default_library",
|
||||
"//pkg/storage/storagebackend:go_default_library",
|
||||
"//pkg/util/cert:go_default_library",
|
||||
"//pkg/util/config:go_default_library",
|
||||
"//pkg/version:go_default_library",
|
||||
"//vendor:github.com/go-openapi/spec",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apimachinery",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/net",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/genericapiserver/api:all-srcs",
|
||||
"//pkg/genericapiserver/authenticator:all-srcs",
|
||||
"//pkg/genericapiserver/authorizer:all-srcs",
|
||||
"//pkg/genericapiserver/filters:all-srcs",
|
||||
"//pkg/genericapiserver/mux:all-srcs",
|
||||
"//pkg/genericapiserver/openapi:all-srcs",
|
||||
"//pkg/genericapiserver/options:all-srcs",
|
||||
"//pkg/genericapiserver/routes:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
42
vendor/k8s.io/kubernetes/pkg/genericapiserver/OWNERS
generated
vendored
Normal file
42
vendor/k8s.io/kubernetes/pkg/genericapiserver/OWNERS
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
approvers:
|
||||
- lavalamp
|
||||
- nikhiljindal
|
||||
- smarterclayton
|
||||
- deads2k
|
||||
reviewers:
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- yujuhong
|
||||
- liggitt
|
||||
- nikhiljindal
|
||||
- bprashanth
|
||||
- gmarek
|
||||
- davidopp
|
||||
- sttts
|
||||
- luxas
|
||||
- justinsb
|
||||
- yifan-gu
|
||||
- eparis
|
||||
- timothysc
|
||||
- jlowdermilk
|
||||
- soltysh
|
||||
- dims
|
||||
- errordeveloper
|
||||
- madhusudancs
|
||||
- hongchaodeng
|
||||
- xiang90
|
||||
- resouer
|
||||
- cjcullen
|
||||
- mbohlool
|
||||
- ericchiang
|
||||
- ping035627
|
||||
- therc
|
||||
- xiangpengzhao
|
||||
- mqliang
|
||||
- philips
|
||||
- jianhuiz
|
||||
- lojies
|
||||
- sjenning
|
||||
- goltermann
|
101
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/BUILD
generated
vendored
Normal file
101
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"apiserver.go",
|
||||
"discovery.go",
|
||||
"doc.go",
|
||||
"groupversion.go",
|
||||
"installer.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers/responsewriters:go_default_library",
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/conversion",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||
"//vendor:k8s.io/apiserver/pkg/handlers/negotiation",
|
||||
"//vendor:k8s.io/apiserver/pkg/metrics",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"apiserver_test.go",
|
||||
"installer_test.go",
|
||||
"proxy_test.go",
|
||||
"watch_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testing:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/genericapiserver/api/filters:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers/responsewriters:go_default_library",
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//pkg/genericapiserver/api/testing:go_default_library",
|
||||
"//plugin/pkg/admission/admit:go_default_library",
|
||||
"//plugin/pkg/admission/deny:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:golang.org/x/net/websocket",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/labels",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/diff",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/net",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/genericapiserver/api/filters:all-srcs",
|
||||
"//pkg/genericapiserver/api/handlers:all-srcs",
|
||||
"//pkg/genericapiserver/api/openapi:all-srcs",
|
||||
"//pkg/genericapiserver/api/rest:all-srcs",
|
||||
"//pkg/genericapiserver/api/testing:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
42
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/OWNERS
generated
vendored
Normal file
42
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/OWNERS
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
approvers:
|
||||
- lavalamp
|
||||
- nikhiljindal
|
||||
- smarterclayton
|
||||
- deads2k
|
||||
reviewers:
|
||||
- thockin
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- bgrant0607
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- nikhiljindal
|
||||
- bprashanth
|
||||
- gmarek
|
||||
- erictune
|
||||
- davidopp
|
||||
- sttts
|
||||
- zmerlynn
|
||||
- luxas
|
||||
- roberthbailey
|
||||
- ncdc
|
||||
- timstclair
|
||||
- yifan-gu
|
||||
- timothysc
|
||||
- feiskyer
|
||||
- soltysh
|
||||
- piosz
|
||||
- jbeda
|
||||
- dims
|
||||
- spxtr
|
||||
- errordeveloper
|
||||
- madhusudancs
|
||||
- hongchaodeng
|
||||
- krousey
|
||||
- vmarmol
|
||||
- fgrzadkowski
|
||||
- xiang90
|
25
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/apiserver.go
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/apiserver.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/metrics"
|
||||
)
|
||||
|
||||
func init() {
|
||||
metrics.Register()
|
||||
}
|
3423
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/apiserver_test.go
generated
vendored
Normal file
3423
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/apiserver_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
172
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/discovery.go
generated
vendored
Normal file
172
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/discovery.go
generated
vendored
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/handlers/negotiation"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// AddApiWebService adds a service to return the supported api versions at the legacy /api.
|
||||
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) {
|
||||
// TODO: InstallREST should register each version automatically
|
||||
|
||||
// Because in release 1.1, /api returns response with empty APIVersion, we
|
||||
// use StripVersionNegotiatedSerializer to keep the response backwards
|
||||
// compatible.
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||
ss := stripVersionNegotiatedSerializer{s}
|
||||
versionHandler := APIVersionHandler(ss, getAPIVersionsFunc)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(apiPrefix)
|
||||
ws.Doc("get available API versions")
|
||||
ws.Route(ws.GET("/").To(versionHandler).
|
||||
Doc("get available API versions").
|
||||
Operation("getAPIVersions").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIVersions{}))
|
||||
container.Add(ws)
|
||||
}
|
||||
|
||||
// stripVersionEncoder strips APIVersion field from the encoding output. It's
|
||||
// used to keep the responses at the discovery endpoints backward compatible
|
||||
// with release-1.1, when the responses have empty APIVersion.
|
||||
type stripVersionEncoder struct {
|
||||
encoder runtime.Encoder
|
||||
serializer runtime.Serializer
|
||||
}
|
||||
|
||||
func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := c.encoder.Encode(obj, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gvk.Group = ""
|
||||
gvk.Version = ""
|
||||
roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
|
||||
return c.serializer.Encode(roundTrippedObj, w)
|
||||
}
|
||||
|
||||
// stripVersionNegotiatedSerializer will return stripVersionEncoder when
|
||||
// EncoderForVersion is called. See comments for stripVersionEncoder.
|
||||
type stripVersionNegotiatedSerializer struct {
|
||||
runtime.NegotiatedSerializer
|
||||
}
|
||||
|
||||
func (n stripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
||||
serializer, ok := encoder.(runtime.Serializer)
|
||||
if !ok {
|
||||
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
|
||||
// decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
|
||||
// decoder.
|
||||
panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
|
||||
}
|
||||
versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
|
||||
return stripVersionEncoder{versioned, serializer}
|
||||
}
|
||||
|
||||
func keepUnversioned(group string) bool {
|
||||
return group == "" || group == "extensions"
|
||||
}
|
||||
|
||||
// NewApisWebService returns a webservice serving the available api version under /apis.
|
||||
func NewApisWebService(s runtime.NegotiatedSerializer, apiPrefix string, f func(req *restful.Request) []metav1.APIGroup) *restful.WebService {
|
||||
// Because in release 1.1, /apis returns response with empty APIVersion, we
|
||||
// use StripVersionNegotiatedSerializer to keep the response backwards
|
||||
// compatible.
|
||||
ss := stripVersionNegotiatedSerializer{s}
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||
rootAPIHandler := handlers.RootAPIHandler(ss, f)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(apiPrefix)
|
||||
ws.Doc("get available API versions")
|
||||
ws.Route(ws.GET("/").To(rootAPIHandler).
|
||||
Doc("get available API versions").
|
||||
Operation("getAPIVersions").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIGroupList{}))
|
||||
return ws
|
||||
}
|
||||
|
||||
// NewGroupWebService returns a webservice serving the supported versions, preferred version, and name
|
||||
// of a group. E.g., such a web service will be registered at /apis/extensions.
|
||||
func NewGroupWebService(s runtime.NegotiatedSerializer, path string, group metav1.APIGroup) *restful.WebService {
|
||||
ss := s
|
||||
if keepUnversioned(group.Name) {
|
||||
// Because in release 1.1, /apis/extensions returns response with empty
|
||||
// APIVersion, we use StripVersionNegotiatedSerializer to keep the
|
||||
// response backwards compatible.
|
||||
ss = stripVersionNegotiatedSerializer{s}
|
||||
}
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||
groupHandler := handlers.GroupHandler(ss, group)
|
||||
ws := new(restful.WebService)
|
||||
ws.Path(path)
|
||||
ws.Doc("get information of a group")
|
||||
ws.Route(ws.GET("/").To(groupHandler).
|
||||
Doc("get information of a group").
|
||||
Operation("getAPIGroup").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIGroup{}))
|
||||
return ws
|
||||
}
|
||||
|
||||
// Adds a service to return the supported resources, E.g., a such web service
|
||||
// will be registered at /apis/extensions/v1.
|
||||
func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion schema.GroupVersion, lister handlers.APIResourceLister) {
|
||||
ss := s
|
||||
if keepUnversioned(groupVersion.Group) {
|
||||
// Because in release 1.1, /apis/extensions/v1beta1 returns response
|
||||
// with empty APIVersion, we use StripVersionNegotiatedSerializer to
|
||||
// keep the response backwards compatible.
|
||||
ss = stripVersionNegotiatedSerializer{s}
|
||||
}
|
||||
mediaTypes, _ := negotiation.MediaTypesForSerializer(s)
|
||||
resourceHandler := handlers.SupportedResourcesHandler(ss, groupVersion, lister)
|
||||
ws.Route(ws.GET("/").To(resourceHandler).
|
||||
Doc("get available resources").
|
||||
Operation("getAPIResources").
|
||||
Produces(mediaTypes...).
|
||||
Consumes(mediaTypes...).
|
||||
Writes(metav1.APIResourceList{}))
|
||||
}
|
||||
|
||||
// APIVersionHandler returns a handler which will list the provided versions as available.
|
||||
func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *metav1.APIVersions) restful.RouteFunction {
|
||||
return func(req *restful.Request, resp *restful.Response) {
|
||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req))
|
||||
}
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
// Package api contains the code that provides a rest.ful api service.
|
||||
package api // import "k8s.io/kubernetes/pkg/genericapiserver/api"
|
68
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/BUILD
generated
vendored
Normal file
68
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"audit.go",
|
||||
"authorization.go",
|
||||
"doc.go",
|
||||
"impersonation.go",
|
||||
"requestinfo.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/authentication:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers/responsewriters:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/pborman/uuid",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/net",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
"//vendor:k8s.io/apiserver/pkg/httplog",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"audit_test.go",
|
||||
"authorization_test.go",
|
||||
"impersonation_test.go",
|
||||
"requestinfo_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/apis/authentication:go_default_library",
|
||||
"//pkg/apis/batch:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers/responsewriters:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
4
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/OWNERS
generated
vendored
Executable file
4
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,4 @@
|
|||
reviewers:
|
||||
- deads2k
|
||||
- sttts
|
||||
- soltysh
|
164
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/audit.go
generated
vendored
Normal file
164
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/audit.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
)
|
||||
|
||||
var _ http.ResponseWriter = &auditResponseWriter{}
|
||||
|
||||
type auditResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
out io.Writer
|
||||
id string
|
||||
}
|
||||
|
||||
func (a *auditResponseWriter) WriteHeader(code int) {
|
||||
line := fmt.Sprintf("%s AUDIT: id=%q response=\"%d\"\n", time.Now().Format(time.RFC3339Nano), a.id, code)
|
||||
if _, err := fmt.Fprint(a.out, line); err != nil {
|
||||
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
|
||||
}
|
||||
|
||||
a.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// fancyResponseWriterDelegator implements http.CloseNotifier, http.Flusher and
|
||||
// http.Hijacker which are needed to make certain http operation (e.g. watch, rsh, etc)
|
||||
// working.
|
||||
type fancyResponseWriterDelegator struct {
|
||||
*auditResponseWriter
|
||||
}
|
||||
|
||||
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
|
||||
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (f *fancyResponseWriterDelegator) Flush() {
|
||||
f.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return f.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
var _ http.CloseNotifier = &fancyResponseWriterDelegator{}
|
||||
var _ http.Flusher = &fancyResponseWriterDelegator{}
|
||||
var _ http.Hijacker = &fancyResponseWriterDelegator{}
|
||||
|
||||
// WithAudit decorates a http.Handler with audit logging information for all the
|
||||
// requests coming to the server. If out is nil, no decoration takes place.
|
||||
// Each audit log contains two entries:
|
||||
// 1. the request line containing:
|
||||
// - unique id allowing to match the response line (see 2)
|
||||
// - source ip of the request
|
||||
// - HTTP method being invoked
|
||||
// - original user invoking the operation
|
||||
// - impersonated user for the operation
|
||||
// - namespace of the request or <none>
|
||||
// - uri is the full URI as requested
|
||||
// 2. the response line containing:
|
||||
// - the unique id from 1
|
||||
// - response code
|
||||
func WithAudit(handler http.Handler, requestContextMapper request.RequestContextMapper, out io.Writer) http.Handler {
|
||||
if out == nil {
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
responsewriters.InternalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
attribs, err := GetAuthorizerAttributes(ctx)
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
username := "<none>"
|
||||
groups := "<none>"
|
||||
if attribs.GetUser() != nil {
|
||||
username = attribs.GetUser().GetName()
|
||||
if userGroups := attribs.GetUser().GetGroups(); len(userGroups) > 0 {
|
||||
groups = auditStringSlice(userGroups)
|
||||
}
|
||||
}
|
||||
asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader)
|
||||
if len(asuser) == 0 {
|
||||
asuser = "<self>"
|
||||
}
|
||||
asgroups := "<lookup>"
|
||||
requestedGroups := req.Header[authenticationapi.ImpersonateGroupHeader]
|
||||
if len(requestedGroups) > 0 {
|
||||
asgroups = auditStringSlice(requestedGroups)
|
||||
}
|
||||
namespace := attribs.GetNamespace()
|
||||
if len(namespace) == 0 {
|
||||
namespace = "<none>"
|
||||
}
|
||||
id := uuid.NewRandom().String()
|
||||
|
||||
line := fmt.Sprintf("%s AUDIT: id=%q ip=%q method=%q user=%q groups=%q as=%q asgroups=%q namespace=%q uri=%q\n",
|
||||
time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, username, groups, asuser, asgroups, namespace, req.URL)
|
||||
if _, err := fmt.Fprint(out, line); err != nil {
|
||||
glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
|
||||
}
|
||||
respWriter := decorateResponseWriter(w, out, id)
|
||||
handler.ServeHTTP(respWriter, req)
|
||||
})
|
||||
}
|
||||
|
||||
func auditStringSlice(inList []string) string {
|
||||
if len(inList) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
quotedElements := make([]string, len(inList))
|
||||
for i, in := range inList {
|
||||
quotedElements[i] = fmt.Sprintf("%q", in)
|
||||
}
|
||||
return strings.Join(quotedElements, ",")
|
||||
}
|
||||
|
||||
func decorateResponseWriter(responseWriter http.ResponseWriter, out io.Writer, id string) http.ResponseWriter {
|
||||
delegate := &auditResponseWriter{ResponseWriter: responseWriter, out: out, id: id}
|
||||
// check if the ResponseWriter we're wrapping is the fancy one we need
|
||||
// or if the basic is sufficient
|
||||
_, cn := responseWriter.(http.CloseNotifier)
|
||||
_, fl := responseWriter.(http.Flusher)
|
||||
_, hj := responseWriter.(http.Hijacker)
|
||||
if cn && fl && hj {
|
||||
return &fancyResponseWriterDelegator{delegate}
|
||||
}
|
||||
return delegate
|
||||
}
|
152
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/audit_test.go
generated
vendored
Normal file
152
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/audit_test.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
)
|
||||
|
||||
type simpleResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (*simpleResponseWriter) WriteHeader(code int) {}
|
||||
|
||||
type fancyResponseWriter struct {
|
||||
simpleResponseWriter
|
||||
}
|
||||
|
||||
func (*fancyResponseWriter) CloseNotify() <-chan bool { return nil }
|
||||
|
||||
func (*fancyResponseWriter) Flush() {}
|
||||
|
||||
func (*fancyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return nil, nil, nil }
|
||||
|
||||
func TestConstructResponseWriter(t *testing.T) {
|
||||
actual := decorateResponseWriter(&simpleResponseWriter{}, ioutil.Discard, "")
|
||||
switch v := actual.(type) {
|
||||
case *auditResponseWriter:
|
||||
default:
|
||||
t.Errorf("Expected auditResponseWriter, got %v", reflect.TypeOf(v))
|
||||
}
|
||||
|
||||
actual = decorateResponseWriter(&fancyResponseWriter{}, ioutil.Discard, "")
|
||||
switch v := actual.(type) {
|
||||
case *fancyResponseWriterDelegator:
|
||||
default:
|
||||
t.Errorf("Expected fancyResponseWriterDelegator, got %v", reflect.TypeOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
type fakeHTTPHandler struct{}
|
||||
|
||||
func (*fakeHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
func TestAudit(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
handler := WithAudit(&fakeHTTPHandler{}, &fakeRequestContextMapper{
|
||||
user: &user.DefaultInfo{Name: "admin"},
|
||||
}, &buf)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
line := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
||||
if len(line) != 2 {
|
||||
t.Fatalf("Unexpected amount of lines in audit log: %d", len(line))
|
||||
}
|
||||
match, err := regexp.MatchString(`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" ip="127.0.0.1" method="GET" user="admin" groups="<none>" as="<self>" asgroups="<lookup>" namespace="default" uri="/api/v1/namespaces/default/pods"`, line[0])
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error matching first line: %v", err)
|
||||
}
|
||||
if !match {
|
||||
t.Errorf("Unexpected first line of audit: %s", line[0])
|
||||
}
|
||||
match, err = regexp.MatchString(`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" response="200"`, line[1])
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error matching second line: %v", err)
|
||||
}
|
||||
if !match {
|
||||
t.Errorf("Unexpected second line of audit: %s", line[1])
|
||||
}
|
||||
}
|
||||
|
||||
type fakeRequestContextMapper struct {
|
||||
user *user.DefaultInfo
|
||||
}
|
||||
|
||||
func (m *fakeRequestContextMapper) Get(req *http.Request) (request.Context, bool) {
|
||||
ctx := request.NewContext()
|
||||
if m.user != nil {
|
||||
ctx = request.WithUser(ctx, m.user)
|
||||
}
|
||||
|
||||
resolver := newTestRequestInfoResolver()
|
||||
info, err := resolver.NewRequestInfo(req)
|
||||
if err == nil {
|
||||
ctx = request.WithRequestInfo(ctx, info)
|
||||
}
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
|
||||
func (*fakeRequestContextMapper) Update(req *http.Request, context request.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAuditNoPanicOnNilUser(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
handler := WithAudit(&fakeHTTPHandler{}, &fakeRequestContextMapper{}, &buf)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
line := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
||||
if len(line) != 2 {
|
||||
t.Fatalf("Unexpected amount of lines in audit log: %d", len(line))
|
||||
}
|
||||
match, err := regexp.MatchString(`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" ip="127.0.0.1" method="GET" user="<none>" groups="<none>" as="<self>" asgroups="<lookup>" namespace="default" uri="/api/v1/namespaces/default/pods"`, line[0])
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error matching first line: %v", err)
|
||||
}
|
||||
if !match {
|
||||
t.Errorf("Unexpected first line of audit: %s", line[0])
|
||||
}
|
||||
match, err = regexp.MatchString(`[\d\:\-\.\+TZ]+ AUDIT: id="[\w-]+" response="200"`, line[1])
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error matching second line: %v", err)
|
||||
}
|
||||
if !match {
|
||||
t.Errorf("Unexpected second line of audit: %s", line[1])
|
||||
}
|
||||
}
|
89
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/authorization.go
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/authorization.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
|
||||
func WithAuthorization(handler http.Handler, requestContextMapper request.RequestContextMapper, a authorizer.Authorizer) http.Handler {
|
||||
if a == nil {
|
||||
glog.Warningf("Authorization is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
responsewriters.InternalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
|
||||
attributes, err := GetAuthorizerAttributes(ctx)
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
authorized, reason, err := a.Authorize(attributes)
|
||||
if authorized {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
|
||||
responsewriters.Forbidden(attributes, w, req, reason)
|
||||
})
|
||||
}
|
||||
|
||||
func GetAuthorizerAttributes(ctx request.Context) (authorizer.Attributes, error) {
|
||||
attribs := authorizer.AttributesRecord{}
|
||||
|
||||
user, ok := request.UserFrom(ctx)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
}
|
||||
|
||||
requestInfo, found := request.RequestInfoFrom(ctx)
|
||||
if !found {
|
||||
return nil, errors.New("no RequestInfo found in the context")
|
||||
}
|
||||
|
||||
// Start with common attributes that apply to resource and non-resource requests
|
||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
||||
attribs.Path = requestInfo.Path
|
||||
attribs.Verb = requestInfo.Verb
|
||||
|
||||
attribs.APIGroup = requestInfo.APIGroup
|
||||
attribs.APIVersion = requestInfo.APIVersion
|
||||
attribs.Resource = requestInfo.Resource
|
||||
attribs.Subresource = requestInfo.Subresource
|
||||
attribs.Namespace = requestInfo.Namespace
|
||||
attribs.Name = requestInfo.Name
|
||||
|
||||
return &attribs, nil
|
||||
}
|
129
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/authorization_test.go
generated
vendored
Normal file
129
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/authorization_test.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/apis/batch"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
)
|
||||
|
||||
func TestGetAuthorizerAttributes(t *testing.T) {
|
||||
mapper := request.NewRequestContextMapper()
|
||||
|
||||
testcases := map[string]struct {
|
||||
Verb string
|
||||
Path string
|
||||
ExpectedAttributes *authorizer.AttributesRecord
|
||||
}{
|
||||
"non-resource root": {
|
||||
Verb: "POST",
|
||||
Path: "/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "post",
|
||||
Path: "/",
|
||||
},
|
||||
},
|
||||
"non-resource api prefix": {
|
||||
Verb: "GET",
|
||||
Path: "/api/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "get",
|
||||
Path: "/api/",
|
||||
},
|
||||
},
|
||||
"non-resource group api prefix": {
|
||||
Verb: "GET",
|
||||
Path: "/apis/extensions/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "get",
|
||||
Path: "/apis/extensions/",
|
||||
},
|
||||
},
|
||||
|
||||
"resource": {
|
||||
Verb: "POST",
|
||||
Path: "/api/v1/nodes/mynode",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "create",
|
||||
Path: "/api/v1/nodes/mynode",
|
||||
ResourceRequest: true,
|
||||
Resource: "nodes",
|
||||
APIVersion: "v1",
|
||||
Name: "mynode",
|
||||
},
|
||||
},
|
||||
"namespaced resource": {
|
||||
Verb: "PUT",
|
||||
Path: "/api/v1/namespaces/myns/pods/mypod",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "update",
|
||||
Path: "/api/v1/namespaces/myns/pods/mypod",
|
||||
ResourceRequest: true,
|
||||
Namespace: "myns",
|
||||
Resource: "pods",
|
||||
APIVersion: "v1",
|
||||
Name: "mypod",
|
||||
},
|
||||
},
|
||||
"API group resource": {
|
||||
Verb: "GET",
|
||||
Path: "/apis/batch/v1/namespaces/myns/jobs",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "list",
|
||||
Path: "/apis/batch/v1/namespaces/myns/jobs",
|
||||
ResourceRequest: true,
|
||||
APIGroup: batch.GroupName,
|
||||
APIVersion: "v1",
|
||||
Namespace: "myns",
|
||||
Resource: "jobs",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
req, _ := http.NewRequest(tc.Verb, tc.Path, nil)
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
|
||||
var attribs authorizer.Attributes
|
||||
var err error
|
||||
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, ok := mapper.Get(req)
|
||||
if !ok {
|
||||
responsewriters.InternalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
attribs, err = GetAuthorizerAttributes(ctx)
|
||||
})
|
||||
handler = WithRequestInfo(handler, newTestRequestInfoResolver(), mapper)
|
||||
handler = request.WithRequestContext(handler, mapper)
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", k, err)
|
||||
} else if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
|
||||
t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs)
|
||||
}
|
||||
}
|
||||
}
|
19
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package filters contains all the http handler chain filters which
|
||||
// _are_ api related.
|
||||
package filters // import "k8s.io/kubernetes/pkg/genericapiserver/api/filters"
|
194
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/impersonation.go
generated
vendored
Normal file
194
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/impersonation.go
generated
vendored
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/httplog"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
)
|
||||
|
||||
// WithImpersonation is a filter that will inspect and check requests that attempt to change the user.Info for their requests
|
||||
func WithImpersonation(handler http.Handler, requestContextMapper request.RequestContextMapper, a authorizer.Authorizer) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
impersonationRequests, err := buildImpersonationRequests(req.Header)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("%v", err)
|
||||
responsewriters.InternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
if len(impersonationRequests) == 0 {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, exists := requestContextMapper.Get(req)
|
||||
if !exists {
|
||||
responsewriters.InternalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
requestor, exists := request.UserFrom(ctx)
|
||||
if !exists {
|
||||
responsewriters.InternalError(w, req, errors.New("no user found for request"))
|
||||
return
|
||||
}
|
||||
|
||||
// if groups are not specified, then we need to look them up differently depending on the type of user
|
||||
// if they are specified, then they are the authority
|
||||
groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0
|
||||
|
||||
// make sure we're allowed to impersonate each thing we're requesting. While we're iterating through, start building username
|
||||
// and group information
|
||||
username := ""
|
||||
groups := []string{}
|
||||
userExtra := map[string][]string{}
|
||||
for _, impersonationRequest := range impersonationRequests {
|
||||
actingAsAttributes := &authorizer.AttributesRecord{
|
||||
User: requestor,
|
||||
Verb: "impersonate",
|
||||
APIGroup: impersonationRequest.GetObjectKind().GroupVersionKind().Group,
|
||||
Namespace: impersonationRequest.Namespace,
|
||||
Name: impersonationRequest.Name,
|
||||
ResourceRequest: true,
|
||||
}
|
||||
|
||||
switch impersonationRequest.GetObjectKind().GroupVersionKind().GroupKind() {
|
||||
case api.Kind("ServiceAccount"):
|
||||
actingAsAttributes.Resource = "serviceaccounts"
|
||||
username = serviceaccount.MakeUsername(impersonationRequest.Namespace, impersonationRequest.Name)
|
||||
if !groupsSpecified {
|
||||
// if groups aren't specified for a service account, we know the groups because its a fixed mapping. Add them
|
||||
groups = serviceaccount.MakeGroupNames(impersonationRequest.Namespace, impersonationRequest.Name)
|
||||
}
|
||||
|
||||
case api.Kind("User"):
|
||||
actingAsAttributes.Resource = "users"
|
||||
username = impersonationRequest.Name
|
||||
|
||||
case api.Kind("Group"):
|
||||
actingAsAttributes.Resource = "groups"
|
||||
groups = append(groups, impersonationRequest.Name)
|
||||
|
||||
case authenticationapi.Kind("UserExtra"):
|
||||
extraKey := impersonationRequest.FieldPath
|
||||
extraValue := impersonationRequest.Name
|
||||
actingAsAttributes.Resource = "userextras"
|
||||
actingAsAttributes.Subresource = extraKey
|
||||
userExtra[extraKey] = append(userExtra[extraKey], extraValue)
|
||||
|
||||
default:
|
||||
glog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest)
|
||||
responsewriters.Forbidden(actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest))
|
||||
return
|
||||
}
|
||||
|
||||
allowed, reason, err := a.Authorize(actingAsAttributes)
|
||||
if err != nil || !allowed {
|
||||
glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err)
|
||||
responsewriters.Forbidden(actingAsAttributes, w, req, reason)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newUser := &user.DefaultInfo{
|
||||
Name: username,
|
||||
Groups: groups,
|
||||
Extra: userExtra,
|
||||
}
|
||||
requestContextMapper.Update(req, request.WithUser(ctx, newUser))
|
||||
|
||||
oldUser, _ := request.UserFrom(ctx)
|
||||
httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser)
|
||||
|
||||
// clear all the impersonation headers from the request
|
||||
req.Header.Del(authenticationapi.ImpersonateUserHeader)
|
||||
req.Header.Del(authenticationapi.ImpersonateGroupHeader)
|
||||
for headerName := range req.Header {
|
||||
if strings.HasPrefix(headerName, authenticationapi.ImpersonateUserExtraHeaderPrefix) {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// buildImpersonationRequests returns a list of objectreferences that represent the different things we're requesting to impersonate.
|
||||
// Also includes a map[string][]string representing user.Info.Extra
|
||||
// Each request must be authorized against the current user before switching contexts.
|
||||
func buildImpersonationRequests(headers http.Header) ([]api.ObjectReference, error) {
|
||||
impersonationRequests := []api.ObjectReference{}
|
||||
|
||||
requestedUser := headers.Get(authenticationapi.ImpersonateUserHeader)
|
||||
hasUser := len(requestedUser) > 0
|
||||
if hasUser {
|
||||
if namespace, name, err := serviceaccount.SplitUsername(requestedUser); err == nil {
|
||||
impersonationRequests = append(impersonationRequests, api.ObjectReference{Kind: "ServiceAccount", Namespace: namespace, Name: name})
|
||||
} else {
|
||||
impersonationRequests = append(impersonationRequests, api.ObjectReference{Kind: "User", Name: requestedUser})
|
||||
}
|
||||
}
|
||||
|
||||
hasGroups := false
|
||||
for _, group := range headers[authenticationapi.ImpersonateGroupHeader] {
|
||||
hasGroups = true
|
||||
impersonationRequests = append(impersonationRequests, api.ObjectReference{Kind: "Group", Name: group})
|
||||
}
|
||||
|
||||
hasUserExtra := false
|
||||
for headerName, values := range headers {
|
||||
if !strings.HasPrefix(headerName, authenticationapi.ImpersonateUserExtraHeaderPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
hasUserExtra = true
|
||||
extraKey := strings.ToLower(headerName[len(authenticationapi.ImpersonateUserExtraHeaderPrefix):])
|
||||
|
||||
// make a separate request for each extra value they're trying to set
|
||||
for _, value := range values {
|
||||
impersonationRequests = append(impersonationRequests,
|
||||
api.ObjectReference{
|
||||
Kind: "UserExtra",
|
||||
// we only parse out a group above, but the parsing will fail if there isn't SOME version
|
||||
// using the internal version will help us fail if anyone starts using it
|
||||
APIVersion: authenticationapi.SchemeGroupVersion.String(),
|
||||
Name: value,
|
||||
// ObjectReference doesn't have a subresource field. FieldPath is close and available, so we'll use that
|
||||
// TODO fight the good fight for ObjectReference to refer to resources and subresources
|
||||
FieldPath: extraKey,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (hasGroups || hasUserExtra) && !hasUser {
|
||||
return nil, fmt.Errorf("requested %v without impersonating a user", impersonationRequests)
|
||||
}
|
||||
|
||||
return impersonationRequests, nil
|
||||
}
|
347
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/impersonation_test.go
generated
vendored
Normal file
347
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/impersonation_test.go
generated
vendored
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
)
|
||||
|
||||
type impersonateAuthorizer struct{}
|
||||
|
||||
func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
|
||||
user := a.GetUser()
|
||||
|
||||
switch {
|
||||
case user.GetName() == "system:admin":
|
||||
return true, "", nil
|
||||
|
||||
case user.GetName() == "tester":
|
||||
return false, "", fmt.Errorf("works on my machine")
|
||||
|
||||
case user.GetName() == "deny-me":
|
||||
return false, "denied", nil
|
||||
}
|
||||
|
||||
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" &&
|
||||
a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
return false, "deny by default", nil
|
||||
}
|
||||
|
||||
func TestImpersonationFilter(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
user user.Info
|
||||
impersonationUser string
|
||||
impersonationGroups []string
|
||||
impersonationUserExtras map[string][]string
|
||||
expectedUser user.Info
|
||||
expectedCode int
|
||||
}{
|
||||
{
|
||||
name: "not-impersonating",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "impersonating-error",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
impersonationUser: "anyone",
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "impersonating-group-without-user",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
impersonationGroups: []string{"some-group"},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "impersonating-extra-without-user",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "disallowed-group",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel"},
|
||||
},
|
||||
impersonationUser: "system:admin",
|
||||
impersonationGroups: []string{"some-group"},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel"},
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "allowed-group",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel", "group-impersonater"},
|
||||
},
|
||||
impersonationUser: "system:admin",
|
||||
impersonationGroups: []string{"some-group"},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "system:admin",
|
||||
Groups: []string{"some-group"},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "disallowed-userextra-1",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel"},
|
||||
},
|
||||
impersonationUser: "system:admin",
|
||||
impersonationGroups: []string{"some-group"},
|
||||
impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel"},
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "disallowed-userextra-2",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel", "extra-setter-project"},
|
||||
},
|
||||
impersonationUser: "system:admin",
|
||||
impersonationGroups: []string{"some-group"},
|
||||
impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel", "extra-setter-project"},
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "disallowed-userextra-3",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel", "extra-setter-particular-scopes"},
|
||||
},
|
||||
impersonationUser: "system:admin",
|
||||
impersonationGroups: []string{"some-group"},
|
||||
impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel", "extra-setter-particular-scopes"},
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "allowed-userextras",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"wheel", "extra-setter-scopes"},
|
||||
},
|
||||
impersonationUser: "system:admin",
|
||||
impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "system:admin",
|
||||
Groups: []string{},
|
||||
Extra: map[string][]string{"scopes": {"scope-a", "scope-b"}},
|
||||
},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "allowed-users-impersonation",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"regular-impersonater"},
|
||||
},
|
||||
impersonationUser: "tester",
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
Groups: []string{},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "disallowed-impersonating",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"sa-impersonater"},
|
||||
},
|
||||
impersonationUser: "tester",
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"sa-impersonater"},
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "allowed-sa-impersonating",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "dev",
|
||||
Groups: []string{"sa-impersonater"},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
impersonationUser: "system:serviceaccount:foo:default",
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "system:serviceaccount:foo:default",
|
||||
Groups: []string{"system:serviceaccounts", "system:serviceaccounts:foo"},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
requestContextMapper := request.NewRequestContextMapper()
|
||||
var ctx request.Context
|
||||
var actualUser user.Info
|
||||
var lock sync.Mutex
|
||||
|
||||
doNothingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
currentCtx, _ := requestContextMapper.Get(req)
|
||||
user, exists := request.UserFrom(currentCtx)
|
||||
if !exists {
|
||||
actualUser = nil
|
||||
return
|
||||
}
|
||||
|
||||
actualUser = user
|
||||
})
|
||||
handler := func(delegate http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Recovered %v", r)
|
||||
}
|
||||
}()
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
requestContextMapper.Update(req, ctx)
|
||||
currentCtx, _ := requestContextMapper.Get(req)
|
||||
|
||||
user, exists := request.UserFrom(currentCtx)
|
||||
if !exists {
|
||||
actualUser = nil
|
||||
return
|
||||
} else {
|
||||
actualUser = user
|
||||
}
|
||||
|
||||
delegate.ServeHTTP(w, req)
|
||||
})
|
||||
}(WithImpersonation(doNothingHandler, requestContextMapper, impersonateAuthorizer{}))
|
||||
handler = request.WithRequestContext(handler, requestContextMapper)
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
for _, tc := range testCases {
|
||||
func() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
ctx = request.WithUser(request.NewContext(), tc.user)
|
||||
}()
|
||||
|
||||
req, err := http.NewRequest("GET", server.URL, nil)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
req.Header.Add(authenticationapi.ImpersonateUserHeader, tc.impersonationUser)
|
||||
for _, group := range tc.impersonationGroups {
|
||||
req.Header.Add(authenticationapi.ImpersonateGroupHeader, group)
|
||||
}
|
||||
for extraKey, values := range tc.impersonationUserExtras {
|
||||
for _, value := range values {
|
||||
req.Header.Add(authenticationapi.ImpersonateUserExtraHeaderPrefix+extraKey, value)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != tc.expectedCode {
|
||||
t.Errorf("%s: expected %v, actual %v", tc.name, tc.expectedCode, resp.StatusCode)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actualUser, tc.expectedUser) {
|
||||
t.Errorf("%s: expected %#v, actual %#v", tc.name, tc.expectedUser, actualUser)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/requestinfo.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/requestinfo.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// WithRequestInfo attaches a RequestInfo to the context.
|
||||
func WithRequestInfo(handler http.Handler, resolver *request.RequestInfoFactory, requestContextMapper request.RequestContextMapper) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
responsewriters.InternalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := resolver.NewRequestInfo(req)
|
||||
if err != nil {
|
||||
responsewriters.InternalError(w, req, fmt.Errorf("failed to create RequestInfo: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
requestContextMapper.Update(req, request.WithRequestInfo(ctx, info))
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
29
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/requestinfo_test.go
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/filters/requestinfo_test.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
)
|
||||
|
||||
func newTestRequestInfoResolver() *request.RequestInfoFactory {
|
||||
return &request.RequestInfoFactory{
|
||||
APIPrefixes: sets.NewString("api", "apis"),
|
||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||
}
|
||||
}
|
149
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/groupversion.go
generated
vendored
Normal file
149
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/groupversion.go
generated
vendored
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
)
|
||||
|
||||
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
||||
// It handles URLs of the form:
|
||||
// /${storage_key}[/${object_name}]
|
||||
// Where 'storage_key' points to a rest.Storage object stored in storage.
|
||||
// This object should contain all parameterization necessary for running a particular API version
|
||||
type APIGroupVersion struct {
|
||||
Storage map[string]rest.Storage
|
||||
|
||||
Root string
|
||||
|
||||
// GroupVersion is the external group version
|
||||
GroupVersion schema.GroupVersion
|
||||
|
||||
// OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver
|
||||
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
|
||||
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
|
||||
// empty, defaults to GroupVersion.
|
||||
OptionsExternalVersion *schema.GroupVersion
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
|
||||
// Serializer is used to determine how to convert responses from API methods into bytes to send over
|
||||
// the wire.
|
||||
Serializer runtime.NegotiatedSerializer
|
||||
ParameterCodec runtime.ParameterCodec
|
||||
|
||||
Typer runtime.ObjectTyper
|
||||
Creater runtime.ObjectCreater
|
||||
Convertor runtime.ObjectConvertor
|
||||
Copier runtime.ObjectCopier
|
||||
Linker runtime.SelfLinker
|
||||
|
||||
Admit admission.Interface
|
||||
Context request.RequestContextMapper
|
||||
|
||||
MinRequestTimeout time.Duration
|
||||
|
||||
// SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
|
||||
// accessible from this API group version. The GroupVersionKind is that of the external version of
|
||||
// the subresource. The key of this map should be the path of the subresource. The keys here should
|
||||
// match the keys in the Storage map above for subresources.
|
||||
SubresourceGroupVersionKind map[string]schema.GroupVersionKind
|
||||
|
||||
// ResourceLister is an interface that knows how to list resources
|
||||
// for this API Group.
|
||||
ResourceLister handlers.APIResourceLister
|
||||
}
|
||||
|
||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
|
||||
// in a slash.
|
||||
func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
||||
installer := g.newInstaller()
|
||||
ws := installer.NewWebService()
|
||||
apiResources, registrationErrors := installer.Install(ws)
|
||||
lister := g.ResourceLister
|
||||
if lister == nil {
|
||||
lister = staticLister{apiResources}
|
||||
}
|
||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
||||
container.Add(ws)
|
||||
return utilerrors.NewAggregate(registrationErrors)
|
||||
}
|
||||
|
||||
// UpdateREST registers the REST handlers for this APIGroupVersion to an existing web service
|
||||
// in the restful Container. It will use the prefix (root/version) to find the existing
|
||||
// web service. If a web service does not exist within the container to support the prefix
|
||||
// this method will return an error.
|
||||
func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
||||
installer := g.newInstaller()
|
||||
var ws *restful.WebService = nil
|
||||
|
||||
for i, s := range container.RegisteredWebServices() {
|
||||
if s.RootPath() == installer.prefix {
|
||||
ws = container.RegisteredWebServices()[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ws == nil {
|
||||
return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix))
|
||||
}
|
||||
apiResources, registrationErrors := installer.Install(ws)
|
||||
lister := g.ResourceLister
|
||||
if lister == nil {
|
||||
lister = staticLister{apiResources}
|
||||
}
|
||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
||||
return utilerrors.NewAggregate(registrationErrors)
|
||||
}
|
||||
|
||||
// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
|
||||
func (g *APIGroupVersion) newInstaller() *APIInstaller {
|
||||
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
|
||||
installer := &APIInstaller{
|
||||
group: g,
|
||||
prefix: prefix,
|
||||
minRequestTimeout: g.MinRequestTimeout,
|
||||
}
|
||||
return installer
|
||||
}
|
||||
|
||||
// staticLister implements the APIResourceLister interface
|
||||
type staticLister struct {
|
||||
list []metav1.APIResource
|
||||
}
|
||||
|
||||
func (s staticLister) ListAPIResources() []metav1.APIResource {
|
||||
return s.list
|
||||
}
|
||||
|
||||
var _ handlers.APIResourceLister = &staticLister{}
|
89
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/BUILD
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["rest_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//pkg/util/strategicpatch:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/evanphx/json-patch",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/diff",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"discovery.go",
|
||||
"doc.go",
|
||||
"proxy.go",
|
||||
"rest.go",
|
||||
"watch.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers/responsewriters:go_default_library",
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//pkg/util:go_default_library",
|
||||
"//pkg/util/httpstream:go_default_library",
|
||||
"//pkg/util/proxy:go_default_library",
|
||||
"//pkg/util/strategicpatch:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/evanphx/json-patch",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:golang.org/x/net/websocket",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/net",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
"//vendor:k8s.io/apiserver/pkg/handlers/negotiation",
|
||||
"//vendor:k8s.io/apiserver/pkg/httplog",
|
||||
"//vendor:k8s.io/apiserver/pkg/metrics",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
"//vendor:k8s.io/apiserver/pkg/util/wsstream",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/genericapiserver/api/handlers/responsewriters:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
54
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/discovery.go
generated
vendored
Normal file
54
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/discovery.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
)
|
||||
|
||||
type APIResourceLister interface {
|
||||
ListAPIResources() []metav1.APIResource
|
||||
}
|
||||
|
||||
// RootAPIHandler returns a handler which will list the provided groups and versions as available.
|
||||
func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []metav1.APIGroup) restful.RouteFunction {
|
||||
return func(req *restful.Request, resp *restful.Response) {
|
||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIGroupList{Groups: f(req)})
|
||||
}
|
||||
}
|
||||
|
||||
// GroupHandler returns a handler which will return the api.GroupAndVersion of
|
||||
// the group.
|
||||
func GroupHandler(s runtime.NegotiatedSerializer, group metav1.APIGroup) restful.RouteFunction {
|
||||
return func(req *restful.Request, resp *restful.Response) {
|
||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &group)
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedResourcesHandler returns a handler which will list the provided resources as available.
|
||||
func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, lister APIResourceLister) restful.RouteFunction {
|
||||
return func(req *restful.Request, resp *restful.Response) {
|
||||
responsewriters.WriteObjectNegotiated(s, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &metav1.APIResourceList{GroupVersion: groupVersion.String(), APIResources: lister.ListAPIResources()})
|
||||
}
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package handlers contains HTTP handlers to implement the apiserver APIs.
|
||||
package handlers // import "k8s.io/kubernetes/pkg/genericapiserver/api/handlers"
|
283
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/proxy.go
generated
vendored
Normal file
283
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/proxy.go
generated
vendored
Normal file
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/httplog"
|
||||
"k8s.io/apiserver/pkg/metrics"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
proxyutil "k8s.io/kubernetes/pkg/util/proxy"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// ProxyHandler provides a http.Handler which will proxy traffic to locations
|
||||
// specified by items implementing Redirector.
|
||||
type ProxyHandler struct {
|
||||
Prefix string
|
||||
Storage map[string]rest.Storage
|
||||
Serializer runtime.NegotiatedSerializer
|
||||
Mapper request.RequestContextMapper
|
||||
}
|
||||
|
||||
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
proxyHandlerTraceID := rand.Int63()
|
||||
|
||||
var verb string
|
||||
var apiResource string
|
||||
var httpCode int
|
||||
reqStart := time.Now()
|
||||
defer func() {
|
||||
metrics.Monitor(&verb, &apiResource,
|
||||
net.GetHTTPClient(req),
|
||||
w.Header().Get("Content-Type"),
|
||||
httpCode, reqStart)
|
||||
}()
|
||||
|
||||
ctx, ok := r.Mapper.Get(req)
|
||||
if !ok {
|
||||
responsewriters.InternalError(w, req, errors.New("Error getting request context"))
|
||||
httpCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
responsewriters.InternalError(w, req, errors.New("Error getting RequestInfo from context"))
|
||||
httpCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
if !requestInfo.IsResourceRequest {
|
||||
responsewriters.NotFound(w, req)
|
||||
httpCode = http.StatusNotFound
|
||||
return
|
||||
}
|
||||
verb = requestInfo.Verb
|
||||
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
|
||||
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
if len(parts) < 2 {
|
||||
responsewriters.NotFound(w, req)
|
||||
httpCode = http.StatusNotFound
|
||||
return
|
||||
}
|
||||
id := parts[1]
|
||||
remainder := ""
|
||||
if len(parts) > 2 {
|
||||
proxyParts := parts[2:]
|
||||
remainder = strings.Join(proxyParts, "/")
|
||||
if strings.HasSuffix(req.URL.Path, "/") {
|
||||
// The original path had a trailing slash, which has been stripped
|
||||
// by KindAndNamespace(). We should add it back because some
|
||||
// servers (like etcd) require it.
|
||||
remainder = remainder + "/"
|
||||
}
|
||||
}
|
||||
storage, ok := r.Storage[resource]
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
|
||||
responsewriters.NotFound(w, req)
|
||||
httpCode = http.StatusNotFound
|
||||
return
|
||||
}
|
||||
apiResource = resource
|
||||
|
||||
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
|
||||
|
||||
redirector, ok := storage.(rest.Redirector)
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
||||
httpCode = responsewriters.ErrorNegotiated(apierrors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.Serializer, gv, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
location, roundTripper, err := redirector.ResourceLocation(ctx, id)
|
||||
if err != nil {
|
||||
httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err)
|
||||
httpCode = responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||
return
|
||||
}
|
||||
if location == nil {
|
||||
httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
|
||||
responsewriters.NotFound(w, req)
|
||||
httpCode = http.StatusNotFound
|
||||
return
|
||||
}
|
||||
|
||||
if roundTripper != nil {
|
||||
glog.V(5).Infof("[%x: %v] using transport %T...", proxyHandlerTraceID, req.URL, roundTripper)
|
||||
}
|
||||
|
||||
// Default to http
|
||||
if location.Scheme == "" {
|
||||
location.Scheme = "http"
|
||||
}
|
||||
// Add the subpath
|
||||
if len(remainder) > 0 {
|
||||
location.Path = singleJoiningSlash(location.Path, remainder)
|
||||
}
|
||||
// Start with anything returned from the storage, and add the original request's parameters
|
||||
values := location.Query()
|
||||
for k, vs := range req.URL.Query() {
|
||||
for _, v := range vs {
|
||||
values.Add(k, v)
|
||||
}
|
||||
}
|
||||
location.RawQuery = values.Encode()
|
||||
|
||||
newReq, err := http.NewRequest(req.Method, location.String(), req.Body)
|
||||
if err != nil {
|
||||
httpCode = responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||
return
|
||||
}
|
||||
httpCode = http.StatusOK
|
||||
newReq.Header = req.Header
|
||||
newReq.ContentLength = req.ContentLength
|
||||
// Copy the TransferEncoding is for future-proofing. Currently Go only supports "chunked" and
|
||||
// it can determine the TransferEncoding based on ContentLength and the Body.
|
||||
newReq.TransferEncoding = req.TransferEncoding
|
||||
|
||||
// TODO convert this entire proxy to an UpgradeAwareProxy similar to
|
||||
// https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go.
|
||||
// That proxy needs to be modified to support multiple backends, not just 1.
|
||||
if r.tryUpgrade(w, req, newReq, location, roundTripper, gv) {
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/"
|
||||
// This is essentially a hack for http://issue.k8s.io/4958.
|
||||
// Note: Keep this code after tryUpgrade to not break that flow.
|
||||
if len(parts) == 2 && !strings.HasSuffix(req.URL.Path, "/") {
|
||||
var queryPart string
|
||||
if len(req.URL.RawQuery) > 0 {
|
||||
queryPart = "?" + req.URL.RawQuery
|
||||
}
|
||||
w.Header().Set("Location", req.URL.Path+"/"+queryPart)
|
||||
w.WriteHeader(http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
glog.V(4).Infof("[%x] Beginning proxy %s...", proxyHandlerTraceID, req.URL)
|
||||
defer func() {
|
||||
glog.V(4).Infof("[%x] Proxy %v finished %v.", proxyHandlerTraceID, req.URL, time.Now().Sub(start))
|
||||
}()
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host})
|
||||
alreadyRewriting := false
|
||||
if roundTripper != nil {
|
||||
_, alreadyRewriting = roundTripper.(*proxyutil.Transport)
|
||||
glog.V(5).Infof("[%x] Not making a rewriting transport for proxy %s...", proxyHandlerTraceID, req.URL)
|
||||
}
|
||||
if !alreadyRewriting {
|
||||
glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL)
|
||||
prepend := path.Join(r.Prefix, resource, id)
|
||||
if len(namespace) > 0 {
|
||||
prepend = path.Join(r.Prefix, "namespaces", namespace, resource, id)
|
||||
}
|
||||
pTransport := &proxyutil.Transport{
|
||||
Scheme: req.URL.Scheme,
|
||||
Host: req.URL.Host,
|
||||
PathPrepend: prepend,
|
||||
RoundTripper: roundTripper,
|
||||
}
|
||||
roundTripper = pTransport
|
||||
}
|
||||
proxy.Transport = roundTripper
|
||||
proxy.FlushInterval = 200 * time.Millisecond
|
||||
proxy.ServeHTTP(w, newReq)
|
||||
}
|
||||
|
||||
// tryUpgrade returns true if the request was handled.
|
||||
func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Request, location *url.URL, transport http.RoundTripper, gv schema.GroupVersion) bool {
|
||||
if !httpstream.IsUpgradeRequest(req) {
|
||||
return false
|
||||
}
|
||||
backendConn, err := proxyutil.DialURL(location, transport)
|
||||
if err != nil {
|
||||
responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||
return true
|
||||
}
|
||||
defer backendConn.Close()
|
||||
|
||||
// TODO should we use _ (a bufio.ReadWriter) instead of requestHijackedConn
|
||||
// when copying between the client and the backend? Docker doesn't when they
|
||||
// hijack, just for reference...
|
||||
requestHijackedConn, _, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||
return true
|
||||
}
|
||||
defer requestHijackedConn.Close()
|
||||
|
||||
if err = newReq.Write(backendConn); err != nil {
|
||||
responsewriters.ErrorNegotiated(err, r.Serializer, gv, w, req)
|
||||
return true
|
||||
}
|
||||
|
||||
done := make(chan struct{}, 2)
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(backendConn, requestHijackedConn)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
glog.Errorf("Error proxying data from client to backend: %v", err)
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(requestHijackedConn, backendConn)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
glog.Errorf("Error proxying data from backend to client: %v", err)
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
<-done
|
||||
return true
|
||||
}
|
||||
|
||||
// borrowed from net/http/httputil/reverseproxy.go
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a + b[1:]
|
||||
case !aslash && !bslash:
|
||||
return a + "/" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
58
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/BUILD
generated
vendored
Normal file
58
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["status_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"errors.go",
|
||||
"status.go",
|
||||
"writers.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//pkg/storage:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
"//vendor:k8s.io/apiserver/pkg/handlers/negotiation",
|
||||
"//vendor:k8s.io/apiserver/pkg/util/flushwriter",
|
||||
"//vendor:k8s.io/apiserver/pkg/util/wsstream",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package responsewriters containers helpers to write responses in HTTP handlers.
|
||||
package responsewriters // import "k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
83
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/errors.go
generated
vendored
Normal file
83
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package responsewriters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// BadGatewayError renders a simple bad gateway error.
|
||||
func BadGatewayError(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
fmt.Fprintf(w, "Bad Gateway: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// Forbidden renders a simple forbidden error
|
||||
func Forbidden(attributes authorizer.Attributes, w http.ResponseWriter, req *http.Request, reason string) {
|
||||
msg := forbiddenMessage(attributes)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
|
||||
if len(reason) == 0 {
|
||||
fmt.Fprintf(w, "%s", msg)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s: %q", msg, reason)
|
||||
}
|
||||
}
|
||||
|
||||
func forbiddenMessage(attributes authorizer.Attributes) string {
|
||||
username := ""
|
||||
if user := attributes.GetUser(); user != nil {
|
||||
username = user.GetName()
|
||||
}
|
||||
|
||||
resource := attributes.GetResource()
|
||||
if group := attributes.GetAPIGroup(); len(group) > 0 {
|
||||
resource = resource + "." + group
|
||||
}
|
||||
if subresource := attributes.GetSubresource(); len(subresource) > 0 {
|
||||
resource = resource + "/" + subresource
|
||||
}
|
||||
|
||||
if ns := attributes.GetNamespace(); len(ns) > 0 {
|
||||
return fmt.Sprintf("User %q cannot %s %s in the namespace %q.", username, attributes.GetVerb(), resource, ns)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("User %q cannot %s %s at the cluster scope.", username, attributes.GetVerb(), resource)
|
||||
}
|
||||
|
||||
// InternalError renders a simple internal error
|
||||
func InternalError(w http.ResponseWriter, req *http.Request, err error) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Internal Server Error: %#v: %v", req.RequestURI, err)
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
|
||||
// NotFound renders a simple not found error.
|
||||
func NotFound(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(w, "Not Found: %#v", req.RequestURI)
|
||||
}
|
70
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/status.go
generated
vendored
Executable file
70
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/status.go
generated
vendored
Executable file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package responsewriters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/storage"
|
||||
)
|
||||
|
||||
// statusError is an object that can be converted into an metav1.Status
|
||||
type statusError interface {
|
||||
Status() metav1.Status
|
||||
}
|
||||
|
||||
// apiStatus converts an error to an metav1.Status object.
|
||||
func apiStatus(err error) *metav1.Status {
|
||||
switch t := err.(type) {
|
||||
case statusError:
|
||||
status := t.Status()
|
||||
if len(status.Status) == 0 {
|
||||
status.Status = metav1.StatusFailure
|
||||
}
|
||||
if status.Code == 0 {
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
status.Code = http.StatusOK
|
||||
case metav1.StatusFailure:
|
||||
status.Code = http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
//TODO: check for invalid responses
|
||||
return &status
|
||||
default:
|
||||
status := http.StatusInternalServerError
|
||||
switch {
|
||||
//TODO: replace me with NewConflictErr
|
||||
case storage.IsTestFailed(err):
|
||||
status = http.StatusConflict
|
||||
}
|
||||
// Log errors that were not converted to an error status
|
||||
// by REST storage - these typically indicate programmer
|
||||
// error by not using pkg/api/errors, or unexpected failure
|
||||
// cases.
|
||||
runtime.HandleError(fmt.Errorf("apiserver received an error that is not an metav1.Status: %v", err))
|
||||
return &metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: int32(status),
|
||||
Reason: metav1.StatusReasonUnknown,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
73
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/status_test.go
generated
vendored
Normal file
73
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/status_test.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package responsewriters
|
||||
|
||||
import (
|
||||
stderrs "errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
func TestAPIStatus(t *testing.T) {
|
||||
cases := map[error]metav1.Status{
|
||||
errors.NewNotFound(schema.GroupResource{Group: "legacy.kubernetes.io", Resource: "foos"}, "bar"): {
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: "foos.legacy.kubernetes.io \"bar\" not found",
|
||||
Details: &metav1.StatusDetails{
|
||||
Group: "legacy.kubernetes.io",
|
||||
Kind: "foos",
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
errors.NewAlreadyExists(api.Resource("foos"), "bar"): {
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: "AlreadyExists",
|
||||
Message: "foos \"bar\" already exists",
|
||||
Details: &metav1.StatusDetails{
|
||||
Group: "",
|
||||
Kind: "foos",
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
errors.NewConflict(api.Resource("foos"), "bar", stderrs.New("failure")): {
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusConflict,
|
||||
Reason: "Conflict",
|
||||
Message: "Operation cannot be fulfilled on foos \"bar\": failure",
|
||||
Details: &metav1.StatusDetails{
|
||||
Group: "",
|
||||
Kind: "foos",
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
for k, v := range cases {
|
||||
actual := apiStatus(k)
|
||||
if !reflect.DeepEqual(actual, &v) {
|
||||
t.Errorf("%s: Expected %#v, Got %#v", k, v, actual)
|
||||
}
|
||||
}
|
||||
}
|
138
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/writers.go
generated
vendored
Normal file
138
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters/writers.go
generated
vendored
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package responsewriters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/util/flushwriter"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
)
|
||||
|
||||
// WriteObject renders a returned runtime.Object to the response as a stream or an encoded object. If the object
|
||||
// returned by the response implements rest.ResourceStreamer that interface will be used to render the
|
||||
// response. The Accept header and current API version will be passed in, and the output will be copied
|
||||
// directly to the response body. If content type is returned it is used, otherwise the content type will
|
||||
// be "application/octet-stream". All other objects are sent to standard JSON serialization.
|
||||
func WriteObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
|
||||
stream, ok := object.(rest.ResourceStreamer)
|
||||
if !ok {
|
||||
WriteObjectNegotiated(s, gv, w, req, statusCode, object)
|
||||
return
|
||||
}
|
||||
|
||||
out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept"))
|
||||
if err != nil {
|
||||
ErrorNegotiated(err, s, gv, w, req)
|
||||
return
|
||||
}
|
||||
if out == nil {
|
||||
// No output provided - return StatusNoContent
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
if wsstream.IsWebSocketRequest(req) {
|
||||
r := wsstream.NewReader(out, true, wsstream.NewDefaultReaderProtocols())
|
||||
if err := r.Copy(w, req); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(contentType) == 0 {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.WriteHeader(statusCode)
|
||||
writer := w.(io.Writer)
|
||||
if flush {
|
||||
writer = flushwriter.Wrap(w)
|
||||
}
|
||||
io.Copy(writer, out)
|
||||
}
|
||||
|
||||
// WriteObjectNegotiated renders an object in the content type negotiated by the client
|
||||
func WriteObjectNegotiated(s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
|
||||
serializer, err := negotiation.NegotiateOutputSerializer(req, s)
|
||||
if err != nil {
|
||||
status := apiStatus(err)
|
||||
WriteRawJSON(int(status.Code), status, w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", serializer.MediaType)
|
||||
w.WriteHeader(statusCode)
|
||||
|
||||
encoder := s.EncoderForVersion(serializer.Serializer, gv)
|
||||
if err := encoder.Encode(object, w); err != nil {
|
||||
errorJSONFatal(err, encoder, w)
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorNegotiated renders an error to the response. Returns the HTTP status code of the error.
|
||||
func ErrorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request) int {
|
||||
status := apiStatus(err)
|
||||
code := int(status.Code)
|
||||
// when writing an error, check to see if the status indicates a retry after period
|
||||
if status.Details != nil && status.Details.RetryAfterSeconds > 0 {
|
||||
delay := strconv.Itoa(int(status.Details.RetryAfterSeconds))
|
||||
w.Header().Set("Retry-After", delay)
|
||||
}
|
||||
WriteObjectNegotiated(s, gv, w, req, code, status)
|
||||
return code
|
||||
}
|
||||
|
||||
// errorJSONFatal renders an error to the response, and if codec fails will render plaintext.
|
||||
// Returns the HTTP status code of the error.
|
||||
func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int {
|
||||
utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
|
||||
status := apiStatus(err)
|
||||
code := int(status.Code)
|
||||
output, err := runtime.Encode(codec, status)
|
||||
if err != nil {
|
||||
w.WriteHeader(code)
|
||||
fmt.Fprintf(w, "%s: %s", status.Reason, status.Message)
|
||||
return code
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
w.Write(output)
|
||||
return code
|
||||
}
|
||||
|
||||
// WriteRawJSON writes a non-API object in JSON.
|
||||
func WriteRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
|
||||
output, err := json.MarshalIndent(object, "", " ")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(output)
|
||||
}
|
1130
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/rest.go
generated
vendored
Normal file
1130
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/rest.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
487
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/rest_test.go
generated
vendored
Normal file
487
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/rest_test.go
generated
vendored
Normal file
|
@ -0,0 +1,487 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/evanphx/json-patch"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
"k8s.io/kubernetes/pkg/util/strategicpatch"
|
||||
)
|
||||
|
||||
type testPatchType struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
TestPatchSubType `json:",inline"`
|
||||
}
|
||||
|
||||
// We explicitly make it public as private types doesn't
|
||||
// work correctly with json inlined types.
|
||||
type TestPatchSubType struct {
|
||||
StringField string `json:"theField"`
|
||||
}
|
||||
|
||||
func (obj *testPatchType) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta }
|
||||
|
||||
func TestPatchAnonymousField(t *testing.T) {
|
||||
originalJS := `{"kind":"testPatchType","theField":"my-value"}`
|
||||
patch := `{"theField": "changed!"}`
|
||||
expectedJS := `{"kind":"testPatchType","theField":"changed!"}`
|
||||
|
||||
actualBytes, err := getPatchedJS(api.StrategicMergePatchType, []byte(originalJS), []byte(patch), &testPatchType{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if string(actualBytes) != expectedJS {
|
||||
t.Errorf("expected %v, got %v", expectedJS, string(actualBytes))
|
||||
}
|
||||
}
|
||||
|
||||
type testPatcher struct {
|
||||
t *testing.T
|
||||
|
||||
// startingPod is used for the first Update
|
||||
startingPod *api.Pod
|
||||
|
||||
// updatePod is the pod that is used for conflict comparison and used for subsequent Update calls
|
||||
updatePod *api.Pod
|
||||
|
||||
numUpdates int
|
||||
}
|
||||
|
||||
func (p *testPatcher) New() runtime.Object {
|
||||
return &api.Pod{}
|
||||
}
|
||||
|
||||
func (p *testPatcher) Update(ctx request.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) {
|
||||
currentPod := p.startingPod
|
||||
if p.numUpdates > 0 {
|
||||
currentPod = p.updatePod
|
||||
}
|
||||
p.numUpdates++
|
||||
|
||||
obj, err := objInfo.UpdatedObject(ctx, currentPod)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
inPod := obj.(*api.Pod)
|
||||
if inPod.ResourceVersion != p.updatePod.ResourceVersion {
|
||||
return nil, false, apierrors.NewConflict(api.Resource("pods"), inPod.Name, fmt.Errorf("existing %v, new %v", p.updatePod.ResourceVersion, inPod.ResourceVersion))
|
||||
}
|
||||
|
||||
return inPod, false, nil
|
||||
}
|
||||
|
||||
func (p *testPatcher) Get(ctx request.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
p.t.Fatal("Unexpected call to testPatcher.Get")
|
||||
return nil, errors.New("Unexpected call to testPatcher.Get")
|
||||
}
|
||||
|
||||
type testNamer struct {
|
||||
namespace string
|
||||
name string
|
||||
}
|
||||
|
||||
func (p *testNamer) Namespace(req *restful.Request) (namespace string, err error) {
|
||||
return p.namespace, nil
|
||||
}
|
||||
|
||||
// Name returns the name from the request, and an optional namespace value if this is a namespace
|
||||
// scoped call. An error is returned if the name is not available.
|
||||
func (p *testNamer) Name(req *restful.Request) (namespace, name string, err error) {
|
||||
return p.namespace, p.name, nil
|
||||
}
|
||||
|
||||
// ObjectName returns the namespace and name from an object if they exist, or an error if the object
|
||||
// does not support names.
|
||||
func (p *testNamer) ObjectName(obj runtime.Object) (namespace, name string, err error) {
|
||||
return p.namespace, p.name, nil
|
||||
}
|
||||
|
||||
// SetSelfLink sets the provided URL onto the object. The method should return nil if the object
|
||||
// does not support selfLinks.
|
||||
func (p *testNamer) SetSelfLink(obj runtime.Object, url string) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// GenerateLink creates a path and query for a given runtime object that represents the canonical path.
|
||||
func (p *testNamer) GenerateLink(req *restful.Request, obj runtime.Object) (uri string, err error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
// GenerateLink creates a path and query for a list that represents the canonical path.
|
||||
func (p *testNamer) GenerateListLink(req *restful.Request) (uri string, err error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
type patchTestCase struct {
|
||||
name string
|
||||
|
||||
// admission chain to use, nil is fine
|
||||
admit updateAdmissionFunc
|
||||
|
||||
// startingPod is used as the starting point for the first Update
|
||||
startingPod *api.Pod
|
||||
// changedPod is the "destination" pod for the patch. The test will create a patch from the startingPod to the changedPod
|
||||
// to use when calling the patch operation
|
||||
changedPod *api.Pod
|
||||
// updatePod is the pod that is used for conflict comparison and as the starting point for the second Update
|
||||
updatePod *api.Pod
|
||||
|
||||
// expectedPod is the pod that you expect to get back after the patch is complete
|
||||
expectedPod *api.Pod
|
||||
expectedError string
|
||||
}
|
||||
|
||||
func (tc *patchTestCase) Run(t *testing.T) {
|
||||
t.Logf("Starting test %s", tc.name)
|
||||
|
||||
namespace := tc.startingPod.Namespace
|
||||
name := tc.startingPod.Name
|
||||
|
||||
codec := testapi.Default.Codec()
|
||||
admit := tc.admit
|
||||
if admit == nil {
|
||||
admit = func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
testPatcher := &testPatcher{}
|
||||
testPatcher.t = t
|
||||
testPatcher.startingPod = tc.startingPod
|
||||
testPatcher.updatePod = tc.updatePod
|
||||
|
||||
ctx := request.NewDefaultContext()
|
||||
ctx = request.WithNamespace(ctx, namespace)
|
||||
|
||||
namer := &testNamer{namespace, name}
|
||||
copier := runtime.ObjectCopier(api.Scheme)
|
||||
resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
versionedObj := &v1.Pod{}
|
||||
|
||||
for _, patchType := range []api.PatchType{api.JSONPatchType, api.MergePatchType, api.StrategicMergePatchType} {
|
||||
// TODO SUPPORT THIS!
|
||||
if patchType == api.JSONPatchType {
|
||||
continue
|
||||
}
|
||||
t.Logf("Working with patchType %v", patchType)
|
||||
|
||||
originalObjJS, err := runtime.Encode(codec, tc.startingPod)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
changedJS, err := runtime.Encode(codec, tc.changedPod)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
patch := []byte{}
|
||||
switch patchType {
|
||||
case api.JSONPatchType:
|
||||
continue
|
||||
|
||||
case api.StrategicMergePatchType:
|
||||
patch, err = strategicpatch.CreateStrategicMergePatch(originalObjJS, changedJS, versionedObj)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
case api.MergePatchType:
|
||||
patch, err = jsonpatch.CreateMergePatch(originalObjJS, changedJS)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resultObj, err := patchResource(ctx, admit, 1*time.Second, versionedObj, testPatcher, name, patchType, patch, namer, copier, resource, codec)
|
||||
if len(tc.expectedError) != 0 {
|
||||
if err == nil || err.Error() != tc.expectedError {
|
||||
t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if tc.expectedPod == nil {
|
||||
if resultObj != nil {
|
||||
t.Errorf("%s: unexpected result: %v", tc.name, resultObj)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
resultPod := resultObj.(*api.Pod)
|
||||
|
||||
// roundtrip to get defaulting
|
||||
expectedJS, err := runtime.Encode(codec, tc.expectedPod)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
expectedObj, err := runtime.Decode(codec, expectedJS)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
return
|
||||
}
|
||||
reallyExpectedPod := expectedObj.(*api.Pod)
|
||||
|
||||
if !reflect.DeepEqual(*reallyExpectedPod, *resultPod) {
|
||||
t.Errorf("%s mismatch: %v\n", tc.name, diff.ObjectGoPrintDiff(reallyExpectedPod, resultPod))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPatchResourceWithVersionConflict(t *testing.T) {
|
||||
namespace := "bar"
|
||||
name := "foo"
|
||||
uid := types.UID("uid")
|
||||
fifteen := int64(15)
|
||||
thirty := int64(30)
|
||||
|
||||
tc := &patchTestCase{
|
||||
name: "TestPatchResourceWithVersionConflict",
|
||||
|
||||
startingPod: &api.Pod{},
|
||||
changedPod: &api.Pod{},
|
||||
updatePod: &api.Pod{},
|
||||
|
||||
expectedPod: &api.Pod{},
|
||||
}
|
||||
|
||||
tc.startingPod.Name = name
|
||||
tc.startingPod.Namespace = namespace
|
||||
tc.startingPod.UID = uid
|
||||
tc.startingPod.ResourceVersion = "1"
|
||||
tc.startingPod.APIVersion = "v1"
|
||||
tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen
|
||||
|
||||
tc.changedPod.Name = name
|
||||
tc.changedPod.Namespace = namespace
|
||||
tc.changedPod.UID = uid
|
||||
tc.changedPod.ResourceVersion = "1"
|
||||
tc.changedPod.APIVersion = "v1"
|
||||
tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty
|
||||
|
||||
tc.updatePod.Name = name
|
||||
tc.updatePod.Namespace = namespace
|
||||
tc.updatePod.UID = uid
|
||||
tc.updatePod.ResourceVersion = "2"
|
||||
tc.updatePod.APIVersion = "v1"
|
||||
tc.updatePod.Spec.ActiveDeadlineSeconds = &fifteen
|
||||
tc.updatePod.Spec.NodeName = "anywhere"
|
||||
|
||||
tc.expectedPod.Name = name
|
||||
tc.expectedPod.Namespace = namespace
|
||||
tc.expectedPod.UID = uid
|
||||
tc.expectedPod.ResourceVersion = "2"
|
||||
tc.expectedPod.Spec.ActiveDeadlineSeconds = &thirty
|
||||
tc.expectedPod.Spec.NodeName = "anywhere"
|
||||
|
||||
tc.Run(t)
|
||||
}
|
||||
|
||||
func TestPatchResourceWithConflict(t *testing.T) {
|
||||
namespace := "bar"
|
||||
name := "foo"
|
||||
uid := types.UID("uid")
|
||||
|
||||
tc := &patchTestCase{
|
||||
name: "TestPatchResourceWithConflict",
|
||||
|
||||
startingPod: &api.Pod{},
|
||||
changedPod: &api.Pod{},
|
||||
updatePod: &api.Pod{},
|
||||
|
||||
expectedError: `Operation cannot be fulfilled on pods "foo": existing 2, new 1`,
|
||||
}
|
||||
|
||||
tc.startingPod.Name = name
|
||||
tc.startingPod.Namespace = namespace
|
||||
tc.startingPod.UID = uid
|
||||
tc.startingPod.ResourceVersion = "1"
|
||||
tc.startingPod.APIVersion = "v1"
|
||||
tc.startingPod.Spec.NodeName = "here"
|
||||
|
||||
tc.changedPod.Name = name
|
||||
tc.changedPod.Namespace = namespace
|
||||
tc.changedPod.UID = uid
|
||||
tc.changedPod.ResourceVersion = "1"
|
||||
tc.changedPod.APIVersion = "v1"
|
||||
tc.changedPod.Spec.NodeName = "there"
|
||||
|
||||
tc.updatePod.Name = name
|
||||
tc.updatePod.Namespace = namespace
|
||||
tc.updatePod.UID = uid
|
||||
tc.updatePod.ResourceVersion = "2"
|
||||
tc.updatePod.APIVersion = "v1"
|
||||
tc.updatePod.Spec.NodeName = "anywhere"
|
||||
|
||||
tc.Run(t)
|
||||
}
|
||||
|
||||
func TestPatchWithAdmissionRejection(t *testing.T) {
|
||||
namespace := "bar"
|
||||
name := "foo"
|
||||
uid := types.UID("uid")
|
||||
fifteen := int64(15)
|
||||
thirty := int64(30)
|
||||
|
||||
tc := &patchTestCase{
|
||||
name: "TestPatchWithAdmissionRejection",
|
||||
|
||||
admit: func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
return errors.New("admission failure")
|
||||
},
|
||||
|
||||
startingPod: &api.Pod{},
|
||||
changedPod: &api.Pod{},
|
||||
updatePod: &api.Pod{},
|
||||
|
||||
expectedError: "admission failure",
|
||||
}
|
||||
|
||||
tc.startingPod.Name = name
|
||||
tc.startingPod.Namespace = namespace
|
||||
tc.startingPod.UID = uid
|
||||
tc.startingPod.ResourceVersion = "1"
|
||||
tc.startingPod.APIVersion = "v1"
|
||||
tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen
|
||||
|
||||
tc.changedPod.Name = name
|
||||
tc.changedPod.Namespace = namespace
|
||||
tc.changedPod.UID = uid
|
||||
tc.changedPod.ResourceVersion = "1"
|
||||
tc.changedPod.APIVersion = "v1"
|
||||
tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty
|
||||
|
||||
tc.Run(t)
|
||||
}
|
||||
|
||||
func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) {
|
||||
namespace := "bar"
|
||||
name := "foo"
|
||||
uid := types.UID("uid")
|
||||
fifteen := int64(15)
|
||||
thirty := int64(30)
|
||||
seen := false
|
||||
|
||||
tc := &patchTestCase{
|
||||
name: "TestPatchWithVersionConflictThenAdmissionFailure",
|
||||
|
||||
admit: func(updatedObject runtime.Object, currentObject runtime.Object) error {
|
||||
if seen {
|
||||
return errors.New("admission failure")
|
||||
}
|
||||
|
||||
seen = true
|
||||
return nil
|
||||
},
|
||||
|
||||
startingPod: &api.Pod{},
|
||||
changedPod: &api.Pod{},
|
||||
updatePod: &api.Pod{},
|
||||
|
||||
expectedError: "admission failure",
|
||||
}
|
||||
|
||||
tc.startingPod.Name = name
|
||||
tc.startingPod.Namespace = namespace
|
||||
tc.startingPod.UID = uid
|
||||
tc.startingPod.ResourceVersion = "1"
|
||||
tc.startingPod.APIVersion = "v1"
|
||||
tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen
|
||||
|
||||
tc.changedPod.Name = name
|
||||
tc.changedPod.Namespace = namespace
|
||||
tc.changedPod.UID = uid
|
||||
tc.changedPod.ResourceVersion = "1"
|
||||
tc.changedPod.APIVersion = "v1"
|
||||
tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty
|
||||
|
||||
tc.updatePod.Name = name
|
||||
tc.updatePod.Namespace = namespace
|
||||
tc.updatePod.UID = uid
|
||||
tc.updatePod.ResourceVersion = "2"
|
||||
tc.updatePod.APIVersion = "v1"
|
||||
tc.updatePod.Spec.ActiveDeadlineSeconds = &fifteen
|
||||
tc.updatePod.Spec.NodeName = "anywhere"
|
||||
|
||||
tc.Run(t)
|
||||
}
|
||||
|
||||
func TestHasUID(t *testing.T) {
|
||||
testcases := []struct {
|
||||
obj runtime.Object
|
||||
hasUID bool
|
||||
}{
|
||||
{obj: nil, hasUID: false},
|
||||
{obj: &api.Pod{}, hasUID: false},
|
||||
{obj: nil, hasUID: false},
|
||||
{obj: runtime.Object(nil), hasUID: false},
|
||||
{obj: &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("A")}}, hasUID: true},
|
||||
}
|
||||
for i, tc := range testcases {
|
||||
actual, err := hasUID(tc.obj)
|
||||
if err != nil {
|
||||
t.Errorf("%d: unexpected error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if tc.hasUID != actual {
|
||||
t.Errorf("%d: expected %v, got %v", i, tc.hasUID, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTimeout(t *testing.T) {
|
||||
if d := parseTimeout(""); d != 30*time.Second {
|
||||
t.Errorf("blank timeout produces %v", d)
|
||||
}
|
||||
if d := parseTimeout("not a timeout"); d != 30*time.Second {
|
||||
t.Errorf("bad timeout produces %v", d)
|
||||
}
|
||||
if d := parseTimeout("10s"); d != 10*time.Second {
|
||||
t.Errorf("10s timeout produced: %v", d)
|
||||
}
|
||||
}
|
292
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/watch.go
generated
vendored
Executable file
292
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/handlers/watch.go
generated
vendored
Executable file
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/httplog"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
// nothing will ever be sent down this channel
|
||||
var neverExitWatch <-chan time.Time = make(chan time.Time)
|
||||
|
||||
// timeoutFactory abstracts watch timeout logic for testing
|
||||
type TimeoutFactory interface {
|
||||
TimeoutCh() (<-chan time.Time, func() bool)
|
||||
}
|
||||
|
||||
// realTimeoutFactory implements timeoutFactory
|
||||
type realTimeoutFactory struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// TimeoutChan returns a channel which will receive something when the watch times out,
|
||||
// and a cleanup function to call when this happens.
|
||||
func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) {
|
||||
if w.timeout == 0 {
|
||||
return neverExitWatch, func() bool { return false }
|
||||
}
|
||||
t := time.NewTimer(w.timeout)
|
||||
return t.C, t.Stop
|
||||
}
|
||||
|
||||
// serveWatch handles serving requests to the server
|
||||
// TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled.
|
||||
func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) {
|
||||
// negotiate for the stream serializer
|
||||
serializer, err := negotiation.NegotiateOutputStreamSerializer(req.Request, scope.Serializer)
|
||||
if err != nil {
|
||||
scope.err(err, res.ResponseWriter, req.Request)
|
||||
return
|
||||
}
|
||||
framer := serializer.StreamSerializer.Framer
|
||||
streamSerializer := serializer.StreamSerializer.Serializer
|
||||
embedded := serializer.Serializer
|
||||
if framer == nil {
|
||||
scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), res.ResponseWriter, req.Request)
|
||||
return
|
||||
}
|
||||
encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion())
|
||||
|
||||
useTextFraming := serializer.EncodesAsText
|
||||
|
||||
// find the embedded serializer matching the media type
|
||||
embeddedEncoder := scope.Serializer.EncoderForVersion(embedded, scope.Kind.GroupVersion())
|
||||
|
||||
// TODO: next step, get back mediaTypeOptions from negotiate and return the exact value here
|
||||
mediaType := serializer.MediaType
|
||||
if mediaType != runtime.ContentTypeJSON {
|
||||
mediaType += ";stream=watch"
|
||||
}
|
||||
|
||||
server := &WatchServer{
|
||||
Watching: watcher,
|
||||
Scope: scope,
|
||||
|
||||
UseTextFraming: useTextFraming,
|
||||
MediaType: mediaType,
|
||||
Framer: framer,
|
||||
Encoder: encoder,
|
||||
EmbeddedEncoder: embeddedEncoder,
|
||||
Fixup: func(obj runtime.Object) {
|
||||
if err := setSelfLink(obj, req, scope.Namer); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to set link for object %v: %v", reflect.TypeOf(obj), err))
|
||||
}
|
||||
},
|
||||
|
||||
TimeoutFactory: &realTimeoutFactory{timeout},
|
||||
}
|
||||
|
||||
server.ServeHTTP(res.ResponseWriter, req.Request)
|
||||
}
|
||||
|
||||
// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
|
||||
type WatchServer struct {
|
||||
Watching watch.Interface
|
||||
Scope RequestScope
|
||||
|
||||
// true if websocket messages should use text framing (as opposed to binary framing)
|
||||
UseTextFraming bool
|
||||
// the media type this watch is being served with
|
||||
MediaType string
|
||||
// used to frame the watch stream
|
||||
Framer runtime.Framer
|
||||
// used to encode the watch stream event itself
|
||||
Encoder runtime.Encoder
|
||||
// used to encode the nested object in the watch stream
|
||||
EmbeddedEncoder runtime.Encoder
|
||||
Fixup func(runtime.Object)
|
||||
|
||||
TimeoutFactory TimeoutFactory
|
||||
}
|
||||
|
||||
// ServeHTTP serves a series of encoded events via HTTP with Transfer-Encoding: chunked
|
||||
// or over a websocket connection.
|
||||
func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
w = httplog.Unlogged(w)
|
||||
|
||||
if wsstream.IsWebSocketRequest(req) {
|
||||
w.Header().Set("Content-Type", s.MediaType)
|
||||
websocket.Handler(s.HandleWS).ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
cn, ok := w.(http.CloseNotifier)
|
||||
if !ok {
|
||||
err := fmt.Errorf("unable to start watch - can't get http.CloseNotifier: %#v", w)
|
||||
utilruntime.HandleError(err)
|
||||
s.Scope.err(errors.NewInternalError(err), w, req)
|
||||
return
|
||||
}
|
||||
flusher, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w)
|
||||
utilruntime.HandleError(err)
|
||||
s.Scope.err(errors.NewInternalError(err), w, req)
|
||||
return
|
||||
}
|
||||
|
||||
framer := s.Framer.NewFrameWriter(w)
|
||||
if framer == nil {
|
||||
// programmer error
|
||||
err := fmt.Errorf("no stream framing support is available for media type %q", s.MediaType)
|
||||
utilruntime.HandleError(err)
|
||||
s.Scope.err(errors.NewBadRequest(err.Error()), w, req)
|
||||
return
|
||||
}
|
||||
e := streaming.NewEncoder(framer, s.Encoder)
|
||||
|
||||
// ensure the connection times out
|
||||
timeoutCh, cleanup := s.TimeoutFactory.TimeoutCh()
|
||||
defer cleanup()
|
||||
defer s.Watching.Stop()
|
||||
|
||||
// begin the stream
|
||||
w.Header().Set("Content-Type", s.MediaType)
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
flusher.Flush()
|
||||
|
||||
var unknown runtime.Unknown
|
||||
internalEvent := &metav1.InternalEvent{}
|
||||
buf := &bytes.Buffer{}
|
||||
ch := s.Watching.ResultChan()
|
||||
for {
|
||||
select {
|
||||
case <-cn.CloseNotify():
|
||||
return
|
||||
case <-timeoutCh:
|
||||
return
|
||||
case event, ok := <-ch:
|
||||
if !ok {
|
||||
// End of results.
|
||||
return
|
||||
}
|
||||
|
||||
obj := event.Object
|
||||
s.Fixup(obj)
|
||||
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||
// unexpected error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// ContentType is not required here because we are defaulting to the serializer
|
||||
// type
|
||||
unknown.Raw = buf.Bytes()
|
||||
event.Object = &unknown
|
||||
|
||||
// the internal event will be versioned by the encoder
|
||||
*internalEvent = metav1.InternalEvent(event)
|
||||
if err := e.Encode(internalEvent); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v (%#v)", err, e))
|
||||
// client disconnect.
|
||||
return
|
||||
}
|
||||
if len(ch) == 0 {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HandleWS implements a websocket handler.
|
||||
func (s *WatchServer) HandleWS(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer utilruntime.HandleCrash()
|
||||
// This blocks until the connection is closed.
|
||||
// Client should not send anything.
|
||||
wsstream.IgnoreReceives(ws, 0)
|
||||
// Once the client closes, we should also close
|
||||
close(done)
|
||||
}()
|
||||
|
||||
var unknown runtime.Unknown
|
||||
internalEvent := &metav1.InternalEvent{}
|
||||
buf := &bytes.Buffer{}
|
||||
streamBuf := &bytes.Buffer{}
|
||||
ch := s.Watching.ResultChan()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
s.Watching.Stop()
|
||||
return
|
||||
case event, ok := <-ch:
|
||||
if !ok {
|
||||
// End of results.
|
||||
return
|
||||
}
|
||||
obj := event.Object
|
||||
s.Fixup(obj)
|
||||
if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
|
||||
// unexpected error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// ContentType is not required here because we are defaulting to the serializer
|
||||
// type
|
||||
unknown.Raw = buf.Bytes()
|
||||
event.Object = &unknown
|
||||
|
||||
// the internal event will be versioned by the encoder
|
||||
*internalEvent = metav1.InternalEvent(event)
|
||||
if err := s.Encoder.Encode(internalEvent, streamBuf); err != nil {
|
||||
// encoding error
|
||||
utilruntime.HandleError(fmt.Errorf("unable to encode event: %v", err))
|
||||
s.Watching.Stop()
|
||||
return
|
||||
}
|
||||
if s.UseTextFraming {
|
||||
if err := websocket.Message.Send(ws, streamBuf.String()); err != nil {
|
||||
// Client disconnect.
|
||||
s.Watching.Stop()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := websocket.Message.Send(ws, streamBuf.Bytes()); err != nil {
|
||||
// Client disconnect.
|
||||
s.Watching.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
buf.Reset()
|
||||
streamBuf.Reset()
|
||||
}
|
||||
}
|
||||
}
|
1109
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/installer.go
generated
vendored
Normal file
1109
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/installer.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
128
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/installer_test.go
generated
vendored
Normal file
128
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/installer_test.go
generated
vendored
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
)
|
||||
|
||||
func TestScopeNamingGenerateLink(t *testing.T) {
|
||||
selfLinker := &setTestSelfLinker{
|
||||
t: t,
|
||||
expectedSet: "/api/v1/namespaces/other/services/foo",
|
||||
name: "foo",
|
||||
namespace: "other",
|
||||
}
|
||||
s := scopeNaming{
|
||||
meta.RESTScopeNamespace,
|
||||
selfLinker,
|
||||
func(name, namespace string) bytes.Buffer {
|
||||
return *bytes.NewBufferString("/api/v1/namespaces/" + namespace + "/services/" + name)
|
||||
},
|
||||
true,
|
||||
}
|
||||
service := &api.Service{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "other",
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
},
|
||||
}
|
||||
_, err := s.GenerateLink(&restful.Request{}, service)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVowel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg rune
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "yes",
|
||||
arg: 'E',
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no",
|
||||
arg: 'n',
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := isVowel(tt.arg); got != tt.want {
|
||||
t.Errorf("%q. IsVowel() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetArticleForNoun(t *testing.T) {
|
||||
type args struct {
|
||||
}
|
||||
tests := []struct {
|
||||
noun string
|
||||
padding string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
noun: "Frog",
|
||||
padding: " ",
|
||||
want: " a ",
|
||||
},
|
||||
{
|
||||
noun: "frogs",
|
||||
padding: " ",
|
||||
want: " ",
|
||||
},
|
||||
{
|
||||
noun: "apple",
|
||||
padding: "",
|
||||
want: "an",
|
||||
},
|
||||
{
|
||||
noun: "Apples",
|
||||
padding: " ",
|
||||
want: " ",
|
||||
},
|
||||
{
|
||||
noun: "Ingress",
|
||||
padding: " ",
|
||||
want: " an ",
|
||||
},
|
||||
{
|
||||
noun: "Class",
|
||||
padding: " ",
|
||||
want: " a ",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := getArticleForNoun(tt.noun, tt.padding); got != tt.want {
|
||||
t.Errorf("%q. GetArticleForNoun() = %v, want %v", tt.noun, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
31
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/openapi/BUILD
generated
vendored
Normal file
31
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/openapi/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["openapi.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/util:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
2
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/openapi/OWNERS
generated
vendored
Executable file
2
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/openapi/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,2 @@
|
|||
reviewers:
|
||||
- mbohlool
|
87
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/openapi/openapi.go
generated
vendored
Normal file
87
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/openapi/openapi.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
var verbs = util.CreateTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
|
||||
|
||||
// ToValidOperationID makes an string a valid op ID (e.g. removing punctuations and whitespaces and make it camel case)
|
||||
func ToValidOperationID(s string, capitalizeFirstLetter bool) string {
|
||||
var buffer bytes.Buffer
|
||||
capitalize := capitalizeFirstLetter
|
||||
for i, r := range s {
|
||||
if unicode.IsLetter(r) || r == '_' || (i != 0 && unicode.IsDigit(r)) {
|
||||
if capitalize {
|
||||
buffer.WriteRune(unicode.ToUpper(r))
|
||||
capitalize = false
|
||||
} else {
|
||||
buffer.WriteRune(r)
|
||||
}
|
||||
} else {
|
||||
capitalize = true
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// GetOperationIDAndTags returns a customize operation ID and a list of tags for kubernetes API server's OpenAPI spec to prevent duplicate IDs.
|
||||
func GetOperationIDAndTags(servePath string, r *restful.Route) (string, []string, error) {
|
||||
op := r.Operation
|
||||
path := r.Path
|
||||
var tags []string
|
||||
// TODO: This is hacky, figure out where this name conflict is created and fix it at the root.
|
||||
if strings.HasPrefix(path, "/apis/extensions/v1beta1/namespaces/{namespace}/") && strings.HasSuffix(op, "ScaleScale") {
|
||||
op = op[:len(op)-10] + strings.Title(strings.Split(path[48:], "/")[0]) + "Scale"
|
||||
}
|
||||
switch servePath {
|
||||
case "/swagger.json":
|
||||
prefix, exists := verbs.GetPrefix(op)
|
||||
if !exists {
|
||||
return op, tags, fmt.Errorf("operation names should start with a verb. Cannot determine operation verb from %v", op)
|
||||
}
|
||||
op = op[len(prefix):]
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
// Assume /api is /apis/core, remove this when we actually server /api/... on /apis/core/...
|
||||
if len(parts) >= 1 && parts[0] == "api" {
|
||||
parts = append([]string{"apis", "core"}, parts[1:]...)
|
||||
}
|
||||
if len(parts) >= 2 && parts[0] == "apis" {
|
||||
prefix = prefix + ToValidOperationID(strings.TrimSuffix(parts[1], ".k8s.io"), prefix != "")
|
||||
tag := ToValidOperationID(strings.TrimSuffix(parts[1], ".k8s.io"), false)
|
||||
if len(parts) > 2 {
|
||||
prefix = prefix + ToValidOperationID(parts[2], prefix != "")
|
||||
tag = tag + "_" + ToValidOperationID(parts[2], false)
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
} else if len(parts) >= 1 {
|
||||
tags = append(tags, ToValidOperationID(parts[0], false))
|
||||
}
|
||||
return prefix + ToValidOperationID(op, prefix != ""), tags, nil
|
||||
default:
|
||||
return op, tags, nil
|
||||
}
|
||||
}
|
569
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/proxy_test.go
generated
vendored
Normal file
569
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/proxy_test.go
generated
vendored
Normal file
|
@ -0,0 +1,569 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
)
|
||||
|
||||
func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) {
|
||||
chunk := func(data []byte) []byte {
|
||||
out := &bytes.Buffer{}
|
||||
chunker := httputil.NewChunkedWriter(out)
|
||||
for _, b := range data {
|
||||
if _, err := chunker.Write([]byte{b}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
chunker.Close()
|
||||
out.Write([]byte("\r\n"))
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
zip := func(data []byte) []byte {
|
||||
out := &bytes.Buffer{}
|
||||
zipper := gzip.NewWriter(out)
|
||||
if _, err := zipper.Write(data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
zipper.Close()
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
sampleData := []byte("abcde")
|
||||
|
||||
table := map[string]struct {
|
||||
reqHeaders http.Header
|
||||
reqBody []byte
|
||||
|
||||
expectedHeaders http.Header
|
||||
expectedBody []byte
|
||||
}{
|
||||
"content-length": {
|
||||
reqHeaders: http.Header{
|
||||
"Content-Length": []string{"5"},
|
||||
},
|
||||
reqBody: sampleData,
|
||||
|
||||
expectedHeaders: http.Header{
|
||||
"Content-Length": []string{"5"},
|
||||
"Content-Encoding": nil, // none set
|
||||
"Transfer-Encoding": nil, // none set
|
||||
},
|
||||
expectedBody: sampleData,
|
||||
},
|
||||
|
||||
"content-length + identity transfer-encoding": {
|
||||
reqHeaders: http.Header{
|
||||
"Content-Length": []string{"5"},
|
||||
"Transfer-Encoding": []string{"identity"},
|
||||
},
|
||||
reqBody: sampleData,
|
||||
|
||||
expectedHeaders: http.Header{
|
||||
"Content-Length": []string{"5"},
|
||||
"Content-Encoding": nil, // none set
|
||||
"Transfer-Encoding": nil, // gets removed
|
||||
},
|
||||
expectedBody: sampleData,
|
||||
},
|
||||
|
||||
"content-length + gzip content-encoding": {
|
||||
reqHeaders: http.Header{
|
||||
"Content-Length": []string{strconv.Itoa(len(zip(sampleData)))},
|
||||
"Content-Encoding": []string{"gzip"},
|
||||
},
|
||||
reqBody: zip(sampleData),
|
||||
|
||||
expectedHeaders: http.Header{
|
||||
"Content-Length": []string{strconv.Itoa(len(zip(sampleData)))},
|
||||
"Content-Encoding": []string{"gzip"},
|
||||
"Transfer-Encoding": nil, // none set
|
||||
},
|
||||
expectedBody: zip(sampleData),
|
||||
},
|
||||
|
||||
"chunked transfer-encoding": {
|
||||
reqHeaders: http.Header{
|
||||
"Transfer-Encoding": []string{"chunked"},
|
||||
},
|
||||
reqBody: chunk(sampleData),
|
||||
|
||||
expectedHeaders: http.Header{
|
||||
"Content-Length": nil, // none set
|
||||
"Content-Encoding": nil, // none set
|
||||
"Transfer-Encoding": nil, // Transfer-Encoding gets removed
|
||||
},
|
||||
expectedBody: sampleData, // sample data is unchunked
|
||||
},
|
||||
|
||||
"chunked transfer-encoding + gzip content-encoding": {
|
||||
reqHeaders: http.Header{
|
||||
"Content-Encoding": []string{"gzip"},
|
||||
"Transfer-Encoding": []string{"chunked"},
|
||||
},
|
||||
reqBody: chunk(zip(sampleData)),
|
||||
|
||||
expectedHeaders: http.Header{
|
||||
"Content-Length": nil, // none set
|
||||
"Content-Encoding": []string{"gzip"},
|
||||
"Transfer-Encoding": nil, // gets removed
|
||||
},
|
||||
expectedBody: zip(sampleData), // sample data is unchunked, but content-encoding is preserved
|
||||
},
|
||||
|
||||
// "Transfer-Encoding: gzip" is not supported by go
|
||||
// See http/transfer.go#fixTransferEncoding (https://golang.org/src/net/http/transfer.go#L427)
|
||||
// Once it is supported, this test case should succeed
|
||||
//
|
||||
// "gzip+chunked transfer-encoding": {
|
||||
// reqHeaders: http.Header{
|
||||
// "Transfer-Encoding": []string{"chunked,gzip"},
|
||||
// },
|
||||
// reqBody: chunk(zip(sampleData)),
|
||||
//
|
||||
// expectedHeaders: http.Header{
|
||||
// "Content-Length": nil, // no content-length headers
|
||||
// "Transfer-Encoding": nil, // Transfer-Encoding gets removed
|
||||
// },
|
||||
// expectedBody: sampleData,
|
||||
// },
|
||||
}
|
||||
|
||||
successfulResponse := "backend passed tests"
|
||||
for k, item := range table {
|
||||
// Start the downstream server
|
||||
downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// Verify headers
|
||||
for header, v := range item.expectedHeaders {
|
||||
if !reflect.DeepEqual(v, req.Header[header]) {
|
||||
t.Errorf("%s: Expected headers for %s to be %v, got %v", k, header, v, req.Header[header])
|
||||
}
|
||||
}
|
||||
|
||||
// Read body
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error %v", k, err)
|
||||
}
|
||||
req.Body.Close()
|
||||
|
||||
// Verify length
|
||||
if req.ContentLength > 0 && req.ContentLength != int64(len(body)) {
|
||||
t.Errorf("%s: ContentLength was %d, len(data) was %d", k, req.ContentLength, len(body))
|
||||
}
|
||||
|
||||
// Verify content
|
||||
if !bytes.Equal(item.expectedBody, body) {
|
||||
t.Errorf("%s: Expected %q, got %q", k, string(item.expectedBody), string(body))
|
||||
}
|
||||
|
||||
// Write successful response
|
||||
w.Write([]byte(successfulResponse))
|
||||
}))
|
||||
defer downstreamServer.Close()
|
||||
|
||||
// Start the proxy server
|
||||
serverURL, _ := url.Parse(downstreamServer.URL)
|
||||
simpleStorage := &SimpleRESTStorage{
|
||||
errors: map[string]error{},
|
||||
resourceLocation: serverURL,
|
||||
expectedResourceNamespace: "default",
|
||||
}
|
||||
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
server := newTestServer(namespaceHandler)
|
||||
defer server.Close()
|
||||
|
||||
// Dial the proxy server
|
||||
conn, err := net.Dial(server.Listener.Addr().Network(), server.Listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error %v", k, err)
|
||||
continue
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Add standard http 1.1 headers
|
||||
if item.reqHeaders == nil {
|
||||
item.reqHeaders = http.Header{}
|
||||
}
|
||||
item.reqHeaders.Add("Connection", "close")
|
||||
item.reqHeaders.Add("Host", server.Listener.Addr().String())
|
||||
|
||||
// We directly write to the connection to bypass the Go library's manipulation of the Request.Header.
|
||||
// Write the request headers
|
||||
post := fmt.Sprintf("POST /%s/%s/%s/proxy/namespaces/default/foo/id/some/dir HTTP/1.1\r\n", prefix, newGroupVersion.Group, newGroupVersion.Version)
|
||||
if _, err := fmt.Fprint(conn, post); err != nil {
|
||||
t.Fatalf("%s: unexpected error %v", k, err)
|
||||
}
|
||||
for header, values := range item.reqHeaders {
|
||||
for _, value := range values {
|
||||
if _, err := fmt.Fprintf(conn, "%s: %s\r\n", header, value); err != nil {
|
||||
t.Fatalf("%s: unexpected error %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Header separator
|
||||
if _, err := fmt.Fprint(conn, "\r\n"); err != nil {
|
||||
t.Fatalf("%s: unexpected error %v", k, err)
|
||||
}
|
||||
// Body
|
||||
if _, err := conn.Write(item.reqBody); err != nil {
|
||||
t.Fatalf("%s: unexpected error %v", k, err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
response, err := ioutil.ReadAll(conn)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error %v", k, err)
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(string(response), successfulResponse) {
|
||||
t.Errorf("%s: Did not get successful response: %s", k, string(response))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy(t *testing.T) {
|
||||
table := []struct {
|
||||
method string
|
||||
path string
|
||||
reqBody string
|
||||
respBody string
|
||||
respContentType string
|
||||
reqNamespace string
|
||||
}{
|
||||
{"GET", "/some/dir", "", "answer", "text/css", "default"},
|
||||
{"GET", "/some/dir", "", "<html><head></head><body>answer</body></html>", "text/html", "default"},
|
||||
{"POST", "/some/other/dir", "question", "answer", "text/css", "default"},
|
||||
{"PUT", "/some/dir/id", "different question", "answer", "text/css", "default"},
|
||||
{"DELETE", "/some/dir/id", "", "ok", "text/css", "default"},
|
||||
{"GET", "/some/dir/id", "", "answer", "text/css", "other"},
|
||||
{"GET", "/trailing/slash/", "", "answer", "text/css", "default"},
|
||||
{"GET", "/", "", "answer", "text/css", "default"},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
gotBody, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
}
|
||||
if e, a := item.reqBody, string(gotBody); e != a {
|
||||
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
||||
}
|
||||
if e, a := item.path, req.URL.Path; e != a {
|
||||
t.Errorf("%v - expected %v, got %v", item.method, e, a)
|
||||
}
|
||||
w.Header().Set("Content-Type", item.respContentType)
|
||||
var out io.Writer = w
|
||||
if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||
// The proxier can ask for gzip'd data; we need to provide it with that
|
||||
// in order to test our processing of that data.
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gzw := gzip.NewWriter(w)
|
||||
out = gzw
|
||||
defer gzw.Close()
|
||||
}
|
||||
fmt.Fprint(out, item.respBody)
|
||||
}))
|
||||
defer downstreamServer.Close()
|
||||
|
||||
serverURL, _ := url.Parse(downstreamServer.URL)
|
||||
simpleStorage := &SimpleRESTStorage{
|
||||
errors: map[string]error{},
|
||||
resourceLocation: serverURL,
|
||||
expectedResourceNamespace: item.reqNamespace,
|
||||
}
|
||||
|
||||
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
namespaceServer := newTestServer(namespaceHandler)
|
||||
defer namespaceServer.Close()
|
||||
|
||||
// test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace
|
||||
serverPatterns := []struct {
|
||||
server *httptest.Server
|
||||
proxyTestPattern string
|
||||
}{
|
||||
{namespaceServer, "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
||||
}
|
||||
|
||||
for _, serverPattern := range serverPatterns {
|
||||
server := serverPattern.server
|
||||
proxyTestPattern := serverPattern.proxyTestPattern
|
||||
req, err := http.NewRequest(
|
||||
item.method,
|
||||
server.URL+proxyTestPattern,
|
||||
strings.NewReader(item.reqBody),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
continue
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
continue
|
||||
}
|
||||
gotResp, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("%v - unexpected error %v", item.method, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if e, a := item.respBody, string(gotResp); e != a {
|
||||
t.Errorf("%v - expected %v, got %v. url: %#v", item.method, e, a, req.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyUpgrade(t *testing.T) {
|
||||
|
||||
localhostPool := x509.NewCertPool()
|
||||
if !localhostPool.AppendCertsFromPEM(localhostCert) {
|
||||
t.Errorf("error setting up localhostCert pool")
|
||||
}
|
||||
|
||||
testcases := map[string]struct {
|
||||
ServerFunc func(http.Handler) *httptest.Server
|
||||
ProxyTransport http.RoundTripper
|
||||
}{
|
||||
"http": {
|
||||
ServerFunc: httptest.NewServer,
|
||||
ProxyTransport: nil,
|
||||
},
|
||||
"https (invalid hostname + InsecureSkipVerify)": {
|
||||
ServerFunc: func(h http.Handler) *httptest.Server {
|
||||
cert, err := tls.X509KeyPair(exampleCert, exampleKey)
|
||||
if err != nil {
|
||||
t.Errorf("https (invalid hostname): proxy_test: %v", err)
|
||||
}
|
||||
ts := httptest.NewUnstartedServer(h)
|
||||
ts.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
ts.StartTLS()
|
||||
return ts
|
||||
},
|
||||
ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}),
|
||||
},
|
||||
"https (valid hostname + RootCAs)": {
|
||||
ServerFunc: func(h http.Handler) *httptest.Server {
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
t.Errorf("https (valid hostname): proxy_test: %v", err)
|
||||
}
|
||||
ts := httptest.NewUnstartedServer(h)
|
||||
ts.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
ts.StartTLS()
|
||||
return ts
|
||||
},
|
||||
ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}),
|
||||
},
|
||||
"https (valid hostname + RootCAs + custom dialer)": {
|
||||
ServerFunc: func(h http.Handler) *httptest.Server {
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
t.Errorf("https (valid hostname): proxy_test: %v", err)
|
||||
}
|
||||
ts := httptest.NewUnstartedServer(h)
|
||||
ts.TLS = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
ts.StartTLS()
|
||||
return ts
|
||||
},
|
||||
ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{Dial: net.Dial, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}),
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
|
||||
backendServer := tc.ServerFunc(websocket.Handler(func(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
body := make([]byte, 5)
|
||||
ws.Read(body)
|
||||
ws.Write([]byte("hello " + string(body)))
|
||||
}))
|
||||
defer backendServer.Close()
|
||||
|
||||
serverURL, _ := url.Parse(backendServer.URL)
|
||||
simpleStorage := &SimpleRESTStorage{
|
||||
errors: map[string]error{},
|
||||
resourceLocation: serverURL,
|
||||
resourceLocationTransport: tc.ProxyTransport,
|
||||
expectedResourceNamespace: "myns",
|
||||
}
|
||||
|
||||
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
|
||||
server := newTestServer(namespaceHandler)
|
||||
defer server.Close()
|
||||
|
||||
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/"+prefix+"/"+newGroupVersion.Group+"/"+newGroupVersion.Version+"/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
|
||||
if err != nil {
|
||||
t.Errorf("%s: websocket dial err: %s", k, err)
|
||||
continue
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
if _, err := ws.Write([]byte("world")); err != nil {
|
||||
t.Errorf("%s: write err: %s", k, err)
|
||||
continue
|
||||
}
|
||||
|
||||
response := make([]byte, 20)
|
||||
n, err := ws.Read(response)
|
||||
if err != nil {
|
||||
t.Errorf("%s: read err: %s", k, err)
|
||||
continue
|
||||
}
|
||||
if e, a := "hello world", string(response[0:n]); e != a {
|
||||
t.Errorf("%s: expected '%#v', got '%#v'", k, e, a)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectOnMissingTrailingSlash(t *testing.T) {
|
||||
table := []struct {
|
||||
// The requested path
|
||||
path string
|
||||
// The path requested on the proxy server.
|
||||
proxyServerPath string
|
||||
// query string
|
||||
query string
|
||||
}{
|
||||
{"/trailing/slash/", "/trailing/slash/", ""},
|
||||
{"/", "/", "test1=value1&test2=value2"},
|
||||
// "/" should be added at the end.
|
||||
{"", "/", "test1=value1&test2=value2"},
|
||||
// "/" should not be added at a non-root path.
|
||||
{"/some/path", "/some/path", ""},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path != item.proxyServerPath {
|
||||
t.Errorf("Unexpected request on path: %s, expected path: %s, item: %v", req.URL.Path, item.proxyServerPath, item)
|
||||
}
|
||||
if req.URL.RawQuery != item.query {
|
||||
t.Errorf("Unexpected query on url: %s, expected: %s", req.URL.RawQuery, item.query)
|
||||
}
|
||||
}))
|
||||
defer downstreamServer.Close()
|
||||
|
||||
serverURL, _ := url.Parse(downstreamServer.URL)
|
||||
simpleStorage := &SimpleRESTStorage{
|
||||
errors: map[string]error{},
|
||||
resourceLocation: serverURL,
|
||||
expectedResourceNamespace: "ns",
|
||||
}
|
||||
|
||||
handler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
server := newTestServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
proxyTestPattern := "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/proxy/namespaces/ns/foo/id" + item.path
|
||||
req, err := http.NewRequest(
|
||||
"GET",
|
||||
server.URL+proxyTestPattern+"?"+item.query,
|
||||
strings.NewReader(""),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
}
|
||||
// Note: We are using a default client here, that follows redirects.
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Unexpected errorCode: %v, expected: 200. Response: %v, item: %v", resp.StatusCode, resp, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// exampleCert was generated from crypto/tls/generate_cert.go with the following command:
|
||||
// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var exampleCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBcjCCAR6gAwIBAgIQBOUTYowZaENkZi0faI9DgTALBgkqhkiG9w0BAQswEjEQ
|
||||
MA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2MDAw
|
||||
MFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCZ
|
||||
xfR3sgeHBraGFfF/24tTn4PRVAHOf2UOOxSQRs+aYjNqimFqf/SRIblQgeXdBJDR
|
||||
gVK5F1Js2zwlehw0bHxRAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIApDATBgNVHSUE
|
||||
DDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4YW1w
|
||||
bGUuY29tMAsGCSqGSIb3DQEBCwNBAI/mfBB8dm33IpUl+acSyWfL6gX5Wc0FFyVj
|
||||
dKeesE1XBuPX1My/rzU6Oy/YwX7LOL4FaeNUS6bbL4axSLPKYSs=
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIBOgIBAAJBAJnF9HeyB4cGtoYV8X/bi1Ofg9FUAc5/ZQ47FJBGz5piM2qKYWp/
|
||||
9JEhuVCB5d0EkNGBUrkXUmzbPCV6HDRsfFECAwEAAQJBAJLH9yPuButniACTn5L5
|
||||
IJQw1mWQt6zBw9eCo41YWkA0866EgjC53aPZaRjXMp0uNJGdIsys2V5rCOOLWN2C
|
||||
ODECIQDICHsi8QQQ9wpuJy8X5l8MAfxHL+DIqI84wQTeVM91FQIhAMTME8A18/7h
|
||||
1Ad6drdnxAkuC0tX6Sx0LDozrmen+HFNAiAlcEDrt0RVkIcpOrg7tuhPLQf0oudl
|
||||
Zvb3Xlj069awSQIgcT15E/43w2+RASifzVNhQ2MCTr1sSA8lL+xzK+REmnUCIBhQ
|
||||
j4139pf8Re1J50zBxS/JlQfgDQi9sO9pYeiHIxNs
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
|
||||
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
|
||||
bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
|
||||
bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
|
||||
IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
|
||||
AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
|
||||
AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
|
||||
Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
// localhostKey is the private key for localhostCert.
|
||||
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
|
||||
0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
|
||||
NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
|
||||
AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
|
||||
MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
|
||||
EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
|
||||
1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
|
||||
-----END RSA PRIVATE KEY-----`)
|
67
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/BUILD
generated
vendored
Normal file
67
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"create.go",
|
||||
"delete.go",
|
||||
"doc.go",
|
||||
"export.go",
|
||||
"meta.go",
|
||||
"rest.go",
|
||||
"types.go",
|
||||
"update.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/validation/genericvalidation:go_default_library",
|
||||
"//pkg/api/validation/path:go_default_library",
|
||||
"//pkg/util/uuid:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
"//vendor:k8s.io/apiserver/pkg/storage/names",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/genericapiserver/api/rest/resttest:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["meta_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/util/uuid:go_default_library",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
35
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/OWNERS
generated
vendored
Executable file
35
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,35 @@
|
|||
reviewers:
|
||||
- thockin
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- brendandburns
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- nikhiljindal
|
||||
- bprashanth
|
||||
- gmarek
|
||||
- kargakis
|
||||
- justinsb
|
||||
- roberthbailey
|
||||
- ncdc
|
||||
- eparis
|
||||
- timothysc
|
||||
- dims
|
||||
- hongchaodeng
|
||||
- krousey
|
||||
- jszczepkowski
|
||||
- euank
|
||||
- markturansky
|
||||
- fgrzadkowski
|
||||
- satnam6502
|
||||
- fabioy
|
||||
- ingvagabund
|
||||
- david-mcmahon
|
||||
- jianhuiz
|
||||
- nhlfr
|
||||
- feihujiang
|
||||
- sdminonne
|
||||
- goltermann
|
134
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/create.go
generated
vendored
Normal file
134
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/create.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
|
||||
"k8s.io/kubernetes/pkg/api/validation/path"
|
||||
)
|
||||
|
||||
// RESTCreateStrategy defines the minimum validation, accepted input, and
|
||||
// name generation behavior to create an object that follows Kubernetes
|
||||
// API conventions.
|
||||
type RESTCreateStrategy interface {
|
||||
runtime.ObjectTyper
|
||||
// The name generate is used when the standard GenerateName field is set.
|
||||
// The NameGenerator will be invoked prior to validation.
|
||||
names.NameGenerator
|
||||
|
||||
// NamespaceScoped returns true if the object must be within a namespace.
|
||||
NamespaceScoped() bool
|
||||
// PrepareForCreate is invoked on create before validation to normalize
|
||||
// the object. For example: remove fields that are not to be persisted,
|
||||
// sort order-insensitive list fields, etc. This should not remove fields
|
||||
// whose presence would be considered a validation error.
|
||||
PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object)
|
||||
// Validate is invoked after default fields in the object have been filled in before
|
||||
// the object is persisted. This method should not mutate the object.
|
||||
Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList
|
||||
// Canonicalize is invoked after validation has succeeded but before the
|
||||
// object has been persisted. This method may mutate the object.
|
||||
Canonicalize(obj runtime.Object)
|
||||
}
|
||||
|
||||
// BeforeCreate ensures that common operations for all resources are performed on creation. It only returns
|
||||
// errors that can be converted to api.Status. It invokes PrepareForCreate, then GenerateName, then Validate.
|
||||
// It returns nil if the object should be created.
|
||||
func BeforeCreate(strategy RESTCreateStrategy, ctx genericapirequest.Context, obj runtime.Object) error {
|
||||
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return kerr
|
||||
}
|
||||
|
||||
if strategy.NamespaceScoped() {
|
||||
if !ValidNamespace(ctx, objectMeta) {
|
||||
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
|
||||
}
|
||||
} else {
|
||||
objectMeta.Namespace = api.NamespaceNone
|
||||
}
|
||||
objectMeta.DeletionTimestamp = nil
|
||||
objectMeta.DeletionGracePeriodSeconds = nil
|
||||
strategy.PrepareForCreate(ctx, obj)
|
||||
FillObjectMetaSystemFields(ctx, objectMeta)
|
||||
if len(objectMeta.GenerateName) > 0 && len(objectMeta.Name) == 0 {
|
||||
objectMeta.Name = strategy.GenerateName(objectMeta.GenerateName)
|
||||
}
|
||||
|
||||
// ClusterName is ignored and should not be saved
|
||||
objectMeta.ClusterName = ""
|
||||
|
||||
if errs := strategy.Validate(ctx, obj); len(errs) > 0 {
|
||||
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
|
||||
}
|
||||
|
||||
// Custom validation (including name validation) passed
|
||||
// Now run common validation on object meta
|
||||
// Do this *after* custom validation so that specific error messages are shown whenever possible
|
||||
if errs := genericvalidation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), path.ValidatePathSegmentName, field.NewPath("metadata")); len(errs) > 0 {
|
||||
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
|
||||
}
|
||||
|
||||
strategy.Canonicalize(obj)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckGeneratedNameError checks whether an error that occurred creating a resource is due
|
||||
// to generation being unable to pick a valid name.
|
||||
func CheckGeneratedNameError(strategy RESTCreateStrategy, err error, obj runtime.Object) error {
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return kerr
|
||||
}
|
||||
|
||||
if len(objectMeta.GenerateName) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.NewServerTimeoutForKind(kind.GroupKind(), "POST", 0)
|
||||
}
|
||||
|
||||
// objectMetaAndKind retrieves kind and ObjectMeta from a runtime object, or returns an error.
|
||||
func objectMetaAndKind(typer runtime.ObjectTyper, obj runtime.Object) (*api.ObjectMeta, schema.GroupVersionKind, error) {
|
||||
objectMeta, err := api.ObjectMetaFor(obj)
|
||||
if err != nil {
|
||||
return nil, schema.GroupVersionKind{}, errors.NewInternalError(err)
|
||||
}
|
||||
kinds, _, err := typer.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return nil, schema.GroupVersionKind{}, errors.NewInternalError(err)
|
||||
}
|
||||
return objectMeta, kinds[0], nil
|
||||
}
|
||||
|
||||
// NamespaceScopedStrategy has a method to tell if the object must be in a namespace.
|
||||
type NamespaceScopedStrategy interface {
|
||||
// NamespaceScoped returns if the object must be in a namespace.
|
||||
NamespaceScoped() bool
|
||||
}
|
128
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/delete.go
generated
vendored
Normal file
128
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/delete.go
generated
vendored
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// RESTDeleteStrategy defines deletion behavior on an object that follows Kubernetes
|
||||
// API conventions.
|
||||
type RESTDeleteStrategy interface {
|
||||
runtime.ObjectTyper
|
||||
}
|
||||
|
||||
type GarbageCollectionPolicy string
|
||||
|
||||
const (
|
||||
DeleteDependents GarbageCollectionPolicy = "DeleteDependents"
|
||||
OrphanDependents GarbageCollectionPolicy = "OrphanDependents"
|
||||
)
|
||||
|
||||
// GarbageCollectionDeleteStrategy must be implemented by the registry that wants to
|
||||
// orphan dependents by default.
|
||||
type GarbageCollectionDeleteStrategy interface {
|
||||
// DefaultGarbageCollectionPolicy returns the default garbage collection behavior.
|
||||
DefaultGarbageCollectionPolicy() GarbageCollectionPolicy
|
||||
}
|
||||
|
||||
// RESTGracefulDeleteStrategy must be implemented by the registry that supports
|
||||
// graceful deletion.
|
||||
type RESTGracefulDeleteStrategy interface {
|
||||
// CheckGracefulDelete should return true if the object can be gracefully deleted and set
|
||||
// any default values on the DeleteOptions.
|
||||
CheckGracefulDelete(ctx genericapirequest.Context, obj runtime.Object, options *api.DeleteOptions) bool
|
||||
}
|
||||
|
||||
// BeforeDelete tests whether the object can be gracefully deleted. If graceful is set the object
|
||||
// should be gracefully deleted, if gracefulPending is set the object has already been gracefully deleted
|
||||
// (and the provided grace period is longer than the time to deletion), and an error is returned if the
|
||||
// condition cannot be checked or the gracePeriodSeconds is invalid. The options argument may be updated with
|
||||
// default values if graceful is true. Second place where we set deletionTimestamp is pkg/registry/generic/registry/store.go
|
||||
// this function is responsible for setting deletionTimestamp during gracefulDeletion, other one for cascading deletions.
|
||||
func BeforeDelete(strategy RESTDeleteStrategy, ctx genericapirequest.Context, obj runtime.Object, options *api.DeleteOptions) (graceful, gracefulPending bool, err error) {
|
||||
objectMeta, gvk, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return false, false, kerr
|
||||
}
|
||||
// Checking the Preconditions here to fail early. They'll be enforced later on when we actually do the deletion, too.
|
||||
if options.Preconditions != nil && options.Preconditions.UID != nil && *options.Preconditions.UID != objectMeta.UID {
|
||||
return false, false, errors.NewConflict(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.Name, fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *options.Preconditions.UID, objectMeta.UID))
|
||||
}
|
||||
gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy)
|
||||
if !ok {
|
||||
// If we're not deleting gracefully there's no point in updating Generation, as we won't update
|
||||
// the obcject before deleting it.
|
||||
return false, false, nil
|
||||
}
|
||||
// if the object is already being deleted, no need to update generation.
|
||||
if objectMeta.DeletionTimestamp != nil {
|
||||
// if we are already being deleted, we may only shorten the deletion grace period
|
||||
// this means the object was gracefully deleted previously but deletionGracePeriodSeconds was not set,
|
||||
// so we force deletion immediately
|
||||
// IMPORTANT:
|
||||
// The deletion operation happens in two phases.
|
||||
// 1. Update to set DeletionGracePeriodSeconds and DeletionTimestamp
|
||||
// 2. Delete the object from storage.
|
||||
// If the update succeeds, but the delete fails (network error, internal storage error, etc.),
|
||||
// a resource was previously left in a state that was non-recoverable. We
|
||||
// check if the existing stored resource has a grace period as 0 and if so
|
||||
// attempt to delete immediately in order to recover from this scenario.
|
||||
if objectMeta.DeletionGracePeriodSeconds == nil || *objectMeta.DeletionGracePeriodSeconds == 0 {
|
||||
return false, false, nil
|
||||
}
|
||||
// only a shorter grace period may be provided by a user
|
||||
if options.GracePeriodSeconds != nil {
|
||||
period := int64(*options.GracePeriodSeconds)
|
||||
if period >= *objectMeta.DeletionGracePeriodSeconds {
|
||||
return false, true, nil
|
||||
}
|
||||
newDeletionTimestamp := metav1.NewTime(
|
||||
objectMeta.DeletionTimestamp.Add(-time.Second * time.Duration(*objectMeta.DeletionGracePeriodSeconds)).
|
||||
Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
|
||||
objectMeta.DeletionTimestamp = &newDeletionTimestamp
|
||||
objectMeta.DeletionGracePeriodSeconds = &period
|
||||
return true, false, nil
|
||||
}
|
||||
// graceful deletion is pending, do nothing
|
||||
options.GracePeriodSeconds = objectMeta.DeletionGracePeriodSeconds
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
if !gracefulStrategy.CheckGracefulDelete(ctx, obj, options) {
|
||||
return false, false, nil
|
||||
}
|
||||
now := metav1.NewTime(metav1.Now().Add(time.Second * time.Duration(*options.GracePeriodSeconds)))
|
||||
objectMeta.DeletionTimestamp = &now
|
||||
objectMeta.DeletionGracePeriodSeconds = options.GracePeriodSeconds
|
||||
// If it's the first graceful deletion we are going to set the DeletionTimestamp to non-nil.
|
||||
// Controllers of the object that's being deleted shouldn't take any nontrivial actions, hence its behavior changes.
|
||||
// Thus we need to bump object's Generation (if set). This handles generation bump during graceful deletion.
|
||||
// The bump for objects that don't support graceful deletion is handled in pkg/registry/generic/registry/store.go.
|
||||
if objectMeta.Generation > 0 {
|
||||
objectMeta.Generation++
|
||||
}
|
||||
return true, false, nil
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
// Package rest defines common logic around changes to Kubernetes resources.
|
||||
package rest // import "k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
29
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/export.go
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/export.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
)
|
||||
|
||||
// RESTExportStrategy is the interface that defines how to export a Kubernetes object
|
||||
type RESTExportStrategy interface {
|
||||
// Export strips fields that can not be set by the user. If 'exact' is false
|
||||
// fields specific to the cluster are also stripped
|
||||
Export(ctx genericapirequest.Context, obj runtime.Object, exact bool) error
|
||||
}
|
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/meta.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/meta.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/uuid"
|
||||
)
|
||||
|
||||
// FillObjectMetaSystemFields populates fields that are managed by the system on ObjectMeta.
|
||||
func FillObjectMetaSystemFields(ctx genericapirequest.Context, meta *api.ObjectMeta) {
|
||||
meta.CreationTimestamp = metav1.Now()
|
||||
// allows admission controllers to assign a UID earlier in the request processing
|
||||
// to support tracking resources pending creation.
|
||||
uid, found := genericapirequest.UIDFrom(ctx)
|
||||
if !found {
|
||||
uid = uuid.NewUUID()
|
||||
}
|
||||
meta.UID = uid
|
||||
meta.SelfLink = ""
|
||||
}
|
||||
|
||||
// ValidNamespace returns false if the namespace on the context differs from the resource. If the resource has no namespace, it is set to the value in the context.
|
||||
// TODO(sttts): move into pkg/genericapiserver/api
|
||||
func ValidNamespace(ctx genericapirequest.Context, resource *api.ObjectMeta) bool {
|
||||
ns, ok := genericapirequest.NamespaceFrom(ctx)
|
||||
if len(resource.Namespace) == 0 {
|
||||
resource.Namespace = ns
|
||||
}
|
||||
return ns == resource.Namespace && ok
|
||||
}
|
85
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/meta_test.go
generated
vendored
Normal file
85
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/meta_test.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/uuid"
|
||||
)
|
||||
|
||||
// TestFillObjectMetaSystemFields validates that system populated fields are set on an object
|
||||
func TestFillObjectMetaSystemFields(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
resource := api.ObjectMeta{}
|
||||
FillObjectMetaSystemFields(ctx, &resource)
|
||||
if resource.CreationTimestamp.Time.IsZero() {
|
||||
t.Errorf("resource.CreationTimestamp is zero")
|
||||
} else if len(resource.UID) == 0 {
|
||||
t.Errorf("resource.UID missing")
|
||||
}
|
||||
// verify we can inject a UID
|
||||
uid := uuid.NewUUID()
|
||||
ctx = genericapirequest.WithUID(ctx, uid)
|
||||
resource = api.ObjectMeta{}
|
||||
FillObjectMetaSystemFields(ctx, &resource)
|
||||
if resource.UID != uid {
|
||||
t.Errorf("resource.UID expected: %v, actual: %v", uid, resource.UID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHasObjectMetaSystemFieldValues validates that true is returned if and only if all fields are populated
|
||||
func TestHasObjectMetaSystemFieldValues(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
resource := api.ObjectMeta{}
|
||||
if api.HasObjectMetaSystemFieldValues(&resource) {
|
||||
t.Errorf("the resource does not have all fields yet populated, but incorrectly reports it does")
|
||||
}
|
||||
FillObjectMetaSystemFields(ctx, &resource)
|
||||
if !api.HasObjectMetaSystemFieldValues(&resource) {
|
||||
t.Errorf("the resource does have all fields populated, but incorrectly reports it does not")
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidNamespace validates that namespace rules are enforced on a resource prior to create or update
|
||||
func TestValidNamespace(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
namespace, _ := genericapirequest.NamespaceFrom(ctx)
|
||||
resource := api.ReplicationController{}
|
||||
if !ValidNamespace(ctx, &resource.ObjectMeta) {
|
||||
t.Fatalf("expected success")
|
||||
}
|
||||
if namespace != resource.Namespace {
|
||||
t.Fatalf("expected resource to have the default namespace assigned during validation")
|
||||
}
|
||||
resource = api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: "other"}}
|
||||
if ValidNamespace(ctx, &resource.ObjectMeta) {
|
||||
t.Fatalf("Expected error that resource and context errors do not match because resource has different namespace")
|
||||
}
|
||||
ctx = genericapirequest.NewContext()
|
||||
if ValidNamespace(ctx, &resource.ObjectMeta) {
|
||||
t.Fatalf("Expected error that resource and context errors do not match since context has no namespace")
|
||||
}
|
||||
|
||||
ctx = genericapirequest.NewContext()
|
||||
ns := genericapirequest.NamespaceValue(ctx)
|
||||
if ns != "" {
|
||||
t.Fatalf("Expected the empty string")
|
||||
}
|
||||
}
|
313
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/rest.go
generated
vendored
Normal file
313
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/rest.go
generated
vendored
Normal file
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
//TODO:
|
||||
// Storage interfaces need to be separated into two groups; those that operate
|
||||
// on collections and those that operate on individually named items.
|
||||
// Collection interfaces:
|
||||
// (Method: Current -> Proposed)
|
||||
// GET: Lister -> CollectionGetter
|
||||
// WATCH: Watcher -> CollectionWatcher
|
||||
// CREATE: Creater -> CollectionCreater
|
||||
// DELETE: (n/a) -> CollectionDeleter
|
||||
// UPDATE: (n/a) -> CollectionUpdater
|
||||
//
|
||||
// Single item interfaces:
|
||||
// (Method: Current -> Proposed)
|
||||
// GET: Getter -> NamedGetter
|
||||
// WATCH: (n/a) -> NamedWatcher
|
||||
// CREATE: (n/a) -> NamedCreater
|
||||
// DELETE: Deleter -> NamedDeleter
|
||||
// UPDATE: Update -> NamedUpdater
|
||||
|
||||
// Storage is a generic interface for RESTful storage services.
|
||||
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
|
||||
// that objects may implement any of the below interfaces.
|
||||
type Storage interface {
|
||||
// New returns an empty object that can be used with Create and Update after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
}
|
||||
|
||||
// KindProvider specifies a different kind for its API than for its internal storage. This is necessary for external
|
||||
// objects that are not compiled into the api server. For such objects, there is no in-memory representation for
|
||||
// the object, so they must be represented as generic objects (e.g. runtime.Unknown), but when we present the object as part of
|
||||
// API discovery we want to present the specific kind, not the generic internal representation.
|
||||
type KindProvider interface {
|
||||
Kind() string
|
||||
}
|
||||
|
||||
// Lister is an object that can retrieve resources that match the provided field and label criteria.
|
||||
type Lister interface {
|
||||
// NewList returns an empty object that can be used with the List call.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
NewList() runtime.Object
|
||||
// List selects resources in the storage which match to the selector. 'options' can be nil.
|
||||
List(ctx genericapirequest.Context, options *api.ListOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Exporter is an object that knows how to strip a RESTful resource for export
|
||||
type Exporter interface {
|
||||
// Export an object. Fields that are not user specified (e.g. Status, ObjectMeta.ResourceVersion) are stripped out
|
||||
// Returns the stripped object. If 'exact' is true, fields that are specific to the cluster (e.g. namespace) are
|
||||
// retained, otherwise they are stripped also.
|
||||
Export(ctx genericapirequest.Context, name string, opts metav1.ExportOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Getter is an object that can retrieve a named RESTful resource.
|
||||
type Getter interface {
|
||||
// Get finds a resource in the storage by name and returns it.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
// returned error value err when the specified resource is not found.
|
||||
Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// GetterWithOptions is an object that retrieve a named RESTful resource and takes
|
||||
// additional options on the get request. It allows a caller to also receive the
|
||||
// subpath of the GET request.
|
||||
type GetterWithOptions interface {
|
||||
// Get finds a resource in the storage by name and returns it.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
// returned error value err when the specified resource is not found.
|
||||
// The options object passed to it is of the same type returned by the NewGetOptions
|
||||
// method.
|
||||
// TODO: Pass metav1.GetOptions.
|
||||
Get(ctx genericapirequest.Context, name string, options runtime.Object) (runtime.Object, error)
|
||||
|
||||
// NewGetOptions returns an empty options object that will be used to pass
|
||||
// options to the Get method. It may return a bool and a string, if true, the
|
||||
// value of the request path below the object will be included as the named
|
||||
// string in the serialization of the runtime object. E.g., returning "path"
|
||||
// will convert the trailing request scheme value to "path" in the map[string][]string
|
||||
// passed to the converter.
|
||||
NewGetOptions() (runtime.Object, bool, string)
|
||||
}
|
||||
|
||||
// Deleter is an object that can delete a named RESTful resource.
|
||||
type Deleter interface {
|
||||
// Delete finds a resource in the storage and deletes it.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
// returned error value err when the specified resource is not found.
|
||||
// Delete *may* return the object that was deleted, or a status object indicating additional
|
||||
// information about deletion.
|
||||
Delete(ctx genericapirequest.Context, name string) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// GracefulDeleter knows how to pass deletion options to allow delayed deletion of a
|
||||
// RESTful object.
|
||||
type GracefulDeleter interface {
|
||||
// Delete finds a resource in the storage and deletes it.
|
||||
// If options are provided, the resource will attempt to honor them or return an invalid
|
||||
// request error.
|
||||
// Although it can return an arbitrary error value, IsNotFound(err) is true for the
|
||||
// returned error value err when the specified resource is not found.
|
||||
// Delete *may* return the object that was deleted, or a status object indicating additional
|
||||
// information about deletion.
|
||||
Delete(ctx genericapirequest.Context, name string, options *api.DeleteOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// GracefulDeleteAdapter adapts the Deleter interface to GracefulDeleter
|
||||
type GracefulDeleteAdapter struct {
|
||||
Deleter
|
||||
}
|
||||
|
||||
// Delete implements RESTGracefulDeleter in terms of Deleter
|
||||
func (w GracefulDeleteAdapter) Delete(ctx genericapirequest.Context, name string, options *api.DeleteOptions) (runtime.Object, error) {
|
||||
return w.Deleter.Delete(ctx, name)
|
||||
}
|
||||
|
||||
// CollectionDeleter is an object that can delete a collection
|
||||
// of RESTful resources.
|
||||
type CollectionDeleter interface {
|
||||
// DeleteCollection selects all resources in the storage matching given 'listOptions'
|
||||
// and deletes them. If 'options' are provided, the resource will attempt to honor
|
||||
// them or return an invalid request error.
|
||||
// DeleteCollection may not be atomic - i.e. it may delete some objects and still
|
||||
// return an error after it. On success, returns a list of deleted objects.
|
||||
DeleteCollection(ctx genericapirequest.Context, options *api.DeleteOptions, listOptions *api.ListOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Creater is an object that can create an instance of a RESTful object.
|
||||
type Creater interface {
|
||||
// New returns an empty object that can be used with Create after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
|
||||
// Create creates a new version of a resource.
|
||||
Create(ctx genericapirequest.Context, obj runtime.Object) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// NamedCreater is an object that can create an instance of a RESTful object using a name parameter.
|
||||
type NamedCreater interface {
|
||||
// New returns an empty object that can be used with Create after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
|
||||
// Create creates a new version of a resource. It expects a name parameter from the path.
|
||||
// This is needed for create operations on subresources which include the name of the parent
|
||||
// resource in the path.
|
||||
Create(ctx genericapirequest.Context, name string, obj runtime.Object) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// UpdatedObjectInfo provides information about an updated object to an Updater.
|
||||
// It requires access to the old object in order to return the newly updated object.
|
||||
type UpdatedObjectInfo interface {
|
||||
// Returns preconditions built from the updated object, if applicable.
|
||||
// May return nil, or a preconditions object containing nil fields,
|
||||
// if no preconditions can be determined from the updated object.
|
||||
Preconditions() *api.Preconditions
|
||||
|
||||
// UpdatedObject returns the updated object, given a context and old object.
|
||||
// The only time an empty oldObj should be passed in is if a "create on update" is occurring (there is no oldObj).
|
||||
UpdatedObject(ctx genericapirequest.Context, oldObj runtime.Object) (newObj runtime.Object, err error)
|
||||
}
|
||||
|
||||
// Updater is an object that can update an instance of a RESTful object.
|
||||
type Updater interface {
|
||||
// New returns an empty object that can be used with Update after request data has been put into it.
|
||||
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||
New() runtime.Object
|
||||
|
||||
// Update finds a resource in the storage and updates it. Some implementations
|
||||
// may allow updates creates the object - they should set the created boolean
|
||||
// to true.
|
||||
Update(ctx genericapirequest.Context, name string, objInfo UpdatedObjectInfo) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CreaterUpdater is a storage object that must support both create and update.
|
||||
// Go prevents embedded interfaces that implement the same method.
|
||||
type CreaterUpdater interface {
|
||||
Creater
|
||||
Update(ctx genericapirequest.Context, name string, objInfo UpdatedObjectInfo) (runtime.Object, bool, error)
|
||||
}
|
||||
|
||||
// CreaterUpdater must satisfy the Updater interface.
|
||||
var _ Updater = CreaterUpdater(nil)
|
||||
|
||||
// Patcher is a storage object that supports both get and update.
|
||||
type Patcher interface {
|
||||
Getter
|
||||
Updater
|
||||
}
|
||||
|
||||
// Watcher should be implemented by all Storage objects that
|
||||
// want to offer the ability to watch for changes through the watch api.
|
||||
type Watcher interface {
|
||||
// 'label' selects on labels; 'field' selects on the object's fields. Not all fields
|
||||
// are supported; an error should be returned if 'field' tries to select on a field that
|
||||
// isn't supported. 'resourceVersion' allows for continuing/starting a watch at a
|
||||
// particular version.
|
||||
Watch(ctx genericapirequest.Context, options *api.ListOptions) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// StandardStorage is an interface covering the common verbs. Provided for testing whether a
|
||||
// resource satisfies the normal storage methods. Use Storage when passing opaque storage objects.
|
||||
type StandardStorage interface {
|
||||
Getter
|
||||
Lister
|
||||
CreaterUpdater
|
||||
GracefulDeleter
|
||||
CollectionDeleter
|
||||
Watcher
|
||||
}
|
||||
|
||||
// Redirector know how to return a remote resource's location.
|
||||
type Redirector interface {
|
||||
// ResourceLocation should return the remote location of the given resource, and an optional transport to use to request it, or an error.
|
||||
ResourceLocation(ctx genericapirequest.Context, id string) (remoteLocation *url.URL, transport http.RoundTripper, err error)
|
||||
}
|
||||
|
||||
// Responder abstracts the normal response behavior for a REST method and is passed to callers that
|
||||
// may wish to handle the response directly in some cases, but delegate to the normal error or object
|
||||
// behavior in other cases.
|
||||
type Responder interface {
|
||||
// Object writes the provided object to the response. Invoking this method multiple times is undefined.
|
||||
Object(statusCode int, obj runtime.Object)
|
||||
// Error writes the provided error to the response. This method may only be invoked once.
|
||||
Error(err error)
|
||||
}
|
||||
|
||||
// Connecter is a storage object that responds to a connection request.
|
||||
type Connecter interface {
|
||||
// Connect returns an http.Handler that will handle the request/response for a given API invocation.
|
||||
// The provided responder may be used for common API responses. The responder will write both status
|
||||
// code and body, so the ServeHTTP method should exit after invoking the responder. The Handler will
|
||||
// be used for a single API request and then discarded. The Responder is guaranteed to write to the
|
||||
// same http.ResponseWriter passed to ServeHTTP.
|
||||
Connect(ctx genericapirequest.Context, id string, options runtime.Object, r Responder) (http.Handler, error)
|
||||
|
||||
// NewConnectOptions returns an empty options object that will be used to pass
|
||||
// options to the Connect method. If nil, then a nil options object is passed to
|
||||
// Connect. It may return a bool and a string. If true, the value of the request
|
||||
// path below the object will be included as the named string in the serialization
|
||||
// of the runtime object.
|
||||
NewConnectOptions() (runtime.Object, bool, string)
|
||||
|
||||
// ConnectMethods returns the list of HTTP methods handled by Connect
|
||||
ConnectMethods() []string
|
||||
}
|
||||
|
||||
// ResourceStreamer is an interface implemented by objects that prefer to be streamed from the server
|
||||
// instead of decoded directly.
|
||||
type ResourceStreamer interface {
|
||||
// InputStream should return an io.ReadCloser if the provided object supports streaming. The desired
|
||||
// api version and an accept header (may be empty) are passed to the call. If no error occurs,
|
||||
// the caller may return a flag indicating whether the result should be flushed as writes occur
|
||||
// and a content type string that indicates the type of the stream.
|
||||
// If a null stream is returned, a StatusNoContent response wil be generated.
|
||||
InputStream(apiVersion, acceptHeader string) (stream io.ReadCloser, flush bool, mimeType string, err error)
|
||||
}
|
||||
|
||||
// StorageMetadata is an optional interface that callers can implement to provide additional
|
||||
// information about their Storage objects.
|
||||
type StorageMetadata interface {
|
||||
// ProducesMIMETypes returns a list of the MIME types the specified HTTP verb (GET, POST, DELETE,
|
||||
// PATCH) can respond with.
|
||||
ProducesMIMETypes(verb string) []string
|
||||
|
||||
// ProducesObject returns an object the specified HTTP verb respond with. It will overwrite storage object if
|
||||
// it is not nil. Only the type of the return object matters, the value will be ignored.
|
||||
ProducesObject(verb string) interface{}
|
||||
}
|
||||
|
||||
// ConnectRequest is an object passed to admission control for Connect operations
|
||||
type ConnectRequest struct {
|
||||
// Name is the name of the object on which the connect request was made
|
||||
Name string
|
||||
|
||||
// Options is the options object passed to the connect request. See the NewConnectOptions method on Connecter
|
||||
Options runtime.Object
|
||||
|
||||
// ResourcePath is the path for the resource in the REST server (ie. "pods/proxy")
|
||||
ResourcePath string
|
||||
}
|
||||
|
||||
func (obj *ConnectRequest) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind }
|
41
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/resttest/BUILD
generated
vendored
Normal file
41
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/resttest/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["resttest.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/validation/path:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/genericapiserver/api/rest:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/conversion",
|
||||
"//vendor:k8s.io/apimachinery/pkg/labels",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
1326
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/resttest/resttest.go
generated
vendored
Normal file
1326
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/resttest/resttest.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
42
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/types.go
generated
vendored
Normal file
42
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/types.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// ObjectFunc is a function to act on a given object. An error may be returned
|
||||
// if the hook cannot be completed. An ObjectFunc may transform the provided
|
||||
// object.
|
||||
type ObjectFunc func(obj runtime.Object) error
|
||||
|
||||
// AllFuncs returns an ObjectFunc that attempts to run all of the provided functions
|
||||
// in order, returning early if there are any errors.
|
||||
func AllFuncs(fns ...ObjectFunc) ObjectFunc {
|
||||
return func(obj runtime.Object) error {
|
||||
for _, fn := range fns {
|
||||
if fn == nil {
|
||||
continue
|
||||
}
|
||||
if err := fn(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
226
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/update.go
generated
vendored
Normal file
226
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/rest/update.go
generated
vendored
Normal file
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/validation/genericvalidation"
|
||||
)
|
||||
|
||||
// RESTUpdateStrategy defines the minimum validation, accepted input, and
|
||||
// name generation behavior to update an object that follows Kubernetes
|
||||
// API conventions. A resource may have many UpdateStrategies, depending on
|
||||
// the call pattern in use.
|
||||
type RESTUpdateStrategy interface {
|
||||
runtime.ObjectTyper
|
||||
// NamespaceScoped returns true if the object must be within a namespace.
|
||||
NamespaceScoped() bool
|
||||
// AllowCreateOnUpdate returns true if the object can be created by a PUT.
|
||||
AllowCreateOnUpdate() bool
|
||||
// PrepareForUpdate is invoked on update before validation to normalize
|
||||
// the object. For example: remove fields that are not to be persisted,
|
||||
// sort order-insensitive list fields, etc. This should not remove fields
|
||||
// whose presence would be considered a validation error.
|
||||
PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object)
|
||||
// ValidateUpdate is invoked after default fields in the object have been
|
||||
// filled in before the object is persisted. This method should not mutate
|
||||
// the object.
|
||||
ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList
|
||||
// Canonicalize is invoked after validation has succeeded but before the
|
||||
// object has been persisted. This method may mutate the object.
|
||||
Canonicalize(obj runtime.Object)
|
||||
// AllowUnconditionalUpdate returns true if the object can be updated
|
||||
// unconditionally (irrespective of the latest resource version), when
|
||||
// there is no resource version specified in the object.
|
||||
AllowUnconditionalUpdate() bool
|
||||
}
|
||||
|
||||
// TODO: add other common fields that require global validation.
|
||||
func validateCommonFields(obj, old runtime.Object) (field.ErrorList, error) {
|
||||
allErrs := field.ErrorList{}
|
||||
objectMeta, err := api.ObjectMetaFor(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get new object metadata: %v", err)
|
||||
}
|
||||
oldObjectMeta, err := api.ObjectMetaFor(old)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get old object metadata: %v", err)
|
||||
}
|
||||
allErrs = append(allErrs, genericvalidation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, field.NewPath("metadata"))...)
|
||||
|
||||
return allErrs, nil
|
||||
}
|
||||
|
||||
// BeforeUpdate ensures that common operations for all resources are performed on update. It only returns
|
||||
// errors that can be converted to api.Status. It will invoke update validation with the provided existing
|
||||
// and updated objects.
|
||||
func BeforeUpdate(strategy RESTUpdateStrategy, ctx genericapirequest.Context, obj, old runtime.Object) error {
|
||||
objectMeta, kind, kerr := objectMetaAndKind(strategy, obj)
|
||||
if kerr != nil {
|
||||
return kerr
|
||||
}
|
||||
if strategy.NamespaceScoped() {
|
||||
if !ValidNamespace(ctx, objectMeta) {
|
||||
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
|
||||
}
|
||||
} else {
|
||||
objectMeta.Namespace = api.NamespaceNone
|
||||
}
|
||||
// Ensure requests cannot update generation
|
||||
oldMeta, err := api.ObjectMetaFor(old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectMeta.Generation = oldMeta.Generation
|
||||
|
||||
strategy.PrepareForUpdate(ctx, obj, old)
|
||||
|
||||
// ClusterName is ignored and should not be saved
|
||||
objectMeta.ClusterName = ""
|
||||
|
||||
// Ensure some common fields, like UID, are validated for all resources.
|
||||
errs, err := validateCommonFields(obj, old)
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
errs = append(errs, strategy.ValidateUpdate(ctx, obj, old)...)
|
||||
if len(errs) > 0 {
|
||||
return errors.NewInvalid(kind.GroupKind(), objectMeta.Name, errs)
|
||||
}
|
||||
|
||||
strategy.Canonicalize(obj)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransformFunc is a function to transform and return newObj
|
||||
type TransformFunc func(ctx genericapirequest.Context, newObj runtime.Object, oldObj runtime.Object) (transformedNewObj runtime.Object, err error)
|
||||
|
||||
// defaultUpdatedObjectInfo implements UpdatedObjectInfo
|
||||
type defaultUpdatedObjectInfo struct {
|
||||
// obj is the updated object
|
||||
obj runtime.Object
|
||||
|
||||
// copier makes a copy of the object before returning it.
|
||||
// this allows repeated calls to UpdatedObject() to return
|
||||
// pristine data, even if the returned value is mutated.
|
||||
copier runtime.ObjectCopier
|
||||
|
||||
// transformers is an optional list of transforming functions that modify or
|
||||
// replace obj using information from the context, old object, or other sources.
|
||||
transformers []TransformFunc
|
||||
}
|
||||
|
||||
// DefaultUpdatedObjectInfo returns an UpdatedObjectInfo impl based on the specified object.
|
||||
func DefaultUpdatedObjectInfo(obj runtime.Object, copier runtime.ObjectCopier, transformers ...TransformFunc) UpdatedObjectInfo {
|
||||
return &defaultUpdatedObjectInfo{obj, copier, transformers}
|
||||
}
|
||||
|
||||
// Preconditions satisfies the UpdatedObjectInfo interface.
|
||||
func (i *defaultUpdatedObjectInfo) Preconditions() *api.Preconditions {
|
||||
// Attempt to get the UID out of the object
|
||||
accessor, err := meta.Accessor(i.obj)
|
||||
if err != nil {
|
||||
// If no UID can be read, no preconditions are possible
|
||||
return nil
|
||||
}
|
||||
|
||||
// If empty, no preconditions needed
|
||||
uid := accessor.GetUID()
|
||||
if len(uid) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &api.Preconditions{UID: &uid}
|
||||
}
|
||||
|
||||
// UpdatedObject satisfies the UpdatedObjectInfo interface.
|
||||
// It returns a copy of the held obj, passed through any configured transformers.
|
||||
func (i *defaultUpdatedObjectInfo) UpdatedObject(ctx genericapirequest.Context, oldObj runtime.Object) (runtime.Object, error) {
|
||||
var err error
|
||||
// Start with the configured object
|
||||
newObj := i.obj
|
||||
|
||||
// If the original is non-nil (might be nil if the first transformer builds the object from the oldObj), make a copy,
|
||||
// so we don't return the original. BeforeUpdate can mutate the returned object, doing things like clearing ResourceVersion.
|
||||
// If we're re-called, we need to be able to return the pristine version.
|
||||
if newObj != nil {
|
||||
newObj, err = i.copier.Copy(newObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Allow any configured transformers to update the new object
|
||||
for _, transformer := range i.transformers {
|
||||
newObj, err = transformer(ctx, newObj, oldObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// wrappedUpdatedObjectInfo allows wrapping an existing objInfo and
|
||||
// chaining additional transformations/checks on the result of UpdatedObject()
|
||||
type wrappedUpdatedObjectInfo struct {
|
||||
// obj is the updated object
|
||||
objInfo UpdatedObjectInfo
|
||||
|
||||
// transformers is an optional list of transforming functions that modify or
|
||||
// replace obj using information from the context, old object, or other sources.
|
||||
transformers []TransformFunc
|
||||
}
|
||||
|
||||
// WrapUpdatedObjectInfo returns an UpdatedObjectInfo impl that delegates to
|
||||
// the specified objInfo, then calls the passed transformers
|
||||
func WrapUpdatedObjectInfo(objInfo UpdatedObjectInfo, transformers ...TransformFunc) UpdatedObjectInfo {
|
||||
return &wrappedUpdatedObjectInfo{objInfo, transformers}
|
||||
}
|
||||
|
||||
// Preconditions satisfies the UpdatedObjectInfo interface.
|
||||
func (i *wrappedUpdatedObjectInfo) Preconditions() *api.Preconditions {
|
||||
return i.objInfo.Preconditions()
|
||||
}
|
||||
|
||||
// UpdatedObject satisfies the UpdatedObjectInfo interface.
|
||||
// It delegates to the wrapped objInfo and passes the result through any configured transformers.
|
||||
func (i *wrappedUpdatedObjectInfo) UpdatedObject(ctx genericapirequest.Context, oldObj runtime.Object) (runtime.Object, error) {
|
||||
newObj, err := i.objInfo.UpdatedObject(ctx, oldObj)
|
||||
if err != nil {
|
||||
return newObj, err
|
||||
}
|
||||
|
||||
// Allow any configured transformers to update the new object or error
|
||||
for _, transformer := range i.transformers {
|
||||
newObj, err = transformer(ctx, newObj, oldObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
37
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/BUILD
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"types.generated.go",
|
||||
"types.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//vendor:github.com/ugorji/go/codec",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
11
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/OWNERS
generated
vendored
Executable file
11
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,11 @@
|
|||
reviewers:
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- caesarxuchao
|
||||
- liggitt
|
||||
- erictune
|
||||
- timothysc
|
||||
- soltysh
|
||||
- mml
|
||||
- mbohlool
|
||||
- jianhuiz
|
1767
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/types.generated.go
generated
vendored
Normal file
1767
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/types.generated.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
70
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/types.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/testing/types.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
)
|
||||
|
||||
type Simple struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
apiv1.ObjectMeta `json:"metadata"`
|
||||
// +optional
|
||||
Other string `json:"other,omitempty"`
|
||||
// +optional
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
func (obj *Simple) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta }
|
||||
|
||||
type SimpleRoot struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
apiv1.ObjectMeta `json:"metadata"`
|
||||
// +optional
|
||||
Other string `json:"other,omitempty"`
|
||||
// +optional
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
func (obj *SimpleRoot) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta }
|
||||
|
||||
type SimpleGetOptions struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
Param1 string `json:"param1"`
|
||||
Param2 string `json:"param2"`
|
||||
Path string `json:"atAPath"`
|
||||
}
|
||||
|
||||
func (SimpleGetOptions) SwaggerDoc() map[string]string {
|
||||
return map[string]string{
|
||||
"param1": "description for param1",
|
||||
"param2": "description for param2",
|
||||
}
|
||||
}
|
||||
|
||||
func (obj *SimpleGetOptions) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta }
|
||||
|
||||
type SimpleList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,inline"`
|
||||
// +optional
|
||||
Items []Simple `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func (obj *SimpleList) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta }
|
767
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/watch_test.go
generated
vendored
Normal file
767
vendor/k8s.io/kubernetes/pkg/genericapiserver/api/watch_test.go
generated
vendored
Normal file
|
@ -0,0 +1,767 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
apitesting "k8s.io/kubernetes/pkg/genericapiserver/api/testing"
|
||||
)
|
||||
|
||||
// watchJSON defines the expected JSON wire equivalent of watch.Event
|
||||
type watchJSON struct {
|
||||
Type watch.EventType `json:"type,omitempty"`
|
||||
Object json.RawMessage `json:"object,omitempty"`
|
||||
}
|
||||
|
||||
// roundTripOrDie round trips an object to get defaults set.
|
||||
func roundTripOrDie(codec runtime.Codec, object runtime.Object) runtime.Object {
|
||||
data, err := runtime.Encode(codec, object)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
obj, err := runtime.Decode(codec, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
var watchTestTable = []struct {
|
||||
t watch.EventType
|
||||
obj runtime.Object
|
||||
}{
|
||||
{watch.Added, &apitesting.Simple{ObjectMeta: apiv1.ObjectMeta{Name: "foo"}}},
|
||||
{watch.Modified, &apitesting.Simple{ObjectMeta: apiv1.ObjectMeta{Name: "bar"}}},
|
||||
{watch.Deleted, &apitesting.Simple{ObjectMeta: apiv1.ObjectMeta{Name: "bar"}}},
|
||||
}
|
||||
|
||||
var podWatchTestTable = []struct {
|
||||
t watch.EventType
|
||||
obj runtime.Object
|
||||
}{
|
||||
{watch.Added, roundTripOrDie(codec, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}})},
|
||||
{watch.Modified, roundTripOrDie(codec, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}})},
|
||||
{watch.Deleted, roundTripOrDie(codec, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}})},
|
||||
}
|
||||
|
||||
func TestWatchWebsocket(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
_ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work.
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Scheme = "ws" // Required by websocket, though the server never sees it.
|
||||
dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
ws, err := websocket.Dial(dest.String(), "", "http://localhost")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
try := func(action watch.EventType, object runtime.Object) {
|
||||
// Send
|
||||
simpleStorage.fakeWatch.Action(action, object)
|
||||
// Test receive
|
||||
var got watchJSON
|
||||
err := websocket.JSON.Receive(ws, &got)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if got.Type != action {
|
||||
t.Errorf("Unexpected type: %v", got.Type)
|
||||
}
|
||||
gotObj, err := runtime.Decode(codec, got.Object)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode error: %v\n%v", err, got)
|
||||
}
|
||||
if _, err := api.GetReference(gotObj); err != nil {
|
||||
t.Errorf("Unable to construct reference: %v", err)
|
||||
}
|
||||
if e, a := object, gotObj; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range watchTestTable {
|
||||
try(item.t, item.obj)
|
||||
}
|
||||
simpleStorage.fakeWatch.Stop()
|
||||
|
||||
var got watchJSON
|
||||
err = websocket.JSON.Receive(ws, &got)
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchWebsocketClientClose(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
_ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work.
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Scheme = "ws" // Required by websocket, though the server never sees it.
|
||||
dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
ws, err := websocket.Dial(dest.String(), "", "http://localhost")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
try := func(action watch.EventType, object runtime.Object) {
|
||||
// Send
|
||||
simpleStorage.fakeWatch.Action(action, object)
|
||||
// Test receive
|
||||
var got watchJSON
|
||||
err := websocket.JSON.Receive(ws, &got)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if got.Type != action {
|
||||
t.Errorf("Unexpected type: %v", got.Type)
|
||||
}
|
||||
gotObj, err := runtime.Decode(codec, got.Object)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode error: %v\n%v", err, got)
|
||||
}
|
||||
if _, err := api.GetReference(gotObj); err != nil {
|
||||
t.Errorf("Unable to construct reference: %v", err)
|
||||
}
|
||||
if e, a := object, gotObj; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// Send/receive should work
|
||||
for _, item := range watchTestTable {
|
||||
try(item.t, item.obj)
|
||||
}
|
||||
|
||||
// Sending normal data should be ignored
|
||||
websocket.JSON.Send(ws, map[string]interface{}{"test": "data"})
|
||||
|
||||
// Send/receive should still work
|
||||
for _, item := range watchTestTable {
|
||||
try(item.t, item.obj)
|
||||
}
|
||||
|
||||
// Client requests a close
|
||||
ws.Close()
|
||||
|
||||
select {
|
||||
case data, ok := <-simpleStorage.fakeWatch.ResultChan():
|
||||
if ok {
|
||||
t.Errorf("expected a closed result channel, but got watch result %#v", data)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Errorf("watcher did not close when client closed")
|
||||
}
|
||||
|
||||
var got watchJSON
|
||||
err = websocket.JSON.Receive(ws, &got)
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchRead(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
_ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work.
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simples"
|
||||
dest.RawQuery = "watch=1"
|
||||
|
||||
connectHTTP := func(accept string) (io.ReadCloser, string) {
|
||||
client := http.Client{}
|
||||
request, err := http.NewRequest("GET", dest.String(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
request.Header.Add("Accept", accept)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
b, _ := ioutil.ReadAll(response.Body)
|
||||
t.Fatalf("Unexpected response for accept: %q: %#v\n%s", accept, response, string(b))
|
||||
}
|
||||
return response.Body, response.Header.Get("Content-Type")
|
||||
}
|
||||
|
||||
connectWebSocket := func(accept string) (io.ReadCloser, string) {
|
||||
dest := *dest
|
||||
dest.Scheme = "ws" // Required by websocket, though the server never sees it.
|
||||
config, err := websocket.NewConfig(dest.String(), "http://localhost")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
config.Header.Add("Accept", accept)
|
||||
ws, err := websocket.DialConfig(config)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
return ws, "__default__"
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Accept string
|
||||
ExpectedContentType string
|
||||
MediaType string
|
||||
}{
|
||||
{
|
||||
Accept: "application/json",
|
||||
ExpectedContentType: "application/json",
|
||||
MediaType: "application/json",
|
||||
},
|
||||
{
|
||||
Accept: "application/json;stream=watch",
|
||||
ExpectedContentType: "application/json", // legacy behavior
|
||||
MediaType: "application/json",
|
||||
},
|
||||
// TODO: yaml stream serialization requires that RawExtension.MarshalJSON
|
||||
// be able to understand nested encoding (since yaml calls json.Marshal
|
||||
// rather than yaml.Marshal, which results in the raw bytes being in yaml).
|
||||
// Same problem as thirdparty object.
|
||||
/*{
|
||||
Accept: "application/yaml",
|
||||
ExpectedContentType: "application/yaml;stream=watch",
|
||||
MediaType: "application/yaml",
|
||||
},*/
|
||||
{
|
||||
Accept: "application/vnd.kubernetes.protobuf",
|
||||
ExpectedContentType: "application/vnd.kubernetes.protobuf;stream=watch",
|
||||
MediaType: "application/vnd.kubernetes.protobuf",
|
||||
},
|
||||
{
|
||||
Accept: "application/vnd.kubernetes.protobuf;stream=watch",
|
||||
ExpectedContentType: "application/vnd.kubernetes.protobuf;stream=watch",
|
||||
MediaType: "application/vnd.kubernetes.protobuf",
|
||||
},
|
||||
}
|
||||
protocols := []struct {
|
||||
name string
|
||||
selfFraming bool
|
||||
fn func(string) (io.ReadCloser, string)
|
||||
}{
|
||||
{name: "http", fn: connectHTTP},
|
||||
{name: "websocket", selfFraming: true, fn: connectWebSocket},
|
||||
}
|
||||
|
||||
for _, protocol := range protocols {
|
||||
for _, test := range testCases {
|
||||
info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), test.MediaType)
|
||||
if !ok || info.StreamSerializer == nil {
|
||||
t.Fatal(info)
|
||||
}
|
||||
streamSerializer := info.StreamSerializer
|
||||
|
||||
r, contentType := protocol.fn(test.Accept)
|
||||
defer r.Close()
|
||||
|
||||
if contentType != "__default__" && contentType != test.ExpectedContentType {
|
||||
t.Errorf("Unexpected content type: %#v", contentType)
|
||||
}
|
||||
objectCodec := api.Codecs.DecoderToVersion(info.Serializer, testInternalGroupVersion)
|
||||
|
||||
var fr io.ReadCloser = r
|
||||
if !protocol.selfFraming {
|
||||
fr = streamSerializer.Framer.NewFrameReader(r)
|
||||
}
|
||||
d := streaming.NewDecoder(fr, streamSerializer.Serializer)
|
||||
|
||||
var w *watch.FakeWatcher
|
||||
for w == nil {
|
||||
w = simpleStorage.Watcher()
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
for i, item := range podWatchTestTable {
|
||||
action, object := item.t, item.obj
|
||||
name := fmt.Sprintf("%s-%s-%d", protocol.name, test.MediaType, i)
|
||||
|
||||
// Send
|
||||
w.Action(action, object)
|
||||
// Test receive
|
||||
var got metav1.WatchEvent
|
||||
_, _, err := d.Decode(nil, &got)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Unexpected error: %v", name, err)
|
||||
}
|
||||
if got.Type != string(action) {
|
||||
t.Errorf("%s: Unexpected type: %v", name, got.Type)
|
||||
}
|
||||
|
||||
gotObj, err := runtime.Decode(objectCodec, got.Object.Raw)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Decode error: %v", name, err)
|
||||
}
|
||||
if _, err := api.GetReference(gotObj); err != nil {
|
||||
t.Errorf("%s: Unable to construct reference: %v", name, err)
|
||||
}
|
||||
if e, a := object, gotObj; !api.Semantic.DeepEqual(e, a) {
|
||||
t.Errorf("%s: different: %s", name, diff.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
||||
w.Stop()
|
||||
|
||||
var got metav1.WatchEvent
|
||||
_, _, err := d.Decode(nil, &got)
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected non-error")
|
||||
}
|
||||
|
||||
r.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchHTTPAccept(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
request, err := http.NewRequest("GET", dest.String(), nil)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("Accept", "application/XYZ")
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// TODO: once this is fixed, this test will change
|
||||
if response.StatusCode != http.StatusNotAcceptable {
|
||||
t.Errorf("Unexpected response %#v", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchParamParsing(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{
|
||||
"simples": simpleStorage,
|
||||
"simpleroots": simpleStorage,
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
|
||||
rootPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples"
|
||||
namespacedPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/other/simpleroots"
|
||||
|
||||
table := []struct {
|
||||
path string
|
||||
rawQuery string
|
||||
resourceVersion string
|
||||
labelSelector string
|
||||
fieldSelector string
|
||||
namespace string
|
||||
}{
|
||||
{
|
||||
path: rootPath,
|
||||
rawQuery: "resourceVersion=1234",
|
||||
resourceVersion: "1234",
|
||||
labelSelector: "",
|
||||
fieldSelector: "",
|
||||
namespace: api.NamespaceAll,
|
||||
}, {
|
||||
path: rootPath,
|
||||
rawQuery: "resourceVersion=314159&fieldSelector=Host%3D&labelSelector=name%3Dfoo",
|
||||
resourceVersion: "314159",
|
||||
labelSelector: "name=foo",
|
||||
fieldSelector: "Host=",
|
||||
namespace: api.NamespaceAll,
|
||||
}, {
|
||||
path: rootPath,
|
||||
rawQuery: "fieldSelector=id%3dfoo&resourceVersion=1492",
|
||||
resourceVersion: "1492",
|
||||
labelSelector: "",
|
||||
fieldSelector: "id=foo",
|
||||
namespace: api.NamespaceAll,
|
||||
}, {
|
||||
path: rootPath,
|
||||
rawQuery: "",
|
||||
resourceVersion: "",
|
||||
labelSelector: "",
|
||||
fieldSelector: "",
|
||||
namespace: api.NamespaceAll,
|
||||
},
|
||||
{
|
||||
path: namespacedPath,
|
||||
rawQuery: "resourceVersion=1234",
|
||||
resourceVersion: "1234",
|
||||
labelSelector: "",
|
||||
fieldSelector: "",
|
||||
namespace: "other",
|
||||
}, {
|
||||
path: namespacedPath,
|
||||
rawQuery: "resourceVersion=314159&fieldSelector=Host%3D&labelSelector=name%3Dfoo",
|
||||
resourceVersion: "314159",
|
||||
labelSelector: "name=foo",
|
||||
fieldSelector: "Host=",
|
||||
namespace: "other",
|
||||
}, {
|
||||
path: namespacedPath,
|
||||
rawQuery: "fieldSelector=id%3dfoo&resourceVersion=1492",
|
||||
resourceVersion: "1492",
|
||||
labelSelector: "",
|
||||
fieldSelector: "id=foo",
|
||||
namespace: "other",
|
||||
}, {
|
||||
path: namespacedPath,
|
||||
rawQuery: "",
|
||||
resourceVersion: "",
|
||||
labelSelector: "",
|
||||
fieldSelector: "",
|
||||
namespace: "other",
|
||||
},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
simpleStorage.requestedLabelSelector = labels.Everything()
|
||||
simpleStorage.requestedFieldSelector = fields.Everything()
|
||||
simpleStorage.requestedResourceVersion = "5" // Prove this is set in all cases
|
||||
simpleStorage.requestedResourceNamespace = ""
|
||||
dest.Path = item.path
|
||||
dest.RawQuery = item.rawQuery
|
||||
resp, err := http.Get(dest.String())
|
||||
if err != nil {
|
||||
t.Errorf("%v: unexpected error: %v", item.rawQuery, err)
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
if e, a := item.namespace, simpleStorage.requestedResourceNamespace; e != a {
|
||||
t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a)
|
||||
}
|
||||
if e, a := item.resourceVersion, simpleStorage.requestedResourceVersion; e != a {
|
||||
t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a)
|
||||
}
|
||||
if e, a := item.labelSelector, simpleStorage.requestedLabelSelector.String(); e != a {
|
||||
t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a)
|
||||
}
|
||||
if e, a := item.fieldSelector, simpleStorage.requestedFieldSelector.String(); e != a {
|
||||
t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchProtocolSelection(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
defer server.CloseClientConnections()
|
||||
client := http.Client{}
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
table := []struct {
|
||||
isWebsocket bool
|
||||
connHeader string
|
||||
}{
|
||||
{true, "Upgrade"},
|
||||
{true, "keep-alive, Upgrade"},
|
||||
{true, "upgrade"},
|
||||
{false, "keep-alive"},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
request, err := http.NewRequest("GET", dest.String(), nil)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
request.Header.Set("Connection", item.connHeader)
|
||||
request.Header.Set("Upgrade", "websocket")
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// The requests recognized as websocket requests based on connection
|
||||
// and upgrade headers will not also have the necessary Sec-Websocket-*
|
||||
// headers so it is expected to throw a 400
|
||||
if item.isWebsocket && response.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("Unexpected response %#v", response)
|
||||
}
|
||||
|
||||
if !item.isWebsocket && response.StatusCode != http.StatusOK {
|
||||
t.Errorf("Unexpected response %#v", response)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type fakeTimeoutFactory struct {
|
||||
timeoutCh chan time.Time
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (t *fakeTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) {
|
||||
return t.timeoutCh, func() bool {
|
||||
defer close(t.done)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchHTTPTimeout(t *testing.T) {
|
||||
watcher := watch.NewFake()
|
||||
timeoutCh := make(chan time.Time)
|
||||
done := make(chan struct{})
|
||||
|
||||
info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
if !ok || info.StreamSerializer == nil {
|
||||
t.Fatal(info)
|
||||
}
|
||||
serializer := info.StreamSerializer
|
||||
|
||||
// Setup a new watchserver
|
||||
watchServer := &handlers.WatchServer{
|
||||
Watching: watcher,
|
||||
|
||||
MediaType: "testcase/json",
|
||||
Framer: serializer.Framer,
|
||||
Encoder: newCodec,
|
||||
EmbeddedEncoder: newCodec,
|
||||
|
||||
Fixup: func(obj runtime.Object) {},
|
||||
TimeoutFactory: &fakeTimeoutFactory{timeoutCh, done},
|
||||
}
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
watchServer.ServeHTTP(w, req)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
// Setup a client
|
||||
dest, _ := url.Parse(s.URL)
|
||||
dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple"
|
||||
dest.RawQuery = "watch=true"
|
||||
|
||||
req, _ := http.NewRequest("GET", dest.String(), nil)
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
watcher.Add(&apitesting.Simple{TypeMeta: metav1.TypeMeta{APIVersion: newGroupVersion.String()}})
|
||||
|
||||
// Make sure we can actually watch an endpoint
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var got watchJSON
|
||||
err = decoder.Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Timeout and check for leaks
|
||||
close(timeoutCh)
|
||||
select {
|
||||
case <-done:
|
||||
if !watcher.Stopped {
|
||||
t.Errorf("Leaked watch on timeout")
|
||||
}
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Errorf("Failed to stop watcher after %s of timeout signal", wait.ForeverTestTimeout.String())
|
||||
}
|
||||
|
||||
// Make sure we can't receive any more events through the timeout watch
|
||||
err = decoder.Decode(&got)
|
||||
if err != io.EOF {
|
||||
t.Errorf("Unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWatchHTTP measures the cost of serving a watch.
|
||||
func BenchmarkWatchHTTP(b *testing.B) {
|
||||
items := benchmarkItems()
|
||||
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
request, err := http.NewRequest("GET", dest.String(), nil)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
b.Fatalf("Unexpected response %#v", response)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer response.Body.Close()
|
||||
if _, err := io.Copy(ioutil.Discard, response.Body); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)])
|
||||
}
|
||||
simpleStorage.fakeWatch.Stop()
|
||||
wg.Wait()
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// BenchmarkWatchWebsocket measures the cost of serving a watch.
|
||||
func BenchmarkWatchWebsocket(b *testing.B) {
|
||||
items := benchmarkItems()
|
||||
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Scheme = "ws" // Required by websocket, though the server never sees it.
|
||||
dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
ws, err := websocket.Dial(dest.String(), "", "http://localhost")
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer ws.Close()
|
||||
if _, err := io.Copy(ioutil.Discard, ws); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)])
|
||||
}
|
||||
simpleStorage.fakeWatch.Stop()
|
||||
wg.Wait()
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
// BenchmarkWatchProtobuf measures the cost of serving a watch.
|
||||
func BenchmarkWatchProtobuf(b *testing.B) {
|
||||
items := benchmarkItems()
|
||||
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
dest, _ := url.Parse(server.URL)
|
||||
dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples"
|
||||
dest.RawQuery = ""
|
||||
|
||||
request, err := http.NewRequest("GET", dest.String(), nil)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
request.Header.Set("Accept", "application/vnd.kubernetes.protobuf")
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
b.Fatalf("Unexpected response %#v\n%s", response, body)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer response.Body.Close()
|
||||
if _, err := io.Copy(ioutil.Discard, response.Body); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)])
|
||||
}
|
||||
simpleStorage.fakeWatch.Stop()
|
||||
wg.Wait()
|
||||
b.StopTimer()
|
||||
}
|
46
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/BUILD
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"delegating.go",
|
||||
"loopback.go",
|
||||
"requestheader.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/util/cert:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/token/webhook:go_default_library",
|
||||
"//vendor:github.com/go-openapi/spec",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/authenticator",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/group",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/request/anonymous",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/request/bearertoken",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/request/headerrequest",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/request/union",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/request/x509",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/token/tokenfile",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
"//vendor:k8s.io/client-go/kubernetes/typed/authentication/v1beta1",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
116
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/delegating.go
generated
vendored
Normal file
116
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/delegating.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package authenticator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/group"
|
||||
"k8s.io/apiserver/pkg/authentication/request/anonymous"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
||||
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
|
||||
"k8s.io/apiserver/pkg/authentication/request/x509"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/cert"
|
||||
webhooktoken "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook"
|
||||
)
|
||||
|
||||
// DelegatingAuthenticatorConfig is the minimal configuration needed to create an authenticator
|
||||
// built to delegate authentication to a kube API server
|
||||
type DelegatingAuthenticatorConfig struct {
|
||||
Anonymous bool
|
||||
|
||||
TokenAccessReviewClient authenticationclient.TokenReviewInterface
|
||||
|
||||
// CacheTTL is the length of time that a token authentication answer will be cached.
|
||||
CacheTTL time.Duration
|
||||
|
||||
// ClientCAFile is the CA bundle file used to authenticate client certificates
|
||||
ClientCAFile string
|
||||
|
||||
RequestHeaderConfig *RequestHeaderConfig
|
||||
}
|
||||
|
||||
func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
|
||||
authenticators := []authenticator.Request{}
|
||||
securityDefinitions := spec.SecurityDefinitions{}
|
||||
|
||||
// front-proxy first, then remote
|
||||
// Add the front proxy authenticator if requested
|
||||
if c.RequestHeaderConfig != nil {
|
||||
requestHeaderAuthenticator, err := headerrequest.NewSecure(
|
||||
c.RequestHeaderConfig.ClientCA,
|
||||
c.RequestHeaderConfig.AllowedClientNames,
|
||||
c.RequestHeaderConfig.UsernameHeaders,
|
||||
c.RequestHeaderConfig.GroupHeaders,
|
||||
c.RequestHeaderConfig.ExtraHeaderPrefixes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, requestHeaderAuthenticator)
|
||||
}
|
||||
|
||||
// x509 client cert auth
|
||||
if len(c.ClientCAFile) > 0 {
|
||||
clientCAs, err := cert.NewPool(c.ClientCAFile)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to load client CA file %s: %v", c.ClientCAFile, err)
|
||||
}
|
||||
verifyOpts := x509.DefaultVerifyOptions()
|
||||
verifyOpts.Roots = clientCAs
|
||||
authenticators = append(authenticators, x509.New(verifyOpts, x509.CommonNameUserConversion))
|
||||
}
|
||||
|
||||
if c.TokenAccessReviewClient != nil {
|
||||
tokenAuth, err := webhooktoken.NewFromInterface(c.TokenAccessReviewClient, c.CacheTTL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, bearertoken.New(tokenAuth))
|
||||
|
||||
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
|
||||
SecuritySchemeProps: spec.SecuritySchemeProps{
|
||||
Type: "apiKey",
|
||||
Name: "authorization",
|
||||
In: "header",
|
||||
Description: "Bearer Token authentication",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(authenticators) == 0 {
|
||||
if c.Anonymous {
|
||||
return anonymous.NewAuthenticator(), &securityDefinitions, nil
|
||||
}
|
||||
return nil, nil, errors.New("No authentication method configured")
|
||||
}
|
||||
|
||||
authenticator := group.NewGroupAdder(unionauth.New(authenticators...), []string{user.AllAuthenticated})
|
||||
if c.Anonymous {
|
||||
authenticator = unionauth.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
||||
}
|
||||
return authenticator, &securityDefinitions, nil
|
||||
}
|
29
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/loopback.go
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/loopback.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package authenticator
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// newAuthenticatorFromToken returns an authenticator.Request or an error
|
||||
func NewAuthenticatorFromTokens(tokens map[string]*user.DefaultInfo) authenticator.Request {
|
||||
return bearertoken.New(tokenfile.New(tokens))
|
||||
}
|
31
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/requestheader.go
generated
vendored
Normal file
31
vendor/k8s.io/kubernetes/pkg/genericapiserver/authenticator/requestheader.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package authenticator
|
||||
|
||||
type RequestHeaderConfig struct {
|
||||
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||
UsernameHeaders []string
|
||||
// GroupHeaders are the headers to check (case-insensitively) for a group names. All values will be used.
|
||||
GroupHeaders []string
|
||||
// ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in
|
||||
// the user.Info.Extra. All values of all matching headers will be added.
|
||||
ExtraHeaderPrefixes []string
|
||||
// ClientCA points to CA bundle file which is used verify the identity of the front proxy
|
||||
ClientCA string
|
||||
// AllowedClientNames is a list of common names that may be presented by the authenticating front proxy. Empty means: accept any.
|
||||
AllowedClientNames []string
|
||||
}
|
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/BUILD
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["authz_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"builtin.go",
|
||||
"delegating.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//plugin/pkg/auth/authorizer/webhook:go_default_library",
|
||||
"//vendor:k8s.io/apiserver/pkg/authorization/authorizer",
|
||||
"//vendor:k8s.io/client-go/kubernetes/typed/authorization/v1beta1",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
4
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/OWNERS
generated
vendored
Executable file
4
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,4 @@
|
|||
reviewers:
|
||||
- deads2k
|
||||
- dims
|
||||
- ericchiang
|
56
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/authz_test.go
generated
vendored
Normal file
56
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/authz_test.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package authorizer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// NewAlwaysAllowAuthorizer must return a struct which implements authorizer.Authorizer
|
||||
// and always return nil.
|
||||
func TestNewAlwaysAllowAuthorizer(t *testing.T) {
|
||||
aaa := NewAlwaysAllowAuthorizer()
|
||||
if authorized, _, _ := aaa.Authorize(nil); !authorized {
|
||||
t.Errorf("AlwaysAllowAuthorizer.Authorize did not authorize successfully.")
|
||||
}
|
||||
}
|
||||
|
||||
// NewAlwaysDenyAuthorizer must return a struct which implements authorizer.Authorizer
|
||||
// and always return an error as everything is forbidden.
|
||||
func TestNewAlwaysDenyAuthorizer(t *testing.T) {
|
||||
ada := NewAlwaysDenyAuthorizer()
|
||||
if authorized, _, _ := ada.Authorize(nil); authorized {
|
||||
t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivilegedGroupAuthorizer(t *testing.T) {
|
||||
auth := NewPrivilegedGroups("allow-01", "allow-01")
|
||||
|
||||
yes := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "allow-01"}}}
|
||||
no := authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"no", "deny-01"}}}
|
||||
|
||||
if authorized, _, _ := auth.Authorize(yes); !authorized {
|
||||
t.Errorf("failed")
|
||||
}
|
||||
if authorized, _, _ := auth.Authorize(no); authorized {
|
||||
t.Errorf("failed")
|
||||
}
|
||||
}
|
87
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/builtin.go
generated
vendored
Normal file
87
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/builtin.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package authorizer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// alwaysAllowAuthorizer is an implementation of authorizer.Attributes
|
||||
// which always says yes to an authorization request.
|
||||
// It is useful in tests and when using kubernetes in an open manner.
|
||||
type alwaysAllowAuthorizer struct{}
|
||||
|
||||
func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
func NewAlwaysAllowAuthorizer() authorizer.Authorizer {
|
||||
return new(alwaysAllowAuthorizer)
|
||||
}
|
||||
|
||||
// alwaysDenyAuthorizer is an implementation of authorizer.Attributes
|
||||
// which always says no to an authorization request.
|
||||
// It is useful in unit tests to force an operation to be forbidden.
|
||||
type alwaysDenyAuthorizer struct{}
|
||||
|
||||
func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
|
||||
return false, "Everything is forbidden.", nil
|
||||
}
|
||||
|
||||
func NewAlwaysDenyAuthorizer() authorizer.Authorizer {
|
||||
return new(alwaysDenyAuthorizer)
|
||||
}
|
||||
|
||||
// alwaysFailAuthorizer is an implementation of authorizer.Attributes
|
||||
// which always says no to an authorization request.
|
||||
// It is useful in unit tests to force an operation to fail with error.
|
||||
type alwaysFailAuthorizer struct{}
|
||||
|
||||
func (alwaysFailAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
|
||||
return false, "", errors.New("Authorization failure.")
|
||||
}
|
||||
|
||||
func NewAlwaysFailAuthorizer() authorizer.Authorizer {
|
||||
return new(alwaysFailAuthorizer)
|
||||
}
|
||||
|
||||
type privilegedGroupAuthorizer struct {
|
||||
groups []string
|
||||
}
|
||||
|
||||
func (r *privilegedGroupAuthorizer) Authorize(attr authorizer.Attributes) (bool, string, error) {
|
||||
if attr.GetUser() == nil {
|
||||
return false, "Error", errors.New("no user on request.")
|
||||
}
|
||||
for _, attr_group := range attr.GetUser().GetGroups() {
|
||||
for _, priv_group := range r.groups {
|
||||
if priv_group == attr_group {
|
||||
return true, "", nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
// NewPrivilegedGroups is for use in loopback scenarios
|
||||
func NewPrivilegedGroups(groups ...string) *privilegedGroupAuthorizer {
|
||||
return &privilegedGroupAuthorizer{
|
||||
groups: groups,
|
||||
}
|
||||
}
|
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/delegating.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/pkg/genericapiserver/authorizer/delegating.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package authorizer
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
authorizationclient "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
|
||||
|
||||
webhooksar "k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook"
|
||||
)
|
||||
|
||||
// DelegatingAuthorizerConfig is the minimal configuration needed to create an authenticator
|
||||
// built to delegate authorization to a kube API server
|
||||
type DelegatingAuthorizerConfig struct {
|
||||
SubjectAccessReviewClient authorizationclient.SubjectAccessReviewInterface
|
||||
|
||||
// AllowCacheTTL is the length of time that a successful authorization response will be cached
|
||||
AllowCacheTTL time.Duration
|
||||
|
||||
// DenyCacheTTL is the length of time that an unsuccessful authorization response will be cached.
|
||||
// You generally want more responsive, "deny, try again" flows.
|
||||
DenyCacheTTL time.Duration
|
||||
}
|
||||
|
||||
func (c DelegatingAuthorizerConfig) New() (authorizer.Authorizer, error) {
|
||||
return webhooksar.NewFromInterface(
|
||||
c.SubjectAccessReviewClient,
|
||||
c.AllowCacheTTL,
|
||||
c.DenyCacheTTL,
|
||||
)
|
||||
}
|
619
vendor/k8s.io/kubernetes/pkg/genericapiserver/config.go
generated
vendored
Normal file
619
vendor/k8s.io/kubernetes/pkg/genericapiserver/config.go
generated
vendored
Normal file
|
@ -0,0 +1,619 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package genericapiserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
goruntime "runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful/swagger"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/pborman/uuid"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
authenticatorunion "k8s.io/apiserver/pkg/authentication/request/union"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
authorizerunion "k8s.io/apiserver/pkg/authorization/union"
|
||||
"k8s.io/apiserver/pkg/healthz"
|
||||
apirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
authhandlers "k8s.io/kubernetes/pkg/auth/handlers"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
genericapifilters "k8s.io/kubernetes/pkg/genericapiserver/api/filters"
|
||||
apiopenapi "k8s.io/kubernetes/pkg/genericapiserver/api/openapi"
|
||||
genericauthenticator "k8s.io/kubernetes/pkg/genericapiserver/authenticator"
|
||||
genericauthorizer "k8s.io/kubernetes/pkg/genericapiserver/authorizer"
|
||||
genericfilters "k8s.io/kubernetes/pkg/genericapiserver/filters"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/options"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/routes"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultLegacyAPIPrefix is where the the legacy APIs will be located.
|
||||
DefaultLegacyAPIPrefix = "/api"
|
||||
|
||||
// APIGroupPrefix is where non-legacy API group will be located.
|
||||
APIGroupPrefix = "/apis"
|
||||
)
|
||||
|
||||
// Config is a structure used to configure a GenericAPIServer.
|
||||
// It's members are sorted rougly in order of importance for composers.
|
||||
type Config struct {
|
||||
// SecureServingInfo is required to serve https
|
||||
SecureServingInfo *SecureServingInfo
|
||||
|
||||
// LoopbackClientConfig is a config for a privileged loopback connection to the API server
|
||||
// This is required for proper functioning of the PostStartHooks on a GenericAPIServer
|
||||
LoopbackClientConfig *restclient.Config
|
||||
// Authenticator determines which subject is making the request
|
||||
Authenticator authenticator.Request
|
||||
// Authorizer determines whether the subject is allowed to make the request based only
|
||||
// on the RequestURI
|
||||
Authorizer authorizer.Authorizer
|
||||
// AdmissionControl performs deep inspection of a given request (including content)
|
||||
// to set values and determine whether its allowed
|
||||
AdmissionControl admission.Interface
|
||||
CorsAllowedOriginList []string
|
||||
|
||||
EnableSwaggerUI bool
|
||||
EnableIndex bool
|
||||
EnableProfiling bool
|
||||
// Requires generic profiling enabled
|
||||
EnableContentionProfiling bool
|
||||
EnableGarbageCollection bool
|
||||
EnableMetrics bool
|
||||
|
||||
// Version will enable the /version endpoint if non-nil
|
||||
Version *version.Info
|
||||
// AuditWriter is the destination for audit logs. If nil, they will not be written.
|
||||
AuditWriter io.Writer
|
||||
// SupportsBasicAuth indicates that's at least one Authenticator supports basic auth
|
||||
// If this is true, a basic auth challenge is returned on authentication failure
|
||||
// TODO(roberthbailey): Remove once the server no longer supports http basic auth.
|
||||
SupportsBasicAuth bool
|
||||
// ExternalAddress is the host name to use for external (public internet) facing URLs (e.g. Swagger)
|
||||
// Will default to a value based on secure serving info and available ipv4 IPs.
|
||||
ExternalAddress string
|
||||
|
||||
//===========================================================================
|
||||
// Fields you probably don't care about changing
|
||||
//===========================================================================
|
||||
|
||||
// BuildHandlerChainsFunc allows you to build custom handler chains by decorating the apiHandler.
|
||||
BuildHandlerChainsFunc func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler)
|
||||
// DiscoveryAddresses is used to build the IPs pass to discovery. If nil, the ExternalAddress is
|
||||
// always reported
|
||||
DiscoveryAddresses DiscoveryAddresses
|
||||
// The default set of healthz checks. There might be more added via AddHealthzChecks dynamically.
|
||||
HealthzChecks []healthz.HealthzChecker
|
||||
// LegacyAPIGroupPrefixes is used to set up URL parsing for authorization and for validating requests
|
||||
// to InstallLegacyAPIGroup. New API servers don't generally have legacy groups at all.
|
||||
LegacyAPIGroupPrefixes sets.String
|
||||
// RequestContextMapper maps requests to contexts. Exported so downstream consumers can provider their own mappers
|
||||
// TODO confirm that anyone downstream actually uses this and doesn't just need an accessor
|
||||
RequestContextMapper apirequest.RequestContextMapper
|
||||
// Serializer is required and provides the interface for serializing and converting objects to and from the wire
|
||||
// The default (api.Codecs) usually works fine.
|
||||
Serializer runtime.NegotiatedSerializer
|
||||
// OpenAPIConfig will be used in generating OpenAPI spec. This is nil by default. Use DefaultOpenAPIConfig for "working" defaults.
|
||||
OpenAPIConfig *openapicommon.Config
|
||||
// SwaggerConfig will be used in generating Swagger spec. This is nil by default. Use DefaultSwaggerConfig for "working" defaults.
|
||||
SwaggerConfig *swagger.Config
|
||||
|
||||
// If specified, requests will be allocated a random timeout between this value, and twice this value.
|
||||
// Note that it is up to the request handlers to ignore or honor this timeout. In seconds.
|
||||
MinRequestTimeout int
|
||||
// MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further
|
||||
// request has to wait. Applies only to non-mutating requests.
|
||||
MaxRequestsInFlight int
|
||||
// MaxMutatingRequestsInFlight is the maximum number of parallel mutating requests. Every further
|
||||
// request has to wait.
|
||||
MaxMutatingRequestsInFlight int
|
||||
// Predicate which is true for paths of long-running http requests
|
||||
LongRunningFunc genericfilters.LongRunningRequestCheck
|
||||
|
||||
// InsecureServingInfo is required to serve http. HTTP does NOT include authentication or authorization.
|
||||
// You shouldn't be using this. It makes sig-auth sad.
|
||||
InsecureServingInfo *ServingInfo
|
||||
|
||||
//===========================================================================
|
||||
// values below here are targets for removal
|
||||
//===========================================================================
|
||||
|
||||
// The port on PublicAddress where a read-write server will be installed.
|
||||
// Defaults to 6443 if not set.
|
||||
ReadWritePort int
|
||||
// PublicAddress is the IP address where members of the cluster (kubelet,
|
||||
// kube-proxy, services, etc.) can reach the GenericAPIServer.
|
||||
// If nil or 0.0.0.0, the host's default interface will be used.
|
||||
PublicAddress net.IP
|
||||
}
|
||||
|
||||
type ServingInfo struct {
|
||||
// BindAddress is the ip:port to serve on
|
||||
BindAddress string
|
||||
// BindNetwork is the type of network to bind to - defaults to "tcp", accepts "tcp",
|
||||
// "tcp4", and "tcp6".
|
||||
BindNetwork string
|
||||
}
|
||||
|
||||
type SecureServingInfo struct {
|
||||
ServingInfo
|
||||
|
||||
// Cert is the main server cert which is used if SNI does not match. Cert must be non-nil and is
|
||||
// allowed to be in SNICerts.
|
||||
Cert *tls.Certificate
|
||||
|
||||
// CACert is an optional certificate authority used for the loopback connection of the Admission controllers.
|
||||
// If this is nil, the certificate authority is extracted from Cert or a matching SNI certificate.
|
||||
CACert *tls.Certificate
|
||||
|
||||
// SNICerts are the TLS certificates by name used for SNI.
|
||||
SNICerts map[string]*tls.Certificate
|
||||
|
||||
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
|
||||
ClientCA *x509.CertPool
|
||||
}
|
||||
|
||||
// NewConfig returns a Config struct with the default values
|
||||
func NewConfig() *Config {
|
||||
config := &Config{
|
||||
Serializer: api.Codecs,
|
||||
ReadWritePort: 6443,
|
||||
RequestContextMapper: apirequest.NewRequestContextMapper(),
|
||||
BuildHandlerChainsFunc: DefaultBuildHandlerChain,
|
||||
LegacyAPIGroupPrefixes: sets.NewString(DefaultLegacyAPIPrefix),
|
||||
HealthzChecks: []healthz.HealthzChecker{healthz.PingHealthz},
|
||||
EnableIndex: true,
|
||||
|
||||
// Default to treating watch as a long-running operation
|
||||
// Generic API servers have no inherent long-running subresources
|
||||
LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString()),
|
||||
}
|
||||
|
||||
// this keeps the defaults in sync
|
||||
defaultOptions := options.NewServerRunOptions()
|
||||
// unset fields that can be overridden to avoid setting values so that we won't end up with lingering values.
|
||||
// TODO we probably want to run the defaults the other way. A default here drives it in the CLI flags
|
||||
defaultOptions.AuditLogPath = ""
|
||||
return config.ApplyOptions(defaultOptions)
|
||||
}
|
||||
|
||||
func DefaultOpenAPIConfig(definitions *openapicommon.OpenAPIDefinitions) *openapicommon.Config {
|
||||
return &openapicommon.Config{
|
||||
ProtocolList: []string{"https"},
|
||||
IgnorePrefixes: []string{"/swaggerapi"},
|
||||
Info: &spec.Info{
|
||||
InfoProps: spec.InfoProps{
|
||||
Title: "Generic API Server",
|
||||
Version: "unversioned",
|
||||
},
|
||||
},
|
||||
DefaultResponse: &spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: "Default Response.",
|
||||
},
|
||||
},
|
||||
GetOperationIDAndTags: apiopenapi.GetOperationIDAndTags,
|
||||
Definitions: definitions,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultSwaggerConfig returns a default configuration without WebServiceURL and
|
||||
// WebServices set.
|
||||
func DefaultSwaggerConfig() *swagger.Config {
|
||||
return &swagger.Config{
|
||||
ApiPath: "/swaggerapi/",
|
||||
SwaggerPath: "/swaggerui/",
|
||||
SwaggerFilePath: "/swagger-ui/",
|
||||
SchemaFormatHandler: func(typeName string) string {
|
||||
switch typeName {
|
||||
case "metav1.Time", "*metav1.Time":
|
||||
return "date-time"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) ApplySecureServingOptions(secureServing *options.SecureServingOptions) (*Config, error) {
|
||||
if secureServing == nil || secureServing.ServingOptions.BindPort <= 0 {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
secureServingInfo := &SecureServingInfo{
|
||||
ServingInfo: ServingInfo{
|
||||
BindAddress: net.JoinHostPort(secureServing.ServingOptions.BindAddress.String(), strconv.Itoa(secureServing.ServingOptions.BindPort)),
|
||||
},
|
||||
}
|
||||
|
||||
serverCertFile, serverKeyFile := secureServing.ServerCert.CertKey.CertFile, secureServing.ServerCert.CertKey.KeyFile
|
||||
|
||||
// load main cert
|
||||
if len(serverCertFile) != 0 || len(serverKeyFile) != 0 {
|
||||
tlsCert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load server certificate: %v", err)
|
||||
}
|
||||
secureServingInfo.Cert = &tlsCert
|
||||
}
|
||||
|
||||
// optionally load CA cert
|
||||
if len(secureServing.ServerCert.CACertFile) != 0 {
|
||||
pemData, err := ioutil.ReadFile(secureServing.ServerCert.CACertFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read certificate authority from %q: %v", secureServing.ServerCert.CACertFile, err)
|
||||
}
|
||||
block, pemData := pem.Decode(pemData)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("no certificate found in certificate authority file %q", secureServing.ServerCert.CACertFile)
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return nil, fmt.Errorf("expected CERTIFICATE block in certiticate authority file %q, found: %s", secureServing.ServerCert.CACertFile, block.Type)
|
||||
}
|
||||
secureServingInfo.CACert = &tls.Certificate{
|
||||
Certificate: [][]byte{block.Bytes},
|
||||
}
|
||||
}
|
||||
|
||||
// load SNI certs
|
||||
namedTlsCerts := make([]namedTlsCert, 0, len(secureServing.SNICertKeys))
|
||||
for _, nck := range secureServing.SNICertKeys {
|
||||
tlsCert, err := tls.LoadX509KeyPair(nck.CertFile, nck.KeyFile)
|
||||
namedTlsCerts = append(namedTlsCerts, namedTlsCert{
|
||||
tlsCert: tlsCert,
|
||||
names: nck.Names,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load SNI cert and key: %v", err)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
secureServingInfo.SNICerts, err = getNamedCertificateMap(namedTlsCerts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.SecureServingInfo = secureServingInfo
|
||||
c.ReadWritePort = secureServing.ServingOptions.BindPort
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Config) ApplyInsecureServingOptions(insecureServing *options.ServingOptions) *Config {
|
||||
if insecureServing == nil || insecureServing.BindPort <= 0 {
|
||||
return c
|
||||
}
|
||||
|
||||
c.InsecureServingInfo = &ServingInfo{
|
||||
BindAddress: net.JoinHostPort(insecureServing.BindAddress.String(), strconv.Itoa(insecureServing.BindPort)),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) ApplyClientCert(clientCAFile string) (*Config, error) {
|
||||
if c.SecureServingInfo != nil {
|
||||
if len(clientCAFile) > 0 {
|
||||
clientCAs, err := certutil.CertsFromFile(clientCAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load client CA file: %v", err)
|
||||
}
|
||||
if c.SecureServingInfo.ClientCA == nil {
|
||||
c.SecureServingInfo.ClientCA = x509.NewCertPool()
|
||||
}
|
||||
for _, cert := range clientCAs {
|
||||
c.SecureServingInfo.ClientCA.AddCert(cert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Config) ApplyDelegatingAuthenticationOptions(o *options.DelegatingAuthenticationOptions) (*Config, error) {
|
||||
if o == nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
c, err = c.ApplyClientCert(o.ClientCert.ClientCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load client CA file: %v", err)
|
||||
}
|
||||
c, err = c.ApplyClientCert(o.RequestHeader.ClientCAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load client CA file: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := o.ToAuthenticationConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authenticator, securityDefinitions, err := cfg.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Authenticator = authenticator
|
||||
if c.OpenAPIConfig != nil {
|
||||
c.OpenAPIConfig.SecurityDefinitions = securityDefinitions
|
||||
}
|
||||
c.SupportsBasicAuth = false
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Config) ApplyDelegatingAuthorizationOptions(o *options.DelegatingAuthorizationOptions) (*Config, error) {
|
||||
if o == nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
cfg, err := o.ToAuthorizationConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorizer, err := cfg.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Authorizer = authorizer
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ApplyOptions applies the run options to the method receiver and returns self
|
||||
func (c *Config) ApplyOptions(options *options.ServerRunOptions) *Config {
|
||||
if len(options.AuditLogPath) != 0 {
|
||||
c.AuditWriter = &lumberjack.Logger{
|
||||
Filename: options.AuditLogPath,
|
||||
MaxAge: options.AuditLogMaxAge,
|
||||
MaxBackups: options.AuditLogMaxBackups,
|
||||
MaxSize: options.AuditLogMaxSize,
|
||||
}
|
||||
}
|
||||
|
||||
c.CorsAllowedOriginList = options.CorsAllowedOriginList
|
||||
c.EnableGarbageCollection = options.EnableGarbageCollection
|
||||
c.EnableProfiling = options.EnableProfiling
|
||||
c.EnableContentionProfiling = options.EnableContentionProfiling
|
||||
c.EnableSwaggerUI = options.EnableSwaggerUI
|
||||
c.ExternalAddress = options.ExternalHost
|
||||
c.MaxRequestsInFlight = options.MaxRequestsInFlight
|
||||
c.MaxMutatingRequestsInFlight = options.MaxMutatingRequestsInFlight
|
||||
c.MinRequestTimeout = options.MinRequestTimeout
|
||||
c.PublicAddress = options.AdvertiseAddress
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type completedConfig struct {
|
||||
*Config
|
||||
}
|
||||
|
||||
// Complete fills in any fields not set that are required to have valid data and can be derived
|
||||
// from other fields. If you're going to `ApplyOptions`, do that first. It's mutating the receiver.
|
||||
func (c *Config) Complete() completedConfig {
|
||||
if len(c.ExternalAddress) == 0 && c.PublicAddress != nil {
|
||||
hostAndPort := c.PublicAddress.String()
|
||||
if c.ReadWritePort != 0 {
|
||||
hostAndPort = net.JoinHostPort(hostAndPort, strconv.Itoa(c.ReadWritePort))
|
||||
}
|
||||
c.ExternalAddress = hostAndPort
|
||||
}
|
||||
if c.OpenAPIConfig != nil && c.OpenAPIConfig.SecurityDefinitions != nil {
|
||||
// Setup OpenAPI security: all APIs will have the same authentication for now.
|
||||
c.OpenAPIConfig.DefaultSecurity = []map[string][]string{}
|
||||
keys := []string{}
|
||||
for k := range *c.OpenAPIConfig.SecurityDefinitions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
c.OpenAPIConfig.DefaultSecurity = append(c.OpenAPIConfig.DefaultSecurity, map[string][]string{k: {}})
|
||||
}
|
||||
if c.OpenAPIConfig.CommonResponses == nil {
|
||||
c.OpenAPIConfig.CommonResponses = map[int]spec.Response{}
|
||||
}
|
||||
if _, exists := c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized]; !exists {
|
||||
c.OpenAPIConfig.CommonResponses[http.StatusUnauthorized] = spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: "Unauthorized",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.SwaggerConfig != nil && len(c.SwaggerConfig.WebServicesUrl) == 0 {
|
||||
if c.SecureServingInfo != nil {
|
||||
c.SwaggerConfig.WebServicesUrl = "https://" + c.ExternalAddress
|
||||
} else {
|
||||
c.SwaggerConfig.WebServicesUrl = "http://" + c.ExternalAddress
|
||||
}
|
||||
}
|
||||
if c.DiscoveryAddresses == nil {
|
||||
c.DiscoveryAddresses = DefaultDiscoveryAddresses{DefaultAddress: c.ExternalAddress}
|
||||
}
|
||||
|
||||
// If the loopbackclientconfig is specified AND it has a token for use against the API server
|
||||
// wrap the authenticator and authorizer in loopback authentication logic
|
||||
if c.Authenticator != nil && c.Authorizer != nil && c.LoopbackClientConfig != nil && len(c.LoopbackClientConfig.BearerToken) > 0 {
|
||||
privilegedLoopbackToken := c.LoopbackClientConfig.BearerToken
|
||||
var uid = uuid.NewRandom().String()
|
||||
tokens := make(map[string]*user.DefaultInfo)
|
||||
tokens[privilegedLoopbackToken] = &user.DefaultInfo{
|
||||
Name: user.APIServerUser,
|
||||
UID: uid,
|
||||
Groups: []string{user.SystemPrivilegedGroup},
|
||||
}
|
||||
|
||||
tokenAuthenticator := genericauthenticator.NewAuthenticatorFromTokens(tokens)
|
||||
c.Authenticator = authenticatorunion.New(tokenAuthenticator, c.Authenticator)
|
||||
|
||||
tokenAuthorizer := genericauthorizer.NewPrivilegedGroups(user.SystemPrivilegedGroup)
|
||||
c.Authorizer = authorizerunion.New(tokenAuthorizer, c.Authorizer)
|
||||
}
|
||||
|
||||
return completedConfig{c}
|
||||
}
|
||||
|
||||
// SkipComplete provides a way to construct a server instance without config completion.
|
||||
func (c *Config) SkipComplete() completedConfig {
|
||||
return completedConfig{c}
|
||||
}
|
||||
|
||||
// New returns a new instance of GenericAPIServer from the given config.
|
||||
// Certain config fields will be set to a default value if unset,
|
||||
// including:
|
||||
// ServiceClusterIPRange
|
||||
// ServiceNodePortRange
|
||||
// MasterCount
|
||||
// ReadWritePort
|
||||
// PublicAddress
|
||||
// Public fields:
|
||||
// Handler -- The returned GenericAPIServer has a field TopHandler which is an
|
||||
// http.Handler which handles all the endpoints provided by the GenericAPIServer,
|
||||
// including the API, the UI, and miscellaneous debugging endpoints. All
|
||||
// these are subject to authorization and authentication.
|
||||
// InsecureHandler -- an http.Handler which handles all the same
|
||||
// endpoints as Handler, but no authorization and authentication is done.
|
||||
// Public methods:
|
||||
// HandleWithAuth -- Allows caller to add an http.Handler for an endpoint
|
||||
// that uses the same authentication and authorization (if any is configured)
|
||||
// as the GenericAPIServer's built-in endpoints.
|
||||
// If the caller wants to add additional endpoints not using the GenericAPIServer's
|
||||
// auth, then the caller should create a handler for those endpoints, which delegates the
|
||||
// any unhandled paths to "Handler".
|
||||
func (c completedConfig) New() (*GenericAPIServer, error) {
|
||||
if c.Serializer == nil {
|
||||
return nil, fmt.Errorf("Genericapiserver.New() called with config.Serializer == nil")
|
||||
}
|
||||
|
||||
s := &GenericAPIServer{
|
||||
discoveryAddresses: c.DiscoveryAddresses,
|
||||
LoopbackClientConfig: c.LoopbackClientConfig,
|
||||
legacyAPIGroupPrefixes: c.LegacyAPIGroupPrefixes,
|
||||
admissionControl: c.AdmissionControl,
|
||||
requestContextMapper: c.RequestContextMapper,
|
||||
Serializer: c.Serializer,
|
||||
|
||||
minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second,
|
||||
|
||||
SecureServingInfo: c.SecureServingInfo,
|
||||
InsecureServingInfo: c.InsecureServingInfo,
|
||||
ExternalAddress: c.ExternalAddress,
|
||||
|
||||
apiGroupsForDiscovery: map[string]metav1.APIGroup{},
|
||||
|
||||
swaggerConfig: c.SwaggerConfig,
|
||||
openAPIConfig: c.OpenAPIConfig,
|
||||
|
||||
postStartHooks: map[string]postStartHookEntry{},
|
||||
healthzChecks: c.HealthzChecks,
|
||||
}
|
||||
|
||||
s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer)
|
||||
|
||||
s.installAPI(c.Config)
|
||||
|
||||
s.Handler, s.InsecureHandler = c.BuildHandlerChainsFunc(s.HandlerContainer.ServeMux, c.Config)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
|
||||
generic := func(handler http.Handler) http.Handler {
|
||||
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
|
||||
handler = genericfilters.WithPanicRecovery(handler, c.RequestContextMapper)
|
||||
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.RequestContextMapper, c.LongRunningFunc)
|
||||
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.RequestContextMapper, c.LongRunningFunc)
|
||||
handler = genericapifilters.WithRequestInfo(handler, NewRequestInfoResolver(c), c.RequestContextMapper)
|
||||
handler = apirequest.WithRequestContext(handler, c.RequestContextMapper)
|
||||
return handler
|
||||
}
|
||||
audit := func(handler http.Handler) http.Handler {
|
||||
return genericapifilters.WithAudit(handler, c.RequestContextMapper, c.AuditWriter)
|
||||
}
|
||||
protect := func(handler http.Handler) http.Handler {
|
||||
handler = genericapifilters.WithAuthorization(handler, c.RequestContextMapper, c.Authorizer)
|
||||
handler = genericapifilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer)
|
||||
handler = audit(handler) // before impersonation to read original user
|
||||
handler = authhandlers.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
|
||||
return handler
|
||||
}
|
||||
|
||||
return generic(protect(apiHandler)), generic(audit(apiHandler))
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) installAPI(c *Config) {
|
||||
if c.EnableIndex {
|
||||
routes.Index{}.Install(s.HandlerContainer)
|
||||
}
|
||||
if c.SwaggerConfig != nil && c.EnableSwaggerUI {
|
||||
routes.SwaggerUI{}.Install(s.HandlerContainer)
|
||||
}
|
||||
if c.EnableProfiling {
|
||||
routes.Profiling{}.Install(s.HandlerContainer)
|
||||
if c.EnableContentionProfiling {
|
||||
goruntime.SetBlockProfileRate(1)
|
||||
}
|
||||
}
|
||||
if c.EnableMetrics {
|
||||
if c.EnableProfiling {
|
||||
routes.MetricsWithReset{}.Install(s.HandlerContainer)
|
||||
} else {
|
||||
routes.DefaultMetrics{}.Install(s.HandlerContainer)
|
||||
}
|
||||
}
|
||||
routes.Version{Version: c.Version}.Install(s.HandlerContainer)
|
||||
s.HandlerContainer.Add(s.DynamicApisDiscovery())
|
||||
}
|
||||
|
||||
func NewRequestInfoResolver(c *Config) *apirequest.RequestInfoFactory {
|
||||
apiPrefixes := sets.NewString(strings.Trim(APIGroupPrefix, "/")) // all possible API prefixes
|
||||
legacyAPIPrefixes := sets.String{} // APIPrefixes that won't have groups (legacy)
|
||||
for legacyAPIPrefix := range c.LegacyAPIGroupPrefixes {
|
||||
apiPrefixes.Insert(strings.Trim(legacyAPIPrefix, "/"))
|
||||
legacyAPIPrefixes.Insert(strings.Trim(legacyAPIPrefix, "/"))
|
||||
}
|
||||
|
||||
return &apirequest.RequestInfoFactory{
|
||||
APIPrefixes: apiPrefixes,
|
||||
GrouplessAPIPrefixes: legacyAPIPrefixes,
|
||||
}
|
||||
}
|
199
vendor/k8s.io/kubernetes/pkg/genericapiserver/config_selfclient.go
generated
vendored
Normal file
199
vendor/k8s.io/kubernetes/pkg/genericapiserver/config_selfclient.go
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package genericapiserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// NewSelfClientConfig returns a clientconfig which can be used to talk to this apiserver.
|
||||
func NewSelfClientConfig(secureServingInfo *SecureServingInfo, insecureServingInfo *ServingInfo, token string) (*restclient.Config, error) {
|
||||
cfg, err := secureServingInfo.NewSelfClientConfig(token)
|
||||
if cfg != nil && err == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
if err != nil {
|
||||
if insecureServingInfo == nil {
|
||||
// be fatal if insecure port is not available
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.Warningf("Failed to create secure local client, falling back to insecure local connection: %v", err)
|
||||
}
|
||||
if cfg, err := insecureServingInfo.NewSelfClientConfig(token); err != nil || cfg != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
return nil, errors.New("Unable to set url for apiserver local client")
|
||||
}
|
||||
|
||||
func (s *SecureServingInfo) NewSelfClientConfig(token string) (*restclient.Config, error) {
|
||||
if s == nil || (s.Cert == nil && len(s.SNICerts) == 0) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(s.ServingInfo.BindAddress)
|
||||
if err != nil {
|
||||
// should never happen
|
||||
return nil, fmt.Errorf("invalid secure bind address: %q", s.ServingInfo.BindAddress)
|
||||
}
|
||||
if host == "0.0.0.0" {
|
||||
// compare MaybeDefaultWithSelfSignedCerts which adds "localhost" to the cert as alternateDNS
|
||||
host = "localhost"
|
||||
}
|
||||
|
||||
clientConfig := &restclient.Config{
|
||||
// Increase QPS limits. The client is currently passed to all admission plugins,
|
||||
// and those can be throttled in case of higher load on apiserver - see #22340 and #22422
|
||||
// for more details. Once #22422 is fixed, we may want to remove it.
|
||||
QPS: 50,
|
||||
Burst: 100,
|
||||
Host: "https://" + net.JoinHostPort(host, port),
|
||||
BearerToken: token,
|
||||
}
|
||||
|
||||
// find certificate for host: either explicitly given, from the server cert bundle or one of the SNI certs,
|
||||
// but only return CA:TRUE certificates.
|
||||
var derCA []byte
|
||||
if s.CACert != nil {
|
||||
derCA = s.CACert.Certificate[0]
|
||||
}
|
||||
if derCA == nil && net.ParseIP(host) == nil {
|
||||
if cert, found := s.SNICerts[host]; found {
|
||||
chain, err := parseChain(cert.Certificate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse SNI certificate for host %q: %v", host, err)
|
||||
}
|
||||
|
||||
if trustedChain(chain) {
|
||||
return clientConfig, nil
|
||||
}
|
||||
|
||||
ca, err := findCA(chain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no CA certificate found in SNI server certificate bundle for host %q: %v", host, err)
|
||||
}
|
||||
derCA = ca.Raw
|
||||
}
|
||||
}
|
||||
if derCA == nil && s.Cert != nil {
|
||||
chain, err := parseChain(s.Cert.Certificate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse server certificate: %v", err)
|
||||
}
|
||||
|
||||
if (net.ParseIP(host) != nil && certMatchesIP(chain[0], host)) || certMatchesName(chain[0], host) {
|
||||
if trustedChain(chain) {
|
||||
return clientConfig, nil
|
||||
}
|
||||
|
||||
ca, err := findCA(chain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no CA certificate found in server certificate bundle: %v", err)
|
||||
}
|
||||
derCA = ca.Raw
|
||||
}
|
||||
}
|
||||
if derCA == nil {
|
||||
return nil, fmt.Errorf("failed to find certificate which matches %q", host)
|
||||
}
|
||||
pemCA := bytes.Buffer{}
|
||||
if err := pem.Encode(&pemCA, &pem.Block{Type: "CERTIFICATE", Bytes: derCA}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfig.CAData = pemCA.Bytes()
|
||||
|
||||
return clientConfig, nil
|
||||
}
|
||||
|
||||
func trustedChain(chain []*x509.Certificate) bool {
|
||||
intermediates := x509.NewCertPool()
|
||||
for _, cert := range chain[1:] {
|
||||
intermediates.AddCert(cert)
|
||||
}
|
||||
_, err := chain[0].Verify(x509.VerifyOptions{
|
||||
Intermediates: intermediates,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func parseChain(bss [][]byte) ([]*x509.Certificate, error) {
|
||||
var result []*x509.Certificate
|
||||
for _, bs := range bss {
|
||||
x509Cert, err := x509.ParseCertificate(bs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, x509Cert)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func findCA(chain []*x509.Certificate) (*x509.Certificate, error) {
|
||||
for _, cert := range chain {
|
||||
if cert.IsCA {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no certificate with CA:TRUE found in chain")
|
||||
}
|
||||
|
||||
func (s *ServingInfo) NewSelfClientConfig(token string) (*restclient.Config, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &restclient.Config{
|
||||
Host: s.BindAddress,
|
||||
// Increase QPS limits. The client is currently passed to all admission plugins,
|
||||
// and those can be throttled in case of higher load on apiserver - see #22340 and #22422
|
||||
// for more details. Once #22422 is fixed, we may want to remove it.
|
||||
QPS: 50,
|
||||
Burst: 100,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func certMatchesName(cert *x509.Certificate, name string) bool {
|
||||
for _, certName := range cert.DNSNames {
|
||||
if certName == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func certMatchesIP(cert *x509.Certificate, ip string) bool {
|
||||
for _, certIP := range cert.IPAddresses {
|
||||
if certIP.String() == ip {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
72
vendor/k8s.io/kubernetes/pkg/genericapiserver/discovery.go
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/pkg/genericapiserver/discovery.go
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package genericapiserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type DiscoveryAddresses interface {
|
||||
ServerAddressByClientCIDRs(net.IP) []metav1.ServerAddressByClientCIDR
|
||||
}
|
||||
|
||||
// DefaultDiscoveryAddresses is a default implementation of DiscoveryAddresses that will work in most cases
|
||||
type DefaultDiscoveryAddresses struct {
|
||||
// DiscoveryCIDRRules is a list of CIDRs and Addresses to use if a client is in the range
|
||||
DiscoveryCIDRRules []DiscoveryCIDRRule
|
||||
|
||||
// DefaultAddress is the address (hostname or IP and port) that should be used in
|
||||
// if no CIDR matches more specifically.
|
||||
DefaultAddress string
|
||||
}
|
||||
|
||||
// DiscoveryCIDRRule is a rule for adding an alternate path to the master based on matching CIDR
|
||||
type DiscoveryCIDRRule struct {
|
||||
IPRange net.IPNet
|
||||
|
||||
// Address is the address (hostname or IP and port) that should be used in
|
||||
// if this CIDR matches
|
||||
Address string
|
||||
}
|
||||
|
||||
func (d DefaultDiscoveryAddresses) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
||||
addressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
||||
{
|
||||
ClientCIDR: "0.0.0.0/0",
|
||||
ServerAddress: d.DefaultAddress,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rule := range d.DiscoveryCIDRRules {
|
||||
addressCIDRMap = append(addressCIDRMap, rule.ServerAddressByClientCIDRs(clientIP)...)
|
||||
}
|
||||
return addressCIDRMap
|
||||
}
|
||||
|
||||
func (d DiscoveryCIDRRule) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
|
||||
addressCIDRMap := []metav1.ServerAddressByClientCIDR{}
|
||||
|
||||
if d.IPRange.Contains(clientIP) {
|
||||
addressCIDRMap = append(addressCIDRMap, metav1.ServerAddressByClientCIDR{
|
||||
ClientCIDR: d.IPRange.String(),
|
||||
ServerAddress: d.Address,
|
||||
})
|
||||
}
|
||||
return addressCIDRMap
|
||||
}
|
23
vendor/k8s.io/kubernetes/pkg/genericapiserver/doc.go
generated
vendored
Normal file
23
vendor/k8s.io/kubernetes/pkg/genericapiserver/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
// Package genericapiserver contains code to setup a generic kubernetes-like API server.
|
||||
// This does not contain any kubernetes API specific code.
|
||||
// Note that this is a work in progress. We are pulling out generic code (specifically from
|
||||
// pkg/master) here.
|
||||
// We plan to move this package into a separate repo on github once it is done.
|
||||
// For more details: https://github.com/kubernetes/kubernetes/issues/2742
|
||||
package genericapiserver // import "k8s.io/kubernetes/pkg/genericapiserver"
|
62
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/BUILD
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cors.go",
|
||||
"doc.go",
|
||||
"longrunning.go",
|
||||
"maxinflight.go",
|
||||
"panics.go",
|
||||
"timeout.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/util:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apiserver/pkg/httplog",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"cors_test.go",
|
||||
"maxinflight_test.go",
|
||||
"timeout_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/genericapiserver/api/filters:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apiserver/pkg/request",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
3
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/OWNERS
generated
vendored
Executable file
3
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,3 @@
|
|||
reviewers:
|
||||
- sttts
|
||||
- dims
|
87
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/cors.go
generated
vendored
Normal file
87
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/cors.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
// TODO: use restful.CrossOriginResourceSharing
|
||||
// See github.com/emicklei/go-restful/blob/master/examples/restful-CORS-filter.go, and
|
||||
// github.com/emicklei/go-restful/blob/master/examples/restful-basic-authentication.go
|
||||
// Or, for a more detailed implementation use https://github.com/martini-contrib/cors
|
||||
// or implement CORS at your proxy layer.
|
||||
|
||||
// WithCORS is a simple CORS implementation that wraps an http Handler.
|
||||
// Pass nil for allowedMethods and allowedHeaders to use the defaults. If allowedOriginPatterns
|
||||
// is empty or nil, no CORS support is installed.
|
||||
func WithCORS(handler http.Handler, allowedOriginPatterns []string, allowedMethods []string, allowedHeaders []string, exposedHeaders []string, allowCredentials string) http.Handler {
|
||||
if len(allowedOriginPatterns) == 0 {
|
||||
return handler
|
||||
}
|
||||
allowedOriginPatternsREs := allowedOriginRegexps(allowedOriginPatterns)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
origin := req.Header.Get("Origin")
|
||||
if origin != "" {
|
||||
allowed := false
|
||||
for _, re := range allowedOriginPatternsREs {
|
||||
if allowed = re.MatchString(origin); allowed {
|
||||
break
|
||||
}
|
||||
}
|
||||
if allowed {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
// Set defaults for methods and headers if nothing was passed
|
||||
if allowedMethods == nil {
|
||||
allowedMethods = []string{"POST", "GET", "OPTIONS", "PUT", "DELETE", "PATCH"}
|
||||
}
|
||||
if allowedHeaders == nil {
|
||||
allowedHeaders = []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "X-Requested-With", "If-Modified-Since"}
|
||||
}
|
||||
if exposedHeaders == nil {
|
||||
exposedHeaders = []string{"Date"}
|
||||
}
|
||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ", "))
|
||||
w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowedHeaders, ", "))
|
||||
w.Header().Set("Access-Control-Expose-Headers", strings.Join(exposedHeaders, ", "))
|
||||
w.Header().Set("Access-Control-Allow-Credentials", allowCredentials)
|
||||
|
||||
// Stop here if its a preflight OPTIONS request
|
||||
if req.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dispatch to the next handler
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
func allowedOriginRegexps(allowedOrigins []string) []*regexp.Regexp {
|
||||
res, err := util.CompileRegexps(allowedOrigins)
|
||||
if err != nil {
|
||||
glog.Fatalf("Invalid CORS allowed origin, --cors-allowed-origins flag was set to %v - %v", strings.Join(allowedOrigins, ","), err)
|
||||
}
|
||||
return res
|
||||
}
|
158
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/cors_test.go
generated
vendored
Normal file
158
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/cors_test.go
generated
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCORSAllowedOrigins(t *testing.T) {
|
||||
table := []struct {
|
||||
allowedOrigins []string
|
||||
origin string
|
||||
allowed bool
|
||||
}{
|
||||
{[]string{}, "example.com", false},
|
||||
{[]string{"example.com"}, "example.com", true},
|
||||
{[]string{"example.com"}, "not-allowed.com", false},
|
||||
{[]string{"not-matching.com", "example.com"}, "example.com", true},
|
||||
{[]string{".*"}, "example.com", true},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
handler := WithCORS(
|
||||
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
|
||||
item.allowedOrigins, nil, nil, nil, "true",
|
||||
)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
request, err := http.NewRequest("GET", server.URL+"/version", nil)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
request.Header.Set("Origin", item.origin)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if item.allowed {
|
||||
if !reflect.DeepEqual(item.origin, response.Header.Get("Access-Control-Allow-Origin")) {
|
||||
t.Errorf("Expected %#v, Got %#v", item.origin, response.Header.Get("Access-Control-Allow-Origin"))
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Allow-Credentials") == "" {
|
||||
t.Errorf("Expected Access-Control-Allow-Credentials header to be set")
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Allow-Headers") == "" {
|
||||
t.Errorf("Expected Access-Control-Allow-Headers header to be set")
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Allow-Methods") == "" {
|
||||
t.Errorf("Expected Access-Control-Allow-Methods header to be set")
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Expose-Headers") != "Date" {
|
||||
t.Errorf("Expected Date in Access-Control-Expose-Headers header")
|
||||
}
|
||||
} else {
|
||||
if response.Header.Get("Access-Control-Allow-Origin") != "" {
|
||||
t.Errorf("Expected Access-Control-Allow-Origin header to not be set")
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Allow-Credentials") != "" {
|
||||
t.Errorf("Expected Access-Control-Allow-Credentials header to not be set")
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Allow-Headers") != "" {
|
||||
t.Errorf("Expected Access-Control-Allow-Headers header to not be set")
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Allow-Methods") != "" {
|
||||
t.Errorf("Expected Access-Control-Allow-Methods header to not be set")
|
||||
}
|
||||
|
||||
if response.Header.Get("Access-Control-Expose-Headers") == "Date" {
|
||||
t.Errorf("Expected Date in Access-Control-Expose-Headers header")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORSAllowedMethods(t *testing.T) {
|
||||
tests := []struct {
|
||||
allowedMethods []string
|
||||
method string
|
||||
allowed bool
|
||||
}{
|
||||
{nil, "POST", true},
|
||||
{nil, "GET", true},
|
||||
{nil, "OPTIONS", true},
|
||||
{nil, "PUT", true},
|
||||
{nil, "DELETE", true},
|
||||
{nil, "PATCH", true},
|
||||
{[]string{"GET", "POST"}, "PATCH", false},
|
||||
}
|
||||
|
||||
allowsMethod := func(res *http.Response, method string) bool {
|
||||
allowedMethods := strings.Split(res.Header.Get("Access-Control-Allow-Methods"), ",")
|
||||
for _, allowedMethod := range allowedMethods {
|
||||
if strings.TrimSpace(allowedMethod) == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
handler := WithCORS(
|
||||
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
|
||||
[]string{".*"}, test.allowedMethods, nil, nil, "true",
|
||||
)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
request, err := http.NewRequest(test.method, server.URL+"/version", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
request.Header.Set("Origin", "allowed.com")
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
methodAllowed := allowsMethod(response, test.method)
|
||||
switch {
|
||||
case test.allowed && !methodAllowed:
|
||||
t.Errorf("Expected %v to be allowed, Got only %#v", test.method, response.Header.Get("Access-Control-Allow-Methods"))
|
||||
case !test.allowed && methodAllowed:
|
||||
t.Errorf("Unexpected allowed method %v, Expected only %#v", test.method, response.Header.Get("Access-Control-Allow-Methods"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
19
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package filters contains all the http handler chain filters which
|
||||
// are not api related.
|
||||
package filters // import "k8s.io/kubernetes/pkg/genericapiserver/filters"
|
40
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/longrunning.go
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/longrunning.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
apirequest "k8s.io/apiserver/pkg/request"
|
||||
)
|
||||
|
||||
// LongRunningRequestCheck is a predicate which is true for long-running http requests.
|
||||
type LongRunningRequestCheck func(r *http.Request, requestInfo *apirequest.RequestInfo) bool
|
||||
|
||||
// BasicLongRunningRequestCheck returns true if the given request has one of the specified verbs or one of the specified subresources
|
||||
func BasicLongRunningRequestCheck(longRunningVerbs, longRunningSubresources sets.String) LongRunningRequestCheck {
|
||||
return func(r *http.Request, requestInfo *apirequest.RequestInfo) bool {
|
||||
if longRunningVerbs.Has(requestInfo.Verb) {
|
||||
return true
|
||||
}
|
||||
if requestInfo.IsResourceRequest && longRunningSubresources.Has(requestInfo.Subresource) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
111
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/maxinflight.go
generated
vendored
Normal file
111
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/maxinflight.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/httplog"
|
||||
apirequest "k8s.io/apiserver/pkg/request"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Constant for the retry-after interval on rate limiting.
|
||||
// TODO: maybe make this dynamic? or user-adjustable?
|
||||
const retryAfter = "1"
|
||||
|
||||
var nonMutatingRequestVerbs = sets.NewString("get", "list", "watch")
|
||||
|
||||
func handleError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Internal Server Error: %#v", r.RequestURI)
|
||||
glog.Errorf(err.Error())
|
||||
}
|
||||
|
||||
// WithMaxInFlightLimit limits the number of in-flight requests to buffer size of the passed in channel.
|
||||
func WithMaxInFlightLimit(
|
||||
handler http.Handler,
|
||||
nonMutatingLimit int,
|
||||
mutatingLimit int,
|
||||
requestContextMapper genericapirequest.RequestContextMapper,
|
||||
longRunningRequestCheck LongRunningRequestCheck,
|
||||
) http.Handler {
|
||||
if nonMutatingLimit == 0 && mutatingLimit == 0 {
|
||||
return handler
|
||||
}
|
||||
var nonMutatingChan chan bool
|
||||
var mutatingChan chan bool
|
||||
if nonMutatingLimit != 0 {
|
||||
nonMutatingChan = make(chan bool, nonMutatingLimit)
|
||||
}
|
||||
if mutatingLimit != 0 {
|
||||
mutatingChan = make(chan bool, mutatingLimit)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, ok := requestContextMapper.Get(r)
|
||||
if !ok {
|
||||
handleError(w, r, fmt.Errorf("no context found for request, handler chain must be wrong"))
|
||||
return
|
||||
}
|
||||
requestInfo, ok := apirequest.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
handleError(w, r, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong"))
|
||||
return
|
||||
}
|
||||
|
||||
// Skip tracking long running events.
|
||||
if longRunningRequestCheck != nil && longRunningRequestCheck(r, requestInfo) {
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var c chan bool
|
||||
if !nonMutatingRequestVerbs.Has(requestInfo.Verb) {
|
||||
c = mutatingChan
|
||||
} else {
|
||||
c = nonMutatingChan
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
handler.ServeHTTP(w, r)
|
||||
} else {
|
||||
select {
|
||||
case c <- true:
|
||||
defer func() { <-c }()
|
||||
handler.ServeHTTP(w, r)
|
||||
default:
|
||||
tooManyRequests(r, w)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func tooManyRequests(req *http.Request, w http.ResponseWriter) {
|
||||
// "Too Many Requests" response is returned before logger is setup for the request.
|
||||
// So we need to explicitly log it here.
|
||||
defer httplog.NewLogged(req, &w).Log()
|
||||
|
||||
// Return a 429 status indicating "Too Many Requests"
|
||||
w.Header().Set("Retry-After", retryAfter)
|
||||
http.Error(w, "Too many requests, please try again later.", errors.StatusTooManyRequests)
|
||||
}
|
240
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/maxinflight_test.go
generated
vendored
Normal file
240
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/maxinflight_test.go
generated
vendored
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
apirequest "k8s.io/apiserver/pkg/request"
|
||||
apifilters "k8s.io/kubernetes/pkg/genericapiserver/api/filters"
|
||||
)
|
||||
|
||||
func createMaxInflightServer(callsWg, blockWg *sync.WaitGroup, disableCallsWg *bool, disableCallsWgMutex *sync.Mutex, nonMutating, mutating int) *httptest.Server {
|
||||
|
||||
longRunningRequestCheck := BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString("proxy"))
|
||||
|
||||
requestContextMapper := apirequest.NewRequestContextMapper()
|
||||
requestInfoFactory := &apirequest.RequestInfoFactory{APIPrefixes: sets.NewString("apis", "api"), GrouplessAPIPrefixes: sets.NewString("api")}
|
||||
handler := WithMaxInFlightLimit(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// A short, accounted request that does not wait for block WaitGroup.
|
||||
if strings.Contains(r.URL.Path, "dontwait") {
|
||||
return
|
||||
}
|
||||
disableCallsWgMutex.Lock()
|
||||
waitForCalls := *disableCallsWg
|
||||
disableCallsWgMutex.Unlock()
|
||||
if waitForCalls {
|
||||
callsWg.Done()
|
||||
}
|
||||
blockWg.Wait()
|
||||
}),
|
||||
nonMutating,
|
||||
mutating,
|
||||
requestContextMapper,
|
||||
longRunningRequestCheck,
|
||||
)
|
||||
handler = apifilters.WithRequestInfo(handler, requestInfoFactory, requestContextMapper)
|
||||
handler = apirequest.WithRequestContext(handler, requestContextMapper)
|
||||
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
// Tests that MaxInFlightLimit works, i.e.
|
||||
// - "long" requests such as proxy or watch, identified by regexp are not accounted despite
|
||||
// hanging for the long time,
|
||||
// - "short" requests are correctly accounted, i.e. there can be only size of channel passed to the
|
||||
// constructor in flight at any given moment,
|
||||
// - subsequent "short" requests are rejected instantly with appropriate error,
|
||||
// - subsequent "long" requests are handled normally,
|
||||
// - we correctly recover after some "short" requests finish, i.e. we can process new ones.
|
||||
func TestMaxInFlightNonMutating(t *testing.T) {
|
||||
const AllowedNonMutatingInflightRequestsNo = 3
|
||||
|
||||
// Calls is used to wait until all server calls are received. We are sending
|
||||
// AllowedNonMutatingInflightRequestsNo of 'long' not-accounted requests and the same number of
|
||||
// 'short' accounted ones.
|
||||
calls := &sync.WaitGroup{}
|
||||
calls.Add(AllowedNonMutatingInflightRequestsNo * 2)
|
||||
|
||||
// Responses is used to wait until all responses are
|
||||
// received. This prevents some async requests getting EOF
|
||||
// errors from prematurely closing the server
|
||||
responses := &sync.WaitGroup{}
|
||||
responses.Add(AllowedNonMutatingInflightRequestsNo * 2)
|
||||
|
||||
// Block is used to keep requests in flight for as long as we need to. All requests will
|
||||
// be unblocked at the same time.
|
||||
block := &sync.WaitGroup{}
|
||||
block.Add(1)
|
||||
|
||||
waitForCalls := true
|
||||
waitForCallsMutex := sync.Mutex{}
|
||||
|
||||
server := createMaxInflightServer(calls, block, &waitForCalls, &waitForCallsMutex, AllowedNonMutatingInflightRequestsNo, 1)
|
||||
defer server.Close()
|
||||
|
||||
// These should hang, but not affect accounting. use a query param match
|
||||
for i := 0; i < AllowedNonMutatingInflightRequestsNo; i++ {
|
||||
// These should hang waiting on block...
|
||||
go func() {
|
||||
if err := expectHTTPGet(server.URL+"/api/v1/namespaces/default/wait?watch=true", http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
responses.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// Check that sever is not saturated by not-accounted calls
|
||||
if err := expectHTTPGet(server.URL+"/dontwait", http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// These should hang and be accounted, i.e. saturate the server
|
||||
for i := 0; i < AllowedNonMutatingInflightRequestsNo; i++ {
|
||||
// These should hang waiting on block...
|
||||
go func() {
|
||||
if err := expectHTTPGet(server.URL, http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
responses.Done()
|
||||
}()
|
||||
}
|
||||
// We wait for all calls to be received by the server
|
||||
calls.Wait()
|
||||
// Disable calls notifications in the server
|
||||
waitForCallsMutex.Lock()
|
||||
waitForCalls = false
|
||||
waitForCallsMutex.Unlock()
|
||||
|
||||
// Do this multiple times to show that rate limit rejected requests don't block.
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := expectHTTPGet(server.URL, errors.StatusTooManyRequests); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
// Validate that non-accounted URLs still work. use a path regex match
|
||||
if err := expectHTTPGet(server.URL+"/api/v1/watch/namespaces/default/dontwait", http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// We should allow a single mutating request.
|
||||
if err := expectHTTPPost(server.URL+"/dontwait", http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Let all hanging requests finish
|
||||
block.Done()
|
||||
|
||||
// Show that we recover from being blocked up.
|
||||
// Too avoid flakyness we need to wait until at least one of the requests really finishes.
|
||||
responses.Wait()
|
||||
if err := expectHTTPGet(server.URL, http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxInFlightMutating(t *testing.T) {
|
||||
const AllowedMutatingInflightRequestsNo = 3
|
||||
|
||||
calls := &sync.WaitGroup{}
|
||||
calls.Add(AllowedMutatingInflightRequestsNo)
|
||||
|
||||
responses := &sync.WaitGroup{}
|
||||
responses.Add(AllowedMutatingInflightRequestsNo)
|
||||
|
||||
// Block is used to keep requests in flight for as long as we need to. All requests will
|
||||
// be unblocked at the same time.
|
||||
block := &sync.WaitGroup{}
|
||||
block.Add(1)
|
||||
|
||||
waitForCalls := true
|
||||
waitForCallsMutex := sync.Mutex{}
|
||||
|
||||
server := createMaxInflightServer(calls, block, &waitForCalls, &waitForCallsMutex, 1, AllowedMutatingInflightRequestsNo)
|
||||
defer server.Close()
|
||||
|
||||
// These should hang and be accounted, i.e. saturate the server
|
||||
for i := 0; i < AllowedMutatingInflightRequestsNo; i++ {
|
||||
// These should hang waiting on block...
|
||||
go func() {
|
||||
if err := expectHTTPPost(server.URL+"/foo/bar", http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
responses.Done()
|
||||
}()
|
||||
}
|
||||
// We wait for all calls to be received by the server
|
||||
calls.Wait()
|
||||
// Disable calls notifications in the server
|
||||
// Disable calls notifications in the server
|
||||
waitForCallsMutex.Lock()
|
||||
waitForCalls = false
|
||||
waitForCallsMutex.Unlock()
|
||||
|
||||
// Do this multiple times to show that rate limit rejected requests don't block.
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := expectHTTPPost(server.URL+"/foo/bar/", errors.StatusTooManyRequests); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
// Validate that non-mutating URLs still work. use a path regex match
|
||||
if err := expectHTTPGet(server.URL+"/dontwait", http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Let all hanging requests finish
|
||||
block.Done()
|
||||
|
||||
// Show that we recover from being blocked up.
|
||||
// Too avoid flakyness we need to wait until at least one of the requests really finishes.
|
||||
responses.Wait()
|
||||
if err := expectHTTPPost(server.URL+"/foo/bar", http.StatusOK); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// We use GET as a sample non-mutating request.
|
||||
func expectHTTPGet(url string, code int) error {
|
||||
r, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if r.StatusCode != code {
|
||||
return fmt.Errorf("unexpected response: %v", r.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// We use POST as a sample mutating request.
|
||||
func expectHTTPPost(url string, code int) error {
|
||||
r, err := http.Post(url, "text/html", strings.NewReader("foo bar"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if r.StatusCode != code {
|
||||
return fmt.Errorf("unexpected response: %v", r.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
76
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/panics.go
generated
vendored
Normal file
76
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/panics.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/httplog"
|
||||
apirequest "k8s.io/apiserver/pkg/request"
|
||||
)
|
||||
|
||||
// WithPanicRecovery wraps an http Handler to recover and log panics.
|
||||
func WithPanicRecovery(handler http.Handler, requestContextMapper apirequest.RequestContextMapper) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
defer runtime.HandleCrash(func(err interface{}) {
|
||||
http.Error(w, "This request caused apisever to panic. Look in log for details.", http.StatusInternalServerError)
|
||||
glog.Errorf("APIServer panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, err, debug.Stack())
|
||||
})
|
||||
|
||||
logger := httplog.NewLogged(req, &w)
|
||||
|
||||
var requestInfo *apirequest.RequestInfo
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
glog.Errorf("no context found for request, handler chain must be wrong")
|
||||
} else {
|
||||
requestInfo, ok = apirequest.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
glog.Errorf("no RequestInfo found in context, handler chain must be wrong")
|
||||
}
|
||||
}
|
||||
|
||||
if !ok || requestInfo.Verb != "proxy" {
|
||||
logger.StacktraceWhen(
|
||||
httplog.StatusIsNot(
|
||||
http.StatusOK,
|
||||
http.StatusCreated,
|
||||
http.StatusAccepted,
|
||||
http.StatusBadRequest,
|
||||
http.StatusMovedPermanently,
|
||||
http.StatusTemporaryRedirect,
|
||||
http.StatusConflict,
|
||||
http.StatusNotFound,
|
||||
http.StatusUnauthorized,
|
||||
http.StatusForbidden,
|
||||
http.StatusNotModified,
|
||||
apierrors.StatusUnprocessableEntity,
|
||||
http.StatusSwitchingProtocols,
|
||||
),
|
||||
)
|
||||
}
|
||||
defer logger.Log()
|
||||
|
||||
// Dispatch to the internal handler
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
273
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/timeout.go
generated
vendored
Normal file
273
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/timeout.go
generated
vendored
Normal file
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
const globalTimeout = time.Minute
|
||||
|
||||
var errConnKilled = fmt.Errorf("kill connection/stream")
|
||||
|
||||
// WithTimeoutForNonLongRunningRequests times out non-long-running requests after the time given by globalTimeout.
|
||||
func WithTimeoutForNonLongRunningRequests(handler http.Handler, requestContextMapper apirequest.RequestContextMapper, longRunning LongRunningRequestCheck) http.Handler {
|
||||
if longRunning == nil {
|
||||
return handler
|
||||
}
|
||||
timeoutFunc := func(req *http.Request) (<-chan time.Time, string) {
|
||||
// TODO unify this with apiserver.MaxInFlightLimit
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
return time.After(globalTimeout), ""
|
||||
}
|
||||
|
||||
requestInfo, ok := apirequest.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
return time.After(globalTimeout), ""
|
||||
}
|
||||
|
||||
if longRunning(req, requestInfo) {
|
||||
return nil, ""
|
||||
}
|
||||
return time.After(globalTimeout), ""
|
||||
}
|
||||
return WithTimeout(handler, timeoutFunc)
|
||||
}
|
||||
|
||||
// WithTimeout returns an http.Handler that runs h with a timeout
|
||||
// determined by timeoutFunc. The new http.Handler calls h.ServeHTTP to handle
|
||||
// each request, but if a call runs for longer than its time limit, the
|
||||
// handler responds with a 503 Service Unavailable error and the message
|
||||
// provided. (If msg is empty, a suitable default message will be sent.) After
|
||||
// the handler times out, writes by h to its http.ResponseWriter will return
|
||||
// http.ErrHandlerTimeout. If timeoutFunc returns a nil timeout channel, no
|
||||
// timeout will be enforced.
|
||||
func WithTimeout(h http.Handler, timeoutFunc func(*http.Request) (timeout <-chan time.Time, msg string)) http.Handler {
|
||||
return &timeoutHandler{h, timeoutFunc}
|
||||
}
|
||||
|
||||
type timeoutHandler struct {
|
||||
handler http.Handler
|
||||
timeout func(*http.Request) (<-chan time.Time, string)
|
||||
}
|
||||
|
||||
func (t *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
after, msg := t.timeout(r)
|
||||
if after == nil {
|
||||
t.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
tw := newTimeoutWriter(w)
|
||||
go func() {
|
||||
t.handler.ServeHTTP(tw, r)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-after:
|
||||
tw.timeout(msg)
|
||||
}
|
||||
}
|
||||
|
||||
type timeoutWriter interface {
|
||||
http.ResponseWriter
|
||||
timeout(string)
|
||||
}
|
||||
|
||||
func newTimeoutWriter(w http.ResponseWriter) timeoutWriter {
|
||||
base := &baseTimeoutWriter{w: w}
|
||||
|
||||
_, notifiable := w.(http.CloseNotifier)
|
||||
_, hijackable := w.(http.Hijacker)
|
||||
|
||||
switch {
|
||||
case notifiable && hijackable:
|
||||
return &closeHijackTimeoutWriter{base}
|
||||
case notifiable:
|
||||
return &closeTimeoutWriter{base}
|
||||
case hijackable:
|
||||
return &hijackTimeoutWriter{base}
|
||||
default:
|
||||
return base
|
||||
}
|
||||
}
|
||||
|
||||
type baseTimeoutWriter struct {
|
||||
w http.ResponseWriter
|
||||
|
||||
mu sync.Mutex
|
||||
// if the timeout handler has timedout
|
||||
timedOut bool
|
||||
// if this timeout writer has wrote header
|
||||
wroteHeader bool
|
||||
// if this timeout writer has been hijacked
|
||||
hijacked bool
|
||||
}
|
||||
|
||||
func (tw *baseTimeoutWriter) Header() http.Header {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
if tw.timedOut {
|
||||
return http.Header{}
|
||||
}
|
||||
|
||||
return tw.w.Header()
|
||||
}
|
||||
|
||||
func (tw *baseTimeoutWriter) Write(p []byte) (int, error) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
if tw.timedOut {
|
||||
return 0, http.ErrHandlerTimeout
|
||||
}
|
||||
if tw.hijacked {
|
||||
return 0, http.ErrHijacked
|
||||
}
|
||||
|
||||
tw.wroteHeader = true
|
||||
return tw.w.Write(p)
|
||||
}
|
||||
|
||||
func (tw *baseTimeoutWriter) Flush() {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
if tw.timedOut {
|
||||
return
|
||||
}
|
||||
|
||||
if flusher, ok := tw.w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (tw *baseTimeoutWriter) WriteHeader(code int) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
if tw.timedOut || tw.wroteHeader || tw.hijacked {
|
||||
return
|
||||
}
|
||||
|
||||
tw.wroteHeader = true
|
||||
tw.w.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (tw *baseTimeoutWriter) timeout(msg string) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
tw.timedOut = true
|
||||
|
||||
// The timeout writer has not been used by the inner handler.
|
||||
// We can safely timeout the HTTP request by sending by a timeout
|
||||
// handler
|
||||
if !tw.wroteHeader && !tw.hijacked {
|
||||
tw.w.WriteHeader(http.StatusGatewayTimeout)
|
||||
if msg != "" {
|
||||
tw.w.Write([]byte(msg))
|
||||
} else {
|
||||
enc := json.NewEncoder(tw.w)
|
||||
enc.Encode(errors.NewServerTimeout(api.Resource(""), "", 0))
|
||||
}
|
||||
} else {
|
||||
// The timeout writer has been used by the inner handler. There is
|
||||
// no way to timeout the HTTP request at the point. We have to shutdown
|
||||
// the connection for HTTP1 or reset stream for HTTP2.
|
||||
//
|
||||
// Note from: Brad Fitzpatrick
|
||||
// if the ServeHTTP goroutine panics, that will do the best possible thing for both
|
||||
// HTTP/1 and HTTP/2. In HTTP/1, assuming you're replying with at least HTTP/1.1 and
|
||||
// you've already flushed the headers so it's using HTTP chunking, it'll kill the TCP
|
||||
// connection immediately without a proper 0-byte EOF chunk, so the peer will recognize
|
||||
// the response as bogus. In HTTP/2 the server will just RST_STREAM the stream, leaving
|
||||
// the TCP connection open, but resetting the stream to the peer so it'll have an error,
|
||||
// like the HTTP/1 case.
|
||||
panic(errConnKilled)
|
||||
}
|
||||
}
|
||||
|
||||
func (tw *baseTimeoutWriter) closeNotify() <-chan bool {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
if tw.timedOut {
|
||||
done := make(chan bool)
|
||||
close(done)
|
||||
return done
|
||||
}
|
||||
|
||||
return tw.w.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (tw *baseTimeoutWriter) hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
if tw.timedOut {
|
||||
return nil, nil, http.ErrHandlerTimeout
|
||||
}
|
||||
conn, rw, err := tw.w.(http.Hijacker).Hijack()
|
||||
if err == nil {
|
||||
tw.hijacked = true
|
||||
}
|
||||
return conn, rw, err
|
||||
}
|
||||
|
||||
type closeTimeoutWriter struct {
|
||||
*baseTimeoutWriter
|
||||
}
|
||||
|
||||
func (tw *closeTimeoutWriter) CloseNotify() <-chan bool {
|
||||
return tw.closeNotify()
|
||||
}
|
||||
|
||||
type hijackTimeoutWriter struct {
|
||||
*baseTimeoutWriter
|
||||
}
|
||||
|
||||
func (tw *hijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return tw.hijack()
|
||||
}
|
||||
|
||||
type closeHijackTimeoutWriter struct {
|
||||
*baseTimeoutWriter
|
||||
}
|
||||
|
||||
func (tw *closeHijackTimeoutWriter) CloseNotify() <-chan bool {
|
||||
return tw.closeNotify()
|
||||
}
|
||||
|
||||
func (tw *closeHijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return tw.hijack()
|
||||
}
|
81
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/timeout_test.go
generated
vendored
Normal file
81
vendor/k8s.io/kubernetes/pkg/genericapiserver/filters/timeout_test.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package filters
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
sendResponse := make(chan struct{}, 1)
|
||||
writeErrors := make(chan error, 1)
|
||||
timeout := make(chan time.Time, 1)
|
||||
resp := "test response"
|
||||
timeoutResp := "test timeout"
|
||||
|
||||
ts := httptest.NewServer(WithTimeout(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
<-sendResponse
|
||||
_, err := w.Write([]byte(resp))
|
||||
writeErrors <- err
|
||||
}),
|
||||
func(*http.Request) (<-chan time.Time, string) {
|
||||
return timeout, timeoutResp
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
// No timeouts
|
||||
sendResponse <- struct{}{}
|
||||
res, err := http.Get(ts.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusOK)
|
||||
}
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
if string(body) != resp {
|
||||
t.Errorf("got body %q; expected %q", string(body), resp)
|
||||
}
|
||||
if err := <-writeErrors; err != nil {
|
||||
t.Errorf("got unexpected Write error on first request: %v", err)
|
||||
}
|
||||
|
||||
// Times out
|
||||
timeout <- time.Time{}
|
||||
res, err = http.Get(ts.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if res.StatusCode != http.StatusGatewayTimeout {
|
||||
t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusServiceUnavailable)
|
||||
}
|
||||
body, _ = ioutil.ReadAll(res.Body)
|
||||
if string(body) != timeoutResp {
|
||||
t.Errorf("got body %q; expected %q", string(body), timeoutResp)
|
||||
}
|
||||
|
||||
// Now try to send a response
|
||||
sendResponse <- struct{}{}
|
||||
if err := <-writeErrors; err != http.ErrHandlerTimeout {
|
||||
t.Errorf("got Write error of %v; expected %v", err, http.ErrHandlerTimeout)
|
||||
}
|
||||
}
|
385
vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver.go
generated
vendored
Normal file
385
vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver.go
generated
vendored
Normal file
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package genericapiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
systemd "github.com/coreos/go-systemd/daemon"
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/emicklei/go-restful/swagger"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apimachinery"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
openapicommon "k8s.io/apimachinery/pkg/openapi"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/healthz"
|
||||
apirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
genericapi "k8s.io/kubernetes/pkg/genericapiserver/api"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/routes"
|
||||
)
|
||||
|
||||
// Info about an API group.
|
||||
type APIGroupInfo struct {
|
||||
GroupMeta apimachinery.GroupMeta
|
||||
// Info about the resources in this group. Its a map from version to resource to the storage.
|
||||
VersionedResourcesStorageMap map[string]map[string]rest.Storage
|
||||
// OptionsExternalVersion controls the APIVersion used for common objects in the
|
||||
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
|
||||
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects.
|
||||
// If nil, defaults to groupMeta.GroupVersion.
|
||||
// TODO: Remove this when https://github.com/kubernetes/kubernetes/issues/19018 is fixed.
|
||||
OptionsExternalVersion *schema.GroupVersion
|
||||
|
||||
// Scheme includes all of the types used by this group and how to convert between them (or
|
||||
// to convert objects from outside of this group that are accepted in this API).
|
||||
// TODO: replace with interfaces
|
||||
Scheme *runtime.Scheme
|
||||
// NegotiatedSerializer controls how this group encodes and decodes data
|
||||
NegotiatedSerializer runtime.NegotiatedSerializer
|
||||
// ParameterCodec performs conversions for query parameters passed to API calls
|
||||
ParameterCodec runtime.ParameterCodec
|
||||
|
||||
// SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
|
||||
// accessible from this API group version. The GroupVersionKind is that of the external version of
|
||||
// the subresource. The key of this map should be the path of the subresource. The keys here should
|
||||
// match the keys in the Storage map above for subresources.
|
||||
SubresourceGroupVersionKind map[string]schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// GenericAPIServer contains state for a Kubernetes cluster api server.
|
||||
type GenericAPIServer struct {
|
||||
// discoveryAddresses is used to build cluster IPs for discovery.
|
||||
discoveryAddresses DiscoveryAddresses
|
||||
|
||||
// LoopbackClientConfig is a config for a privileged loopback connection to the API server
|
||||
LoopbackClientConfig *restclient.Config
|
||||
|
||||
// minRequestTimeout is how short the request timeout can be. This is used to build the RESTHandler
|
||||
minRequestTimeout time.Duration
|
||||
|
||||
// legacyAPIGroupPrefixes is used to set up URL parsing for authorization and for validating requests
|
||||
// to InstallLegacyAPIGroup
|
||||
legacyAPIGroupPrefixes sets.String
|
||||
|
||||
// admissionControl is used to build the RESTStorage that backs an API Group.
|
||||
admissionControl admission.Interface
|
||||
|
||||
// requestContextMapper provides a way to get the context for a request. It may be nil.
|
||||
requestContextMapper apirequest.RequestContextMapper
|
||||
|
||||
// The registered APIs
|
||||
HandlerContainer *genericmux.APIContainer
|
||||
|
||||
SecureServingInfo *SecureServingInfo
|
||||
InsecureServingInfo *ServingInfo
|
||||
|
||||
// numerical ports, set after listening
|
||||
effectiveSecurePort, effectiveInsecurePort int
|
||||
|
||||
// ExternalAddress is the address (hostname or IP and port) that should be used in
|
||||
// external (public internet) URLs for this GenericAPIServer.
|
||||
ExternalAddress string
|
||||
|
||||
// storage contains the RESTful endpoints exposed by this GenericAPIServer
|
||||
storage map[string]rest.Storage
|
||||
|
||||
// Serializer controls how common API objects not in a group/version prefix are serialized for this server.
|
||||
// Individual APIGroups may define their own serializers.
|
||||
Serializer runtime.NegotiatedSerializer
|
||||
|
||||
// "Outputs"
|
||||
Handler http.Handler
|
||||
InsecureHandler http.Handler
|
||||
|
||||
// Map storing information about all groups to be exposed in discovery response.
|
||||
// The map is from name to the group.
|
||||
apiGroupsForDiscoveryLock sync.RWMutex
|
||||
apiGroupsForDiscovery map[string]metav1.APIGroup
|
||||
|
||||
// Enable swagger and/or OpenAPI if these configs are non-nil.
|
||||
swaggerConfig *swagger.Config
|
||||
openAPIConfig *openapicommon.Config
|
||||
|
||||
// PostStartHooks are each called after the server has started listening, in a separate go func for each
|
||||
// with no guarantee of ordering between them. The map key is a name used for error reporting.
|
||||
// It may kill the process with a panic if it wishes to by returning an error
|
||||
postStartHookLock sync.Mutex
|
||||
postStartHooks map[string]postStartHookEntry
|
||||
postStartHooksCalled bool
|
||||
|
||||
// healthz checks
|
||||
healthzLock sync.Mutex
|
||||
healthzChecks []healthz.HealthzChecker
|
||||
healthzCreated bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Send correct mime type for .svg files.
|
||||
// TODO: remove when https://github.com/golang/go/commit/21e47d831bafb59f22b1ea8098f709677ec8ce33
|
||||
// makes it into all of our supported go versions (only in v1.7.1 now).
|
||||
mime.AddExtensionType(".svg", "image/svg+xml")
|
||||
}
|
||||
|
||||
// RequestContextMapper is exposed so that third party resource storage can be build in a different location.
|
||||
// TODO refactor third party resource storage
|
||||
func (s *GenericAPIServer) RequestContextMapper() apirequest.RequestContextMapper {
|
||||
return s.requestContextMapper
|
||||
}
|
||||
|
||||
// MinRequestTimeout is exposed so that third party resource storage can be build in a different location.
|
||||
// TODO refactor third party resource storage
|
||||
func (s *GenericAPIServer) MinRequestTimeout() time.Duration {
|
||||
return s.minRequestTimeout
|
||||
}
|
||||
|
||||
type preparedGenericAPIServer struct {
|
||||
*GenericAPIServer
|
||||
}
|
||||
|
||||
// PrepareRun does post API installation setup steps.
|
||||
func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
|
||||
if s.swaggerConfig != nil {
|
||||
routes.Swagger{Config: s.swaggerConfig}.Install(s.HandlerContainer)
|
||||
}
|
||||
if s.openAPIConfig != nil {
|
||||
routes.OpenAPI{
|
||||
Config: s.openAPIConfig,
|
||||
}.Install(s.HandlerContainer)
|
||||
}
|
||||
|
||||
s.installHealthz()
|
||||
|
||||
return preparedGenericAPIServer{s}
|
||||
}
|
||||
|
||||
// Run spawns the http servers (secure and insecure). It only returns if stopCh is closed
|
||||
// or one of the ports cannot be listened on initially.
|
||||
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) {
|
||||
if s.SecureServingInfo != nil && s.Handler != nil {
|
||||
if err := s.serveSecurely(stopCh); err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if s.InsecureServingInfo != nil && s.InsecureHandler != nil {
|
||||
if err := s.serveInsecurely(stopCh); err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
s.RunPostStartHooks()
|
||||
|
||||
// err == systemd.SdNotifyNoSocket when not running on a systemd system
|
||||
if err := systemd.SdNotify("READY=1\n"); err != nil && err != systemd.SdNotifyNoSocket {
|
||||
glog.Errorf("Unable to send systemd daemon successful start message: %v\n", err)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
// installAPIResources is a private method for installing the REST storage backing each api groupversionresource
|
||||
func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
|
||||
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
|
||||
apiGroupVersion := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
|
||||
if apiGroupInfo.OptionsExternalVersion != nil {
|
||||
apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion
|
||||
}
|
||||
|
||||
if err := apiGroupVersion.InstallREST(s.HandlerContainer.Container); err != nil {
|
||||
return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error {
|
||||
if !s.legacyAPIGroupPrefixes.Has(apiPrefix) {
|
||||
return fmt.Errorf("%q is not in the allowed legacy API prefixes: %v", apiPrefix, s.legacyAPIGroupPrefixes.List())
|
||||
}
|
||||
if err := s.installAPIResources(apiPrefix, apiGroupInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// setup discovery
|
||||
apiVersions := []string{}
|
||||
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
|
||||
apiVersions = append(apiVersions, groupVersion.Version)
|
||||
}
|
||||
// Install the version handler.
|
||||
// Add a handler at /<apiPrefix> to enumerate the supported api versions.
|
||||
genericapi.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *metav1.APIVersions {
|
||||
clientIP := utilnet.GetClientIP(req.Request)
|
||||
|
||||
apiVersionsForDiscovery := metav1.APIVersions{
|
||||
ServerAddressByClientCIDRs: s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP),
|
||||
Versions: apiVersions,
|
||||
}
|
||||
return &apiVersionsForDiscovery
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exposes the given api group in the API.
|
||||
func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
|
||||
// Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned.
|
||||
// Catching these here places the error much closer to its origin
|
||||
if len(apiGroupInfo.GroupMeta.GroupVersion.Group) == 0 {
|
||||
return fmt.Errorf("cannot register handler with an empty group for %#v", *apiGroupInfo)
|
||||
}
|
||||
if len(apiGroupInfo.GroupMeta.GroupVersion.Version) == 0 {
|
||||
return fmt.Errorf("cannot register handler with an empty version for %#v", *apiGroupInfo)
|
||||
}
|
||||
|
||||
if err := s.installAPIResources(APIGroupPrefix, apiGroupInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// setup discovery
|
||||
// Install the version handler.
|
||||
// Add a handler at /apis/<groupName> to enumerate all versions supported by this group.
|
||||
apiVersionsForDiscovery := []metav1.GroupVersionForDiscovery{}
|
||||
for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions {
|
||||
// Check the config to make sure that we elide versions that don't have any resources
|
||||
if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
|
||||
continue
|
||||
}
|
||||
apiVersionsForDiscovery = append(apiVersionsForDiscovery, metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: groupVersion.String(),
|
||||
Version: groupVersion.Version,
|
||||
})
|
||||
}
|
||||
preferedVersionForDiscovery := metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: apiGroupInfo.GroupMeta.GroupVersion.String(),
|
||||
Version: apiGroupInfo.GroupMeta.GroupVersion.Version,
|
||||
}
|
||||
apiGroup := metav1.APIGroup{
|
||||
Name: apiGroupInfo.GroupMeta.GroupVersion.Group,
|
||||
Versions: apiVersionsForDiscovery,
|
||||
PreferredVersion: preferedVersionForDiscovery,
|
||||
}
|
||||
|
||||
s.AddAPIGroupForDiscovery(apiGroup)
|
||||
s.HandlerContainer.Add(genericapi.NewGroupWebService(s.Serializer, APIGroupPrefix+"/"+apiGroup.Name, apiGroup))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) AddAPIGroupForDiscovery(apiGroup metav1.APIGroup) {
|
||||
s.apiGroupsForDiscoveryLock.Lock()
|
||||
defer s.apiGroupsForDiscoveryLock.Unlock()
|
||||
|
||||
s.apiGroupsForDiscovery[apiGroup.Name] = apiGroup
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) RemoveAPIGroupForDiscovery(groupName string) {
|
||||
s.apiGroupsForDiscoveryLock.Lock()
|
||||
defer s.apiGroupsForDiscoveryLock.Unlock()
|
||||
|
||||
delete(s.apiGroupsForDiscovery, groupName)
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion schema.GroupVersion, apiPrefix string) *genericapi.APIGroupVersion {
|
||||
storage := make(map[string]rest.Storage)
|
||||
for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
|
||||
storage[strings.ToLower(k)] = v
|
||||
}
|
||||
version := s.newAPIGroupVersion(apiGroupInfo, groupVersion)
|
||||
version.Root = apiPrefix
|
||||
version.Storage = storage
|
||||
return version
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion schema.GroupVersion) *genericapi.APIGroupVersion {
|
||||
return &genericapi.APIGroupVersion{
|
||||
GroupVersion: groupVersion,
|
||||
|
||||
ParameterCodec: apiGroupInfo.ParameterCodec,
|
||||
Serializer: apiGroupInfo.NegotiatedSerializer,
|
||||
Creater: apiGroupInfo.Scheme,
|
||||
Convertor: apiGroupInfo.Scheme,
|
||||
Copier: apiGroupInfo.Scheme,
|
||||
Typer: apiGroupInfo.Scheme,
|
||||
SubresourceGroupVersionKind: apiGroupInfo.SubresourceGroupVersionKind,
|
||||
Linker: apiGroupInfo.GroupMeta.SelfLinker,
|
||||
Mapper: apiGroupInfo.GroupMeta.RESTMapper,
|
||||
|
||||
Admit: s.admissionControl,
|
||||
Context: s.RequestContextMapper(),
|
||||
MinRequestTimeout: s.minRequestTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// DynamicApisDiscovery returns a webservice serving api group discovery.
|
||||
// Note: during the server runtime apiGroupsForDiscovery might change.
|
||||
func (s *GenericAPIServer) DynamicApisDiscovery() *restful.WebService {
|
||||
return genericapi.NewApisWebService(s.Serializer, APIGroupPrefix, func(req *restful.Request) []metav1.APIGroup {
|
||||
s.apiGroupsForDiscoveryLock.RLock()
|
||||
defer s.apiGroupsForDiscoveryLock.RUnlock()
|
||||
|
||||
// sort to have a deterministic order
|
||||
sortedGroups := []metav1.APIGroup{}
|
||||
groupNames := make([]string, 0, len(s.apiGroupsForDiscovery))
|
||||
for groupName := range s.apiGroupsForDiscovery {
|
||||
groupNames = append(groupNames, groupName)
|
||||
}
|
||||
sort.Strings(groupNames)
|
||||
for _, groupName := range groupNames {
|
||||
sortedGroups = append(sortedGroups, s.apiGroupsForDiscovery[groupName])
|
||||
}
|
||||
|
||||
clientIP := utilnet.GetClientIP(req.Request)
|
||||
serverCIDR := s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP)
|
||||
groups := make([]metav1.APIGroup, len(sortedGroups))
|
||||
for i := range sortedGroups {
|
||||
groups[i] = sortedGroups[i]
|
||||
groups[i].ServerAddressByClientCIDRs = serverCIDR
|
||||
}
|
||||
return groups
|
||||
})
|
||||
}
|
||||
|
||||
// NewDefaultAPIGroupInfo returns an APIGroupInfo stubbed with "normal" values
|
||||
// exposed for easier composition from other packages
|
||||
func NewDefaultAPIGroupInfo(group string) APIGroupInfo {
|
||||
groupMeta := api.Registry.GroupOrDie(group)
|
||||
|
||||
return APIGroupInfo{
|
||||
GroupMeta: *groupMeta,
|
||||
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
|
||||
OptionsExternalVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion,
|
||||
Scheme: api.Scheme,
|
||||
ParameterCodec: api.ParameterCodec,
|
||||
NegotiatedSerializer: api.Codecs,
|
||||
}
|
||||
}
|
620
vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver_test.go
generated
vendored
Normal file
620
vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver_test.go
generated
vendored
Normal file
|
@ -0,0 +1,620 @@
|
|||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
package genericapiserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apimachinery"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
genericapirequest "k8s.io/apiserver/pkg/request"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
openapigen "k8s.io/kubernetes/pkg/generated/openapi"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/rest"
|
||||
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// setUp is a convience function for setting up for (most) tests.
|
||||
func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
|
||||
etcdServer, _ := etcdtesting.NewUnsecuredEtcd3TestClientServer(t)
|
||||
|
||||
config := NewConfig()
|
||||
config.PublicAddress = net.ParseIP("192.168.10.4")
|
||||
config.RequestContextMapper = genericapirequest.NewRequestContextMapper()
|
||||
config.LegacyAPIGroupPrefixes = sets.NewString("/api")
|
||||
|
||||
config.OpenAPIConfig = DefaultOpenAPIConfig(openapigen.OpenAPIDefinitions)
|
||||
config.OpenAPIConfig.Info = &spec.Info{
|
||||
InfoProps: spec.InfoProps{
|
||||
Title: "Kubernetes",
|
||||
Version: "unversioned",
|
||||
},
|
||||
}
|
||||
config.SwaggerConfig = DefaultSwaggerConfig()
|
||||
|
||||
return etcdServer, *config, assert.New(t)
|
||||
}
|
||||
|
||||
func newMaster(t *testing.T) (*GenericAPIServer, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
|
||||
etcdserver, config, assert := setUp(t)
|
||||
|
||||
s, err := config.Complete().New()
|
||||
if err != nil {
|
||||
t.Fatalf("Error in bringing up the server: %v", err)
|
||||
}
|
||||
return s, etcdserver, config, assert
|
||||
}
|
||||
|
||||
// TestNew verifies that the New function returns a GenericAPIServer
|
||||
// using the configuration properly.
|
||||
func TestNew(t *testing.T) {
|
||||
s, etcdserver, config, assert := newMaster(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
// Verify many of the variables match their config counterparts
|
||||
assert.Equal(s.legacyAPIGroupPrefixes, config.LegacyAPIGroupPrefixes)
|
||||
assert.Equal(s.admissionControl, config.AdmissionControl)
|
||||
assert.Equal(s.RequestContextMapper(), config.RequestContextMapper)
|
||||
|
||||
// these values get defaulted
|
||||
assert.Equal(net.JoinHostPort(config.PublicAddress.String(), "6443"), s.ExternalAddress)
|
||||
assert.NotNil(s.swaggerConfig)
|
||||
assert.Equal("http://"+s.ExternalAddress, s.swaggerConfig.WebServicesUrl)
|
||||
}
|
||||
|
||||
// Verifies that AddGroupVersions works as expected.
|
||||
func TestInstallAPIGroups(t *testing.T) {
|
||||
etcdserver, config, assert := setUp(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix")
|
||||
config.DiscoveryAddresses = DefaultDiscoveryAddresses{DefaultAddress: "ExternalAddress"}
|
||||
|
||||
s, err := config.SkipComplete().New()
|
||||
if err != nil {
|
||||
t.Fatalf("Error in bringing up the server: %v", err)
|
||||
}
|
||||
|
||||
testAPI := func(gv schema.GroupVersion) APIGroupInfo {
|
||||
getter, noVerbs := testGetterStorage{}, testNoVerbsStorage{}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
scheme.AddKnownTypeWithName(gv.WithKind("Getter"), getter.New())
|
||||
scheme.AddKnownTypeWithName(gv.WithKind("NoVerb"), noVerbs.New())
|
||||
scheme.AddKnownTypes(v1.SchemeGroupVersion,
|
||||
&v1.ListOptions{},
|
||||
&v1.DeleteOptions{},
|
||||
&metav1.ExportOptions{},
|
||||
&metav1.Status{},
|
||||
)
|
||||
|
||||
interfacesFor := func(version schema.GroupVersion) (*meta.VersionInterfaces, error) {
|
||||
return &meta.VersionInterfaces{
|
||||
ObjectConvertor: scheme,
|
||||
MetadataAccessor: meta.NewAccessor(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
mapper := api.NewDefaultRESTMapperFromScheme([]schema.GroupVersion{gv}, interfacesFor, "", sets.NewString(), sets.NewString(), scheme)
|
||||
groupMeta := apimachinery.GroupMeta{
|
||||
GroupVersion: gv,
|
||||
GroupVersions: []schema.GroupVersion{gv},
|
||||
RESTMapper: mapper,
|
||||
InterfacesFor: interfacesFor,
|
||||
}
|
||||
|
||||
return APIGroupInfo{
|
||||
GroupMeta: groupMeta,
|
||||
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{
|
||||
gv.Version: {
|
||||
"getter": &testGetterStorage{Version: gv.Version},
|
||||
"noverbs": &testNoVerbsStorage{Version: gv.Version},
|
||||
},
|
||||
},
|
||||
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"},
|
||||
ParameterCodec: api.ParameterCodec,
|
||||
NegotiatedSerializer: api.Codecs,
|
||||
Scheme: scheme,
|
||||
}
|
||||
}
|
||||
|
||||
apis := []APIGroupInfo{
|
||||
testAPI(schema.GroupVersion{Group: "", Version: "v1"}),
|
||||
testAPI(schema.GroupVersion{Group: "extensions", Version: "v1"}),
|
||||
testAPI(schema.GroupVersion{Group: "batch", Version: "v1"}),
|
||||
}
|
||||
|
||||
err = s.InstallLegacyAPIGroup("/apiPrefix", &apis[0])
|
||||
assert.NoError(err)
|
||||
groupPaths := []string{
|
||||
config.LegacyAPIGroupPrefixes.List()[0], // /apiPrefix
|
||||
}
|
||||
for _, api := range apis[1:] {
|
||||
err = s.InstallAPIGroup(&api)
|
||||
assert.NoError(err)
|
||||
groupPaths = append(groupPaths, APIGroupPrefix+"/"+api.GroupMeta.GroupVersion.Group) // /apis/<group>
|
||||
}
|
||||
|
||||
server := httptest.NewServer(s.InsecureHandler)
|
||||
defer server.Close()
|
||||
|
||||
for i := range apis {
|
||||
// should serve APIGroup at group path
|
||||
info := &apis[i]
|
||||
path := groupPaths[i]
|
||||
resp, err := http.Get(server.URL + path)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error getting path %q path: %v", i, path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error reading body at path %q: %v", i, path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
t.Logf("[%d] json at %s: %s", i, path, string(body))
|
||||
|
||||
if i == 0 {
|
||||
// legacy API returns APIVersions
|
||||
group := metav1.APIVersions{}
|
||||
err = json.Unmarshal(body, &group)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// API groups return APIGroup
|
||||
group := metav1.APIGroup{}
|
||||
err = json.Unmarshal(body, &group)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if got, expected := group.Name, info.GroupMeta.GroupVersion.Group; got != expected {
|
||||
t.Errorf("[%d] unexpected group name at path %q: got=%q expected=%q", i, path, got, expected)
|
||||
continue
|
||||
}
|
||||
|
||||
if got, expected := group.PreferredVersion.Version, info.GroupMeta.GroupVersion.Version; got != expected {
|
||||
t.Errorf("[%d] unexpected group version at path %q: got=%q expected=%q", i, path, got, expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// should serve APIResourceList at group path + /<group-version>
|
||||
path = path + "/" + info.GroupMeta.GroupVersion.Version
|
||||
resp, err = http.Get(server.URL + path)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error getting path %q path: %v", i, path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error reading body at path %q: %v", i, path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
t.Logf("[%d] json at %s: %s", i, path, string(body))
|
||||
|
||||
resources := metav1.APIResourceList{}
|
||||
err = json.Unmarshal(body, &resources)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error parsing json body at path %q: %v", i, path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if got, expected := resources.GroupVersion, info.GroupMeta.GroupVersion.String(); got != expected {
|
||||
t.Errorf("[%d] unexpected groupVersion at path %q: got=%q expected=%q", i, path, got, expected)
|
||||
continue
|
||||
}
|
||||
|
||||
// the verbs should match the features of resources
|
||||
for _, r := range resources.APIResources {
|
||||
switch r.Name {
|
||||
case "getter":
|
||||
if got, expected := sets.NewString([]string(r.Verbs)...), sets.NewString("get"); !got.Equal(expected) {
|
||||
t.Errorf("[%d] unexpected verbs for resource %s/%s: got=%v expected=%v", i, resources.GroupVersion, r.Name, got, expected)
|
||||
}
|
||||
case "noverbs":
|
||||
if r.Verbs == nil {
|
||||
t.Errorf("[%d] unexpected nil verbs slice. Expected: []string{}", i)
|
||||
}
|
||||
if got, expected := sets.NewString([]string(r.Verbs)...), sets.NewString(); !got.Equal(expected) {
|
||||
t.Errorf("[%d] unexpected verbs for resource %s/%s: got=%v expected=%v", i, resources.GroupVersion, r.Name, got, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareRun(t *testing.T) {
|
||||
s, etcdserver, config, assert := newMaster(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
assert.NotNil(config.SwaggerConfig)
|
||||
assert.NotNil(config.OpenAPIConfig)
|
||||
|
||||
server := httptest.NewServer(s.HandlerContainer.ServeMux)
|
||||
defer server.Close()
|
||||
|
||||
s.PrepareRun()
|
||||
|
||||
// openapi is installed in PrepareRun
|
||||
resp, err := http.Get(server.URL + "/swagger.json")
|
||||
assert.NoError(err)
|
||||
assert.Equal(http.StatusOK, resp.StatusCode)
|
||||
|
||||
// swagger is installed in PrepareRun
|
||||
resp, err = http.Get(server.URL + "/swaggerapi/")
|
||||
assert.NoError(err)
|
||||
assert.Equal(http.StatusOK, resp.StatusCode)
|
||||
|
||||
// healthz checks are installed in PrepareRun
|
||||
resp, err = http.Get(server.URL + "/healthz")
|
||||
assert.NoError(err)
|
||||
assert.Equal(http.StatusOK, resp.StatusCode)
|
||||
resp, err = http.Get(server.URL + "/healthz/ping")
|
||||
assert.NoError(err)
|
||||
assert.Equal(http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
// TestCustomHandlerChain verifies the handler chain with custom handler chain builder functions.
|
||||
func TestCustomHandlerChain(t *testing.T) {
|
||||
etcdserver, config, _ := setUp(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
var protected, called bool
|
||||
|
||||
config.BuildHandlerChainsFunc = func(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
protected = true
|
||||
apiHandler.ServeHTTP(w, req)
|
||||
}), http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
protected = false
|
||||
apiHandler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
handler := http.HandlerFunc(func(r http.ResponseWriter, req *http.Request) {
|
||||
called = true
|
||||
})
|
||||
|
||||
s, err := config.SkipComplete().New()
|
||||
if err != nil {
|
||||
t.Fatalf("Error in bringing up the server: %v", err)
|
||||
}
|
||||
|
||||
s.HandlerContainer.NonSwaggerRoutes.Handle("/nonswagger", handler)
|
||||
s.HandlerContainer.UnlistedRoutes.Handle("/secret", handler)
|
||||
|
||||
type Test struct {
|
||||
handler http.Handler
|
||||
path string
|
||||
protected bool
|
||||
}
|
||||
for i, test := range []Test{
|
||||
{s.Handler, "/nonswagger", true},
|
||||
{s.Handler, "/secret", true},
|
||||
{s.InsecureHandler, "/nonswagger", false},
|
||||
{s.InsecureHandler, "/secret", false},
|
||||
} {
|
||||
protected, called = false, false
|
||||
|
||||
var w io.Reader
|
||||
req, err := http.NewRequest("GET", test.path, w)
|
||||
if err != nil {
|
||||
t.Errorf("%d: Unexpected http error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
test.handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if !called {
|
||||
t.Errorf("%d: Expected handler to be called.", i)
|
||||
}
|
||||
if test.protected != protected {
|
||||
t.Errorf("%d: Expected protected=%v, got protected=%v.", i, test.protected, protected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotRestRoutesHaveAuth checks that special non-routes are behind authz/authn.
|
||||
func TestNotRestRoutesHaveAuth(t *testing.T) {
|
||||
etcdserver, config, _ := setUp(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
authz := mockAuthorizer{}
|
||||
|
||||
config.LegacyAPIGroupPrefixes = sets.NewString("/apiPrefix")
|
||||
config.Authorizer = &authz
|
||||
|
||||
config.EnableSwaggerUI = true
|
||||
config.EnableIndex = true
|
||||
config.EnableProfiling = true
|
||||
config.SwaggerConfig = DefaultSwaggerConfig()
|
||||
|
||||
kubeVersion := version.Get()
|
||||
config.Version = &kubeVersion
|
||||
|
||||
s, err := config.SkipComplete().New()
|
||||
if err != nil {
|
||||
t.Fatalf("Error in bringing up the server: %v", err)
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
route string
|
||||
}{
|
||||
{"/"},
|
||||
{"/swagger-ui/"},
|
||||
{"/debug/pprof/"},
|
||||
{"/version"},
|
||||
} {
|
||||
resp := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", test.route, nil)
|
||||
s.Handler.ServeHTTP(resp, req)
|
||||
if resp.Code != 200 {
|
||||
t.Errorf("route %q expected to work: code %d", test.route, resp.Code)
|
||||
continue
|
||||
}
|
||||
|
||||
if authz.lastURI != test.route {
|
||||
t.Errorf("route %q expected to go through authorization, last route did: %q", test.route, authz.lastURI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockAuthorizer struct {
|
||||
lastURI string
|
||||
}
|
||||
|
||||
func (authz *mockAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
|
||||
authz.lastURI = a.GetPath()
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
type mockAuthenticator struct {
|
||||
lastURI string
|
||||
}
|
||||
|
||||
func (authn *mockAuthenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
|
||||
authn.lastURI = req.RequestURI
|
||||
return &user.DefaultInfo{
|
||||
Name: "foo",
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func decodeResponse(resp *http.Response, obj interface{}) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGroupList(server *httptest.Server) (*metav1.APIGroupList, error) {
|
||||
resp, err := http.Get(server.URL + "/apis")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected server response, expected %d, actual: %d", http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
groupList := metav1.APIGroupList{}
|
||||
err = decodeResponse(resp, &groupList)
|
||||
return &groupList, err
|
||||
}
|
||||
|
||||
func TestDiscoveryAtAPIS(t *testing.T) {
|
||||
master, etcdserver, _, assert := newMaster(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
server := httptest.NewServer(master.InsecureHandler)
|
||||
groupList, err := getGroupList(server)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
assert.Equal(0, len(groupList.Groups))
|
||||
|
||||
// Add a Group.
|
||||
extensionsVersions := []metav1.GroupVersionForDiscovery{
|
||||
{
|
||||
GroupVersion: testapi.Extensions.GroupVersion().String(),
|
||||
Version: testapi.Extensions.GroupVersion().Version,
|
||||
},
|
||||
}
|
||||
extensionsPreferredVersion := metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: extensions.GroupName + "/preferred",
|
||||
Version: "preferred",
|
||||
}
|
||||
master.AddAPIGroupForDiscovery(metav1.APIGroup{
|
||||
Name: extensions.GroupName,
|
||||
Versions: extensionsVersions,
|
||||
PreferredVersion: extensionsPreferredVersion,
|
||||
})
|
||||
|
||||
groupList, err = getGroupList(server)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(1, len(groupList.Groups))
|
||||
groupListGroup := groupList.Groups[0]
|
||||
assert.Equal(extensions.GroupName, groupListGroup.Name)
|
||||
assert.Equal(extensionsVersions, groupListGroup.Versions)
|
||||
assert.Equal(extensionsPreferredVersion, groupListGroup.PreferredVersion)
|
||||
assert.Equal(master.discoveryAddresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&http.Request{})), groupListGroup.ServerAddressByClientCIDRs)
|
||||
|
||||
// Remove the group.
|
||||
master.RemoveAPIGroupForDiscovery(extensions.GroupName)
|
||||
groupList, err = getGroupList(server)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(0, len(groupList.Groups))
|
||||
}
|
||||
|
||||
func TestGetServerAddressByClientCIDRs(t *testing.T) {
|
||||
publicAddressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
||||
{
|
||||
ClientCIDR: "0.0.0.0/0",
|
||||
ServerAddress: "ExternalAddress",
|
||||
},
|
||||
}
|
||||
internalAddressCIDRMap := []metav1.ServerAddressByClientCIDR{
|
||||
publicAddressCIDRMap[0],
|
||||
{
|
||||
ClientCIDR: "10.0.0.0/24",
|
||||
ServerAddress: "serviceIP",
|
||||
},
|
||||
}
|
||||
internalIP := "10.0.0.1"
|
||||
publicIP := "1.1.1.1"
|
||||
testCases := []struct {
|
||||
Request http.Request
|
||||
ExpectedMap []metav1.ServerAddressByClientCIDR
|
||||
}{
|
||||
{
|
||||
Request: http.Request{},
|
||||
ExpectedMap: publicAddressCIDRMap,
|
||||
},
|
||||
{
|
||||
Request: http.Request{
|
||||
Header: map[string][]string{
|
||||
"X-Real-Ip": {internalIP},
|
||||
},
|
||||
},
|
||||
ExpectedMap: internalAddressCIDRMap,
|
||||
},
|
||||
{
|
||||
Request: http.Request{
|
||||
Header: map[string][]string{
|
||||
"X-Real-Ip": {publicIP},
|
||||
},
|
||||
},
|
||||
ExpectedMap: publicAddressCIDRMap,
|
||||
},
|
||||
{
|
||||
Request: http.Request{
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-For": {internalIP},
|
||||
},
|
||||
},
|
||||
ExpectedMap: internalAddressCIDRMap,
|
||||
},
|
||||
{
|
||||
Request: http.Request{
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-For": {publicIP},
|
||||
},
|
||||
},
|
||||
ExpectedMap: publicAddressCIDRMap,
|
||||
},
|
||||
|
||||
{
|
||||
Request: http.Request{
|
||||
RemoteAddr: internalIP,
|
||||
},
|
||||
ExpectedMap: internalAddressCIDRMap,
|
||||
},
|
||||
{
|
||||
Request: http.Request{
|
||||
RemoteAddr: publicIP,
|
||||
},
|
||||
ExpectedMap: publicAddressCIDRMap,
|
||||
},
|
||||
{
|
||||
Request: http.Request{
|
||||
RemoteAddr: "invalidIP",
|
||||
},
|
||||
ExpectedMap: publicAddressCIDRMap,
|
||||
},
|
||||
}
|
||||
|
||||
_, ipRange, _ := net.ParseCIDR("10.0.0.0/24")
|
||||
discoveryAddresses := DefaultDiscoveryAddresses{DefaultAddress: "ExternalAddress"}
|
||||
discoveryAddresses.DiscoveryCIDRRules = append(discoveryAddresses.DiscoveryCIDRRules,
|
||||
DiscoveryCIDRRule{IPRange: *ipRange, Address: "serviceIP"})
|
||||
|
||||
for i, test := range testCases {
|
||||
if a, e := discoveryAddresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&test.Request)), test.ExpectedMap; reflect.DeepEqual(e, a) != true {
|
||||
t.Fatalf("test case %d failed. expected: %v, actual: %v", i+1, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testGetterStorage struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (p *testGetterStorage) New() runtime.Object {
|
||||
return &metav1.APIGroup{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Getter",
|
||||
APIVersion: p.Version,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *testGetterStorage) Get(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type testNoVerbsStorage struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (p *testNoVerbsStorage) New() runtime.Object {
|
||||
return &metav1.APIGroup{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "NoVerbs",
|
||||
APIVersion: p.Version,
|
||||
},
|
||||
}
|
||||
}
|
45
vendor/k8s.io/kubernetes/pkg/genericapiserver/healthz.go
generated
vendored
Normal file
45
vendor/k8s.io/kubernetes/pkg/genericapiserver/healthz.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package genericapiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/healthz"
|
||||
)
|
||||
|
||||
// AddHealthzCheck allows you to add a HealthzCheck.
|
||||
func (s *GenericAPIServer) AddHealthzChecks(checks ...healthz.HealthzChecker) error {
|
||||
s.healthzLock.Lock()
|
||||
defer s.healthzLock.Unlock()
|
||||
|
||||
if s.healthzCreated {
|
||||
return fmt.Errorf("unable to add because the healthz endpoint has already been created")
|
||||
}
|
||||
|
||||
s.healthzChecks = append(s.healthzChecks, checks...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// installHealthz creates the healthz endpoint for this server
|
||||
func (s *GenericAPIServer) installHealthz() {
|
||||
s.healthzLock.Lock()
|
||||
defer s.healthzLock.Unlock()
|
||||
s.healthzCreated = true
|
||||
|
||||
healthz.InstallHandler(&s.HandlerContainer.NonSwaggerRoutes, s.healthzChecks...)
|
||||
}
|
140
vendor/k8s.io/kubernetes/pkg/genericapiserver/hooks.go
generated
vendored
Normal file
140
vendor/k8s.io/kubernetes/pkg/genericapiserver/hooks.go
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
package genericapiserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/healthz"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
)
|
||||
|
||||
// PostStartHookFunc is a function that is called after the server has started.
|
||||
// It must properly handle cases like:
|
||||
// 1. asynchronous start in multiple API server processes
|
||||
// 2. conflicts between the different processes all trying to perform the same action
|
||||
// 3. partially complete work (API server crashes while running your hook)
|
||||
// 4. API server access **BEFORE** your hook has completed
|
||||
// Think of it like a mini-controller that is super privileged and gets to run in-process
|
||||
// If you use this feature, tag @deads2k on github who has promised to review code for anyone's PostStartHook
|
||||
// until it becomes easier to use.
|
||||
type PostStartHookFunc func(context PostStartHookContext) error
|
||||
|
||||
// PostStartHookContext provides information about this API server to a PostStartHookFunc
|
||||
type PostStartHookContext struct {
|
||||
// LoopbackClientConfig is a config for a privileged loopback connection to the API server
|
||||
LoopbackClientConfig *restclient.Config
|
||||
}
|
||||
|
||||
// PostStartHookProvider is an interface in addition to provide a post start hook for the api server
|
||||
type PostStartHookProvider interface {
|
||||
PostStartHook() (string, PostStartHookFunc, error)
|
||||
}
|
||||
|
||||
type postStartHookEntry struct {
|
||||
hook PostStartHookFunc
|
||||
|
||||
// done will be closed when the postHook is finished
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// AddPostStartHook allows you to add a PostStartHook.
|
||||
func (s *GenericAPIServer) AddPostStartHook(name string, hook PostStartHookFunc) error {
|
||||
if len(name) == 0 {
|
||||
return fmt.Errorf("missing name")
|
||||
}
|
||||
if hook == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.postStartHookLock.Lock()
|
||||
defer s.postStartHookLock.Unlock()
|
||||
|
||||
if s.postStartHooksCalled {
|
||||
return fmt.Errorf("unable to add %q because PostStartHooks have already been called", name)
|
||||
}
|
||||
if _, exists := s.postStartHooks[name]; exists {
|
||||
return fmt.Errorf("unable to add %q because it is already registered", name)
|
||||
}
|
||||
|
||||
// done is closed when the poststarthook is finished. This is used by the health check to be able to indicate
|
||||
// that the poststarthook is finished
|
||||
done := make(chan struct{})
|
||||
s.AddHealthzChecks(postStartHookHealthz{name: "poststarthook/" + name, done: done})
|
||||
s.postStartHooks[name] = postStartHookEntry{hook: hook, done: done}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunPostStartHooks runs the PostStartHooks for the server
|
||||
func (s *GenericAPIServer) RunPostStartHooks() {
|
||||
s.postStartHookLock.Lock()
|
||||
defer s.postStartHookLock.Unlock()
|
||||
s.postStartHooksCalled = true
|
||||
|
||||
context := PostStartHookContext{LoopbackClientConfig: s.LoopbackClientConfig}
|
||||
|
||||
for hookName, hookEntry := range s.postStartHooks {
|
||||
go runPostStartHook(hookName, hookEntry, context)
|
||||
}
|
||||
}
|
||||
|
||||
func runPostStartHook(name string, entry postStartHookEntry, context PostStartHookContext) {
|
||||
var err error
|
||||
func() {
|
||||
// don't let the hook *accidentally* panic and kill the server
|
||||
defer utilruntime.HandleCrash()
|
||||
err = entry.hook(context)
|
||||
}()
|
||||
// if the hook intentionally wants to kill server, let it.
|
||||
if err != nil {
|
||||
glog.Fatalf("PostStartHook %q failed: %v", name, err)
|
||||
}
|
||||
|
||||
close(entry.done)
|
||||
}
|
||||
|
||||
// postStartHookHealthz implements a healthz check for poststarthooks. It will return a "hookNotFinished"
|
||||
// error until the poststarthook is finished.
|
||||
type postStartHookHealthz struct {
|
||||
name string
|
||||
|
||||
// done will be closed when the postStartHook is finished
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
var _ healthz.HealthzChecker = postStartHookHealthz{}
|
||||
|
||||
func (h postStartHookHealthz) Name() string {
|
||||
return h.name
|
||||
}
|
||||
|
||||
var hookNotFinished = errors.New("not finished")
|
||||
|
||||
func (h postStartHookHealthz) Check(req *http.Request) error {
|
||||
select {
|
||||
case <-h.done:
|
||||
return nil
|
||||
default:
|
||||
return hookNotFinished
|
||||
}
|
||||
}
|
49
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/BUILD
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"container.go",
|
||||
"doc.go",
|
||||
"pathrecorder.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/genericapiserver/api/handlers/responsewriters:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["container_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor:github.com/stretchr/testify/assert"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
2
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/OWNERS
generated
vendored
Executable file
2
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,2 @@
|
|||
reviewers:
|
||||
- sttts
|
96
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/container.go
generated
vendored
Normal file
96
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/container.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
rt "runtime"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/golang/glog"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver/api/handlers/responsewriters"
|
||||
)
|
||||
|
||||
// APIContainer is a restful container which in addition support registering
|
||||
// handlers that do not show up in swagger or in /
|
||||
type APIContainer struct {
|
||||
*restful.Container
|
||||
|
||||
// NonSwaggerRoutes are recorded and are visible at /, but do not show up in Swagger.
|
||||
NonSwaggerRoutes PathRecorderMux
|
||||
|
||||
// UnlistedRoutes are not recorded, therefore not visible at / and do not show up in Swagger.
|
||||
UnlistedRoutes *http.ServeMux
|
||||
}
|
||||
|
||||
// NewAPIContainer constructs a new container for APIs
|
||||
func NewAPIContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *APIContainer {
|
||||
c := APIContainer{
|
||||
Container: restful.NewContainer(),
|
||||
NonSwaggerRoutes: PathRecorderMux{
|
||||
mux: mux,
|
||||
},
|
||||
UnlistedRoutes: mux,
|
||||
}
|
||||
c.Container.ServeMux = mux
|
||||
c.Container.Router(restful.CurlyRouter{}) // e.g. for proxy/{kind}/{name}/{*}
|
||||
c.Container.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
|
||||
logStackOnRecover(s, panicReason, httpWriter)
|
||||
})
|
||||
c.Container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||
serviceErrorHandler(s, serviceErr, request, response)
|
||||
})
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
//TODO: Unify with RecoverPanics?
|
||||
func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
|
||||
for i := 2; ; i++ {
|
||||
_, file, line, ok := rt.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
|
||||
}
|
||||
glog.Errorln(buffer.String())
|
||||
|
||||
headers := http.Header{}
|
||||
if ct := w.Header().Get("Content-Type"); len(ct) > 0 {
|
||||
headers.Set("Accept", ct)
|
||||
}
|
||||
responsewriters.ErrorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", api.Resource(""), "", "", 0, false), s, schema.GroupVersion{}, w, &http.Request{Header: headers})
|
||||
}
|
||||
|
||||
func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, resp *restful.Response) {
|
||||
responsewriters.ErrorNegotiated(
|
||||
apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false),
|
||||
s,
|
||||
schema.GroupVersion{},
|
||||
resp,
|
||||
request.Request,
|
||||
)
|
||||
}
|
40
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/container_test.go
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/container_test.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewAPIContainer(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
c := NewAPIContainer(mux, nil)
|
||||
assert.Equal(t, mux, c.UnlistedRoutes, "UnlistedRoutes ServeMux's do not match")
|
||||
assert.Equal(t, mux, c.Container.ServeMux, "Container ServeMux's do not match")
|
||||
}
|
||||
|
||||
func TestSecretHandlers(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
c := NewAPIContainer(mux, nil)
|
||||
c.UnlistedRoutes.HandleFunc("/secret", func(http.ResponseWriter, *http.Request) {})
|
||||
c.NonSwaggerRoutes.HandleFunc("/nonswagger", func(http.ResponseWriter, *http.Request) {})
|
||||
assert.NotContains(t, c.NonSwaggerRoutes.HandledPaths(), "/secret")
|
||||
assert.Contains(t, c.NonSwaggerRoutes.HandledPaths(), "/nonswagger")
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package mux contains abstractions for http multiplexing of APIs.
|
||||
package mux
|
63
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/pathrecorder.go
generated
vendored
Normal file
63
vendor/k8s.io/kubernetes/pkg/genericapiserver/mux/pathrecorder.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package mux
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Mux is an object that can register http handlers.
|
||||
type Mux interface {
|
||||
Handle(pattern string, handler http.Handler)
|
||||
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
|
||||
}
|
||||
|
||||
// PathRecorderMux wraps a mux object and records the registered paths. It is _not_ go routine safe.
|
||||
type PathRecorderMux struct {
|
||||
mux Mux
|
||||
paths []string
|
||||
}
|
||||
|
||||
// NewPathRecorderMux creates a new PathRecorderMux with the given mux as the base mux.
|
||||
func NewPathRecorderMux(mux Mux) *PathRecorderMux {
|
||||
return &PathRecorderMux{
|
||||
mux: mux,
|
||||
}
|
||||
}
|
||||
|
||||
// BaseMux returns the underlying mux.
|
||||
func (m *PathRecorderMux) BaseMux() Mux {
|
||||
return m.mux
|
||||
}
|
||||
|
||||
// HandledPaths returns the registered handler paths.
|
||||
func (m *PathRecorderMux) HandledPaths() []string {
|
||||
return append([]string{}, m.paths...)
|
||||
}
|
||||
|
||||
// Handle registers the handler for the given pattern.
|
||||
// If a handler already exists for pattern, Handle panics.
|
||||
func (m *PathRecorderMux) Handle(path string, handler http.Handler) {
|
||||
m.paths = append(m.paths, path)
|
||||
m.mux.Handle(path, handler)
|
||||
}
|
||||
|
||||
// HandleFunc registers the handler function for the given pattern.
|
||||
func (m *PathRecorderMux) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
m.paths = append(m.paths, path)
|
||||
m.mux.HandleFunc(path, handler)
|
||||
}
|
56
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/BUILD
generated
vendored
Normal file
56
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"openapi.go",
|
||||
"util.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/genericapiserver/mux:go_default_library",
|
||||
"//pkg/util:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/go-openapi/spec",
|
||||
"//vendor:k8s.io/apimachinery/pkg/openapi",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/json",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["openapi_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/go-openapi/spec",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
"//vendor:k8s.io/apimachinery/pkg/openapi",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/genericapiserver/openapi/common:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
5
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/OWNERS
generated
vendored
Executable file
5
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/OWNERS
generated
vendored
Executable file
|
@ -0,0 +1,5 @@
|
|||
reviewers:
|
||||
- yujuhong
|
||||
- gmarek
|
||||
- mbohlool
|
||||
- philips
|
0
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/.readonly
generated
vendored
Normal file
0
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/.readonly
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/BUILD
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"common.go",
|
||||
"doc.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/go-openapi/spec",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
143
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/common.go
generated
vendored
Normal file
143
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/common.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
// OpenAPIDefinition describes single type. Normally these definitions are auto-generated using gen-openapi.
|
||||
type OpenAPIDefinition struct {
|
||||
Schema spec.Schema
|
||||
Dependencies []string
|
||||
}
|
||||
|
||||
// OpenAPIDefinitions is collection of all definitions.
|
||||
type OpenAPIDefinitions map[string]OpenAPIDefinition
|
||||
|
||||
// OpenAPIDefinitionGetter gets openAPI definitions for a given type. If a type implements this interface,
|
||||
// the definition returned by it will be used, otherwise the auto-generated definitions will be used. See
|
||||
// GetOpenAPITypeFormat for more information about trade-offs of using this interface or GetOpenAPITypeFormat method when
|
||||
// possible.
|
||||
type OpenAPIDefinitionGetter interface {
|
||||
OpenAPIDefinition() *OpenAPIDefinition
|
||||
}
|
||||
|
||||
// Config is set of configuration for openAPI spec generation.
|
||||
type Config struct {
|
||||
// List of supported protocols such as https, http, etc.
|
||||
ProtocolList []string
|
||||
|
||||
// Info is general information about the API.
|
||||
Info *spec.Info
|
||||
|
||||
// DefaultResponse will be used if an operation does not have any responses listed. It
|
||||
// will show up as ... "responses" : {"default" : $DefaultResponse} in the spec.
|
||||
DefaultResponse *spec.Response
|
||||
|
||||
// CommonResponses will be added as a response to all operation specs. This is a good place to add common
|
||||
// responses such as authorization failed.
|
||||
CommonResponses map[int]spec.Response
|
||||
|
||||
// List of webservice's path prefixes to ignore
|
||||
IgnorePrefixes []string
|
||||
|
||||
// OpenAPIDefinitions should provide definition for all models used by routes. Failure to provide this map
|
||||
// or any of the models will result in spec generation failure.
|
||||
Definitions *OpenAPIDefinitions
|
||||
|
||||
// GetOperationIDAndTags returns operation id and tags for a restful route. It is an optional function to customize operation IDs.
|
||||
GetOperationIDAndTags func(servePath string, r *restful.Route) (string, []string, error)
|
||||
|
||||
// SecurityDefinitions is list of all security definitions for OpenAPI service. If this is not nil, the user of config
|
||||
// is responsible to provide DefaultSecurity and (maybe) add unauthorized response to CommonResponses.
|
||||
SecurityDefinitions *spec.SecurityDefinitions
|
||||
|
||||
// DefaultSecurity for all operations. This will pass as spec.SwaggerProps.Security to OpenAPI.
|
||||
// For most cases, this will be list of acceptable definitions in SecurityDefinitions.
|
||||
DefaultSecurity []map[string][]string
|
||||
}
|
||||
|
||||
// This function is a reference for converting go (or any custom type) to a simple open API type,format pair. There are
|
||||
// two ways to customize spec for a type. If you add it here, a type will be converted to a simple type and the type
|
||||
// comment (the comment that is added before type definition) will be lost. The spec will still have the property
|
||||
// comment. The second way is to implement OpenAPIDefinitionGetter interface. That function can customize the spec (so
|
||||
// the spec does not need to be simple type,format) or can even return a simple type,format (e.g. IntOrString). For simple
|
||||
// type formats, the benefit of adding OpenAPIDefinitionGetter interface is to keep both type and property documentation.
|
||||
// Example:
|
||||
// type Sample struct {
|
||||
// ...
|
||||
// // port of the server
|
||||
// port IntOrString
|
||||
// ...
|
||||
// }
|
||||
// // IntOrString documentation...
|
||||
// type IntOrString { ... }
|
||||
//
|
||||
// Adding IntOrString to this function:
|
||||
// "port" : {
|
||||
// format: "string",
|
||||
// type: "int-or-string",
|
||||
// Description: "port of the server"
|
||||
// }
|
||||
//
|
||||
// Implement OpenAPIDefinitionGetter for IntOrString:
|
||||
//
|
||||
// "port" : {
|
||||
// $Ref: "#/definitions/IntOrString"
|
||||
// Description: "port of the server"
|
||||
// }
|
||||
// ...
|
||||
// definitions:
|
||||
// {
|
||||
// "IntOrString": {
|
||||
// format: "string",
|
||||
// type: "int-or-string",
|
||||
// Description: "IntOrString documentation..." // new
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func GetOpenAPITypeFormat(typeName string) (string, string) {
|
||||
schemaTypeFormatMap := map[string][]string{
|
||||
"uint": {"integer", "int32"},
|
||||
"uint8": {"integer", "byte"},
|
||||
"uint16": {"integer", "int32"},
|
||||
"uint32": {"integer", "int64"},
|
||||
"uint64": {"integer", "int64"},
|
||||
"int": {"integer", "int32"},
|
||||
"int8": {"integer", "byte"},
|
||||
"int16": {"integer", "int32"},
|
||||
"int32": {"integer", "int32"},
|
||||
"int64": {"integer", "int64"},
|
||||
"byte": {"integer", "byte"},
|
||||
"float64": {"number", "double"},
|
||||
"float32": {"number", "float"},
|
||||
"bool": {"boolean", ""},
|
||||
"time.Time": {"string", "date-time"},
|
||||
"string": {"string", ""},
|
||||
"integer": {"integer", ""},
|
||||
"number": {"number", ""},
|
||||
"boolean": {"boolean", ""},
|
||||
"[]byte": {"string", "byte"}, // base64 encoded characters
|
||||
}
|
||||
mapped, ok := schemaTypeFormatMap[typeName]
|
||||
if !ok {
|
||||
return "", ""
|
||||
}
|
||||
return mapped[0], mapped[1]
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/common/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// package common holds shared codes and types between open API code generator and spec generator.
|
||||
package common
|
20
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package openapi contains code to generate OpenAPI discovery spec (which
|
||||
// initial version of it also known as Swagger 2.0).
|
||||
// For more details: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
|
||||
package openapi
|
384
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/openapi.go
generated
vendored
Normal file
384
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/openapi.go
generated
vendored
Normal file
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/go-openapi/spec"
|
||||
|
||||
"k8s.io/apimachinery/pkg/openapi"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
OpenAPIVersion = "2.0"
|
||||
)
|
||||
|
||||
type openAPI struct {
|
||||
config *openapi.Config
|
||||
swagger *spec.Swagger
|
||||
protocolList []string
|
||||
servePath string
|
||||
}
|
||||
|
||||
// RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
|
||||
func RegisterOpenAPIService(servePath string, webServices []*restful.WebService, config *openapi.Config, container *genericmux.APIContainer) (err error) {
|
||||
o := openAPI{
|
||||
config: config,
|
||||
servePath: servePath,
|
||||
swagger: &spec.Swagger{
|
||||
SwaggerProps: spec.SwaggerProps{
|
||||
Swagger: OpenAPIVersion,
|
||||
Definitions: spec.Definitions{},
|
||||
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
|
||||
Info: config.Info,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = o.init(webServices)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container.UnlistedRoutes.HandleFunc(servePath, func(w http.ResponseWriter, r *http.Request) {
|
||||
resp := restful.NewResponse(w)
|
||||
if r.URL.Path != servePath {
|
||||
resp.WriteErrorString(http.StatusNotFound, "Path not found!")
|
||||
}
|
||||
// TODO: we can cache json string and return it here.
|
||||
resp.WriteAsJson(o.swagger)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *openAPI) init(webServices []*restful.WebService) error {
|
||||
if o.config.GetOperationIDAndTags == nil {
|
||||
o.config.GetOperationIDAndTags = func(_ string, r *restful.Route) (string, []string, error) {
|
||||
return r.Operation, nil, nil
|
||||
}
|
||||
}
|
||||
if o.config.CommonResponses == nil {
|
||||
o.config.CommonResponses = map[int]spec.Response{}
|
||||
}
|
||||
err := o.buildPaths(webServices)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.config.SecurityDefinitions != nil {
|
||||
o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
|
||||
o.swagger.Security = o.config.DefaultSecurity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *openAPI) buildDefinitionRecursively(name string) error {
|
||||
if _, ok := o.swagger.Definitions[name]; ok {
|
||||
return nil
|
||||
}
|
||||
if item, ok := (*o.config.Definitions)[name]; ok {
|
||||
o.swagger.Definitions[name] = item.Schema
|
||||
for _, v := range item.Dependencies {
|
||||
if err := o.buildDefinitionRecursively(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again.", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildDefinitionForType build a definition for a given type and return a referable name to it's definition.
|
||||
// This is the main function that keep track of definitions used in this spec and is depend on code generated
|
||||
// by k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen.
|
||||
func (o *openAPI) buildDefinitionForType(sample interface{}) (string, error) {
|
||||
t := reflect.TypeOf(sample)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
name := t.String()
|
||||
if err := o.buildDefinitionRecursively(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "#/definitions/" + name, nil
|
||||
}
|
||||
|
||||
// buildPaths builds OpenAPI paths using go-restful's web services.
|
||||
func (o *openAPI) buildPaths(webServices []*restful.WebService) error {
|
||||
pathsToIgnore := util.CreateTrie(o.config.IgnorePrefixes)
|
||||
duplicateOpId := make(map[string]string)
|
||||
for _, w := range webServices {
|
||||
rootPath := w.RootPath()
|
||||
if pathsToIgnore.HasPrefix(rootPath) {
|
||||
continue
|
||||
}
|
||||
commonParams, err := o.buildParameters(w.PathParameters())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for path, routes := range groupRoutesByPath(w.Routes()) {
|
||||
// go-swagger has special variable definition {$NAME:*} that can only be
|
||||
// used at the end of the path and it is not recognized by OpenAPI.
|
||||
if strings.HasSuffix(path, ":*}") {
|
||||
path = path[:len(path)-3] + "}"
|
||||
}
|
||||
if pathsToIgnore.HasPrefix(path) {
|
||||
continue
|
||||
}
|
||||
// Aggregating common parameters make API spec (and generated clients) simpler
|
||||
inPathCommonParamsMap, err := o.findCommonParameters(routes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pathItem, exists := o.swagger.Paths.Paths[path]
|
||||
if exists {
|
||||
return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
|
||||
}
|
||||
pathItem = spec.PathItem{
|
||||
PathItemProps: spec.PathItemProps{
|
||||
Parameters: make([]spec.Parameter, 0),
|
||||
},
|
||||
}
|
||||
// add web services's parameters as well as any parameters appears in all ops, as common parameters
|
||||
pathItem.Parameters = append(pathItem.Parameters, commonParams...)
|
||||
for _, p := range inPathCommonParamsMap {
|
||||
pathItem.Parameters = append(pathItem.Parameters, p)
|
||||
}
|
||||
sortParameters(pathItem.Parameters)
|
||||
for _, route := range routes {
|
||||
op, err := o.buildOperations(route, inPathCommonParamsMap)
|
||||
sortParameters(op.Parameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dpath, exists := duplicateOpId[op.ID]
|
||||
if exists {
|
||||
return fmt.Errorf("Duplicate Operation ID %v for path %v and %v.", op.ID, dpath, path)
|
||||
} else {
|
||||
duplicateOpId[op.ID] = path
|
||||
}
|
||||
switch strings.ToUpper(route.Method) {
|
||||
case "GET":
|
||||
pathItem.Get = op
|
||||
case "POST":
|
||||
pathItem.Post = op
|
||||
case "HEAD":
|
||||
pathItem.Head = op
|
||||
case "PUT":
|
||||
pathItem.Put = op
|
||||
case "DELETE":
|
||||
pathItem.Delete = op
|
||||
case "OPTIONS":
|
||||
pathItem.Options = op
|
||||
case "PATCH":
|
||||
pathItem.Patch = op
|
||||
}
|
||||
}
|
||||
o.swagger.Paths.Paths[path] = pathItem
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildOperations builds operations for each webservice path
|
||||
func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) {
|
||||
ret = &spec.Operation{
|
||||
OperationProps: spec.OperationProps{
|
||||
Description: route.Doc,
|
||||
Consumes: route.Consumes,
|
||||
Produces: route.Produces,
|
||||
Schemes: o.config.ProtocolList,
|
||||
Responses: &spec.Responses{
|
||||
ResponsesProps: spec.ResponsesProps{
|
||||
StatusCodeResponses: make(map[int]spec.Response),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTags(o.servePath, &route); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Build responses
|
||||
for _, resp := range route.ResponseErrors {
|
||||
ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
// If there is no response but a write sample, assume that write sample is an http.StatusOK response.
|
||||
if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil {
|
||||
ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
for code, resp := range o.config.CommonResponses {
|
||||
if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
|
||||
ret.Responses.StatusCodeResponses[code] = resp
|
||||
}
|
||||
}
|
||||
// If there is still no response, use default response provided.
|
||||
if len(ret.Responses.StatusCodeResponses) == 0 {
|
||||
ret.Responses.Default = o.config.DefaultResponse
|
||||
}
|
||||
// If there is a read sample, there will be a body param referring to it.
|
||||
if route.ReadSample != nil {
|
||||
if _, err := o.toSchema(reflect.TypeOf(route.ReadSample).String(), route.ReadSample); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
|
||||
// Build non-common Parameters
|
||||
ret.Parameters = make([]spec.Parameter, 0)
|
||||
for _, param := range route.ParameterDocs {
|
||||
if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
|
||||
openAPIParam, err := o.buildParameter(param.Data())
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret.Parameters = append(ret.Parameters, openAPIParam)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
|
||||
typeName := reflect.TypeOf(model).String()
|
||||
schema, err := o.toSchema(typeName, model)
|
||||
if err != nil {
|
||||
return spec.Response{}, err
|
||||
}
|
||||
return spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: description,
|
||||
Schema: schema,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
|
||||
commonParamsMap := make(map[interface{}]spec.Parameter, 0)
|
||||
paramOpsCountByName := make(map[interface{}]int, 0)
|
||||
paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0)
|
||||
for _, route := range routes {
|
||||
routeParamDuplicateMap := make(map[interface{}]bool)
|
||||
s := ""
|
||||
for _, param := range route.ParameterDocs {
|
||||
m, _ := json.Marshal(param.Data())
|
||||
s += string(m) + "\n"
|
||||
key := mapKeyFromParam(param)
|
||||
if routeParamDuplicateMap[key] {
|
||||
msg, _ := json.Marshal(route.ParameterDocs)
|
||||
return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v.", param.Data().Name, string(msg), s)
|
||||
}
|
||||
routeParamDuplicateMap[key] = true
|
||||
paramOpsCountByName[key]++
|
||||
paramNameKindToDataMap[key] = param.Data()
|
||||
}
|
||||
}
|
||||
for key, count := range paramOpsCountByName {
|
||||
if count == len(routes) {
|
||||
openAPIParam, err := o.buildParameter(paramNameKindToDataMap[key])
|
||||
if err != nil {
|
||||
return commonParamsMap, err
|
||||
}
|
||||
commonParamsMap[key] = openAPIParam
|
||||
}
|
||||
}
|
||||
return commonParamsMap, nil
|
||||
}
|
||||
|
||||
func (o *openAPI) toSchema(typeName string, model interface{}) (_ *spec.Schema, err error) {
|
||||
if openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(typeName); openAPIType != "" {
|
||||
return &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{openAPIType},
|
||||
Format: openAPIFormat,
|
||||
},
|
||||
}, nil
|
||||
} else {
|
||||
ref := "#/definitions/" + typeName
|
||||
if model != nil {
|
||||
ref, err = o.buildDefinitionForType(model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: spec.MustCreateRef(ref),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *openAPI) buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err error) {
|
||||
ret = spec.Parameter{
|
||||
ParamProps: spec.ParamProps{
|
||||
Name: restParam.Name,
|
||||
Description: restParam.Description,
|
||||
Required: restParam.Required,
|
||||
},
|
||||
}
|
||||
switch restParam.Kind {
|
||||
case restful.BodyParameterKind:
|
||||
ret.In = "body"
|
||||
ret.Schema, err = o.toSchema(restParam.DataType, nil)
|
||||
return ret, err
|
||||
case restful.PathParameterKind:
|
||||
ret.In = "path"
|
||||
if !restParam.Required {
|
||||
return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
|
||||
}
|
||||
case restful.QueryParameterKind:
|
||||
ret.In = "query"
|
||||
case restful.HeaderParameterKind:
|
||||
ret.In = "header"
|
||||
case restful.FormParameterKind:
|
||||
ret.In = "form"
|
||||
default:
|
||||
return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind)
|
||||
}
|
||||
openAPIType, openAPIFormat := openapi.GetOpenAPITypeFormat(restParam.DataType)
|
||||
if openAPIType == "" {
|
||||
return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType)
|
||||
}
|
||||
ret.Type = openAPIType
|
||||
ret.Format = openAPIFormat
|
||||
ret.UniqueItems = !restParam.AllowMultiple
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
|
||||
ret = make([]spec.Parameter, len(restParam))
|
||||
for i, v := range restParam {
|
||||
ret[i], err = o.buildParameter(v.Data())
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
408
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/openapi_test.go
generated
vendored
Normal file
408
vendor/k8s.io/kubernetes/pkg/genericapiserver/openapi/openapi_test.go
generated
vendored
Normal file
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/openapi"
|
||||
)
|
||||
|
||||
// setUp is a convenience function for setting up for (most) tests.
|
||||
func setUp(t *testing.T, fullMethods bool) (openAPI, *restful.Container, *assert.Assertions) {
|
||||
assert := assert.New(t)
|
||||
config, container := getConfig(fullMethods)
|
||||
return openAPI{
|
||||
config: config,
|
||||
swagger: &spec.Swagger{
|
||||
SwaggerProps: spec.SwaggerProps{
|
||||
Swagger: OpenAPIVersion,
|
||||
Definitions: spec.Definitions{},
|
||||
Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
|
||||
Info: config.Info,
|
||||
},
|
||||
},
|
||||
}, container, assert
|
||||
}
|
||||
|
||||
func noOp(request *restful.Request, response *restful.Response) {}
|
||||
|
||||
// Test input
|
||||
type TestInput struct {
|
||||
// Name of the input
|
||||
Name string `json:"name,omitempty"`
|
||||
// ID of the input
|
||||
ID int `json:"id,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// Test output
|
||||
type TestOutput struct {
|
||||
// Name of the output
|
||||
Name string `json:"name,omitempty"`
|
||||
// Number of outputs
|
||||
Count int `json:"count,omitempty"`
|
||||
}
|
||||
|
||||
func (_ TestInput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
||||
schema := spec.Schema{}
|
||||
schema.Description = "Test input"
|
||||
schema.Properties = map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the input",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"id": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ID of the input",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"tags": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return &openapi.OpenAPIDefinition{
|
||||
Schema: schema,
|
||||
Dependencies: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (_ TestOutput) OpenAPIDefinition() *openapi.OpenAPIDefinition {
|
||||
schema := spec.Schema{}
|
||||
schema.Description = "Test output"
|
||||
schema.Properties = map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the output",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"count": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Number of outputs",
|
||||
Type: []string{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
}
|
||||
return &openapi.OpenAPIDefinition{
|
||||
Schema: schema,
|
||||
Dependencies: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
var _ openapi.OpenAPIDefinitionGetter = TestInput{}
|
||||
var _ openapi.OpenAPIDefinitionGetter = TestOutput{}
|
||||
|
||||
func getTestRoute(ws *restful.WebService, method string, additionalParams bool, opPrefix string) *restful.RouteBuilder {
|
||||
ret := ws.Method(method).
|
||||
Path("/test/{path:*}").
|
||||
Doc(fmt.Sprintf("%s test input", method)).
|
||||
Operation(fmt.Sprintf("%s%sTestInput", method, opPrefix)).
|
||||
Produces(restful.MIME_JSON).
|
||||
Consumes(restful.MIME_JSON).
|
||||
Param(ws.PathParameter("path", "path to the resource").DataType("string")).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Reads(TestInput{}).
|
||||
Returns(200, "OK", TestOutput{}).
|
||||
Writes(TestOutput{}).
|
||||
To(noOp)
|
||||
if additionalParams {
|
||||
ret.Param(ws.HeaderParameter("hparam", "a test head parameter").DataType("integer"))
|
||||
ret.Param(ws.FormParameter("fparam", "a test form parameter").DataType("number"))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getConfig(fullMethods bool) (*openapi.Config, *restful.Container) {
|
||||
mux := http.NewServeMux()
|
||||
container := restful.NewContainer()
|
||||
container.ServeMux = mux
|
||||
ws := new(restful.WebService)
|
||||
ws.Path("/foo")
|
||||
ws.Route(getTestRoute(ws, "get", true, "foo"))
|
||||
if fullMethods {
|
||||
ws.Route(getTestRoute(ws, "post", false, "foo")).
|
||||
Route(getTestRoute(ws, "put", false, "foo")).
|
||||
Route(getTestRoute(ws, "head", false, "foo")).
|
||||
Route(getTestRoute(ws, "patch", false, "foo")).
|
||||
Route(getTestRoute(ws, "options", false, "foo")).
|
||||
Route(getTestRoute(ws, "delete", false, "foo"))
|
||||
|
||||
}
|
||||
ws.Path("/bar")
|
||||
ws.Route(getTestRoute(ws, "get", true, "bar"))
|
||||
if fullMethods {
|
||||
ws.Route(getTestRoute(ws, "post", false, "bar")).
|
||||
Route(getTestRoute(ws, "put", false, "bar")).
|
||||
Route(getTestRoute(ws, "head", false, "bar")).
|
||||
Route(getTestRoute(ws, "patch", false, "bar")).
|
||||
Route(getTestRoute(ws, "options", false, "bar")).
|
||||
Route(getTestRoute(ws, "delete", false, "bar"))
|
||||
|
||||
}
|
||||
container.Add(ws)
|
||||
return &openapi.Config{
|
||||
ProtocolList: []string{"https"},
|
||||
Info: &spec.Info{
|
||||
InfoProps: spec.InfoProps{
|
||||
Title: "TestAPI",
|
||||
Description: "Test API",
|
||||
},
|
||||
},
|
||||
Definitions: &openapi.OpenAPIDefinitions{
|
||||
"openapi.TestInput": *TestInput{}.OpenAPIDefinition(),
|
||||
"openapi.TestOutput": *TestOutput{}.OpenAPIDefinition(),
|
||||
},
|
||||
}, container
|
||||
}
|
||||
|
||||
func getTestOperation(method string, opPrefix string) *spec.Operation {
|
||||
return &spec.Operation{
|
||||
OperationProps: spec.OperationProps{
|
||||
Description: fmt.Sprintf("%s test input", method),
|
||||
Consumes: []string{"application/json"},
|
||||
Produces: []string{"application/json"},
|
||||
Schemes: []string{"https"},
|
||||
Parameters: []spec.Parameter{},
|
||||
Responses: getTestResponses(),
|
||||
ID: fmt.Sprintf("%s%sTestInput", method, opPrefix),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestPathItem(allMethods bool, opPrefix string) spec.PathItem {
|
||||
ret := spec.PathItem{
|
||||
PathItemProps: spec.PathItemProps{
|
||||
Get: getTestOperation("get", opPrefix),
|
||||
Parameters: getTestCommonParameters(),
|
||||
},
|
||||
}
|
||||
ret.Get.Parameters = getAdditionalTestParameters()
|
||||
if allMethods {
|
||||
ret.PathItemProps.Put = getTestOperation("put", opPrefix)
|
||||
ret.PathItemProps.Post = getTestOperation("post", opPrefix)
|
||||
ret.PathItemProps.Head = getTestOperation("head", opPrefix)
|
||||
ret.PathItemProps.Patch = getTestOperation("patch", opPrefix)
|
||||
ret.PathItemProps.Delete = getTestOperation("delete", opPrefix)
|
||||
ret.PathItemProps.Options = getTestOperation("options", opPrefix)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getRefSchema(ref string) *spec.Schema {
|
||||
return &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: spec.MustCreateRef(ref),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestResponses() *spec.Responses {
|
||||
ret := spec.Responses{
|
||||
ResponsesProps: spec.ResponsesProps{
|
||||
StatusCodeResponses: map[int]spec.Response{},
|
||||
},
|
||||
}
|
||||
ret.StatusCodeResponses[200] = spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: "OK",
|
||||
Schema: getRefSchema("#/definitions/openapi.TestOutput"),
|
||||
},
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
func getTestCommonParameters() []spec.Parameter {
|
||||
ret := make([]spec.Parameter, 3)
|
||||
ret[0] = spec.Parameter{
|
||||
ParamProps: spec.ParamProps{
|
||||
Name: "body",
|
||||
In: "body",
|
||||
Required: true,
|
||||
Schema: getRefSchema("#/definitions/openapi.TestInput"),
|
||||
},
|
||||
}
|
||||
ret[1] = spec.Parameter{
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "string",
|
||||
},
|
||||
ParamProps: spec.ParamProps{
|
||||
Description: "path to the resource",
|
||||
Name: "path",
|
||||
In: "path",
|
||||
Required: true,
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
ret[2] = spec.Parameter{
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "string",
|
||||
},
|
||||
ParamProps: spec.ParamProps{
|
||||
Description: "If 'true', then the output is pretty printed.",
|
||||
Name: "pretty",
|
||||
In: "query",
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getAdditionalTestParameters() []spec.Parameter {
|
||||
ret := make([]spec.Parameter, 2)
|
||||
ret[0] = spec.Parameter{
|
||||
ParamProps: spec.ParamProps{
|
||||
Name: "fparam",
|
||||
Description: "a test form parameter",
|
||||
In: "form",
|
||||
},
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "number",
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
ret[1] = spec.Parameter{
|
||||
SimpleSchema: spec.SimpleSchema{
|
||||
Type: "integer",
|
||||
},
|
||||
ParamProps: spec.ParamProps{
|
||||
Description: "a test head parameter",
|
||||
Name: "hparam",
|
||||
In: "header",
|
||||
},
|
||||
CommonValidations: spec.CommonValidations{
|
||||
UniqueItems: true,
|
||||
},
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getTestInputDefinition() spec.Schema {
|
||||
return spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Test input",
|
||||
Properties: map[string]spec.Schema{
|
||||
"id": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ID of the input",
|
||||
Type: spec.StringOrArray{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the input",
|
||||
Type: spec.StringOrArray{"string"},
|
||||
},
|
||||
},
|
||||
"tags": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: spec.StringOrArray{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: spec.StringOrArray{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestOutputDefinition() spec.Schema {
|
||||
return spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Test output",
|
||||
Properties: map[string]spec.Schema{
|
||||
"count": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Number of outputs",
|
||||
Type: spec.StringOrArray{"integer"},
|
||||
Format: "int32",
|
||||
},
|
||||
},
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name of the output",
|
||||
Type: spec.StringOrArray{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSwaggerSpec(t *testing.T) {
|
||||
o, container, assert := setUp(t, true)
|
||||
expected := &spec.Swagger{
|
||||
SwaggerProps: spec.SwaggerProps{
|
||||
Info: &spec.Info{
|
||||
InfoProps: spec.InfoProps{
|
||||
Title: "TestAPI",
|
||||
Description: "Test API",
|
||||
},
|
||||
},
|
||||
Swagger: "2.0",
|
||||
Paths: &spec.Paths{
|
||||
Paths: map[string]spec.PathItem{
|
||||
"/foo/test/{path}": getTestPathItem(true, "foo"),
|
||||
"/bar/test/{path}": getTestPathItem(true, "bar"),
|
||||
},
|
||||
},
|
||||
Definitions: spec.Definitions{
|
||||
"openapi.TestInput": getTestInputDefinition(),
|
||||
"openapi.TestOutput": getTestOutputDefinition(),
|
||||
},
|
||||
},
|
||||
}
|
||||
err := o.init(container.RegisteredWebServices())
|
||||
if assert.NoError(err) {
|
||||
assert.Equal(expected, o.swagger)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue