Add glide.yaml and vendor deps
This commit is contained in:
parent
db918f12ad
commit
5b3d5e81bd
18880 changed files with 5166045 additions and 1 deletions
116
vendor/k8s.io/kubernetes/pkg/apiserver/BUILD
generated
vendored
Normal file
116
vendor/k8s.io/kubernetes/pkg/apiserver/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"api_installer.go",
|
||||
"apiserver.go",
|
||||
"doc.go",
|
||||
"errors.go",
|
||||
"negotiate.go",
|
||||
"proxy.go",
|
||||
"resthandler.go",
|
||||
"serviceerror.go",
|
||||
"validator.go",
|
||||
"watch.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/api/meta:go_default_library",
|
||||
"//pkg/api/rest:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/apiserver/metrics:go_default_library",
|
||||
"//pkg/apiserver/request:go_default_library",
|
||||
"//pkg/conversion:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/httplog:go_default_library",
|
||||
"//pkg/probe:go_default_library",
|
||||
"//pkg/probe/http:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/runtime/schema:go_default_library",
|
||||
"//pkg/runtime/serializer/streaming:go_default_library",
|
||||
"//pkg/storage:go_default_library",
|
||||
"//pkg/util:go_default_library",
|
||||
"//pkg/util/errors:go_default_library",
|
||||
"//pkg/util/flushwriter:go_default_library",
|
||||
"//pkg/util/httpstream:go_default_library",
|
||||
"//pkg/util/net:go_default_library",
|
||||
"//pkg/util/proxy:go_default_library",
|
||||
"//pkg/util/runtime:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//pkg/util/strategicpatch:go_default_library",
|
||||
"//pkg/util/strings:go_default_library",
|
||||
"//pkg/util/wsstream:go_default_library",
|
||||
"//pkg/version:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//pkg/watch/versioned:go_default_library",
|
||||
"//vendor:bitbucket.org/ww/goautoneg",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/evanphx/json-patch",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:golang.org/x/net/websocket",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"api_installer_test.go",
|
||||
"apiserver_test.go",
|
||||
"errors_test.go",
|
||||
"negotiate_test.go",
|
||||
"proxy_test.go",
|
||||
"resthandler_test.go",
|
||||
"validator_test.go",
|
||||
"watch_test.go",
|
||||
],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/api/meta:go_default_library",
|
||||
"//pkg/api/rest:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/testing:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/apiserver/filters:go_default_library",
|
||||
"//pkg/apiserver/request:go_default_library",
|
||||
"//pkg/apiserver/testing:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/labels:go_default_library",
|
||||
"//pkg/probe:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/runtime/schema:go_default_library",
|
||||
"//pkg/runtime/serializer/streaming:go_default_library",
|
||||
"//pkg/types:go_default_library",
|
||||
"//pkg/util/diff:go_default_library",
|
||||
"//pkg/util/net:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//pkg/util/strategicpatch:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//pkg/watch/versioned:go_default_library",
|
||||
"//plugin/pkg/admission/admit:go_default_library",
|
||||
"//plugin/pkg/admission/deny:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/evanphx/json-patch",
|
||||
"//vendor:golang.org/x/net/websocket",
|
||||
],
|
||||
)
|
||||
4
vendor/k8s.io/kubernetes/pkg/apiserver/OWNERS
generated
vendored
Normal file
4
vendor/k8s.io/kubernetes/pkg/apiserver/OWNERS
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
assignees:
|
||||
- lavalamp
|
||||
- nikhiljindal
|
||||
- smarterclayton
|
||||
1040
vendor/k8s.io/kubernetes/pkg/apiserver/api_installer.go
generated
vendored
Normal file
1040
vendor/k8s.io/kubernetes/pkg/apiserver/api_installer.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
58
vendor/k8s.io/kubernetes/pkg/apiserver/api_installer_test.go
generated
vendored
Normal file
58
vendor/k8s.io/kubernetes/pkg/apiserver/api_installer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/meta"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
|
||||
"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)
|
||||
}
|
||||
}
|
||||
529
vendor/k8s.io/kubernetes/pkg/apiserver/apiserver.go
generated
vendored
Normal file
529
vendor/k8s.io/kubernetes/pkg/apiserver/apiserver.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
3422
vendor/k8s.io/kubernetes/pkg/apiserver/apiserver_test.go
generated
vendored
Normal file
3422
vendor/k8s.io/kubernetes/pkg/apiserver/apiserver_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
41
vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/BUILD
generated
vendored
Normal file
41
vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/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_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"builtin.go",
|
||||
"delegating.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/authenticator/bearertoken:go_default_library",
|
||||
"//pkg/auth/group:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//pkg/util/cert:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/password/keystone:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/password/passwordfile:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/anonymous:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/basicauth:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/headerrequest:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/union:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/x509:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/token/anytoken:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/token/oidc:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/token/tokenfile:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/token/webhook:go_default_library",
|
||||
"//vendor:github.com/go-openapi/spec",
|
||||
],
|
||||
)
|
||||
312
vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/builtin.go
generated
vendored
Normal file
312
vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/builtin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
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
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
|
||||
"k8s.io/kubernetes/pkg/auth/group"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/anytoken"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type AuthenticatorConfig struct {
|
||||
Anonymous bool
|
||||
AnyToken bool
|
||||
BasicAuthFile string
|
||||
ClientCAFile string
|
||||
TokenAuthFile string
|
||||
OIDCIssuerURL string
|
||||
OIDCClientID string
|
||||
OIDCCAFile string
|
||||
OIDCUsernameClaim string
|
||||
OIDCGroupsClaim string
|
||||
ServiceAccountKeyFiles []string
|
||||
ServiceAccountLookup bool
|
||||
KeystoneURL string
|
||||
KeystoneCAFile string
|
||||
WebhookTokenAuthnConfigFile string
|
||||
WebhookTokenAuthnCacheTTL time.Duration
|
||||
|
||||
RequestHeaderConfig *RequestHeaderConfig
|
||||
|
||||
// TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig
|
||||
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
|
||||
}
|
||||
|
||||
// New returns an authenticator.Request or an error that supports the standard
|
||||
// Kubernetes authentication mechanisms.
|
||||
func New(config AuthenticatorConfig) (authenticator.Request, *spec.SecurityDefinitions, error) {
|
||||
var authenticators []authenticator.Request
|
||||
securityDefinitions := spec.SecurityDefinitions{}
|
||||
hasBasicAuth := false
|
||||
hasTokenAuth := false
|
||||
|
||||
// front-proxy, BasicAuth methods, local first, then remote
|
||||
// Add the front proxy authenticator if requested
|
||||
if config.RequestHeaderConfig != nil {
|
||||
requestHeaderAuthenticator, err := headerrequest.NewSecure(
|
||||
config.RequestHeaderConfig.ClientCA,
|
||||
config.RequestHeaderConfig.AllowedClientNames,
|
||||
config.RequestHeaderConfig.UsernameHeaders,
|
||||
config.RequestHeaderConfig.GroupHeaders,
|
||||
config.RequestHeaderConfig.ExtraHeaderPrefixes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, requestHeaderAuthenticator)
|
||||
}
|
||||
|
||||
if len(config.BasicAuthFile) > 0 {
|
||||
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, basicAuth)
|
||||
hasBasicAuth = true
|
||||
}
|
||||
if len(config.KeystoneURL) > 0 {
|
||||
keystoneAuth, err := newAuthenticatorFromKeystoneURL(config.KeystoneURL, config.KeystoneCAFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, keystoneAuth)
|
||||
hasBasicAuth = true
|
||||
}
|
||||
|
||||
// X509 methods
|
||||
if len(config.ClientCAFile) > 0 {
|
||||
certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, certAuth)
|
||||
}
|
||||
|
||||
// Bearer token methods, local first, then remote
|
||||
if len(config.TokenAuthFile) > 0 {
|
||||
tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, tokenAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
if len(config.ServiceAccountKeyFiles) > 0 {
|
||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, serviceAccountAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
|
||||
//
|
||||
// Because both plugins verify JWTs whichever comes first in the union experiences
|
||||
// cache misses for all requests using the other. While the service account plugin
|
||||
// simply returns an error, the OpenID Connect plugin may query the provider to
|
||||
// update the keys, causing performance hits.
|
||||
if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
|
||||
oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(config.OIDCIssuerURL, config.OIDCClientID, config.OIDCCAFile, config.OIDCUsernameClaim, config.OIDCGroupsClaim)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, oidcAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
if len(config.WebhookTokenAuthnConfigFile) > 0 {
|
||||
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authenticators = append(authenticators, webhookTokenAuth)
|
||||
hasTokenAuth = true
|
||||
}
|
||||
|
||||
// always add anytoken last, so that every other token authenticator gets to try first
|
||||
if config.AnyToken {
|
||||
authenticators = append(authenticators, bearertoken.New(anytoken.AnyTokenAuthenticator{}))
|
||||
hasTokenAuth = true
|
||||
}
|
||||
|
||||
if hasBasicAuth {
|
||||
securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
|
||||
SecuritySchemeProps: spec.SecuritySchemeProps{
|
||||
Type: "basic",
|
||||
Description: "HTTP Basic authentication",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if hasTokenAuth {
|
||||
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
|
||||
SecuritySchemeProps: spec.SecuritySchemeProps{
|
||||
Type: "apiKey",
|
||||
Name: "authorization",
|
||||
In: "header",
|
||||
Description: "Bearer Token authentication",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(authenticators) == 0 {
|
||||
if config.Anonymous {
|
||||
return anonymous.NewAuthenticator(), &securityDefinitions, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch len(authenticators) {
|
||||
case 0:
|
||||
return nil, &securityDefinitions, nil
|
||||
}
|
||||
|
||||
authenticator := union.New(authenticators...)
|
||||
|
||||
authenticator = group.NewGroupAdder(authenticator, []string{user.AllAuthenticated})
|
||||
|
||||
if config.Anonymous {
|
||||
// If the authenticator chain returns an error, return an error (don't consider a bad bearer token anonymous).
|
||||
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
|
||||
}
|
||||
|
||||
return authenticator, &securityDefinitions, nil
|
||||
}
|
||||
|
||||
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
|
||||
func IsValidServiceAccountKeyFile(file string) bool {
|
||||
_, err := serviceaccount.ReadPublicKeys(file)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// newAuthenticatorFromBasicAuthFile returns an authenticator.Request or an error
|
||||
func newAuthenticatorFromBasicAuthFile(basicAuthFile string) (authenticator.Request, error) {
|
||||
basicAuthenticator, err := passwordfile.NewCSV(basicAuthFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return basicauth.New(basicAuthenticator), nil
|
||||
}
|
||||
|
||||
// newAuthenticatorFromTokenFile returns an authenticator.Request or an error
|
||||
func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request, error) {
|
||||
tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bearertoken.New(tokenAuthenticator), nil
|
||||
}
|
||||
|
||||
// newAuthenticatorFromToken returns an authenticator.Request or an error
|
||||
func NewAuthenticatorFromTokens(tokens map[string]*user.DefaultInfo) authenticator.Request {
|
||||
return bearertoken.New(tokenfile.New(tokens))
|
||||
}
|
||||
|
||||
// newAuthenticatorFromOIDCIssuerURL returns an authenticator.Request or an error.
|
||||
func newAuthenticatorFromOIDCIssuerURL(issuerURL, clientID, caFile, usernameClaim, groupsClaim string) (authenticator.Request, error) {
|
||||
tokenAuthenticator, err := oidc.New(oidc.OIDCOptions{
|
||||
IssuerURL: issuerURL,
|
||||
ClientID: clientID,
|
||||
CAFile: caFile,
|
||||
UsernameClaim: usernameClaim,
|
||||
GroupsClaim: groupsClaim,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bearertoken.New(tokenAuthenticator), nil
|
||||
}
|
||||
|
||||
// newServiceAccountAuthenticator returns an authenticator.Request or an error
|
||||
func newServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) {
|
||||
allPublicKeys := []interface{}{}
|
||||
for _, keyfile := range keyfiles {
|
||||
publicKeys, err := serviceaccount.ReadPublicKeys(keyfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allPublicKeys = append(allPublicKeys, publicKeys...)
|
||||
}
|
||||
|
||||
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(allPublicKeys, lookup, serviceAccountGetter)
|
||||
return bearertoken.New(tokenAuthenticator), nil
|
||||
}
|
||||
|
||||
// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
|
||||
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
|
||||
roots, err := certutil.NewPool(clientCAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := x509.DefaultVerifyOptions()
|
||||
opts.Roots = roots
|
||||
|
||||
return x509.New(opts, x509.CommonNameUserConversion), nil
|
||||
}
|
||||
|
||||
// newAuthenticatorFromKeystoneURL returns an authenticator.Request or an error
|
||||
func newAuthenticatorFromKeystoneURL(keystoneURL string, keystoneCAFile string) (authenticator.Request, error) {
|
||||
keystoneAuthenticator, err := keystone.NewKeystoneAuthenticator(keystoneURL, keystoneCAFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return basicauth.New(keystoneAuthenticator), nil
|
||||
}
|
||||
|
||||
func newWebhookTokenAuthenticator(webhookConfigFile string, ttl time.Duration) (authenticator.Request, error) {
|
||||
webhookTokenAuthenticator, err := webhook.New(webhookConfigFile, ttl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bearertoken.New(webhookTokenAuthenticator), nil
|
||||
}
|
||||
97
vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/delegating.go
generated
vendored
Normal file
97
vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/delegating.go
generated
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
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/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken"
|
||||
"k8s.io/kubernetes/pkg/auth/group"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
authenticationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/util/cert"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous"
|
||||
unionauth "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
|
||||
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
|
||||
}
|
||||
|
||||
func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
|
||||
authenticators := []authenticator.Request{}
|
||||
securityDefinitions := spec.SecurityDefinitions{}
|
||||
|
||||
// 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
|
||||
|
||||
}
|
||||
18
vendor/k8s.io/kubernetes/pkg/apiserver/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/apiserver/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 apiserver contains the code that provides a rest.ful api service.
|
||||
package apiserver // import "k8s.io/kubernetes/pkg/apiserver"
|
||||
141
vendor/k8s.io/kubernetes/pkg/apiserver/errors.go
generated
vendored
Executable file
141
vendor/k8s.io/kubernetes/pkg/apiserver/errors.go
generated
vendored
Executable file
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/storage"
|
||||
"k8s.io/kubernetes/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// statusError is an object that can be converted into an metav1.Status
|
||||
type statusError interface {
|
||||
Status() metav1.Status
|
||||
}
|
||||
|
||||
// errToAPIStatus converts an error to an metav1.Status object.
|
||||
func errToAPIStatus(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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// internalError renders a simple internal error
|
||||
func internalError(w http.ResponseWriter, req *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Internal Server Error: %#v", req.RequestURI)
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
|
||||
// errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under
|
||||
// any known API prefixes
|
||||
type errAPIPrefixNotFound struct {
|
||||
SpecifiedPrefix string
|
||||
}
|
||||
|
||||
func (e *errAPIPrefixNotFound) Error() string {
|
||||
return fmt.Sprintf("no valid API prefix found matching %v", e.SpecifiedPrefix)
|
||||
}
|
||||
|
||||
func IsAPIPrefixNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := err.(*errAPIPrefixNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
// errNotAcceptable indicates Accept negotiation has failed
|
||||
// TODO: move to api/errors if other code needs to return this
|
||||
type errNotAcceptable struct {
|
||||
accepted []string
|
||||
}
|
||||
|
||||
func (e errNotAcceptable) Error() string {
|
||||
return fmt.Sprintf("only the following media types are accepted: %v", strings.Join(e.accepted, ", "))
|
||||
}
|
||||
|
||||
func (e errNotAcceptable) Status() metav1.Status {
|
||||
return metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotAcceptable,
|
||||
Reason: metav1.StatusReason("NotAcceptable"),
|
||||
Message: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// errUnsupportedMediaType indicates Content-Type is not recognized
|
||||
// TODO: move to api/errors if other code needs to return this
|
||||
type errUnsupportedMediaType struct {
|
||||
accepted []string
|
||||
}
|
||||
|
||||
func (e errUnsupportedMediaType) Error() string {
|
||||
return fmt.Sprintf("the body of the request was in an unknown format - accepted media types include: %v", strings.Join(e.accepted, ", "))
|
||||
}
|
||||
|
||||
func (e errUnsupportedMediaType) Status() metav1.Status {
|
||||
return metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusUnsupportedMediaType,
|
||||
Reason: metav1.StatusReason("UnsupportedMediaType"),
|
||||
Message: e.Error(),
|
||||
}
|
||||
}
|
||||
73
vendor/k8s.io/kubernetes/pkg/apiserver/errors_test.go
generated
vendored
Normal file
73
vendor/k8s.io/kubernetes/pkg/apiserver/errors_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 apiserver
|
||||
|
||||
import (
|
||||
stderrs "errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func TestErrorsToAPIStatus(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 := errToAPIStatus(k)
|
||||
if !reflect.DeepEqual(actual, &v) {
|
||||
t.Errorf("%s: Expected %#v, Got %#v", k, v, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
58
vendor/k8s.io/kubernetes/pkg/apiserver/filters/BUILD
generated
vendored
Normal file
58
vendor/k8s.io/kubernetes/pkg/apiserver/filters/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_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"audit.go",
|
||||
"authorization.go",
|
||||
"doc.go",
|
||||
"errors.go",
|
||||
"impersonation.go",
|
||||
"requestinfo.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/authentication:go_default_library",
|
||||
"//pkg/apiserver/request:go_default_library",
|
||||
"//pkg/auth/authorizer:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/httplog:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//pkg/util/net:go_default_library",
|
||||
"//pkg/util/runtime:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/pborman/uuid",
|
||||
],
|
||||
)
|
||||
|
||||
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/api:go_default_library",
|
||||
"//pkg/apis/authentication:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/apiserver/request:go_default_library",
|
||||
"//pkg/auth/authorizer:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
159
vendor/k8s.io/kubernetes/pkg/apiserver/filters/audit.go
generated
vendored
Normal file
159
vendor/k8s.io/kubernetes/pkg/apiserver/filters/audit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
)
|
||||
|
||||
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 api.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 {
|
||||
internalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
attribs, err := GetAuthorizerAttributes(ctx)
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
groups := "<none>"
|
||||
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, attribs.GetUser().GetName(), 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
|
||||
}
|
||||
125
vendor/k8s.io/kubernetes/pkg/apiserver/filters/audit_test.go
generated
vendored
Normal file
125
vendor/k8s.io/kubernetes/pkg/apiserver/filters/audit_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
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/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
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) (api.Context, bool) {
|
||||
ctx := api.NewContext()
|
||||
if m.user != nil {
|
||||
ctx = api.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 api.Context) error {
|
||||
return nil
|
||||
}
|
||||
89
vendor/k8s.io/kubernetes/pkg/apiserver/filters/authorization.go
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/pkg/apiserver/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/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
)
|
||||
|
||||
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
|
||||
func WithAuthorization(handler http.Handler, requestContextMapper api.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 {
|
||||
internalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
|
||||
attrs, err := GetAuthorizerAttributes(ctx)
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
}
|
||||
authorized, reason, err := a.Authorize(attrs)
|
||||
if authorized {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Forbidden: %#v, Reason: %s", req.RequestURI, reason)
|
||||
forbidden(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
func GetAuthorizerAttributes(ctx api.Context) (authorizer.Attributes, error) {
|
||||
attribs := authorizer.AttributesRecord{}
|
||||
|
||||
user, ok := api.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
|
||||
}
|
||||
128
vendor/k8s.io/kubernetes/pkg/apiserver/filters/authorization_test.go
generated
vendored
Normal file
128
vendor/k8s.io/kubernetes/pkg/apiserver/filters/authorization_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
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/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
)
|
||||
|
||||
func TestGetAuthorizerAttributes(t *testing.T) {
|
||||
mapper := api.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/extensions/v1beta1/namespaces/myns/jobs",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "list",
|
||||
Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
|
||||
ResourceRequest: true,
|
||||
APIGroup: extensions.GroupName,
|
||||
APIVersion: "v1beta1",
|
||||
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 {
|
||||
internalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
attribs, err = GetAuthorizerAttributes(ctx)
|
||||
})
|
||||
handler = WithRequestInfo(handler, newTestRequestInfoResolver(), mapper)
|
||||
handler = api.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/apiserver/filters/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/apiserver/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/apiserver/filters"
|
||||
43
vendor/k8s.io/kubernetes/pkg/apiserver/filters/errors.go
generated
vendored
Executable file
43
vendor/k8s.io/kubernetes/pkg/apiserver/filters/errors.go
generated
vendored
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
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 filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// badGatewayError renders a simple bad gateway error.
|
||||
func badGatewayError(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
fmt.Fprintf(w, "Bad Gateway: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// forbidden renders a simple forbidden error
|
||||
func forbidden(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, "Forbidden: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// internalError renders a simple internal error
|
||||
func internalError(w http.ResponseWriter, req *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Internal Server Error: %#v", req.RequestURI)
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
191
vendor/k8s.io/kubernetes/pkg/apiserver/filters/impersonation.go
generated
vendored
Normal file
191
vendor/k8s.io/kubernetes/pkg/apiserver/filters/impersonation.go
generated
vendored
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/httplog"
|
||||
"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 api.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)
|
||||
forbidden(w, req)
|
||||
return
|
||||
}
|
||||
if len(impersonationRequests) == 0 {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, exists := requestContextMapper.Get(req)
|
||||
if !exists {
|
||||
forbidden(w, req)
|
||||
return
|
||||
}
|
||||
requestor, exists := api.UserFrom(ctx)
|
||||
if !exists {
|
||||
forbidden(w, req)
|
||||
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\n", impersonationRequest)
|
||||
forbidden(w, req)
|
||||
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)
|
||||
forbidden(w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newUser := &user.DefaultInfo{
|
||||
Name: username,
|
||||
Groups: groups,
|
||||
Extra: userExtra,
|
||||
}
|
||||
requestContextMapper.Update(req, api.WithUser(ctx, newUser))
|
||||
|
||||
oldUser, _ := api.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/apiserver/filters/impersonation_test.go
generated
vendored
Normal file
347
vendor/k8s.io/kubernetes/pkg/apiserver/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/kubernetes/pkg/api"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
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.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "impersonating-extra-without-user",
|
||||
user: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "tester",
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
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 := api.NewRequestContextMapper()
|
||||
var ctx api.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 := api.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 := api.UserFrom(currentCtx)
|
||||
if !exists {
|
||||
actualUser = nil
|
||||
return
|
||||
} else {
|
||||
actualUser = user
|
||||
}
|
||||
|
||||
delegate.ServeHTTP(w, req)
|
||||
})
|
||||
}(WithImpersonation(doNothingHandler, requestContextMapper, impersonateAuthorizer{}))
|
||||
handler = api.WithRequestContext(handler, requestContextMapper)
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
for _, tc := range testCases {
|
||||
func() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
ctx = api.WithUser(api.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/apiserver/filters/requestinfo.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/pkg/apiserver/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/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
)
|
||||
|
||||
// WithRequestInfo attaches a RequestInfo to the context.
|
||||
func WithRequestInfo(handler http.Handler, resolver *request.RequestInfoFactory, requestContextMapper api.RequestContextMapper) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
internalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := resolver.NewRequestInfo(req)
|
||||
if err != nil {
|
||||
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/apiserver/filters/requestinfo_test.go
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/pkg/apiserver/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/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
func newTestRequestInfoResolver() *request.RequestInfoFactory {
|
||||
return &request.RequestInfoFactory{
|
||||
APIPrefixes: sets.NewString("api", "apis"),
|
||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||
}
|
||||
}
|
||||
22
vendor/k8s.io/kubernetes/pkg/apiserver/metrics/BUILD
generated
vendored
Normal file
22
vendor/k8s.io/kubernetes/pkg/apiserver/metrics/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["metrics.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/util/net:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/prometheus/client_golang/prometheus",
|
||||
],
|
||||
)
|
||||
245
vendor/k8s.io/kubernetes/pkg/apiserver/metrics/metrics.go
generated
vendored
Normal file
245
vendor/k8s.io/kubernetes/pkg/apiserver/metrics/metrics.go
generated
vendored
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
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 metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO(a-robinson): Add unit tests for the handling of these metrics once
|
||||
// the upstream library supports it.
|
||||
requestCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "apiserver_request_count",
|
||||
Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.",
|
||||
},
|
||||
[]string{"verb", "resource", "client", "contentType", "code"},
|
||||
)
|
||||
requestLatencies = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "apiserver_request_latencies",
|
||||
Help: "Response latency distribution in microseconds for each verb, resource and client.",
|
||||
// Use buckets ranging from 125 ms to 8 seconds.
|
||||
Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
|
||||
},
|
||||
[]string{"verb", "resource"},
|
||||
)
|
||||
requestLatenciesSummary = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "apiserver_request_latencies_summary",
|
||||
Help: "Response latency summary in microseconds for each verb and resource.",
|
||||
// Make the sliding window of 1h.
|
||||
MaxAge: time.Hour,
|
||||
},
|
||||
[]string{"verb", "resource"},
|
||||
)
|
||||
)
|
||||
|
||||
// Register all metrics.
|
||||
func Register() {
|
||||
prometheus.MustRegister(requestCounter)
|
||||
prometheus.MustRegister(requestLatencies)
|
||||
prometheus.MustRegister(requestLatenciesSummary)
|
||||
}
|
||||
|
||||
func Monitor(verb, resource *string, client, contentType string, httpCode int, reqStart time.Time) {
|
||||
elapsed := float64((time.Since(reqStart)) / time.Microsecond)
|
||||
requestCounter.WithLabelValues(*verb, *resource, client, contentType, codeToString(httpCode)).Inc()
|
||||
requestLatencies.WithLabelValues(*verb, *resource).Observe(elapsed)
|
||||
requestLatenciesSummary.WithLabelValues(*verb, *resource).Observe(elapsed)
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
requestCounter.Reset()
|
||||
requestLatencies.Reset()
|
||||
requestLatenciesSummary.Reset()
|
||||
}
|
||||
|
||||
// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
|
||||
// the go-restful RouteFunction instead of a HandlerFunc
|
||||
func InstrumentRouteFunc(verb, resource string, routeFunc restful.RouteFunction) restful.RouteFunction {
|
||||
return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
|
||||
now := time.Now()
|
||||
|
||||
delegate := &responseWriterDelegator{ResponseWriter: response.ResponseWriter}
|
||||
|
||||
_, cn := response.ResponseWriter.(http.CloseNotifier)
|
||||
_, fl := response.ResponseWriter.(http.Flusher)
|
||||
_, hj := response.ResponseWriter.(http.Hijacker)
|
||||
var rw http.ResponseWriter
|
||||
if cn && fl && hj {
|
||||
rw = &fancyResponseWriterDelegator{delegate}
|
||||
} else {
|
||||
rw = delegate
|
||||
}
|
||||
response.ResponseWriter = rw
|
||||
|
||||
routeFunc(request, response)
|
||||
Monitor(&verb, &resource, utilnet.GetHTTPClient(request.Request), rw.Header().Get("Content-Type"), delegate.status, now)
|
||||
})
|
||||
}
|
||||
|
||||
type responseWriterDelegator struct {
|
||||
http.ResponseWriter
|
||||
|
||||
status int
|
||||
written int64
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (r *responseWriterDelegator) WriteHeader(code int) {
|
||||
r.status = code
|
||||
r.wroteHeader = true
|
||||
r.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
||||
if !r.wroteHeader {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err := r.ResponseWriter.Write(b)
|
||||
r.written += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
type fancyResponseWriterDelegator struct {
|
||||
*responseWriterDelegator
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// Small optimization over Itoa
|
||||
func codeToString(s int) string {
|
||||
switch s {
|
||||
case 100:
|
||||
return "100"
|
||||
case 101:
|
||||
return "101"
|
||||
|
||||
case 200:
|
||||
return "200"
|
||||
case 201:
|
||||
return "201"
|
||||
case 202:
|
||||
return "202"
|
||||
case 203:
|
||||
return "203"
|
||||
case 204:
|
||||
return "204"
|
||||
case 205:
|
||||
return "205"
|
||||
case 206:
|
||||
return "206"
|
||||
|
||||
case 300:
|
||||
return "300"
|
||||
case 301:
|
||||
return "301"
|
||||
case 302:
|
||||
return "302"
|
||||
case 304:
|
||||
return "304"
|
||||
case 305:
|
||||
return "305"
|
||||
case 307:
|
||||
return "307"
|
||||
|
||||
case 400:
|
||||
return "400"
|
||||
case 401:
|
||||
return "401"
|
||||
case 402:
|
||||
return "402"
|
||||
case 403:
|
||||
return "403"
|
||||
case 404:
|
||||
return "404"
|
||||
case 405:
|
||||
return "405"
|
||||
case 406:
|
||||
return "406"
|
||||
case 407:
|
||||
return "407"
|
||||
case 408:
|
||||
return "408"
|
||||
case 409:
|
||||
return "409"
|
||||
case 410:
|
||||
return "410"
|
||||
case 411:
|
||||
return "411"
|
||||
case 412:
|
||||
return "412"
|
||||
case 413:
|
||||
return "413"
|
||||
case 414:
|
||||
return "414"
|
||||
case 415:
|
||||
return "415"
|
||||
case 416:
|
||||
return "416"
|
||||
case 417:
|
||||
return "417"
|
||||
case 418:
|
||||
return "418"
|
||||
|
||||
case 500:
|
||||
return "500"
|
||||
case 501:
|
||||
return "501"
|
||||
case 502:
|
||||
return "502"
|
||||
case 503:
|
||||
return "503"
|
||||
case 504:
|
||||
return "504"
|
||||
case 505:
|
||||
return "505"
|
||||
|
||||
case 428:
|
||||
return "428"
|
||||
case 429:
|
||||
return "429"
|
||||
case 431:
|
||||
return "431"
|
||||
case 511:
|
||||
return "511"
|
||||
|
||||
default:
|
||||
return strconv.Itoa(s)
|
||||
}
|
||||
}
|
||||
305
vendor/k8s.io/kubernetes/pkg/apiserver/negotiate.go
generated
vendored
Normal file
305
vendor/k8s.io/kubernetes/pkg/apiserver/negotiate.go
generated
vendored
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/ww/goautoneg"
|
||||
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// mediaTypesForSerializer returns a list of media and stream media types for the server.
|
||||
func mediaTypesForSerializer(ns runtime.NegotiatedSerializer) (mediaTypes, streamMediaTypes []string) {
|
||||
for _, info := range ns.SupportedMediaTypes() {
|
||||
mediaTypes = append(mediaTypes, info.MediaType)
|
||||
if info.StreamSerializer != nil {
|
||||
// stream=watch is the existing mime-type parameter for watch
|
||||
streamMediaTypes = append(streamMediaTypes, info.MediaType+";stream=watch")
|
||||
}
|
||||
}
|
||||
return mediaTypes, streamMediaTypes
|
||||
}
|
||||
|
||||
func negotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||
mediaType, ok := negotiateMediaTypeOptions(req.Header.Get("Accept"), acceptedMediaTypesForEndpoint(ns), defaultEndpointRestrictions)
|
||||
if !ok {
|
||||
supported, _ := mediaTypesForSerializer(ns)
|
||||
return runtime.SerializerInfo{}, errNotAcceptable{supported}
|
||||
}
|
||||
// TODO: move into resthandler
|
||||
info := mediaType.accepted.Serializer
|
||||
if (mediaType.pretty || isPrettyPrint(req)) && info.PrettySerializer != nil {
|
||||
info.Serializer = info.PrettySerializer
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func negotiateOutputStreamSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||
mediaType, ok := negotiateMediaTypeOptions(req.Header.Get("Accept"), acceptedMediaTypesForEndpoint(ns), defaultEndpointRestrictions)
|
||||
if !ok || mediaType.accepted.Serializer.StreamSerializer == nil {
|
||||
_, supported := mediaTypesForSerializer(ns)
|
||||
return runtime.SerializerInfo{}, errNotAcceptable{supported}
|
||||
}
|
||||
return mediaType.accepted.Serializer, nil
|
||||
}
|
||||
|
||||
func negotiateInputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
|
||||
mediaTypes := ns.SupportedMediaTypes()
|
||||
mediaType := req.Header.Get("Content-Type")
|
||||
if len(mediaType) == 0 {
|
||||
mediaType = mediaTypes[0].MediaType
|
||||
}
|
||||
mediaType, _, err := mime.ParseMediaType(mediaType)
|
||||
if err != nil {
|
||||
_, supported := mediaTypesForSerializer(ns)
|
||||
return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
|
||||
}
|
||||
|
||||
for _, info := range mediaTypes {
|
||||
if info.MediaType != mediaType {
|
||||
continue
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
_, supported := mediaTypesForSerializer(ns)
|
||||
return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
|
||||
}
|
||||
|
||||
// isPrettyPrint returns true if the "pretty" query parameter is true or if the User-Agent
|
||||
// matches known "human" clients.
|
||||
func isPrettyPrint(req *http.Request) bool {
|
||||
// DEPRECATED: should be part of the content type
|
||||
if req.URL != nil {
|
||||
pp := req.URL.Query().Get("pretty")
|
||||
if len(pp) > 0 {
|
||||
pretty, _ := strconv.ParseBool(pp)
|
||||
return pretty
|
||||
}
|
||||
}
|
||||
userAgent := req.UserAgent()
|
||||
// This covers basic all browers and cli http tools
|
||||
if strings.HasPrefix(userAgent, "curl") || strings.HasPrefix(userAgent, "Wget") || strings.HasPrefix(userAgent, "Mozilla/5.0") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// negotiate the most appropriate content type given the accept header and a list of
|
||||
// alternatives.
|
||||
func negotiate(header string, alternatives []string) (goautoneg.Accept, bool) {
|
||||
alternates := make([][]string, 0, len(alternatives))
|
||||
for _, alternate := range alternatives {
|
||||
alternates = append(alternates, strings.SplitN(alternate, "/", 2))
|
||||
}
|
||||
for _, clause := range goautoneg.ParseAccept(header) {
|
||||
for _, alternate := range alternates {
|
||||
if clause.Type == alternate[0] && clause.SubType == alternate[1] {
|
||||
return clause, true
|
||||
}
|
||||
if clause.Type == alternate[0] && clause.SubType == "*" {
|
||||
clause.SubType = alternate[1]
|
||||
return clause, true
|
||||
}
|
||||
if clause.Type == "*" && clause.SubType == "*" {
|
||||
clause.Type = alternate[0]
|
||||
clause.SubType = alternate[1]
|
||||
return clause, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return goautoneg.Accept{}, false
|
||||
}
|
||||
|
||||
// endpointRestrictions is an interface that allows content-type negotiation
|
||||
// to verify server support for specific options
|
||||
type endpointRestrictions interface {
|
||||
// AllowsConversion should return true if the specified group version kind
|
||||
// is an allowed target object.
|
||||
AllowsConversion(schema.GroupVersionKind) bool
|
||||
// AllowsServerVersion should return true if the specified version is valid
|
||||
// for the server group.
|
||||
AllowsServerVersion(version string) bool
|
||||
// AllowsStreamSchema should return true if the specified stream schema is
|
||||
// valid for the server group.
|
||||
AllowsStreamSchema(schema string) bool
|
||||
}
|
||||
|
||||
var defaultEndpointRestrictions = emptyEndpointRestrictions{}
|
||||
|
||||
type emptyEndpointRestrictions struct{}
|
||||
|
||||
func (emptyEndpointRestrictions) AllowsConversion(schema.GroupVersionKind) bool { return false }
|
||||
func (emptyEndpointRestrictions) AllowsServerVersion(string) bool { return false }
|
||||
func (emptyEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" }
|
||||
|
||||
// acceptedMediaType contains information about a valid media type that the
|
||||
// server can serialize.
|
||||
type acceptedMediaType struct {
|
||||
// Type is the first part of the media type ("application")
|
||||
Type string
|
||||
// SubType is the second part of the media type ("json")
|
||||
SubType string
|
||||
// Serializer is the serialization info this object accepts
|
||||
Serializer runtime.SerializerInfo
|
||||
}
|
||||
|
||||
// mediaTypeOptions describes information for a given media type that may alter
|
||||
// the server response
|
||||
type mediaTypeOptions struct {
|
||||
// pretty is true if the requested representation should be formatted for human
|
||||
// viewing
|
||||
pretty bool
|
||||
|
||||
// stream, if set, indicates that a streaming protocol variant of this encoding
|
||||
// is desired. The only currently supported value is watch which returns versioned
|
||||
// events. In the future, this may refer to other stream protocols.
|
||||
stream string
|
||||
|
||||
// convert is a request to alter the type of object returned by the server from the
|
||||
// normal response
|
||||
convert *schema.GroupVersionKind
|
||||
// useServerVersion is an optional version for the server group
|
||||
useServerVersion string
|
||||
|
||||
// export is true if the representation requested should exclude fields the server
|
||||
// has set
|
||||
export bool
|
||||
|
||||
// unrecognized is a list of all unrecognized keys
|
||||
unrecognized []string
|
||||
|
||||
// the accepted media type from the client
|
||||
accepted *acceptedMediaType
|
||||
}
|
||||
|
||||
// acceptMediaTypeOptions returns an options object that matches the provided media type params. If
|
||||
// it returns false, the provided options are not allowed and the media type must be skipped. These
|
||||
// parameters are unversioned and may not be changed.
|
||||
func acceptMediaTypeOptions(params map[string]string, accepts *acceptedMediaType, endpoint endpointRestrictions) (mediaTypeOptions, bool) {
|
||||
var options mediaTypeOptions
|
||||
|
||||
// extract all known parameters
|
||||
for k, v := range params {
|
||||
switch k {
|
||||
|
||||
// controls transformation of the object when returned
|
||||
case "as":
|
||||
if options.convert == nil {
|
||||
options.convert = &schema.GroupVersionKind{}
|
||||
}
|
||||
options.convert.Kind = v
|
||||
case "g":
|
||||
if options.convert == nil {
|
||||
options.convert = &schema.GroupVersionKind{}
|
||||
}
|
||||
options.convert.Group = v
|
||||
case "v":
|
||||
if options.convert == nil {
|
||||
options.convert = &schema.GroupVersionKind{}
|
||||
}
|
||||
options.convert.Version = v
|
||||
|
||||
// controls the streaming schema
|
||||
case "stream":
|
||||
if len(v) > 0 && (accepts.Serializer.StreamSerializer == nil || !endpoint.AllowsStreamSchema(v)) {
|
||||
return mediaTypeOptions{}, false
|
||||
}
|
||||
options.stream = v
|
||||
|
||||
// controls the version of the server API group used
|
||||
// for generic output
|
||||
case "sv":
|
||||
if len(v) > 0 && !endpoint.AllowsServerVersion(v) {
|
||||
return mediaTypeOptions{}, false
|
||||
}
|
||||
options.useServerVersion = v
|
||||
|
||||
// if specified, the server should transform the returned
|
||||
// output and remove fields that are always server specified,
|
||||
// or which fit the default behavior.
|
||||
case "export":
|
||||
options.export = v == "1"
|
||||
|
||||
// if specified, the pretty serializer will be used
|
||||
case "pretty":
|
||||
options.pretty = v == "1"
|
||||
|
||||
default:
|
||||
options.unrecognized = append(options.unrecognized, k)
|
||||
}
|
||||
}
|
||||
|
||||
if options.convert != nil && !endpoint.AllowsConversion(*options.convert) {
|
||||
return mediaTypeOptions{}, false
|
||||
}
|
||||
|
||||
options.accepted = accepts
|
||||
|
||||
return options, true
|
||||
}
|
||||
|
||||
// negotiateMediaTypeOptions returns the most appropriate content type given the accept header and
|
||||
// a list of alternatives along with the accepted media type parameters.
|
||||
func negotiateMediaTypeOptions(header string, accepted []acceptedMediaType, endpoint endpointRestrictions) (mediaTypeOptions, bool) {
|
||||
if len(header) == 0 && len(accepted) > 0 {
|
||||
return mediaTypeOptions{
|
||||
accepted: &accepted[0],
|
||||
}, true
|
||||
}
|
||||
|
||||
clauses := goautoneg.ParseAccept(header)
|
||||
for _, clause := range clauses {
|
||||
for i := range accepted {
|
||||
accepts := &accepted[i]
|
||||
switch {
|
||||
case clause.Type == accepts.Type && clause.SubType == accepts.SubType,
|
||||
clause.Type == accepts.Type && clause.SubType == "*",
|
||||
clause.Type == "*" && clause.SubType == "*":
|
||||
// TODO: should we prefer the first type with no unrecognized options? Do we need to ignore unrecognized
|
||||
// parameters.
|
||||
return acceptMediaTypeOptions(clause.Params, accepts, endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
return mediaTypeOptions{}, false
|
||||
}
|
||||
|
||||
// acceptedMediaTypesForEndpoint returns an array of structs that are used to efficiently check which
|
||||
// allowed media types the server exposes.
|
||||
func acceptedMediaTypesForEndpoint(ns runtime.NegotiatedSerializer) []acceptedMediaType {
|
||||
var acceptedMediaTypes []acceptedMediaType
|
||||
for _, info := range ns.SupportedMediaTypes() {
|
||||
segments := strings.SplitN(info.MediaType, "/", 2)
|
||||
if len(segments) == 1 {
|
||||
segments = append(segments, "*")
|
||||
}
|
||||
t := acceptedMediaType{
|
||||
Type: segments[0],
|
||||
SubType: segments[1],
|
||||
Serializer: info,
|
||||
}
|
||||
acceptedMediaTypes = append(acceptedMediaTypes, t)
|
||||
}
|
||||
return acceptedMediaTypes
|
||||
}
|
||||
240
vendor/k8s.io/kubernetes/pkg/apiserver/negotiate_test.go
generated
vendored
Normal file
240
vendor/k8s.io/kubernetes/pkg/apiserver/negotiate_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
type fakeNegotiater struct {
|
||||
serializer, streamSerializer runtime.Serializer
|
||||
framer runtime.Framer
|
||||
types, streamTypes []string
|
||||
}
|
||||
|
||||
func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo {
|
||||
var out []runtime.SerializerInfo
|
||||
for _, s := range n.types {
|
||||
info := runtime.SerializerInfo{Serializer: n.serializer, MediaType: s, EncodesAsText: true}
|
||||
for _, t := range n.streamTypes {
|
||||
if t == s {
|
||||
info.StreamSerializer = &runtime.StreamSerializerInfo{
|
||||
EncodesAsText: true,
|
||||
Framer: n.framer,
|
||||
Serializer: n.streamSerializer,
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, info)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
||||
return n.serializer
|
||||
}
|
||||
|
||||
func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
||||
return n.serializer
|
||||
}
|
||||
|
||||
var fakeCodec = runtime.NewCodec(runtime.NoopEncoder{}, runtime.NoopDecoder{})
|
||||
|
||||
func TestNegotiate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
accept string
|
||||
req *http.Request
|
||||
ns *fakeNegotiater
|
||||
serializer runtime.Serializer
|
||||
contentType string
|
||||
params map[string]string
|
||||
errFn func(error) bool
|
||||
}{
|
||||
// pick a default
|
||||
{
|
||||
req: &http.Request{},
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
},
|
||||
{
|
||||
accept: "",
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
},
|
||||
{
|
||||
accept: "*/*",
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
},
|
||||
{
|
||||
accept: "application/*",
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
},
|
||||
{
|
||||
accept: "application/json",
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
},
|
||||
{
|
||||
accept: "application/json",
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json", "application/protobuf"}},
|
||||
serializer: fakeCodec,
|
||||
},
|
||||
{
|
||||
accept: "application/protobuf",
|
||||
contentType: "application/protobuf",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json", "application/protobuf"}},
|
||||
serializer: fakeCodec,
|
||||
},
|
||||
{
|
||||
accept: "application/json; pretty=1",
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
params: map[string]string{"pretty": "1"},
|
||||
},
|
||||
{
|
||||
accept: "unrecognized/stuff,application/json; pretty=1",
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
params: map[string]string{"pretty": "1"},
|
||||
},
|
||||
|
||||
// query param triggers pretty
|
||||
{
|
||||
req: &http.Request{
|
||||
Header: http.Header{"Accept": []string{"application/json"}},
|
||||
URL: &url.URL{RawQuery: "pretty=1"},
|
||||
},
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
params: map[string]string{"pretty": "1"},
|
||||
},
|
||||
|
||||
// certain user agents trigger pretty
|
||||
{
|
||||
req: &http.Request{
|
||||
Header: http.Header{
|
||||
"Accept": []string{"application/json"},
|
||||
"User-Agent": []string{"curl"},
|
||||
},
|
||||
},
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
params: map[string]string{"pretty": "1"},
|
||||
},
|
||||
{
|
||||
req: &http.Request{
|
||||
Header: http.Header{
|
||||
"Accept": []string{"application/json"},
|
||||
"User-Agent": []string{"Wget"},
|
||||
},
|
||||
},
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
params: map[string]string{"pretty": "1"},
|
||||
},
|
||||
{
|
||||
req: &http.Request{
|
||||
Header: http.Header{
|
||||
"Accept": []string{"application/json"},
|
||||
"User-Agent": []string{"Mozilla/5.0"},
|
||||
},
|
||||
},
|
||||
contentType: "application/json",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}},
|
||||
serializer: fakeCodec,
|
||||
params: map[string]string{"pretty": "1"},
|
||||
},
|
||||
|
||||
// "application" is not a valid media type, so the server will reject the response during
|
||||
// negotiation (the server, in error, has specified an invalid media type)
|
||||
{
|
||||
accept: "application",
|
||||
ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application"}},
|
||||
errFn: func(err error) bool {
|
||||
return err.Error() == "only the following media types are accepted: application"
|
||||
},
|
||||
},
|
||||
{
|
||||
ns: &fakeNegotiater{},
|
||||
errFn: func(err error) bool {
|
||||
return err.Error() == "only the following media types are accepted: "
|
||||
},
|
||||
},
|
||||
{
|
||||
accept: "*/*",
|
||||
ns: &fakeNegotiater{},
|
||||
errFn: func(err error) bool {
|
||||
return err.Error() == "only the following media types are accepted: "
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range testCases {
|
||||
req := test.req
|
||||
if req == nil {
|
||||
req = &http.Request{Header: http.Header{}}
|
||||
req.Header.Set("Accept", test.accept)
|
||||
}
|
||||
s, err := negotiateOutputSerializer(req, test.ns)
|
||||
switch {
|
||||
case err == nil && test.errFn != nil:
|
||||
t.Errorf("%d: failed: expected error", i)
|
||||
continue
|
||||
case err != nil && test.errFn == nil:
|
||||
t.Errorf("%d: failed: %v", i, err)
|
||||
continue
|
||||
case err != nil:
|
||||
if !test.errFn(err) {
|
||||
t.Errorf("%d: failed: %v", i, err)
|
||||
}
|
||||
status, ok := err.(statusError)
|
||||
if !ok {
|
||||
t.Errorf("%d: failed, error should be statusError: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if status.Status().Status != metav1.StatusFailure || status.Status().Code != http.StatusNotAcceptable {
|
||||
t.Errorf("%d: failed: %v", i, err)
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
if test.contentType != s.MediaType {
|
||||
t.Errorf("%d: unexpected %s %s", i, test.contentType, s.MediaType)
|
||||
}
|
||||
if s.Serializer != test.serializer {
|
||||
t.Errorf("%d: unexpected %s %s", i, test.serializer, s.Serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
21
vendor/k8s.io/kubernetes/pkg/apiserver/openapi/BUILD
generated
vendored
Normal file
21
vendor/k8s.io/kubernetes/pkg/apiserver/openapi/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["openapi.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/util:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
],
|
||||
)
|
||||
87
vendor/k8s.io/kubernetes/pkg/apiserver/openapi/openapi.go
generated
vendored
Normal file
87
vendor/k8s.io/kubernetes/pkg/apiserver/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
|
||||
}
|
||||
}
|
||||
277
vendor/k8s.io/kubernetes/pkg/apiserver/proxy.go
generated
vendored
Normal file
277
vendor/k8s.io/kubernetes/pkg/apiserver/proxy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
||||
"k8s.io/kubernetes/pkg/httplog"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/util/httpstream"
|
||||
"k8s.io/kubernetes/pkg/util/net"
|
||||
proxyutil "k8s.io/kubernetes/pkg/util/proxy"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
)
|
||||
|
||||
// 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 api.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 metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart)
|
||||
|
||||
ctx, ok := r.mapper.Get(req)
|
||||
if !ok {
|
||||
internalError(w, req, errors.New("Error getting request context"))
|
||||
httpCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
internalError(w, req, errors.New("Error getting RequestInfo from context"))
|
||||
httpCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
if !requestInfo.IsResourceRequest {
|
||||
notFound(w, req)
|
||||
httpCode = http.StatusNotFound
|
||||
return
|
||||
}
|
||||
verb = requestInfo.Verb
|
||||
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
|
||||
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
if len(parts) < 2 {
|
||||
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)
|
||||
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 = 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 = errorNegotiated(err, r.serializer, gv, w, req)
|
||||
return
|
||||
}
|
||||
if location == nil {
|
||||
httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id)
|
||||
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 = 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 reriting 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 {
|
||||
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 {
|
||||
errorNegotiated(err, r.serializer, gv, w, req)
|
||||
return true
|
||||
}
|
||||
defer requestHijackedConn.Close()
|
||||
|
||||
if err = newReq.Write(backendConn); err != nil {
|
||||
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
|
||||
}
|
||||
569
vendor/k8s.io/kubernetes/pkg/apiserver/proxy_test.go
generated
vendored
Normal file
569
vendor/k8s.io/kubernetes/pkg/apiserver/proxy_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
36
vendor/k8s.io/kubernetes/pkg/apiserver/request/BUILD
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/pkg/apiserver/request/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"requestinfo.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["requestinfo_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
20
vendor/k8s.io/kubernetes/pkg/apiserver/request/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/kubernetes/pkg/apiserver/request/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 request contains everything around extracting info from
|
||||
// a http request object.
|
||||
// TODO: this package is temporary. Handlers must move into pkg/apiserver/handlers to avoid dependency cycle
|
||||
package request // import "k8s.io/kubernetes/pkg/apiserver/request"
|
||||
242
vendor/k8s.io/kubernetes/pkg/apiserver/request/requestinfo.go
generated
vendored
Normal file
242
vendor/k8s.io/kubernetes/pkg/apiserver/request/requestinfo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
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 request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
// RequestInfo holds information parsed from the http.Request
|
||||
type RequestInfo struct {
|
||||
// IsResourceRequest indicates whether or not the request is for an API resource or subresource
|
||||
IsResourceRequest bool
|
||||
// Path is the URL path of the request
|
||||
Path string
|
||||
// Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch.
|
||||
// for non-resource requests, this is the lowercase http verb
|
||||
Verb string
|
||||
|
||||
APIPrefix string
|
||||
APIGroup string
|
||||
APIVersion string
|
||||
Namespace string
|
||||
// Resource is the name of the resource being requested. This is not the kind. For example: pods
|
||||
Resource string
|
||||
// Subresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
|
||||
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
|
||||
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
|
||||
Subresource string
|
||||
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
|
||||
Name string
|
||||
// Parts are the path parts for the request, always starting with /{resource}/{name}
|
||||
Parts []string
|
||||
}
|
||||
|
||||
// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
|
||||
// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
|
||||
// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
|
||||
// master's Mux.
|
||||
var specialVerbs = sets.NewString("proxy", "redirect", "watch")
|
||||
|
||||
// specialVerbsNoSubresources contains root verbs which do not allow subresources
|
||||
var specialVerbsNoSubresources = sets.NewString("proxy", "redirect")
|
||||
|
||||
// namespaceSubresources contains subresources of namespace
|
||||
// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
|
||||
var namespaceSubresources = sets.NewString("status", "finalize")
|
||||
|
||||
// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift
|
||||
var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
|
||||
|
||||
type RequestInfoFactory struct {
|
||||
APIPrefixes sets.String // without leading and trailing slashes
|
||||
GrouplessAPIPrefixes sets.String // without leading and trailing slashes
|
||||
}
|
||||
|
||||
// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
|
||||
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
|
||||
// It handles both resource and non-resource requests and fills in all the pertinent information for each.
|
||||
// Valid Inputs:
|
||||
// Resource paths
|
||||
// /apis/{api-group}/{version}/namespaces
|
||||
// /api/{version}/namespaces
|
||||
// /api/{version}/namespaces/{namespace}
|
||||
// /api/{version}/namespaces/{namespace}/{resource}
|
||||
// /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /api/{version}/{resource}
|
||||
// /api/{version}/{resource}/{resourceName}
|
||||
//
|
||||
// Special verbs without subresources:
|
||||
// /api/{version}/proxy/{resource}/{resourceName}
|
||||
// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /api/{version}/redirect/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /api/{version}/redirect/{resource}/{resourceName}
|
||||
//
|
||||
// Special verbs with subresources:
|
||||
// /api/{version}/watch/{resource}
|
||||
// /api/{version}/watch/namespaces/{namespace}/{resource}
|
||||
//
|
||||
// NonResource paths
|
||||
// /apis/{api-group}/{version}
|
||||
// /apis/{api-group}
|
||||
// /apis
|
||||
// /api/{version}
|
||||
// /api
|
||||
// /healthz
|
||||
// /
|
||||
func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
|
||||
// start with a non-resource request until proven otherwise
|
||||
requestInfo := RequestInfo{
|
||||
IsResourceRequest: false,
|
||||
Path: req.URL.Path,
|
||||
Verb: strings.ToLower(req.Method),
|
||||
}
|
||||
|
||||
currentParts := splitPath(req.URL.Path)
|
||||
if len(currentParts) < 3 {
|
||||
// return a non-resource request
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
if !r.APIPrefixes.Has(currentParts[0]) {
|
||||
// return a non-resource request
|
||||
return &requestInfo, nil
|
||||
}
|
||||
requestInfo.APIPrefix = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
|
||||
if len(currentParts) < 3 {
|
||||
// return a non-resource request
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
requestInfo.APIGroup = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
}
|
||||
|
||||
requestInfo.IsResourceRequest = true
|
||||
requestInfo.APIVersion = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
// handle input of form /{specialVerb}/*
|
||||
if specialVerbs.Has(currentParts[0]) {
|
||||
if len(currentParts) < 2 {
|
||||
return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
|
||||
}
|
||||
|
||||
requestInfo.Verb = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
} else {
|
||||
switch req.Method {
|
||||
case "POST":
|
||||
requestInfo.Verb = "create"
|
||||
case "GET", "HEAD":
|
||||
requestInfo.Verb = "get"
|
||||
case "PUT":
|
||||
requestInfo.Verb = "update"
|
||||
case "PATCH":
|
||||
requestInfo.Verb = "patch"
|
||||
case "DELETE":
|
||||
requestInfo.Verb = "delete"
|
||||
default:
|
||||
requestInfo.Verb = ""
|
||||
}
|
||||
}
|
||||
|
||||
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||
if currentParts[0] == "namespaces" {
|
||||
if len(currentParts) > 1 {
|
||||
requestInfo.Namespace = currentParts[1]
|
||||
|
||||
// if there is another step after the namespace name and it is not a known namespace subresource
|
||||
// move currentParts to include it as a resource in its own right
|
||||
if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
|
||||
currentParts = currentParts[2:]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
requestInfo.Namespace = api.NamespaceNone
|
||||
}
|
||||
|
||||
// parsing successful, so we now know the proper value for .Parts
|
||||
requestInfo.Parts = currentParts
|
||||
|
||||
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
|
||||
switch {
|
||||
case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
|
||||
requestInfo.Subresource = requestInfo.Parts[2]
|
||||
fallthrough
|
||||
case len(requestInfo.Parts) >= 2:
|
||||
requestInfo.Name = requestInfo.Parts[1]
|
||||
fallthrough
|
||||
case len(requestInfo.Parts) >= 1:
|
||||
requestInfo.Resource = requestInfo.Parts[0]
|
||||
}
|
||||
|
||||
// if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch
|
||||
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
|
||||
// Assumes v1.ListOptions
|
||||
// Duplicates logic of Convert_Slice_string_To_bool
|
||||
switch strings.ToLower(req.URL.Query().Get("watch")) {
|
||||
case "false", "0", "":
|
||||
requestInfo.Verb = "list"
|
||||
default:
|
||||
requestInfo.Verb = "watch"
|
||||
}
|
||||
}
|
||||
// if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
|
||||
if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
|
||||
requestInfo.Verb = "deletecollection"
|
||||
}
|
||||
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
type requestInfoKeyType int
|
||||
|
||||
// requestInfoKey is the RequestInfo key for the context. It's of private type here. Because
|
||||
// keys are interfaces and interfaces are equal when the type and the value is equal, this
|
||||
// does not conflict with the keys defined in pkg/api.
|
||||
const requestInfoKey requestInfoKeyType = iota
|
||||
|
||||
// WithRequestInfo returns a copy of parent in which the request info value is set
|
||||
func WithRequestInfo(parent api.Context, info *RequestInfo) api.Context {
|
||||
return api.WithValue(parent, requestInfoKey, info)
|
||||
}
|
||||
|
||||
// RequestInfoFrom returns the value of the RequestInfo key on the ctx
|
||||
func RequestInfoFrom(ctx api.Context) (*RequestInfo, bool) {
|
||||
info, ok := ctx.Value(requestInfoKey).(*RequestInfo)
|
||||
return info, ok
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
||||
func splitPath(path string) []string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, "/")
|
||||
}
|
||||
205
vendor/k8s.io/kubernetes/pkg/apiserver/request/requestinfo_test.go
generated
vendored
Normal file
205
vendor/k8s.io/kubernetes/pkg/apiserver/request/requestinfo_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
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 request
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
type fakeRL bool
|
||||
|
||||
func (fakeRL) Stop() {}
|
||||
func (f fakeRL) TryAccept() bool { return bool(f) }
|
||||
func (f fakeRL) Accept() {}
|
||||
|
||||
func getPath(resource, namespace, name string) string {
|
||||
return testapi.Default.ResourcePath(resource, namespace, name)
|
||||
}
|
||||
|
||||
func pathWithPrefix(prefix, resource, namespace, name string) string {
|
||||
return testapi.Default.ResourcePathWithPrefix(prefix, resource, namespace, name)
|
||||
}
|
||||
|
||||
func TestGetAPIRequestInfo(t *testing.T) {
|
||||
successCases := []struct {
|
||||
method string
|
||||
url string
|
||||
expectedVerb string
|
||||
expectedAPIPrefix string
|
||||
expectedAPIGroup string
|
||||
expectedAPIVersion string
|
||||
expectedNamespace string
|
||||
expectedResource string
|
||||
expectedSubresource string
|
||||
expectedName string
|
||||
expectedParts []string
|
||||
}{
|
||||
|
||||
// resource paths
|
||||
{"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}},
|
||||
{"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "other", []string{"namespaces", "other"}},
|
||||
|
||||
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||
{"HEAD", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
|
||||
{"HEAD", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
|
||||
// special verbs
|
||||
{"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/proxy/namespaces/other/pods/foo/subpath/not/a/subresource", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo", "subpath", "not", "a", "subresource"}},
|
||||
{"GET", "/api/v1/redirect/namespaces/other/pods/foo", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/redirect/namespaces/other/pods/foo/subpath/not/a/subresource", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo", "subpath", "not", "a", "subresource"}},
|
||||
{"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/pods?watch=true", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/pods?watch=false", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods?watch=1", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods?watch=0", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
|
||||
// subresource identification
|
||||
{"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "foo", []string{"pods", "foo", "status"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods/foo/proxy/subpath", "get", "api", "", "v1", "other", "pods", "proxy", "foo", []string{"pods", "foo", "proxy", "subpath"}},
|
||||
{"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "namespaces", "finalize", "other", []string{"namespaces", "other", "finalize"}},
|
||||
{"PUT", "/api/v1/namespaces/other/status", "update", "api", "", "v1", "other", "namespaces", "status", "other", []string{"namespaces", "other", "status"}},
|
||||
|
||||
// verb identification
|
||||
{"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||
{"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||
{"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
|
||||
// deletecollection verb identification
|
||||
{"DELETE", "/api/v1/nodes", "deletecollection", "api", "", "v1", "", "nodes", "", "", []string{"nodes"}},
|
||||
{"DELETE", "/api/v1/namespaces", "deletecollection", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}},
|
||||
{"DELETE", "/api/v1/namespaces/other/pods", "deletecollection", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
{"DELETE", "/apis/extensions/v1/namespaces/other/pods", "deletecollection", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
|
||||
// api group identification
|
||||
{"POST", "/apis/extensions/v1/namespaces/other/pods", "create", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||
|
||||
// api version identification
|
||||
{"POST", "/apis/extensions/v1beta3/namespaces/other/pods", "create", "api", "extensions", "v1beta3", "other", "pods", "", "", []string{"pods"}},
|
||||
}
|
||||
|
||||
resolver := newTestRequestInfoResolver()
|
||||
|
||||
for _, successCase := range successCases {
|
||||
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
|
||||
|
||||
apiRequestInfo, err := resolver.NewRequestInfo(req)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for url: %s %v", successCase.url, err)
|
||||
}
|
||||
if !apiRequestInfo.IsResourceRequest {
|
||||
t.Errorf("Expected resource request")
|
||||
}
|
||||
if successCase.expectedVerb != apiRequestInfo.Verb {
|
||||
t.Errorf("Unexpected verb for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedVerb, apiRequestInfo.Verb)
|
||||
}
|
||||
if successCase.expectedAPIVersion != apiRequestInfo.APIVersion {
|
||||
t.Errorf("Unexpected apiVersion for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedAPIVersion, apiRequestInfo.APIVersion)
|
||||
}
|
||||
if successCase.expectedNamespace != apiRequestInfo.Namespace {
|
||||
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace)
|
||||
}
|
||||
if successCase.expectedResource != apiRequestInfo.Resource {
|
||||
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource)
|
||||
}
|
||||
if successCase.expectedSubresource != apiRequestInfo.Subresource {
|
||||
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedSubresource, apiRequestInfo.Subresource)
|
||||
}
|
||||
if successCase.expectedName != apiRequestInfo.Name {
|
||||
t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name)
|
||||
}
|
||||
if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
|
||||
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := map[string]string{
|
||||
"no resource path": "/",
|
||||
"just apiversion": "/api/version/",
|
||||
"just prefix, group, version": "/apis/group/version/",
|
||||
"apiversion with no resource": "/api/version/",
|
||||
"bad prefix": "/badprefix/version/resource",
|
||||
"missing api group": "/apis/version/resource",
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
req, err := http.NewRequest("GET", v, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
apiRequestInfo, err := resolver.NewRequestInfo(req)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Unexpected error %v", k, err)
|
||||
}
|
||||
if apiRequestInfo.IsResourceRequest {
|
||||
t.Errorf("%s: expected non-resource request", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNonAPIRequestInfo(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
url string
|
||||
expected bool
|
||||
}{
|
||||
"simple groupless": {"/api/version/resource", true},
|
||||
"simple group": {"/apis/group/version/resource/name/subresource", true},
|
||||
"more steps": {"/api/version/resource/name/subresource", true},
|
||||
"group list": {"/apis/extensions/v1beta1/job", true},
|
||||
"group get": {"/apis/extensions/v1beta1/job/foo", true},
|
||||
"group subresource": {"/apis/extensions/v1beta1/job/foo/scale", true},
|
||||
|
||||
"bad root": {"/not-api/version/resource", false},
|
||||
"group without enough steps": {"/apis/extensions/v1beta1", false},
|
||||
"group without enough steps 2": {"/apis/extensions/v1beta1/", false},
|
||||
"not enough steps": {"/api/version", false},
|
||||
"one step": {"/api", false},
|
||||
"zero step": {"/", false},
|
||||
"empty": {"", false},
|
||||
}
|
||||
|
||||
resolver := newTestRequestInfoResolver()
|
||||
|
||||
for testName, tc := range tests {
|
||||
req, _ := http.NewRequest("GET", tc.url, nil)
|
||||
|
||||
apiRequestInfo, err := resolver.NewRequestInfo(req)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Unexpected error %v", testName, err)
|
||||
}
|
||||
if e, a := tc.expected, apiRequestInfo.IsResourceRequest; e != a {
|
||||
t.Errorf("%s: expected %v, actual %v", testName, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTestRequestInfoResolver() *RequestInfoFactory {
|
||||
return &RequestInfoFactory{
|
||||
APIPrefixes: sets.NewString("api", "apis"),
|
||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||
}
|
||||
}
|
||||
1099
vendor/k8s.io/kubernetes/pkg/apiserver/resthandler.go
generated
vendored
Normal file
1099
vendor/k8s.io/kubernetes/pkg/apiserver/resthandler.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
474
vendor/k8s.io/kubernetes/pkg/apiserver/resthandler_test.go
generated
vendored
Normal file
474
vendor/k8s.io/kubernetes/pkg/apiserver/resthandler_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/evanphx/json-patch"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/diff"
|
||||
"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 api.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 api.Context, name string) (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 := api.NewDefaultContext()
|
||||
ctx = api.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
42
vendor/k8s.io/kubernetes/pkg/apiserver/serviceerror.go
generated
vendored
Normal file
42
vendor/k8s.io/kubernetes/pkg/apiserver/serviceerror.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 apiserver
|
||||
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func InstallServiceErrorHandler(s runtime.NegotiatedSerializer, container *restful.Container) {
|
||||
container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||
serviceErrorHandler(s, serviceErr, request, response)
|
||||
})
|
||||
}
|
||||
|
||||
func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||
errorNegotiated(
|
||||
apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false),
|
||||
s,
|
||||
schema.GroupVersion{},
|
||||
response.ResponseWriter,
|
||||
request.Request,
|
||||
)
|
||||
}
|
||||
27
vendor/k8s.io/kubernetes/pkg/apiserver/testing/BUILD
generated
vendored
Normal file
27
vendor/k8s.io/kubernetes/pkg/apiserver/testing/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"types.generated.go",
|
||||
"types.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/runtime/schema:go_default_library",
|
||||
"//pkg/types:go_default_library",
|
||||
"//vendor:github.com/ugorji/go/codec",
|
||||
],
|
||||
)
|
||||
1608
vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.generated.go
generated
vendored
Normal file
1608
vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.generated.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
70
vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/pkg/apiserver/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 (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type Simple struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
api.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"`
|
||||
api.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 }
|
||||
82
vendor/k8s.io/kubernetes/pkg/apiserver/validator.go
generated
vendored
Normal file
82
vendor/k8s.io/kubernetes/pkg/apiserver/validator.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/probe"
|
||||
httpprober "k8s.io/kubernetes/pkg/probe/http"
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
)
|
||||
|
||||
const (
|
||||
probeTimeOut = 20 * time.Second
|
||||
)
|
||||
|
||||
// TODO: this basic interface is duplicated in N places. consolidate?
|
||||
type httpGet interface {
|
||||
Get(url string) (*http.Response, error)
|
||||
}
|
||||
|
||||
type ValidatorFn func([]byte) error
|
||||
|
||||
type Server struct {
|
||||
Addr string
|
||||
Port int
|
||||
Path string
|
||||
EnableHTTPS bool
|
||||
Validate ValidatorFn
|
||||
}
|
||||
|
||||
type ServerStatus struct {
|
||||
// +optional
|
||||
Component string `json:"component,omitempty"`
|
||||
// +optional
|
||||
Health string `json:"health,omitempty"`
|
||||
// +optional
|
||||
HealthCode probe.Result `json:"healthCode,omitempty"`
|
||||
// +optional
|
||||
Msg string `json:"msg,omitempty"`
|
||||
// +optional
|
||||
Err string `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
func (server *Server) DoServerCheck(prober httpprober.HTTPProber) (probe.Result, string, error) {
|
||||
scheme := "http"
|
||||
if server.EnableHTTPS {
|
||||
scheme = "https"
|
||||
}
|
||||
url := utilnet.FormatURL(scheme, server.Addr, server.Port, server.Path)
|
||||
|
||||
result, data, err := prober.Probe(url, nil, probeTimeOut)
|
||||
|
||||
if err != nil {
|
||||
return probe.Unknown, "", err
|
||||
}
|
||||
if result == probe.Failure {
|
||||
return probe.Failure, string(data), err
|
||||
}
|
||||
if server.Validate != nil {
|
||||
if err := server.Validate([]byte(data)); err != nil {
|
||||
return probe.Failure, string(data), err
|
||||
}
|
||||
}
|
||||
return result, string(data), nil
|
||||
}
|
||||
92
vendor/k8s.io/kubernetes/pkg/apiserver/validator_test.go
generated
vendored
Normal file
92
vendor/k8s.io/kubernetes/pkg/apiserver/validator_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/probe"
|
||||
)
|
||||
|
||||
type fakeHttpProber struct {
|
||||
result probe.Result
|
||||
body string
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeHttpProber) Probe(*url.URL, http.Header, time.Duration) (probe.Result, string, error) {
|
||||
return f.result, f.body, f.err
|
||||
}
|
||||
|
||||
func alwaysError([]byte) error { return errors.New("test error") }
|
||||
|
||||
func matchError(data []byte) error {
|
||||
if string(data) != "bar" {
|
||||
return errors.New("match error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
probeResult probe.Result
|
||||
probeData string
|
||||
probeErr error
|
||||
|
||||
expectResult probe.Result
|
||||
expectData string
|
||||
expectErr bool
|
||||
|
||||
validator ValidatorFn
|
||||
}{
|
||||
{probe.Unknown, "", fmt.Errorf("probe error"), probe.Unknown, "", true, nil},
|
||||
{probe.Failure, "", nil, probe.Failure, "", false, nil},
|
||||
{probe.Success, "foo", nil, probe.Failure, "foo", true, matchError},
|
||||
{probe.Success, "foo", nil, probe.Success, "foo", false, nil},
|
||||
}
|
||||
|
||||
s := Server{Addr: "foo.com", Port: 8080, Path: "/healthz"}
|
||||
|
||||
for _, test := range tests {
|
||||
fakeProber := &fakeHttpProber{
|
||||
result: test.probeResult,
|
||||
body: test.probeData,
|
||||
err: test.probeErr,
|
||||
}
|
||||
|
||||
s.Validate = test.validator
|
||||
result, data, err := s.DoServerCheck(fakeProber)
|
||||
if test.expectErr && err == nil {
|
||||
t.Error("unexpected non-error")
|
||||
}
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if data != test.expectData {
|
||||
t.Errorf("expected %s, got %s", test.expectData, data)
|
||||
}
|
||||
if result != test.expectResult {
|
||||
t.Errorf("expected %s, got %s", test.expectResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
291
vendor/k8s.io/kubernetes/pkg/apiserver/watch.go
generated
vendored
Executable file
291
vendor/k8s.io/kubernetes/pkg/apiserver/watch.go
generated
vendored
Executable file
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/httplog"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime/serializer/streaming"
|
||||
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/wsstream"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/watch/versioned"
|
||||
|
||||
"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 := 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))
|
||||
}
|
||||
},
|
||||
|
||||
t: &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)
|
||||
|
||||
t 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.t.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 := &versioned.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 = versioned.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 := &versioned.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 = versioned.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
780
vendor/k8s.io/kubernetes/pkg/apiserver/watch_test.go
generated
vendored
Normal file
780
vendor/k8s.io/kubernetes/pkg/apiserver/watch_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue