git subrepo clone (merge) https://github.com/kubernetes-incubator/metrics-server.git metrics-server
subrepo:
subdir: "metrics-server"
merged: "92d8412"
upstream:
origin: "https://github.com/kubernetes-incubator/metrics-server.git"
branch: "master"
commit: "92d8412"
git-subrepo:
version: "0.4.0"
origin: "???"
commit: "???"
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/addresses.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/addresses.go
new file mode 100644
index 0000000..d175d15
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/addresses.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package discovery
+
+import (
+ "net"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type Addresses interface {
+ ServerAddressByClientCIDRs(net.IP) []metav1.ServerAddressByClientCIDR
+}
+
+// DefaultAddresses is a default implementation of Addresses that will work in most cases
+type DefaultAddresses struct {
+ // CIDRRules is a list of CIDRs and Addresses to use if a client is in the range
+ CIDRRules []CIDRRule
+
+ // DefaultAddress is the address (hostname or IP and port) that should be used in
+ // if no CIDR matches more specifically.
+ DefaultAddress string
+}
+
+// CIDRRule is a rule for adding an alternate path to the master based on matching CIDR
+type CIDRRule struct {
+ IPRange net.IPNet
+
+ // Address is the address (hostname or IP and port) that should be used in
+ // if this CIDR matches
+ Address string
+}
+
+func (d DefaultAddresses) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
+ addressCIDRMap := []metav1.ServerAddressByClientCIDR{
+ {
+ ClientCIDR: "0.0.0.0/0",
+ ServerAddress: d.DefaultAddress,
+ },
+ }
+
+ for _, rule := range d.CIDRRules {
+ addressCIDRMap = append(addressCIDRMap, rule.ServerAddressByClientCIDRs(clientIP)...)
+ }
+ return addressCIDRMap
+}
+
+func (d CIDRRule) ServerAddressByClientCIDRs(clientIP net.IP) []metav1.ServerAddressByClientCIDR {
+ addressCIDRMap := []metav1.ServerAddressByClientCIDR{}
+
+ if d.IPRange.Contains(clientIP) {
+ addressCIDRMap = append(addressCIDRMap, metav1.ServerAddressByClientCIDR{
+ ClientCIDR: d.IPRange.String(),
+ ServerAddress: d.Address,
+ })
+ }
+ return addressCIDRMap
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/group.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/group.go
new file mode 100644
index 0000000..02330e9
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/group.go
@@ -0,0 +1,73 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package discovery
+
+import (
+ "net/http"
+
+ "github.com/emicklei/go-restful"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+)
+
+// APIGroupHandler creates a webservice serving the supported versions, preferred version, and name
+// of a group. E.g., such a web service will be registered at /apis/extensions.
+type APIGroupHandler struct {
+ serializer runtime.NegotiatedSerializer
+ group metav1.APIGroup
+}
+
+func NewAPIGroupHandler(serializer runtime.NegotiatedSerializer, group metav1.APIGroup) *APIGroupHandler {
+ if keepUnversioned(group.Name) {
+ // Because in release 1.1, /apis/extensions returns response with empty
+ // APIVersion, we use stripVersionNegotiatedSerializer to keep the
+ // response backwards compatible.
+ serializer = stripVersionNegotiatedSerializer{serializer}
+ }
+
+ return &APIGroupHandler{
+ serializer: serializer,
+ group: group,
+ }
+}
+
+func (s *APIGroupHandler) WebService() *restful.WebService {
+ mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
+ ws := new(restful.WebService)
+ ws.Path(APIGroupPrefix + "/" + s.group.Name)
+ ws.Doc("get information of a group")
+ ws.Route(ws.GET("/").To(s.handle).
+ Doc("get information of a group").
+ Operation("getAPIGroup").
+ Produces(mediaTypes...).
+ Consumes(mediaTypes...).
+ Writes(metav1.APIGroup{}))
+ return ws
+}
+
+// handle returns a handler which will return the api.GroupAndVersion of the group.
+func (s *APIGroupHandler) handle(req *restful.Request, resp *restful.Response) {
+ s.ServeHTTP(resp.ResponseWriter, req.Request)
+}
+
+func (s *APIGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK, &s.group)
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
new file mode 100644
index 0000000..fb648e5
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/legacy.go
@@ -0,0 +1,78 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package discovery
+
+import (
+ "net/http"
+
+ "github.com/emicklei/go-restful"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ utilnet "k8s.io/apimachinery/pkg/util/net"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+)
+
+// legacyRootAPIHandler creates a webservice serving api group discovery.
+type legacyRootAPIHandler struct {
+ // addresses is used to build cluster IPs for discovery.
+ addresses Addresses
+ apiPrefix string
+ serializer runtime.NegotiatedSerializer
+ apiVersions []string
+}
+
+func NewLegacyRootAPIHandler(addresses Addresses, serializer runtime.NegotiatedSerializer, apiPrefix string, apiVersions []string) *legacyRootAPIHandler {
+ // Because in release 1.1, /apis returns response with empty APIVersion, we
+ // use stripVersionNegotiatedSerializer to keep the response backwards
+ // compatible.
+ serializer = stripVersionNegotiatedSerializer{serializer}
+
+ return &legacyRootAPIHandler{
+ addresses: addresses,
+ apiPrefix: apiPrefix,
+ serializer: serializer,
+ apiVersions: apiVersions,
+ }
+}
+
+// AddApiWebService adds a service to return the supported api versions at the legacy /api.
+func (s *legacyRootAPIHandler) WebService() *restful.WebService {
+ mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
+ ws := new(restful.WebService)
+ ws.Path(s.apiPrefix)
+ ws.Doc("get available API versions")
+ ws.Route(ws.GET("/").To(s.handle).
+ Doc("get available API versions").
+ Operation("getAPIVersions").
+ Produces(mediaTypes...).
+ Consumes(mediaTypes...).
+ Writes(metav1.APIVersions{}))
+ return ws
+}
+
+func (s *legacyRootAPIHandler) handle(req *restful.Request, resp *restful.Response) {
+ clientIP := utilnet.GetClientIP(req.Request)
+ apiVersions := &metav1.APIVersions{
+ ServerAddressByClientCIDRs: s.addresses.ServerAddressByClientCIDRs(clientIP),
+ Versions: s.apiVersions,
+ }
+
+ responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, apiVersions)
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/root.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/root.go
new file mode 100644
index 0000000..7ed64a9
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/root.go
@@ -0,0 +1,135 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package discovery
+
+import (
+ "net/http"
+ "sync"
+
+ restful "github.com/emicklei/go-restful"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ utilnet "k8s.io/apimachinery/pkg/util/net"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+)
+
+// GroupManager is an interface that allows dynamic mutation of the existing webservice to handle
+// API groups being added or removed.
+type GroupManager interface {
+ AddGroup(apiGroup metav1.APIGroup)
+ RemoveGroup(groupName string)
+
+ WebService() *restful.WebService
+}
+
+// rootAPIsHandler creates a webservice serving api group discovery.
+// The list of APIGroups may change while the server is running because additional resources
+// are registered or removed. It is not safe to cache the values.
+type rootAPIsHandler struct {
+ // addresses is used to build cluster IPs for discovery.
+ addresses Addresses
+
+ serializer runtime.NegotiatedSerializer
+
+ // Map storing information about all groups to be exposed in discovery response.
+ // The map is from name to the group.
+ lock sync.RWMutex
+ apiGroups map[string]metav1.APIGroup
+ // apiGroupNames preserves insertion order
+ apiGroupNames []string
+}
+
+func NewRootAPIsHandler(addresses Addresses, serializer runtime.NegotiatedSerializer) *rootAPIsHandler {
+ // Because in release 1.1, /apis returns response with empty APIVersion, we
+ // use stripVersionNegotiatedSerializer to keep the response backwards
+ // compatible.
+ serializer = stripVersionNegotiatedSerializer{serializer}
+
+ return &rootAPIsHandler{
+ addresses: addresses,
+ serializer: serializer,
+ apiGroups: map[string]metav1.APIGroup{},
+ }
+}
+
+func (s *rootAPIsHandler) AddGroup(apiGroup metav1.APIGroup) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ _, alreadyExists := s.apiGroups[apiGroup.Name]
+
+ s.apiGroups[apiGroup.Name] = apiGroup
+ if !alreadyExists {
+ s.apiGroupNames = append(s.apiGroupNames, apiGroup.Name)
+ }
+}
+
+func (s *rootAPIsHandler) RemoveGroup(groupName string) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ delete(s.apiGroups, groupName)
+ for i := range s.apiGroupNames {
+ if s.apiGroupNames[i] == groupName {
+ s.apiGroupNames = append(s.apiGroupNames[:i], s.apiGroupNames[i+1:]...)
+ break
+ }
+ }
+}
+
+func (s *rootAPIsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+
+ orderedGroups := []metav1.APIGroup{}
+ for _, groupName := range s.apiGroupNames {
+ orderedGroups = append(orderedGroups, s.apiGroups[groupName])
+ }
+
+ clientIP := utilnet.GetClientIP(req)
+ serverCIDR := s.addresses.ServerAddressByClientCIDRs(clientIP)
+ groups := make([]metav1.APIGroup, len(orderedGroups))
+ for i := range orderedGroups {
+ groups[i] = orderedGroups[i]
+ groups[i].ServerAddressByClientCIDRs = serverCIDR
+ }
+
+ responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp, req, http.StatusOK, &metav1.APIGroupList{Groups: groups})
+}
+
+func (s *rootAPIsHandler) restfulHandle(req *restful.Request, resp *restful.Response) {
+ s.ServeHTTP(resp.ResponseWriter, req.Request)
+}
+
+// WebService returns a webservice serving api group discovery.
+// Note: during the server runtime apiGroups might change.
+func (s *rootAPIsHandler) WebService() *restful.WebService {
+ mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
+ ws := new(restful.WebService)
+ ws.Path(APIGroupPrefix)
+ ws.Doc("get available API versions")
+ ws.Route(ws.GET("/").To(s.restfulHandle).
+ Doc("get available API versions").
+ Operation("getAPIVersions").
+ Produces(mediaTypes...).
+ Consumes(mediaTypes...).
+ Writes(metav1.APIGroupList{}))
+ return ws
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/util.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/util.go
new file mode 100644
index 0000000..81a13d6
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/util.go
@@ -0,0 +1,73 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package discovery
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "k8s.io/apimachinery/pkg/runtime"
+)
+
+const APIGroupPrefix = "/apis"
+
+func keepUnversioned(group string) bool {
+ return group == "" || group == "extensions"
+}
+
+// stripVersionEncoder strips APIVersion field from the encoding output. It's
+// used to keep the responses at the discovery endpoints backward compatible
+// with release-1.1, when the responses have empty APIVersion.
+type stripVersionEncoder struct {
+ encoder runtime.Encoder
+ serializer runtime.Serializer
+}
+
+func (c stripVersionEncoder) Encode(obj runtime.Object, w io.Writer) error {
+ buf := bytes.NewBuffer([]byte{})
+ err := c.encoder.Encode(obj, buf)
+ if err != nil {
+ return err
+ }
+ roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil)
+ if err != nil {
+ return err
+ }
+ gvk.Group = ""
+ gvk.Version = ""
+ roundTrippedObj.GetObjectKind().SetGroupVersionKind(*gvk)
+ return c.serializer.Encode(roundTrippedObj, w)
+}
+
+// stripVersionNegotiatedSerializer will return stripVersionEncoder when
+// EncoderForVersion is called. See comments for stripVersionEncoder.
+type stripVersionNegotiatedSerializer struct {
+ runtime.NegotiatedSerializer
+}
+
+func (n stripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
+ serializer, ok := encoder.(runtime.Serializer)
+ if !ok {
+ // The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
+ // decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
+ // decoder.
+ panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
+ }
+ versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
+ return stripVersionEncoder{versioned, serializer}
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/version.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/version.go
new file mode 100644
index 0000000..aadfc7a
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/discovery/version.go
@@ -0,0 +1,83 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package discovery
+
+import (
+ "net/http"
+
+ restful "github.com/emicklei/go-restful"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+)
+
+type APIResourceLister interface {
+ ListAPIResources() []metav1.APIResource
+}
+
+type APIResourceListerFunc func() []metav1.APIResource
+
+func (f APIResourceListerFunc) ListAPIResources() []metav1.APIResource {
+ return f()
+}
+
+// APIVersionHandler creates a webservice serving the supported resources for the version
+// E.g., such a web service will be registered at /apis/extensions/v1beta1.
+type APIVersionHandler struct {
+ serializer runtime.NegotiatedSerializer
+
+ groupVersion schema.GroupVersion
+ apiResourceLister APIResourceLister
+}
+
+func NewAPIVersionHandler(serializer runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, apiResourceLister APIResourceLister) *APIVersionHandler {
+ if keepUnversioned(groupVersion.Group) {
+ // Because in release 1.1, /apis/extensions returns response with empty
+ // APIVersion, we use stripVersionNegotiatedSerializer to keep the
+ // response backwards compatible.
+ serializer = stripVersionNegotiatedSerializer{serializer}
+ }
+
+ return &APIVersionHandler{
+ serializer: serializer,
+ groupVersion: groupVersion,
+ apiResourceLister: apiResourceLister,
+ }
+}
+
+func (s *APIVersionHandler) AddToWebService(ws *restful.WebService) {
+ mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
+ ws.Route(ws.GET("/").To(s.handle).
+ Doc("get available resources").
+ Operation("getAPIResources").
+ Produces(mediaTypes...).
+ Consumes(mediaTypes...).
+ Writes(metav1.APIResourceList{}))
+}
+
+// handle returns a handler which will return the api.VersionAndVersion of the group.
+func (s *APIVersionHandler) handle(req *restful.Request, resp *restful.Response) {
+ s.ServeHTTP(resp.ResponseWriter, req.Request)
+}
+
+func (s *APIVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK,
+ &metav1.APIResourceList{GroupVersion: s.groupVersion.String(), APIResources: s.apiResourceLister.ListAPIResources()})
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/doc.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/doc.go
new file mode 100644
index 0000000..ef99114
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/doc.go
@@ -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 endpoints contains the generic code that provides a RESTful Kubernetes-style API service.
+package endpoints // import "k8s.io/apiserver/pkg/endpoints"
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
new file mode 100755
index 0000000..6f780ff
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/OWNERS
@@ -0,0 +1,4 @@
+reviewers:
+- deads2k
+- sttts
+- soltysh
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
new file mode 100644
index 0000000..4946341
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go
@@ -0,0 +1,248 @@
+/*
+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"
+ "net"
+ "net/http"
+ "sync"
+ "time"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ auditinternal "k8s.io/apiserver/pkg/apis/audit"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/audit/policy"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+ "k8s.io/apiserver/pkg/endpoints/request"
+)
+
+// WithAudit decorates a http.Handler with audit logging information for all the
+// requests coming to the server. Audit level is decided according to requests'
+// attributes and audit policy. Logs are emitted to the audit sink to
+// process events. If sink or audit policy is nil, no decoration takes place.
+func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker, longRunningCheck request.LongRunningRequestCheck) http.Handler {
+ if sink == nil || policy == nil {
+ return handler
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ req, ev, omitStages, err := createAuditEventAndAttachToContext(req, policy)
+ if err != nil {
+ utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
+ responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
+ return
+ }
+ ctx := req.Context()
+ if ev == nil || ctx == nil {
+ handler.ServeHTTP(w, req)
+ return
+ }
+
+ ev.Stage = auditinternal.StageRequestReceived
+ processAuditEvent(sink, ev, omitStages)
+
+ // intercept the status code
+ var longRunningSink audit.Sink
+ if longRunningCheck != nil {
+ ri, _ := request.RequestInfoFrom(ctx)
+ if longRunningCheck(req, ri) {
+ longRunningSink = sink
+ }
+ }
+ respWriter := decorateResponseWriter(w, ev, longRunningSink, omitStages)
+
+ // send audit event when we leave this func, either via a panic or cleanly. In the case of long
+ // running requests, this will be the second audit event.
+ defer func() {
+ if r := recover(); r != nil {
+ defer panic(r)
+ ev.Stage = auditinternal.StagePanic
+ ev.ResponseStatus = &metav1.Status{
+ Code: http.StatusInternalServerError,
+ Status: metav1.StatusFailure,
+ Reason: metav1.StatusReasonInternalError,
+ Message: fmt.Sprintf("APIServer panic'd: %v", r),
+ }
+ processAuditEvent(sink, ev, omitStages)
+ return
+ }
+
+ // if no StageResponseStarted event was sent b/c neither a status code nor a body was sent, fake it here
+ // But Audit-Id http header will only be sent when http.ResponseWriter.WriteHeader is called.
+ fakedSuccessStatus := &metav1.Status{
+ Code: http.StatusOK,
+ Status: metav1.StatusSuccess,
+ Message: "Connection closed early",
+ }
+ if ev.ResponseStatus == nil && longRunningSink != nil {
+ ev.ResponseStatus = fakedSuccessStatus
+ ev.Stage = auditinternal.StageResponseStarted
+ processAuditEvent(longRunningSink, ev, omitStages)
+ }
+
+ ev.Stage = auditinternal.StageResponseComplete
+ if ev.ResponseStatus == nil {
+ ev.ResponseStatus = fakedSuccessStatus
+ }
+ processAuditEvent(sink, ev, omitStages)
+ }()
+ handler.ServeHTTP(respWriter, req)
+ })
+}
+
+// createAuditEventAndAttachToContext is responsible for creating the audit event
+// and attaching it to the appropriate request context. It returns:
+// - context with audit event attached to it
+// - created audit event
+// - error if anything bad happened
+func createAuditEventAndAttachToContext(req *http.Request, policy policy.Checker) (*http.Request, *auditinternal.Event, []auditinternal.Stage, error) {
+ ctx := req.Context()
+
+ attribs, err := GetAuthorizerAttributes(ctx)
+ if err != nil {
+ return req, nil, nil, fmt.Errorf("failed to GetAuthorizerAttributes: %v", err)
+ }
+
+ level, omitStages := policy.LevelAndStages(attribs)
+ audit.ObservePolicyLevel(level)
+ if level == auditinternal.LevelNone {
+ // Don't audit.
+ return req, nil, nil, nil
+ }
+
+ ev, err := audit.NewEventFromRequest(req, level, attribs)
+ if err != nil {
+ return req, nil, nil, fmt.Errorf("failed to complete audit event from request: %v", err)
+ }
+
+ req = req.WithContext(request.WithAuditEvent(ctx, ev))
+
+ return req, ev, omitStages, nil
+}
+
+func processAuditEvent(sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) {
+ for _, stage := range omitStages {
+ if ev.Stage == stage {
+ return
+ }
+ }
+
+ if ev.Stage == auditinternal.StageRequestReceived {
+ ev.StageTimestamp = metav1.NewMicroTime(ev.RequestReceivedTimestamp.Time)
+ } else {
+ ev.StageTimestamp = metav1.NewMicroTime(time.Now())
+ }
+ audit.ObserveEvent()
+ sink.ProcessEvents(ev)
+}
+
+func decorateResponseWriter(responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter {
+ delegate := &auditResponseWriter{
+ ResponseWriter: responseWriter,
+ event: ev,
+ sink: sink,
+ omitStages: omitStages,
+ }
+
+ // 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
+}
+
+var _ http.ResponseWriter = &auditResponseWriter{}
+
+// auditResponseWriter intercepts WriteHeader, sets it in the event. If the sink is set, it will
+// create immediately an event (for long running requests).
+type auditResponseWriter struct {
+ http.ResponseWriter
+ event *auditinternal.Event
+ once sync.Once
+ sink audit.Sink
+ omitStages []auditinternal.Stage
+}
+
+func (a *auditResponseWriter) setHttpHeader() {
+ a.ResponseWriter.Header().Set(auditinternal.HeaderAuditID, string(a.event.AuditID))
+}
+
+func (a *auditResponseWriter) processCode(code int) {
+ a.once.Do(func() {
+ if a.event.ResponseStatus == nil {
+ a.event.ResponseStatus = &metav1.Status{}
+ }
+ a.event.ResponseStatus.Code = int32(code)
+ a.event.Stage = auditinternal.StageResponseStarted
+
+ if a.sink != nil {
+ processAuditEvent(a.sink, a.event, a.omitStages)
+ }
+ })
+}
+
+func (a *auditResponseWriter) Write(bs []byte) (int, error) {
+ // the Go library calls WriteHeader internally if no code was written yet. But this will go unnoticed for us
+ a.processCode(http.StatusOK)
+ a.setHttpHeader()
+ return a.ResponseWriter.Write(bs)
+}
+
+func (a *auditResponseWriter) WriteHeader(code int) {
+ a.processCode(code)
+ a.setHttpHeader()
+ 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) {
+ // fake a response status before protocol switch happens
+ f.processCode(http.StatusSwitchingProtocols)
+
+ // This will be ignored if WriteHeader() function has already been called.
+ // It's not guaranteed Audit-ID http header is sent for all requests.
+ // For example, when user run "kubectl exec", apiserver uses a proxy handler
+ // to deal with the request, users can only get http headers returned by kubelet node.
+ f.setHttpHeader()
+
+ return f.ResponseWriter.(http.Hijacker).Hijack()
+}
+
+var _ http.CloseNotifier = &fancyResponseWriterDelegator{}
+var _ http.Flusher = &fancyResponseWriterDelegator{}
+var _ http.Hijacker = &fancyResponseWriterDelegator{}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
new file mode 100644
index 0000000..ba53fc6
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authentication.go
@@ -0,0 +1,116 @@
+/*
+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 (
+ "errors"
+ "net/http"
+ "strings"
+
+ "github.com/golang/glog"
+ "github.com/prometheus/client_golang/prometheus"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apiserver/pkg/authentication/authenticator"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+ genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
+)
+
+var (
+ authenticatedUserCounter = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "authenticated_user_requests",
+ Help: "Counter of authenticated requests broken out by username.",
+ },
+ []string{"username"},
+ )
+)
+
+func init() {
+ prometheus.MustRegister(authenticatedUserCounter)
+}
+
+// WithAuthentication creates an http handler that tries to authenticate the given request as a user, and then
+// stores any such user found onto the provided context for the request. If authentication fails or returns an error
+// the failed handler is used. On success, "Authorization" header is removed from the request and handler
+// is invoked to serve the request.
+func WithAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler) http.Handler {
+ if auth == nil {
+ glog.Warningf("Authentication is disabled")
+ return handler
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ user, ok, err := auth.AuthenticateRequest(req)
+ if err != nil || !ok {
+ if err != nil {
+ glog.Errorf("Unable to authenticate the request due to an error: %v", err)
+ }
+ failed.ServeHTTP(w, req)
+ return
+ }
+
+ // authorization header is not required anymore in case of a successful authentication.
+ req.Header.Del("Authorization")
+
+ req = req.WithContext(genericapirequest.WithUser(req.Context(), user))
+
+ authenticatedUserCounter.WithLabelValues(compressUsername(user.GetName())).Inc()
+
+ handler.ServeHTTP(w, req)
+ })
+}
+
+func Unauthorized(s runtime.NegotiatedSerializer, supportsBasicAuth bool) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ if supportsBasicAuth {
+ w.Header().Set("WWW-Authenticate", `Basic realm="kubernetes-master"`)
+ }
+ ctx := req.Context()
+ requestInfo, found := genericapirequest.RequestInfoFrom(ctx)
+ if !found {
+ responsewriters.InternalError(w, req, errors.New("no RequestInfo found in the context"))
+ return
+ }
+
+ gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
+ responsewriters.ErrorNegotiated(apierrors.NewUnauthorized("Unauthorized"), s, gv, w, req)
+ })
+}
+
+// compressUsername maps all possible usernames onto a small set of categories
+// of usernames. This is done both to limit the cardinality of the
+// authorized_user_requests metric, and to avoid pushing actual usernames in the
+// metric.
+func compressUsername(username string) string {
+ switch {
+ // Known internal identities.
+ case username == "admin" ||
+ username == "client" ||
+ username == "kube_proxy" ||
+ username == "kubelet" ||
+ username == "system:serviceaccount:kube-system:default":
+ return username
+ // Probably an email address.
+ case strings.Contains(username, "@"):
+ return "email_id"
+ // Anything else (custom service accounts, custom external identities, etc.)
+ default:
+ return "other"
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go
new file mode 100644
index 0000000..09d7db8
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go
@@ -0,0 +1,86 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package filters
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ auditinternal "k8s.io/apiserver/pkg/apis/audit"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/audit/policy"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+)
+
+// WithFailedAuthenticationAudit decorates a failed http.Handler used in WithAuthentication handler.
+// It is meant to log only failed authentication requests.
+func WithFailedAuthenticationAudit(failedHandler http.Handler, sink audit.Sink, policy policy.Checker) http.Handler {
+ if sink == nil || policy == nil {
+ return failedHandler
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ req, ev, omitStages, err := createAuditEventAndAttachToContext(req, policy)
+ if err != nil {
+ utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
+ responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
+ return
+ }
+ if ev == nil {
+ failedHandler.ServeHTTP(w, req)
+ return
+ }
+
+ ev.ResponseStatus = &metav1.Status{}
+ ev.ResponseStatus.Message = getAuthMethods(req)
+ ev.Stage = auditinternal.StageResponseStarted
+
+ rw := decorateResponseWriter(w, ev, sink, omitStages)
+ failedHandler.ServeHTTP(rw, req)
+ })
+}
+
+func getAuthMethods(req *http.Request) string {
+ authMethods := []string{}
+
+ if _, _, ok := req.BasicAuth(); ok {
+ authMethods = append(authMethods, "basic")
+ }
+
+ auth := strings.TrimSpace(req.Header.Get("Authorization"))
+ parts := strings.Split(auth, " ")
+ if len(parts) > 1 && strings.ToLower(parts[0]) == "bearer" {
+ authMethods = append(authMethods, "bearer")
+ }
+
+ token := strings.TrimSpace(req.URL.Query().Get("access_token"))
+ if len(token) > 0 {
+ authMethods = append(authMethods, "access_token")
+ }
+
+ if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
+ authMethods = append(authMethods, "x509")
+ }
+
+ if len(authMethods) > 0 {
+ return fmt.Sprintf("Authentication failed, attempted: %s", strings.Join(authMethods, ", "))
+ }
+ return "Authentication failed, no credentials provided"
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
new file mode 100644
index 0000000..4c9f140
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/authorization.go
@@ -0,0 +1,106 @@
+/*
+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 (
+ "context"
+ "errors"
+ "net/http"
+
+ "github.com/golang/glog"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/authorization/authorizer"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+ "k8s.io/apiserver/pkg/endpoints/request"
+)
+
+const (
+ // Annotation key names set in advanced audit
+ decisionAnnotationKey = "authorization.k8s.io/decision"
+ reasonAnnotationKey = "authorization.k8s.io/reason"
+
+ // Annotation values set in advanced audit
+ decisionAllow = "allow"
+ decisionForbid = "forbid"
+ reasonError = "internal error"
+)
+
+// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
+func WithAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
+ if a == nil {
+ glog.Warningf("Authorization is disabled")
+ return handler
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := req.Context()
+ ae := request.AuditEventFrom(ctx)
+
+ attributes, err := GetAuthorizerAttributes(ctx)
+ if err != nil {
+ responsewriters.InternalError(w, req, err)
+ return
+ }
+ authorized, reason, err := a.Authorize(attributes)
+ // an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
+ if authorized == authorizer.DecisionAllow {
+ audit.LogAnnotation(ae, decisionAnnotationKey, decisionAllow)
+ audit.LogAnnotation(ae, reasonAnnotationKey, reason)
+ handler.ServeHTTP(w, req)
+ return
+ }
+ if err != nil {
+ audit.LogAnnotation(ae, reasonAnnotationKey, reasonError)
+ responsewriters.InternalError(w, req, err)
+ return
+ }
+
+ glog.V(4).Infof("Forbidden: %#v, Reason: %q", req.RequestURI, reason)
+ audit.LogAnnotation(ae, decisionAnnotationKey, decisionForbid)
+ audit.LogAnnotation(ae, reasonAnnotationKey, reason)
+ responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
+ })
+}
+
+func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
+ attribs := authorizer.AttributesRecord{}
+
+ user, ok := request.UserFrom(ctx)
+ if ok {
+ attribs.User = user
+ }
+
+ requestInfo, found := request.RequestInfoFrom(ctx)
+ if !found {
+ return nil, errors.New("no RequestInfo found in the context")
+ }
+
+ // Start with common attributes that apply to resource and non-resource requests
+ attribs.ResourceRequest = requestInfo.IsResourceRequest
+ attribs.Path = requestInfo.Path
+ attribs.Verb = requestInfo.Verb
+
+ attribs.APIGroup = requestInfo.APIGroup
+ attribs.APIVersion = requestInfo.APIVersion
+ attribs.Resource = requestInfo.Resource
+ attribs.Subresource = requestInfo.Subresource
+ attribs.Namespace = requestInfo.Namespace
+ attribs.Name = requestInfo.Name
+
+ return &attribs, nil
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/doc.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/doc.go
new file mode 100644
index 0000000..a13125a
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/doc.go
@@ -0,0 +1,21 @@
+/*
+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, i.e. which are prerequisite for the API services
+// to work (in contrast to the filters in the server package which are
+// not part of the API contract).
+package filters // import "k8s.io/apiserver/pkg/endpoints/filters"
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
new file mode 100644
index 0000000..726cbe4
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
@@ -0,0 +1,210 @@
+/*
+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"
+ "net/url"
+ "strings"
+
+ "github.com/golang/glog"
+
+ authenticationv1 "k8s.io/api/authentication/v1"
+ "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/authentication/serviceaccount"
+ "k8s.io/apiserver/pkg/authentication/user"
+ "k8s.io/apiserver/pkg/authorization/authorizer"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/server/httplog"
+)
+
+// 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, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ impersonationRequests, err := buildImpersonationRequests(req.Header)
+ if err != nil {
+ glog.V(4).Infof("%v", err)
+ responsewriters.InternalError(w, req, err)
+ return
+ }
+ if len(impersonationRequests) == 0 {
+ handler.ServeHTTP(w, req)
+ return
+ }
+
+ ctx := req.Context()
+ requestor, exists := request.UserFrom(ctx)
+ if !exists {
+ responsewriters.InternalError(w, req, errors.New("no user found for request"))
+ return
+ }
+
+ // if groups are not specified, then we need to look them up differently depending on the type of user
+ // if they are specified, then they are the authority (including the inclusion of system:authenticated/system:unauthenticated groups)
+ groupsSpecified := len(req.Header[authenticationv1.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 v1.SchemeGroupVersion.WithKind("ServiceAccount").GroupKind():
+ 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)
+ }
+
+ case v1.SchemeGroupVersion.WithKind("User").GroupKind():
+ actingAsAttributes.Resource = "users"
+ username = impersonationRequest.Name
+
+ case v1.SchemeGroupVersion.WithKind("Group").GroupKind():
+ actingAsAttributes.Resource = "groups"
+ groups = append(groups, impersonationRequest.Name)
+
+ case authenticationv1.SchemeGroupVersion.WithKind("UserExtra").GroupKind():
+ extraKey := impersonationRequest.FieldPath
+ extraValue := impersonationRequest.Name
+ actingAsAttributes.Resource = "userextras"
+ actingAsAttributes.Subresource = extraKey
+ userExtra[extraKey] = append(userExtra[extraKey], extraValue)
+
+ default:
+ glog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest)
+ responsewriters.Forbidden(ctx, actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest), s)
+ return
+ }
+
+ decision, reason, err := a.Authorize(actingAsAttributes)
+ if err != nil || decision != authorizer.DecisionAllow {
+ glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err)
+ responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s)
+ return
+ }
+ }
+
+ if !groupsSpecified && username != user.Anonymous {
+ // When impersonating a non-anonymous user, if no groups were specified
+ // include the system:authenticated group in the impersonated user info
+ groups = append(groups, user.AllAuthenticated)
+ }
+
+ newUser := &user.DefaultInfo{
+ Name: username,
+ Groups: groups,
+ Extra: userExtra,
+ }
+ req = req.WithContext(request.WithUser(ctx, newUser))
+
+ oldUser, _ := request.UserFrom(ctx)
+ httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser)
+
+ ae := request.AuditEventFrom(ctx)
+ audit.LogImpersonatedUser(ae, newUser)
+
+ // clear all the impersonation headers from the request
+ req.Header.Del(authenticationv1.ImpersonateUserHeader)
+ req.Header.Del(authenticationv1.ImpersonateGroupHeader)
+ for headerName := range req.Header {
+ if strings.HasPrefix(headerName, authenticationv1.ImpersonateUserExtraHeaderPrefix) {
+ req.Header.Del(headerName)
+ }
+ }
+
+ handler.ServeHTTP(w, req)
+ })
+}
+
+func unescapeExtraKey(encodedKey string) string {
+ key, err := url.PathUnescape(encodedKey) // Decode %-encoded bytes.
+ if err != nil {
+ return encodedKey // Always record extra strings, even if malformed/unencoded.
+ }
+ return key
+}
+
+// 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) ([]v1.ObjectReference, error) {
+ impersonationRequests := []v1.ObjectReference{}
+
+ requestedUser := headers.Get(authenticationv1.ImpersonateUserHeader)
+ hasUser := len(requestedUser) > 0
+ if hasUser {
+ if namespace, name, err := serviceaccount.SplitUsername(requestedUser); err == nil {
+ impersonationRequests = append(impersonationRequests, v1.ObjectReference{Kind: "ServiceAccount", Namespace: namespace, Name: name})
+ } else {
+ impersonationRequests = append(impersonationRequests, v1.ObjectReference{Kind: "User", Name: requestedUser})
+ }
+ }
+
+ hasGroups := false
+ for _, group := range headers[authenticationv1.ImpersonateGroupHeader] {
+ hasGroups = true
+ impersonationRequests = append(impersonationRequests, v1.ObjectReference{Kind: "Group", Name: group})
+ }
+
+ hasUserExtra := false
+ for headerName, values := range headers {
+ if !strings.HasPrefix(headerName, authenticationv1.ImpersonateUserExtraHeaderPrefix) {
+ continue
+ }
+
+ hasUserExtra = true
+ extraKey := unescapeExtraKey(strings.ToLower(headerName[len(authenticationv1.ImpersonateUserExtraHeaderPrefix):]))
+
+ // make a separate request for each extra value they're trying to set
+ for _, value := range values {
+ impersonationRequests = append(impersonationRequests,
+ v1.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: authenticationv1.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
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/legacy_audit.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/legacy_audit.go
new file mode 100644
index 0000000..bdf13c5
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/legacy_audit.go
@@ -0,0 +1,161 @@
+/*
+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"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/golang/glog"
+ "github.com/pborman/uuid"
+
+ authenticationapi "k8s.io/api/authentication/v1"
+ utilnet "k8s.io/apimachinery/pkg/util/net"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+)
+
+var _ http.ResponseWriter = &legacyAuditResponseWriter{}
+
+type legacyAuditResponseWriter struct {
+ http.ResponseWriter
+ out io.Writer
+ id string
+}
+
+func (a *legacyAuditResponseWriter) printResponse(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)
+ }
+}
+
+func (a *legacyAuditResponseWriter) WriteHeader(code int) {
+ a.printResponse(code)
+ a.ResponseWriter.WriteHeader(code)
+}
+
+// fancyLegacyResponseWriterDelegator implements http.CloseNotifier, http.Flusher and
+// http.Hijacker which are needed to make certain http operation (e.g. watch, rsh, etc)
+// working.
+type fancyLegacyResponseWriterDelegator struct {
+ *legacyAuditResponseWriter
+}
+
+func (f *fancyLegacyResponseWriterDelegator) CloseNotify() <-chan bool {
+ return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
+}
+
+func (f *fancyLegacyResponseWriterDelegator) Flush() {
+ f.ResponseWriter.(http.Flusher).Flush()
+}
+
+func (f *fancyLegacyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ // fake a response status before protocol switch happens
+ f.printResponse(http.StatusSwitchingProtocols)
+ return f.ResponseWriter.(http.Hijacker).Hijack()
+}
+
+var _ http.CloseNotifier = &fancyLegacyResponseWriterDelegator{}
+var _ http.Flusher = &fancyLegacyResponseWriterDelegator{}
+var _ http.Hijacker = &fancyLegacyResponseWriterDelegator{}
+
+// WithLegacyAudit 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
+// - original user's groups info
+// - impersonated user for the operation
+// - impersonated groups info
+// - 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 WithLegacyAudit(handler http.Handler, out io.Writer) http.Handler {
+ if out == nil {
+ return handler
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := req.Context()
+ attribs, err := GetAuthorizerAttributes(ctx)
+ if err != nil {
+ responsewriters.InternalError(w, req, err)
+ return
+ }
+
+ username := "<none>"
+ groups := "<none>"
+ if attribs.GetUser() != nil {
+ username = attribs.GetUser().GetName()
+ if userGroups := attribs.GetUser().GetGroups(); len(userGroups) > 0 {
+ groups = auditStringSlice(userGroups)
+ }
+ }
+ asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader)
+ if len(asuser) == 0 {
+ asuser = "<self>"
+ }
+ asgroups := "<lookup>"
+ requestedGroups := req.Header[authenticationapi.ImpersonateGroupHeader]
+ if len(requestedGroups) > 0 {
+ asgroups = auditStringSlice(requestedGroups)
+ }
+ namespace := attribs.GetNamespace()
+ if len(namespace) == 0 {
+ namespace = "<none>"
+ }
+ id := uuid.NewRandom().String()
+
+ line := fmt.Sprintf("%s AUDIT: id=%q ip=%q method=%q user=%q groups=%q as=%q asgroups=%q namespace=%q uri=%q\n",
+ time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, username, groups, asuser, asgroups, namespace, req.URL)
+ if _, err := fmt.Fprint(out, line); err != nil {
+ glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err)
+ }
+ respWriter := legacyDecorateResponseWriter(w, out, id)
+ handler.ServeHTTP(respWriter, req)
+ })
+}
+
+func auditStringSlice(inList []string) string {
+ quotedElements := make([]string, len(inList))
+ for i, in := range inList {
+ quotedElements[i] = fmt.Sprintf("%q", in)
+ }
+ return strings.Join(quotedElements, ",")
+}
+
+func legacyDecorateResponseWriter(responseWriter http.ResponseWriter, out io.Writer, id string) http.ResponseWriter {
+ delegate := &legacyAuditResponseWriter{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 &fancyLegacyResponseWriterDelegator{delegate}
+ }
+ return delegate
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/requestinfo.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/requestinfo.go
new file mode 100644
index 0000000..9cc524d
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/filters/requestinfo.go
@@ -0,0 +1,41 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package filters
+
+import (
+ "fmt"
+ "net/http"
+
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+ "k8s.io/apiserver/pkg/endpoints/request"
+)
+
+// WithRequestInfo attaches a RequestInfo to the context.
+func WithRequestInfo(handler http.Handler, resolver request.RequestInfoResolver) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := req.Context()
+ info, err := resolver.NewRequestInfo(req)
+ if err != nil {
+ responsewriters.InternalError(w, req, fmt.Errorf("failed to create RequestInfo: %v", err))
+ return
+ }
+
+ req = req.WithContext(request.WithRequestInfo(ctx, info))
+
+ handler.ServeHTTP(w, req)
+ })
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
new file mode 100644
index 0000000..23d13ad
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/groupversion.go
@@ -0,0 +1,114 @@
+/*
+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 endpoints
+
+import (
+ "path"
+ "time"
+
+ "github.com/emicklei/go-restful"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
+ "k8s.io/apimachinery/pkg/util/sets"
+ "k8s.io/apiserver/pkg/admission"
+ "k8s.io/apiserver/pkg/endpoints/discovery"
+ "k8s.io/apiserver/pkg/registry/rest"
+ openapicommon "k8s.io/kube-openapi/pkg/common"
+)
+
+// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
+// It handles URLs of the form:
+// /${storage_key}[/${object_name}]
+// Where 'storage_key' points to a rest.Storage object stored in storage.
+// This object should contain all parameterization necessary for running a particular API version
+type APIGroupVersion struct {
+ Storage map[string]rest.Storage
+
+ Root string
+
+ // GroupVersion is the external group version
+ GroupVersion schema.GroupVersion
+
+ // OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver
+ // schema like api.Status, api.DeleteOptions, and metav1.ListOptions. Other implementors may
+ // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
+ // empty, defaults to GroupVersion.
+ OptionsExternalVersion *schema.GroupVersion
+ // MetaGroupVersion defaults to "meta.k8s.io/v1" and is the scheme group version used to decode
+ // common API implementations like ListOptions. Future changes will allow this to vary by group
+ // version (for when the inevitable meta/v2 group emerges).
+ MetaGroupVersion *schema.GroupVersion
+
+ // RootScopedKinds are the root scoped kinds for the primary GroupVersion
+ RootScopedKinds sets.String
+
+ // Serializer is used to determine how to convert responses from API methods into bytes to send over
+ // the wire.
+ Serializer runtime.NegotiatedSerializer
+ ParameterCodec runtime.ParameterCodec
+
+ Typer runtime.ObjectTyper
+ Creater runtime.ObjectCreater
+ Convertor runtime.ObjectConvertor
+ Defaulter runtime.ObjectDefaulter
+ Linker runtime.SelfLinker
+ UnsafeConvertor runtime.ObjectConvertor
+
+ Admit admission.Interface
+
+ MinRequestTimeout time.Duration
+
+ // EnableAPIResponseCompression indicates whether API Responses should support compression
+ // if the client requests it via Accept-Encoding
+ EnableAPIResponseCompression bool
+
+ // OpenAPIConfig lets the individual handlers build a subset of the OpenAPI schema before they are installed.
+ OpenAPIConfig *openapicommon.Config
+}
+
+// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
+// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
+// in a slash.
+func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
+ prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
+ installer := &APIInstaller{
+ group: g,
+ prefix: prefix,
+ minRequestTimeout: g.MinRequestTimeout,
+ enableAPIResponseCompression: g.EnableAPIResponseCompression,
+ }
+
+ apiResources, ws, registrationErrors := installer.Install()
+ versionDiscoveryHandler := discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, staticLister{apiResources})
+ versionDiscoveryHandler.AddToWebService(ws)
+ container.Add(ws)
+ return utilerrors.NewAggregate(registrationErrors)
+}
+
+// staticLister implements the APIResourceLister interface
+type staticLister struct {
+ list []metav1.APIResource
+}
+
+func (s staticLister) ListAPIResources() []metav1.APIResource {
+ return s.list
+}
+
+var _ discovery.APIResourceLister = &staticLister{}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
new file mode 100644
index 0000000..5427600
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
@@ -0,0 +1,175 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "time"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apiserver/pkg/admission"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/registry/rest"
+ utiltrace "k8s.io/apiserver/pkg/util/trace"
+)
+
+func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ // For performance tracking purposes.
+ trace := utiltrace.New("Create " + req.URL.Path)
+ defer trace.LogIfLong(500 * time.Millisecond)
+
+ if isDryRun(req.URL) {
+ scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
+ return
+ }
+
+ // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
+ timeout := parseTimeout(req.URL.Query().Get("timeout"))
+
+ var (
+ namespace, name string
+ err error
+ )
+ if includeName {
+ namespace, name, err = scope.Namer.Name(req)
+ } else {
+ namespace, err = scope.Namer.Namespace(req)
+ }
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+
+ gv := scope.Kind.GroupVersion()
+ s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal})
+
+ body, err := readBody(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ defaultGVK := scope.Kind
+ original := r.New()
+ trace.Step("About to convert to expected version")
+ obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
+ if err != nil {
+ err = transformDecodeError(scope.Typer, err, original, gvk, body)
+ scope.err(err, w, req)
+ return
+ }
+ if gvk.GroupVersion() != gv {
+ err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%v)", gvk.GroupVersion().String(), gv.String()))
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Conversion done")
+
+ ae := request.AuditEventFrom(ctx)
+ admit = admission.WithAudit(admit, ae)
+ audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
+
+ userInfo, _ := request.UserFrom(ctx)
+ admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)
+ if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
+ err = mutatingAdmission.Admit(admissionAttributes)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+
+ // TODO: replace with content type negotiation?
+ includeUninitialized := req.URL.Query().Get("includeUninitialized") == "1"
+
+ trace.Step("About to store object in database")
+ result, err := finishRequest(timeout, func() (runtime.Object, error) {
+ return r.Create(
+ ctx,
+ name,
+ obj,
+ rest.AdmissionToValidateObjectFunc(admit, admissionAttributes),
+ includeUninitialized,
+ )
+ })
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Object stored in database")
+
+ requestInfo, ok := request.RequestInfoFrom(ctx)
+ if !ok {
+ scope.err(fmt.Errorf("missing requestInfo"), w, req)
+ return
+ }
+ if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Self-link added")
+
+ // If the object is partially initialized, always indicate it via StatusAccepted
+ code := http.StatusCreated
+ if accessor, err := meta.Accessor(result); err == nil {
+ if accessor.GetInitializers() != nil {
+ code = http.StatusAccepted
+ }
+ }
+ status, ok := result.(*metav1.Status)
+ if ok && err == nil && status.Code == 0 {
+ status.Code = int32(code)
+ }
+
+ transformResponseObject(ctx, scope, req, w, code, result)
+ }
+}
+
+// CreateNamedResource returns a function that will handle a resource creation with name.
+func CreateNamedResource(r rest.NamedCreater, scope RequestScope, admission admission.Interface) http.HandlerFunc {
+ return createHandler(r, scope, admission, true)
+}
+
+// CreateResource returns a function that will handle a resource creation.
+func CreateResource(r rest.Creater, scope RequestScope, admission admission.Interface) http.HandlerFunc {
+ return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
+}
+
+type namedCreaterAdapter struct {
+ rest.Creater
+}
+
+func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
+ return c.Creater.Create(ctx, obj, createValidatingAdmission, includeUninitialized)
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
new file mode 100644
index 0000000..03576d7
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/delete.go
@@ -0,0 +1,296 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apiserver/pkg/admission"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/registry/rest"
+ utiltrace "k8s.io/apiserver/pkg/util/trace"
+)
+
+// DeleteResource returns a function that will handle a resource deletion
+// TODO admission here becomes solely validating admission
+func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ // For performance tracking purposes.
+ trace := utiltrace.New("Delete " + req.URL.Path)
+ defer trace.LogIfLong(500 * time.Millisecond)
+
+ if isDryRun(req.URL) {
+ scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
+ return
+ }
+
+ // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
+ timeout := parseTimeout(req.URL.Query().Get("timeout"))
+
+ namespace, name, err := scope.Namer.Name(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+ ae := request.AuditEventFrom(ctx)
+ admit = admission.WithAudit(admit, ae)
+
+ options := &metav1.DeleteOptions{}
+ if allowsOptions {
+ body, err := readBody(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ if len(body) > 0 {
+ s, err := negotiation.NegotiateInputSerializer(req, false, metainternalversion.Codecs)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ // For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions
+ // It is also allowed to pass a body with meta.k8s.io/v1.DeleteOptions
+ defaultGVK := scope.MetaGroupVersion.WithKind("DeleteOptions")
+ obj, _, err := metainternalversion.Codecs.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ if obj != options {
+ scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req)
+ return
+ }
+ trace.Step("Decoded delete options")
+
+ ae := request.AuditEventFrom(ctx)
+ audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
+ trace.Step("Recorded the audit event")
+ } else {
+ if values := req.URL.Query(); len(values) > 0 {
+ if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, options); err != nil {
+ err = errors.NewBadRequest(err.Error())
+ scope.err(err, w, req)
+ return
+ }
+ }
+ }
+ }
+
+ trace.Step("About to check admission control")
+ if admit != nil && admit.Handles(admission.Delete) {
+ userInfo, _ := request.UserFrom(ctx)
+ attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)
+ if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
+ if err := mutatingAdmission.Admit(attrs); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+ if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
+ if err := validatingAdmission.Validate(attrs); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+ }
+
+ trace.Step("About to delete object from database")
+ wasDeleted := true
+ result, err := finishRequest(timeout, func() (runtime.Object, error) {
+ obj, deleted, err := r.Delete(ctx, name, options)
+ wasDeleted = deleted
+ return obj, err
+ })
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Object deleted from database")
+
+ status := http.StatusOK
+ // Return http.StatusAccepted if the resource was not deleted immediately and
+ // user requested cascading deletion by setting OrphanDependents=false.
+ // Note: We want to do this always if resource was not deleted immediately, but
+ // that will break existing clients.
+ // Other cases where resource is not instantly deleted are: namespace deletion
+ // and pod graceful deletion.
+ if !wasDeleted && options.OrphanDependents != nil && *options.OrphanDependents == false {
+ status = http.StatusAccepted
+ }
+ // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
+ // object with the response.
+ if result == nil {
+ result = &metav1.Status{
+ Status: metav1.StatusSuccess,
+ Code: int32(status),
+ Details: &metav1.StatusDetails{
+ Name: name,
+ Kind: scope.Kind.Kind,
+ },
+ }
+ } else {
+ // when a non-status response is returned, set the self link
+ requestInfo, ok := request.RequestInfoFrom(ctx)
+ if !ok {
+ scope.err(fmt.Errorf("missing requestInfo"), w, req)
+ return
+ }
+ if _, ok := result.(*metav1.Status); !ok {
+ if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+ }
+
+ transformResponseObject(ctx, scope, req, w, status, result)
+ }
+}
+
+// DeleteCollection returns a function that will handle a collection deletion
+func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ if isDryRun(req.URL) {
+ scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
+ return
+ }
+
+ // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
+ timeout := parseTimeout(req.URL.Query().Get("timeout"))
+
+ namespace, err := scope.Namer.Namespace(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+ ae := request.AuditEventFrom(ctx)
+ admit = admission.WithAudit(admit, ae)
+
+ if admit != nil && admit.Handles(admission.Delete) {
+ userInfo, _ := request.UserFrom(ctx)
+ attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)
+ if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
+ err = mutatingAdmission.Admit(attrs)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+
+ if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
+ err = validatingAdmission.Validate(attrs)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+ }
+
+ listOptions := metainternalversion.ListOptions{}
+ if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &listOptions); err != nil {
+ err = errors.NewBadRequest(err.Error())
+ scope.err(err, w, req)
+ return
+ }
+
+ // transform fields
+ // TODO: DecodeParametersInto should do this.
+ if listOptions.FieldSelector != nil {
+ fn := func(label, value string) (newLabel, newValue string, err error) {
+ return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value)
+ }
+ if listOptions.FieldSelector, err = listOptions.FieldSelector.Transform(fn); err != nil {
+ // TODO: allow bad request to set field causes based on query parameters
+ err = errors.NewBadRequest(err.Error())
+ scope.err(err, w, req)
+ return
+ }
+ }
+
+ options := &metav1.DeleteOptions{}
+ if checkBody {
+ body, err := readBody(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ if len(body) > 0 {
+ s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions")
+ obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ if obj != options {
+ scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), w, req)
+ return
+ }
+
+ ae := request.AuditEventFrom(ctx)
+ audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
+ }
+ }
+
+ result, err := finishRequest(timeout, func() (runtime.Object, error) {
+ return r.DeleteCollection(ctx, options, &listOptions)
+ })
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
+ // object with the response.
+ if result == nil {
+ result = &metav1.Status{
+ Status: metav1.StatusSuccess,
+ Code: http.StatusOK,
+ Details: &metav1.StatusDetails{
+ Kind: scope.Kind.Kind,
+ },
+ }
+ } else {
+ // when a non-status response is returned, set the self link
+ if _, ok := result.(*metav1.Status); !ok {
+ if _, err := setListSelfLink(result, ctx, req, scope.Namer); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+ }
+
+ transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/doc.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/doc.go
new file mode 100644
index 0000000..d398338
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/doc.go
@@ -0,0 +1,18 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package handlers contains HTTP handlers to implement the apiserver APIs.
+package handlers // import "k8s.io/apiserver/pkg/endpoints/handlers"
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
new file mode 100644
index 0000000..7672859
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/get.go
@@ -0,0 +1,285 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/golang/glog"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/fields"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apiserver/pkg/endpoints/metrics"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/registry/rest"
+ utiltrace "k8s.io/apiserver/pkg/util/trace"
+)
+
+// getterFunc performs a get request with the given context and object name. The request
+// may be used to deserialize an options object to pass to the getter.
+type getterFunc func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error)
+
+// getResourceHandler is an HTTP handler function for get requests. It delegates to the
+// passed-in getterFunc to perform the actual get.
+func getResourceHandler(scope RequestScope, getter getterFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ trace := utiltrace.New("Get " + req.URL.Path)
+ defer trace.LogIfLong(500 * time.Millisecond)
+
+ namespace, name, err := scope.Namer.Name(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+
+ result, err := getter(ctx, name, req, trace)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ requestInfo, ok := request.RequestInfoFrom(ctx)
+ if !ok {
+ scope.err(fmt.Errorf("missing requestInfo"), w, req)
+ return
+ }
+ if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ trace.Step("About to write a response")
+ transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
+ }
+}
+
+// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
+func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) http.HandlerFunc {
+ return getResourceHandler(scope,
+ func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) {
+ // check for export
+ options := metav1.GetOptions{}
+ if values := req.URL.Query(); len(values) > 0 {
+ exports := metav1.ExportOptions{}
+ if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &exports); err != nil {
+ err = errors.NewBadRequest(err.Error())
+ return nil, err
+ }
+ if exports.Export {
+ if e == nil {
+ return nil, errors.NewBadRequest(fmt.Sprintf("export of %q is not supported", scope.Resource.Resource))
+ }
+ return e.Export(ctx, name, exports)
+ }
+ if err := metainternalversion.ParameterCodec.DecodeParameters(values, scope.MetaGroupVersion, &options); err != nil {
+ err = errors.NewBadRequest(err.Error())
+ return nil, err
+ }
+ }
+ if trace != nil {
+ trace.Step("About to Get from storage")
+ }
+ return r.Get(ctx, name, &options)
+ })
+}
+
+// GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object.
+func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope, isSubresource bool) http.HandlerFunc {
+ return getResourceHandler(scope,
+ func(ctx context.Context, name string, req *http.Request, trace *utiltrace.Trace) (runtime.Object, error) {
+ opts, subpath, subpathKey := r.NewGetOptions()
+ trace.Step("About to process Get options")
+ if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil {
+ err = errors.NewBadRequest(err.Error())
+ return nil, err
+ }
+ if trace != nil {
+ trace.Step("About to Get from storage")
+ }
+ return r.Get(ctx, name, opts)
+ })
+}
+
+// getRequestOptions parses out options and can include path information. The path information shouldn't include the subresource.
+func getRequestOptions(req *http.Request, scope RequestScope, into runtime.Object, subpath bool, subpathKey string, isSubresource bool) error {
+ if into == nil {
+ return nil
+ }
+
+ query := req.URL.Query()
+ if subpath {
+ newQuery := make(url.Values)
+ for k, v := range query {
+ newQuery[k] = v
+ }
+
+ ctx := req.Context()
+ requestInfo, _ := request.RequestInfoFrom(ctx)
+ startingIndex := 2
+ if isSubresource {
+ startingIndex = 3
+ }
+
+ p := strings.Join(requestInfo.Parts[startingIndex:], "/")
+
+ // ensure non-empty subpaths correctly reflect a leading slash
+ if len(p) > 0 && !strings.HasPrefix(p, "/") {
+ p = "/" + p
+ }
+
+ // ensure subpaths correctly reflect the presence of a trailing slash on the original request
+ if strings.HasSuffix(requestInfo.Path, "/") && !strings.HasSuffix(p, "/") {
+ p += "/"
+ }
+
+ newQuery[subpathKey] = []string{p}
+ query = newQuery
+ }
+ return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into)
+}
+
+func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ // For performance tracking purposes.
+ trace := utiltrace.New("List " + req.URL.Path)
+
+ namespace, err := scope.Namer.Namespace(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ // Watches for single objects are routed to this function.
+ // Treat a name parameter the same as a field selector entry.
+ hasName := true
+ _, name, err := scope.Namer.Name(req)
+ if err != nil {
+ hasName = false
+ }
+
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+
+ opts := metainternalversion.ListOptions{}
+ if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, &opts); err != nil {
+ err = errors.NewBadRequest(err.Error())
+ scope.err(err, w, req)
+ return
+ }
+
+ // transform fields
+ // TODO: DecodeParametersInto should do this.
+ if opts.FieldSelector != nil {
+ fn := func(label, value string) (newLabel, newValue string, err error) {
+ return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value)
+ }
+ if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil {
+ // TODO: allow bad request to set field causes based on query parameters
+ err = errors.NewBadRequest(err.Error())
+ scope.err(err, w, req)
+ return
+ }
+ }
+
+ if hasName {
+ // metadata.name is the canonical internal name.
+ // SelectionPredicate will notice that this is a request for
+ // a single object and optimize the storage query accordingly.
+ nameSelector := fields.OneTermEqualSelector("metadata.name", name)
+
+ // Note that fieldSelector setting explicitly the "metadata.name"
+ // will result in reaching this branch (as the value of that field
+ // is propagated to requestInfo as the name parameter.
+ // That said, the allowed field selectors in this branch are:
+ // nil, fields.Everything and field selector matching metadata.name
+ // for our name.
+ if opts.FieldSelector != nil && !opts.FieldSelector.Empty() {
+ selectedName, ok := opts.FieldSelector.RequiresExactMatch("metadata.name")
+ if !ok || name != selectedName {
+ scope.err(errors.NewBadRequest("fieldSelector metadata.name doesn't match requested name"), w, req)
+ return
+ }
+ } else {
+ opts.FieldSelector = nameSelector
+ }
+ }
+
+ if opts.Watch || forceWatch {
+ if rw == nil {
+ scope.err(errors.NewMethodNotSupported(scope.Resource.GroupResource(), "watch"), w, req)
+ return
+ }
+ // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=.
+ timeout := time.Duration(0)
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ if timeout == 0 && minRequestTimeout > 0 {
+ timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0))
+ }
+ glog.V(2).Infof("Starting watch for %s, rv=%s labels=%s fields=%s timeout=%s", req.URL.Path, opts.ResourceVersion, opts.LabelSelector, opts.FieldSelector, timeout)
+
+ watcher, err := rw.Watch(ctx, &opts)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ requestInfo, _ := request.RequestInfoFrom(ctx)
+ metrics.RecordLongRunning(req, requestInfo, func() {
+ serveWatch(watcher, scope, req, w, timeout)
+ })
+ return
+ }
+
+ // Log only long List requests (ignore Watch).
+ defer trace.LogIfLong(500 * time.Millisecond)
+ trace.Step("About to List from storage")
+ result, err := r.List(ctx, &opts)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Listing from storage done")
+ numberOfItems, err := setListSelfLink(result, ctx, req, scope.Namer)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Self-linking done")
+ // Ensure empty lists return a non-nil items slice
+ if numberOfItems == 0 && meta.IsListType(result) {
+ if err := meta.SetList(result, []runtime.Object{}); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+
+ transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
+ trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems))
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/namer.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/namer.go
new file mode 100644
index 0000000..16b4199
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/namer.go
@@ -0,0 +1,135 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apiserver/pkg/endpoints/request"
+)
+
+// ScopeNamer handles accessing names from requests and objects
+type ScopeNamer interface {
+ // Namespace returns the appropriate namespace value from the request (may be empty) or an
+ // error.
+ Namespace(req *http.Request) (namespace string, err error)
+ // 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.
+ Name(req *http.Request) (namespace, name string, err error)
+ // ObjectName returns the namespace and name from an object if they exist, or an error if the object
+ // does not support names.
+ ObjectName(obj runtime.Object) (namespace, name string, err error)
+ // SetSelfLink sets the provided URL onto the object. The method should return nil if the object
+ // does not support selfLinks.
+ SetSelfLink(obj runtime.Object, url string) error
+ // GenerateLink creates an encoded URI for a given runtime object that represents the canonical path
+ // and query.
+ GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error)
+ // GenerateListLink creates an encoded URI for a list that represents the canonical path and query.
+ GenerateListLink(req *http.Request) (uri string, err error)
+}
+
+type ContextBasedNaming struct {
+ SelfLinker runtime.SelfLinker
+ ClusterScoped bool
+
+ SelfLinkPathPrefix string
+ SelfLinkPathSuffix string
+}
+
+// ContextBasedNaming implements ScopeNamer
+var _ ScopeNamer = ContextBasedNaming{}
+
+func (n ContextBasedNaming) SetSelfLink(obj runtime.Object, url string) error {
+ return n.SelfLinker.SetSelfLink(obj, url)
+}
+
+func (n ContextBasedNaming) Namespace(req *http.Request) (namespace string, err error) {
+ requestInfo, ok := request.RequestInfoFrom(req.Context())
+ if !ok {
+ return "", fmt.Errorf("missing requestInfo")
+ }
+ return requestInfo.Namespace, nil
+}
+
+func (n ContextBasedNaming) Name(req *http.Request) (namespace, name string, err error) {
+ requestInfo, ok := request.RequestInfoFrom(req.Context())
+ if !ok {
+ return "", "", fmt.Errorf("missing requestInfo")
+ }
+ ns, err := n.Namespace(req)
+ if err != nil {
+ return "", "", err
+ }
+
+ if len(requestInfo.Name) == 0 {
+ return "", "", errEmptyName
+ }
+ return ns, requestInfo.Name, nil
+}
+
+func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) {
+ namespace, name, err := n.ObjectName(obj)
+ if err == errEmptyName && len(requestInfo.Name) > 0 {
+ name = requestInfo.Name
+ } else if err != nil {
+ return "", err
+ }
+ if len(namespace) == 0 && len(requestInfo.Namespace) > 0 {
+ namespace = requestInfo.Namespace
+ }
+
+ if n.ClusterScoped {
+ return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil
+ }
+
+ return n.SelfLinkPathPrefix +
+ url.QueryEscape(namespace) +
+ "/" + url.QueryEscape(requestInfo.Resource) + "/" +
+ url.QueryEscape(name) +
+ n.SelfLinkPathSuffix,
+ nil
+}
+
+func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) {
+ if len(req.URL.RawPath) > 0 {
+ return req.URL.RawPath, nil
+ }
+ return req.URL.EscapedPath(), nil
+}
+
+func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
+ name, err = n.SelfLinker.Name(obj)
+ if err != nil {
+ return "", "", err
+ }
+ if len(name) == 0 {
+ return "", "", errEmptyName
+ }
+ namespace, err = n.SelfLinker.Namespace(obj)
+ if err != nil {
+ return "", "", err
+ }
+ return namespace, name, err
+}
+
+// errEmptyName is returned when API requests do not fill the name section of the path.
+var errEmptyName = errors.NewBadRequest("name must be provided")
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/doc.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/doc.go
new file mode 100644
index 0000000..80f4feb
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/doc.go
@@ -0,0 +1,18 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package negotiation contains media type negotiation logic.
+package negotiation // import "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
new file mode 100644
index 0000000..93b17cf
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/errors.go
@@ -0,0 +1,69 @@
+/*
+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 negotiation
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// errNotAcceptable indicates Accept negotiation has failed
+type errNotAcceptable struct {
+ accepted []string
+}
+
+func NewNotAcceptableError(accepted []string) error {
+ return errNotAcceptable{accepted}
+}
+
+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.StatusReasonNotAcceptable,
+ Message: e.Error(),
+ }
+}
+
+// errUnsupportedMediaType indicates Content-Type is not recognized
+type errUnsupportedMediaType struct {
+ accepted []string
+}
+
+func NewUnsupportedMediaTypeError(accepted []string) error {
+ return errUnsupportedMediaType{accepted}
+}
+
+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.StatusReasonUnsupportedMediaType,
+ Message: e.Error(),
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
new file mode 100644
index 0000000..f9bb47b
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate.go
@@ -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 negotiation
+
+import (
+ "mime"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "bitbucket.org/ww/goautoneg"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/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
+}
+
+// NegotiateOutputMediaType negotiates the output structured media type and a serializer, or
+// returns an error.
+func NegotiateOutputMediaType(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (MediaTypeOptions, runtime.SerializerInfo, error) {
+ mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), AcceptedMediaTypesForEndpoint(ns), restrictions)
+ if !ok {
+ supported, _ := MediaTypesForSerializer(ns)
+ return mediaType, runtime.SerializerInfo{}, NewNotAcceptableError(supported)
+ }
+ // TODO: move into resthandler
+ info := mediaType.Accepted.Serializer
+ if (mediaType.Pretty || isPrettyPrint(req)) && info.PrettySerializer != nil {
+ info.Serializer = info.PrettySerializer
+ }
+ return mediaType, info, nil
+}
+
+// NegotiateOutputSerializer returns a serializer for the output.
+func NegotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
+ _, info, err := NegotiateOutputMediaType(req, ns, DefaultEndpointRestrictions)
+ return info, err
+}
+
+// NegotiateOutputStreamSerializer returns a stream serializer for the given request.
+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{}, NewNotAcceptableError(supported)
+ }
+ return mediaType.Accepted.Serializer, nil
+}
+
+// NegotiateInputSerializer returns the input serializer for the provided request.
+func NegotiateInputSerializer(req *http.Request, streaming bool, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
+ mediaType := req.Header.Get("Content-Type")
+ return NegotiateInputSerializerForMediaType(mediaType, streaming, ns)
+}
+
+// NegotiateInputSerializerForMediaType returns the appropriate serializer for the given media type or an error.
+func NegotiateInputSerializerForMediaType(mediaType string, streaming bool, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
+ mediaTypes := ns.SupportedMediaTypes()
+ if len(mediaType) == 0 {
+ mediaType = mediaTypes[0].MediaType
+ }
+ if mediaType, _, err := mime.ParseMediaType(mediaType); err == nil {
+ for _, info := range mediaTypes {
+ if info.MediaType != mediaType {
+ continue
+ }
+ return info, nil
+ }
+ }
+
+ supported, streamingSupported := MediaTypesForSerializer(ns)
+ if streaming {
+ return runtime.SerializerInfo{}, NewUnsupportedMediaTypeError(streamingSupported)
+ }
+ return runtime.SerializerInfo{}, NewUnsupportedMediaTypeError(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 browsers and cli http tools
+ if strings.HasPrefix(userAgent, "curl") || strings.HasPrefix(userAgent, "Wget") || strings.HasPrefix(userAgent, "Mozilla/5.0") {
+ return true
+ }
+ return 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
+}
+
+type candidateMediaType struct {
+ accepted *AcceptedMediaType
+ clauses goautoneg.Accept
+}
+
+type candidateMediaTypeSlice []candidateMediaType
+
+// 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
+ }
+
+ var candidates candidateMediaTypeSlice
+ 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 == "*":
+ candidates = append(candidates, candidateMediaType{accepted: accepts, clauses: clause})
+ }
+ }
+ }
+
+ for _, v := range candidates {
+ if retVal, ret := acceptMediaTypeOptions(v.clauses.Params, v.accepted, endpoint); ret {
+ return retVal, true
+ }
+ }
+
+ 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
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
new file mode 100644
index 0000000..0801dce
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/patch.go
@@ -0,0 +1,400 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/evanphx/json-patch"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/json"
+ "k8s.io/apimachinery/pkg/util/mergepatch"
+ "k8s.io/apimachinery/pkg/util/sets"
+ "k8s.io/apimachinery/pkg/util/strategicpatch"
+ "k8s.io/apiserver/pkg/admission"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/registry/rest"
+ utiltrace "k8s.io/apiserver/pkg/util/trace"
+)
+
+// PatchResource returns a function that will handle a resource patch.
+func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface, patchTypes []string) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ // For performance tracking purposes.
+ trace := utiltrace.New("Patch " + req.URL.Path)
+ defer trace.LogIfLong(500 * time.Millisecond)
+
+ if isDryRun(req.URL) {
+ scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
+ return
+ }
+
+ // Do this first, otherwise name extraction can fail for unrecognized content types
+ // TODO: handle this in negotiation
+ contentType := req.Header.Get("Content-Type")
+ // Remove "; charset=" if included in header.
+ if idx := strings.Index(contentType, ";"); idx > 0 {
+ contentType = contentType[:idx]
+ }
+ patchType := types.PatchType(contentType)
+
+ // Ensure the patchType is one we support
+ if !sets.NewString(patchTypes...).Has(contentType) {
+ scope.err(negotiation.NewUnsupportedMediaTypeError(patchTypes), w, req)
+ return
+ }
+
+ // TODO: we either want to remove timeout or document it (if we
+ // document, move timeout out of this function and declare it in
+ // api_installer)
+ timeout := parseTimeout(req.URL.Query().Get("timeout"))
+
+ namespace, name, err := scope.Namer.Name(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+
+ patchJS, err := readBody(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ ae := request.AuditEventFrom(ctx)
+ admit = admission.WithAudit(admit, ae)
+
+ audit.LogRequestPatch(ae, patchJS)
+ trace.Step("Recorded the audit event")
+
+ s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
+ if !ok {
+ scope.err(fmt.Errorf("no serializer defined for JSON"), w, req)
+ return
+ }
+ gv := scope.Kind.GroupVersion()
+ codec := runtime.NewCodec(
+ scope.Serializer.EncoderForVersion(s.Serializer, gv),
+ scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}),
+ )
+
+ userInfo, _ := request.UserFrom(ctx)
+ staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)
+ admissionCheck := func(updatedObject runtime.Object, currentObject runtime.Object) error {
+ if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && admit.Handles(admission.Update) {
+ return mutatingAdmission.Admit(admission.NewAttributesRecord(updatedObject, currentObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
+ }
+ return nil
+ }
+
+ p := patcher{
+ namer: scope.Namer,
+ creater: scope.Creater,
+ defaulter: scope.Defaulter,
+ unsafeConvertor: scope.UnsafeConvertor,
+ kind: scope.Kind,
+ resource: scope.Resource,
+
+ createValidation: rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
+ updateValidation: rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
+ admissionCheck: admissionCheck,
+
+ codec: codec,
+
+ timeout: timeout,
+
+ restPatcher: r,
+ name: name,
+ patchType: patchType,
+ patchJS: patchJS,
+
+ trace: trace,
+ }
+
+ result, err := p.patchResource(ctx)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Object stored in database")
+
+ requestInfo, ok := request.RequestInfoFrom(ctx)
+ if !ok {
+ scope.err(fmt.Errorf("missing requestInfo"), w, req)
+ return
+ }
+ if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Self-link added")
+
+ transformResponseObject(ctx, scope, req, w, http.StatusOK, result)
+ }
+}
+
+type mutateObjectUpdateFunc func(obj, old runtime.Object) error
+
+// patcher breaks the process of patch application and retries into smaller
+// pieces of functionality.
+// TODO: Use builder pattern to construct this object?
+// TODO: As part of that effort, some aspects of PatchResource above could be
+// moved into this type.
+type patcher struct {
+ // Pieces of RequestScope
+ namer ScopeNamer
+ creater runtime.ObjectCreater
+ defaulter runtime.ObjectDefaulter
+ unsafeConvertor runtime.ObjectConvertor
+ resource schema.GroupVersionResource
+ kind schema.GroupVersionKind
+
+ // Validation functions
+ createValidation rest.ValidateObjectFunc
+ updateValidation rest.ValidateObjectUpdateFunc
+ admissionCheck mutateObjectUpdateFunc
+
+ codec runtime.Codec
+
+ timeout time.Duration
+
+ // Operation information
+ restPatcher rest.Patcher
+ name string
+ patchType types.PatchType
+ patchJS []byte
+
+ trace *utiltrace.Trace
+
+ // Set at invocation-time (by applyPatch) and immutable thereafter
+ namespace string
+ updatedObjectInfo rest.UpdatedObjectInfo
+ mechanism patchMechanism
+}
+
+func (p *patcher) toUnversioned(versionedObj runtime.Object) (runtime.Object, error) {
+ gvk := p.kind.GroupKind().WithVersion(runtime.APIVersionInternal)
+ return p.unsafeConvertor.ConvertToVersion(versionedObj, gvk.GroupVersion())
+}
+
+type patchMechanism interface {
+ applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error)
+}
+
+type jsonPatcher struct {
+ *patcher
+}
+
+func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
+ // Encode will convert & return a versioned object in JSON.
+ currentObjJS, err := runtime.Encode(p.codec, currentObject)
+ if err != nil {
+ return nil, err
+ }
+
+ // Apply the patch.
+ patchedObjJS, err := p.applyJSPatch(currentObjJS)
+ if err != nil {
+ return nil, interpretPatchError(err)
+ }
+
+ // Construct the resulting typed, unversioned object.
+ objToUpdate := p.restPatcher.New()
+ if err := runtime.DecodeInto(p.codec, patchedObjJS, objToUpdate); err != nil {
+ return nil, err
+ }
+
+ return objToUpdate, nil
+}
+
+// patchJS applies the patch. Input and output objects must both have
+// the external version, since that is what the patch must have been constructed against.
+func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
+ switch p.patchType {
+ case types.JSONPatchType:
+ patchObj, err := jsonpatch.DecodePatch(p.patchJS)
+ if err != nil {
+ return nil, err
+ }
+ return patchObj.Apply(versionedJS)
+ case types.MergePatchType:
+ return jsonpatch.MergePatch(versionedJS, p.patchJS)
+ default:
+ // only here as a safety net - go-restful filters content-type
+ return nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType)
+ }
+}
+
+type smpPatcher struct {
+ *patcher
+
+ // Schema
+ schemaReferenceObj runtime.Object
+}
+
+func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
+ // Since the patch is applied on versioned objects, we need to convert the
+ // current object to versioned representation first.
+ currentVersionedObject, err := p.unsafeConvertor.ConvertToVersion(currentObject, p.kind.GroupVersion())
+ if err != nil {
+ return nil, err
+ }
+ versionedObjToUpdate, err := p.creater.New(p.kind)
+ if err != nil {
+ return nil, err
+ }
+ if err := strategicPatchObject(p.codec, p.defaulter, currentVersionedObject, p.patchJS, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
+ return nil, err
+ }
+ // Convert the object back to unversioned (aka internal version).
+ unversionedObjToUpdate, err := p.toUnversioned(versionedObjToUpdate)
+ if err != nil {
+ return nil, err
+ }
+
+ return unversionedObjToUpdate, nil
+}
+
+// strategicPatchObject applies a strategic merge patch of <patchJS> to
+// <originalObject> and stores the result in <objToUpdate>.
+// It additionally returns the map[string]interface{} representation of the
+// <originalObject> and <patchJS>.
+// NOTE: Both <originalObject> and <objToUpdate> are supposed to be versioned.
+func strategicPatchObject(
+ codec runtime.Codec,
+ defaulter runtime.ObjectDefaulter,
+ originalObject runtime.Object,
+ patchJS []byte,
+ objToUpdate runtime.Object,
+ schemaReferenceObj runtime.Object,
+) error {
+ originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject)
+ if err != nil {
+ return err
+ }
+
+ patchMap := make(map[string]interface{})
+ if err := json.Unmarshal(patchJS, &patchMap); err != nil {
+ return errors.NewBadRequest(err.Error())
+ }
+
+ if err := applyPatchToObject(codec, defaulter, originalObjMap, patchMap, objToUpdate, schemaReferenceObj); err != nil {
+ return err
+ }
+ return nil
+}
+
+// applyPatch is called every time GuaranteedUpdate asks for the updated object,
+// and is given the currently persisted object as input.
+func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (runtime.Object, error) {
+ // Make sure we actually have a persisted currentObject
+ p.trace.Step("About to apply patch")
+ if hasUID, err := hasUID(currentObject); err != nil {
+ return nil, err
+ } else if !hasUID {
+ return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
+ }
+
+ objToUpdate, err := p.mechanism.applyPatchToCurrentObject(currentObject)
+ if err != nil {
+ return nil, err
+ }
+ if err := checkName(objToUpdate, p.name, p.namespace, p.namer); err != nil {
+ return nil, err
+ }
+ return objToUpdate, nil
+}
+
+// applyAdmission is called every time GuaranteedUpdate asks for the updated object,
+// and is given the currently persisted object and the patched object as input.
+func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) {
+ p.trace.Step("About to check admission control")
+ return patchedObject, p.admissionCheck(patchedObject, currentObject)
+}
+
+// patchResource divides PatchResource for easier unit testing
+func (p *patcher) patchResource(ctx context.Context) (runtime.Object, error) {
+ p.namespace = request.NamespaceValue(ctx)
+ switch p.patchType {
+ case types.JSONPatchType, types.MergePatchType:
+ p.mechanism = &jsonPatcher{patcher: p}
+ case types.StrategicMergePatchType:
+ schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion())
+ if err != nil {
+ return nil, err
+ }
+ p.mechanism = &smpPatcher{patcher: p, schemaReferenceObj: schemaReferenceObj}
+ default:
+ return nil, fmt.Errorf("%v: unimplemented patch type", p.patchType)
+ }
+ p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
+ return finishRequest(p.timeout, func() (runtime.Object, error) {
+ updateObject, _, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation)
+ return updateObject, updateErr
+ })
+}
+
+// applyPatchToObject applies a strategic merge patch of <patchMap> to
+// <originalMap> and stores the result in <objToUpdate>.
+// NOTE: <objToUpdate> must be a versioned object.
+func applyPatchToObject(
+ codec runtime.Codec,
+ defaulter runtime.ObjectDefaulter,
+ originalMap map[string]interface{},
+ patchMap map[string]interface{},
+ objToUpdate runtime.Object,
+ schemaReferenceObj runtime.Object,
+) error {
+ patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, schemaReferenceObj)
+ if err != nil {
+ return interpretPatchError(err)
+ }
+
+ // Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object
+ if err := runtime.DefaultUnstructuredConverter.FromUnstructured(patchedObjMap, objToUpdate); err != nil {
+ return err
+ }
+ // Decoding from JSON to a versioned object would apply defaults, so we do the same here
+ defaulter.Default(objToUpdate)
+
+ return nil
+}
+
+// interpretPatchError interprets the error type and returns an error with appropriate HTTP code.
+func interpretPatchError(err error) error {
+ switch err {
+ case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat:
+ return errors.NewBadRequest(err.Error())
+ case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys:
+ return errors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
+ default:
+ return err
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
new file mode 100644
index 0000000..8cee470
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/response.go
@@ -0,0 +1,195 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+)
+
+// transformResponseObject takes an object loaded from storage and performs any necessary transformations.
+// Will write the complete response object.
+func transformResponseObject(ctx context.Context, scope RequestScope, req *http.Request, w http.ResponseWriter, statusCode int, result runtime.Object) {
+ // TODO: fetch the media type much earlier in request processing and pass it into this method.
+ mediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
+ if err != nil {
+ status := responsewriters.ErrorToAPIStatus(err)
+ responsewriters.WriteRawJSON(int(status.Code), status, w)
+ return
+ }
+
+ // If conversion was allowed by the scope, perform it before writing the response
+ if target := mediaType.Convert; target != nil {
+ switch {
+
+ case target.Kind == "PartialObjectMetadata" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
+ if meta.IsListType(result) {
+ // TODO: this should be calculated earlier
+ err = newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadata, but the requested object is a list (%T)", result))
+ scope.err(err, w, req)
+ return
+ }
+ m, err := meta.Accessor(result)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ partial := meta.AsPartialObjectMetadata(m)
+ partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
+
+ // renegotiate under the internal version
+ _, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
+ responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, partial)
+ return
+
+ case target.Kind == "PartialObjectMetadataList" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
+ if !meta.IsListType(result) {
+ // TODO: this should be calculated earlier
+ err = newNotAcceptableError(fmt.Sprintf("you requested PartialObjectMetadataList, but the requested object is not a list (%T)", result))
+ scope.err(err, w, req)
+ return
+ }
+ list := &metav1beta1.PartialObjectMetadataList{}
+ err := meta.EachListItem(result, func(obj runtime.Object) error {
+ m, err := meta.Accessor(obj)
+ if err != nil {
+ return err
+ }
+ partial := meta.AsPartialObjectMetadata(m)
+ partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
+ list.Items = append(list.Items, partial)
+ return nil
+ })
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ // renegotiate under the internal version
+ _, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
+ responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, list)
+ return
+
+ case target.Kind == "Table" && target.GroupVersion() == metav1beta1.SchemeGroupVersion:
+ // TODO: relax the version abstraction
+ // TODO: skip if this is a status response (delete without body)?
+
+ opts := &metav1beta1.TableOptions{}
+ if err := metav1beta1.ParameterCodec.DecodeParameters(req.URL.Query(), metav1beta1.SchemeGroupVersion, opts); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ table, err := scope.TableConvertor.ConvertToTable(ctx, result, opts)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ for i := range table.Rows {
+ item := &table.Rows[i]
+ switch opts.IncludeObject {
+ case metav1beta1.IncludeObject:
+ item.Object.Object, err = scope.Convertor.ConvertToVersion(item.Object.Object, scope.Kind.GroupVersion())
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ // TODO: rely on defaulting for the value here?
+ case metav1beta1.IncludeMetadata, "":
+ m, err := meta.Accessor(item.Object.Object)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ // TODO: turn this into an internal type and do conversion in order to get object kind automatically set?
+ partial := meta.AsPartialObjectMetadata(m)
+ partial.GetObjectKind().SetGroupVersionKind(metav1beta1.SchemeGroupVersion.WithKind("PartialObjectMetadata"))
+ item.Object.Object = partial
+ case metav1beta1.IncludeNone:
+ item.Object.Object = nil
+ default:
+ // TODO: move this to validation on the table options?
+ err = errors.NewBadRequest(fmt.Sprintf("unrecognized includeObject value: %q", opts.IncludeObject))
+ scope.err(err, w, req)
+ }
+ }
+
+ // renegotiate under the internal version
+ _, info, err := negotiation.NegotiateOutputMediaType(req, metainternalversion.Codecs, &scope)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ encoder := metainternalversion.Codecs.EncoderForVersion(info.Serializer, metav1beta1.SchemeGroupVersion)
+ responsewriters.SerializeObject(info.MediaType, encoder, w, req, statusCode, table)
+ return
+
+ default:
+ // this block should only be hit if scope AllowsConversion is incorrect
+ accepted, _ := negotiation.MediaTypesForSerializer(metainternalversion.Codecs)
+ err := negotiation.NewNotAcceptableError(accepted)
+ status := responsewriters.ErrorToAPIStatus(err)
+ responsewriters.WriteRawJSON(int(status.Code), status, w)
+ return
+ }
+ }
+
+ responsewriters.WriteObject(statusCode, scope.Kind.GroupVersion(), scope.Serializer, result, w, req)
+}
+
+// errNotAcceptable indicates Accept negotiation has failed
+type errNotAcceptable struct {
+ message string
+}
+
+func newNotAcceptableError(message string) error {
+ return errNotAcceptable{message}
+}
+
+func (e errNotAcceptable) Error() string {
+ return e.message
+}
+
+func (e errNotAcceptable) Status() metav1.Status {
+ return metav1.Status{
+ Status: metav1.StatusFailure,
+ Code: http.StatusNotAcceptable,
+ Reason: metav1.StatusReason("NotAcceptable"),
+ Message: e.Error(),
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/doc.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/doc.go
new file mode 100644
index 0000000..b76758b
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/doc.go
@@ -0,0 +1,18 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package responsewriters containers helpers to write responses in HTTP handlers.
+package responsewriters // import "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go
new file mode 100644
index 0000000..007efe9
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go
@@ -0,0 +1,97 @@
+/*
+Copyright 2014 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package responsewriters
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apiserver/pkg/authorization/authorizer"
+)
+
+// Avoid emitting errors that look like valid HTML. Quotes are okay.
+var sanitizer = strings.NewReplacer(`&`, "&", `<`, "<", `>`, ">")
+
+// BadGatewayError renders a simple bad gateway error.
+func BadGatewayError(w http.ResponseWriter, req *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+ w.WriteHeader(http.StatusBadGateway)
+ fmt.Fprintf(w, "Bad Gateway: %q", sanitizer.Replace(req.RequestURI))
+}
+
+// Forbidden renders a simple forbidden error
+func Forbidden(ctx context.Context, attributes authorizer.Attributes, w http.ResponseWriter, req *http.Request, reason string, s runtime.NegotiatedSerializer) {
+ msg := sanitizer.Replace(forbiddenMessage(attributes))
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+
+ var errMsg string
+ if len(reason) == 0 {
+ errMsg = fmt.Sprintf("%s", msg)
+ } else {
+ errMsg = fmt.Sprintf("%s: %s", msg, reason)
+ }
+ gv := schema.GroupVersion{Group: attributes.GetAPIGroup(), Version: attributes.GetAPIVersion()}
+ gr := schema.GroupResource{Group: attributes.GetAPIGroup(), Resource: attributes.GetResource()}
+ ErrorNegotiated(apierrors.NewForbidden(gr, attributes.GetName(), fmt.Errorf(errMsg)), s, gv, w, req)
+}
+
+func forbiddenMessage(attributes authorizer.Attributes) string {
+ username := ""
+ if user := attributes.GetUser(); user != nil {
+ username = user.GetName()
+ }
+
+ if !attributes.IsResourceRequest() {
+ return fmt.Sprintf("User %q cannot %s path %q", username, attributes.GetVerb(), attributes.GetPath())
+ }
+
+ resource := attributes.GetResource()
+ if group := attributes.GetAPIGroup(); len(group) > 0 {
+ resource = resource + "." + group
+ }
+ if subresource := attributes.GetSubresource(); len(subresource) > 0 {
+ resource = resource + "/" + subresource
+ }
+
+ if ns := attributes.GetNamespace(); len(ns) > 0 {
+ return fmt.Sprintf("User %q cannot %s %s in the namespace %q", username, attributes.GetVerb(), resource, ns)
+ }
+
+ return fmt.Sprintf("User %q cannot %s %s at the cluster scope", username, attributes.GetVerb(), resource)
+}
+
+// InternalError renders a simple internal error
+func InternalError(w http.ResponseWriter, req *http.Request, err error) {
+ w.Header().Set("Content-Type", "text/plain")
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprintf(w, "Internal Server Error: %q: %v", sanitizer.Replace(req.RequestURI), err)
+ utilruntime.HandleError(err)
+}
+
+// NotFound renders a simple not found error.
+func NotFound(w http.ResponseWriter, req *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ fmt.Fprintf(w, "Not Found: %q", sanitizer.Replace(req.RequestURI))
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
new file mode 100755
index 0000000..9967307
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/status.go
@@ -0,0 +1,76 @@
+/*
+Copyright 2014 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package responsewriters
+
+import (
+ "fmt"
+ "net/http"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apiserver/pkg/storage"
+)
+
+// statusError is an object that can be converted into an metav1.Status
+type statusError interface {
+ Status() metav1.Status
+}
+
+// ErrorToAPIStatus converts an error to an metav1.Status object.
+func ErrorToAPIStatus(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
+ }
+ }
+ status.Kind = "Status"
+ status.APIVersion = "v1"
+ //TODO: check for invalid responses
+ return &status
+ default:
+ status := http.StatusInternalServerError
+ switch {
+ //TODO: replace me with NewConflictErr
+ case storage.IsConflict(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{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Status",
+ APIVersion: "v1",
+ },
+ Status: metav1.StatusFailure,
+ Code: int32(status),
+ Reason: metav1.StatusReasonUnknown,
+ Message: err.Error(),
+ }
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
new file mode 100644
index 0000000..0add873
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go
@@ -0,0 +1,174 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package responsewriters
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/metrics"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/registry/rest"
+ "k8s.io/apiserver/pkg/util/flushwriter"
+ "k8s.io/apiserver/pkg/util/wsstream"
+)
+
+// WriteObject renders a returned runtime.Object to the response as a stream or an encoded object. If the object
+// returned by the response implements rest.ResourceStreamer that interface will be used to render the
+// response. The Accept header and current API version will be passed in, and the output will be copied
+// directly to the response body. If content type is returned it is used, otherwise the content type will
+// be "application/octet-stream". All other objects are sent to standard JSON serialization.
+func WriteObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) {
+ stream, ok := object.(rest.ResourceStreamer)
+ if ok {
+ requestInfo, _ := request.RequestInfoFrom(req.Context())
+ metrics.RecordLongRunning(req, requestInfo, func() {
+ StreamObject(statusCode, gv, s, stream, w, req)
+ })
+ return
+ }
+ WriteObjectNegotiated(s, gv, w, req, statusCode, object)
+}
+
+// StreamObject performs input stream negotiation from a ResourceStreamer and writes that to the response.
+// If the client requests a websocket upgrade, negotiate for a websocket reader protocol (because many
+// browser clients cannot easily handle binary streaming protocols).
+func StreamObject(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, stream rest.ResourceStreamer, w http.ResponseWriter, req *http.Request) {
+ out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept"))
+ if err != nil {
+ ErrorNegotiated(err, s, gv, w, req)
+ return
+ }
+ if out == nil {
+ // No output provided - return StatusNoContent
+ w.WriteHeader(http.StatusNoContent)
+ return
+ }
+ defer out.Close()
+
+ if wsstream.IsWebSocketRequest(req) {
+ r := wsstream.NewReader(out, true, wsstream.NewDefaultReaderProtocols())
+ if err := r.Copy(w, req); err != nil {
+ utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err))
+ }
+ return
+ }
+
+ if len(contentType) == 0 {
+ contentType = "application/octet-stream"
+ }
+ w.Header().Set("Content-Type", contentType)
+ w.WriteHeader(statusCode)
+ writer := w.(io.Writer)
+ if flush {
+ writer = flushwriter.Wrap(w)
+ }
+ io.Copy(writer, out)
+}
+
+// SerializeObject renders an object in the content type negotiated by the client using the provided encoder.
+// The context is optional and can be nil.
+func SerializeObject(mediaType string, encoder runtime.Encoder, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
+ w.Header().Set("Content-Type", mediaType)
+ w.WriteHeader(statusCode)
+
+ if err := encoder.Encode(object, w); err != nil {
+ errorJSONFatal(err, encoder, w)
+ }
+}
+
+// WriteObjectNegotiated renders an object in the content type negotiated by the client.
+// The context is optional and can be nil.
+func WriteObjectNegotiated(s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
+ serializer, err := negotiation.NegotiateOutputSerializer(req, s)
+ if err != nil {
+ // if original statusCode was not successful we need to return the original error
+ // we cannot hide it behind negotiation problems
+ if statusCode < http.StatusOK || statusCode >= http.StatusBadRequest {
+ WriteRawJSON(int(statusCode), object, w)
+ return
+ }
+ status := ErrorToAPIStatus(err)
+ WriteRawJSON(int(status.Code), status, w)
+ return
+ }
+
+ if ae := request.AuditEventFrom(req.Context()); ae != nil {
+ audit.LogResponseObject(ae, object, gv, s)
+ }
+
+ encoder := s.EncoderForVersion(serializer.Serializer, gv)
+ SerializeObject(serializer.MediaType, encoder, w, req, statusCode, object)
+}
+
+// ErrorNegotiated renders an error to the response. Returns the HTTP status code of the error.
+// The context is optional and may be nil.
+func ErrorNegotiated(err error, s runtime.NegotiatedSerializer, gv schema.GroupVersion, w http.ResponseWriter, req *http.Request) int {
+ status := ErrorToAPIStatus(err)
+ code := int(status.Code)
+ // when writing an error, check to see if the status indicates a retry after period
+ if status.Details != nil && status.Details.RetryAfterSeconds > 0 {
+ delay := strconv.Itoa(int(status.Details.RetryAfterSeconds))
+ w.Header().Set("Retry-After", delay)
+ }
+
+ if code == http.StatusNoContent {
+ w.WriteHeader(code)
+ return code
+ }
+
+ WriteObjectNegotiated(s, gv, w, req, code, status)
+ return code
+}
+
+// errorJSONFatal renders an error to the response, and if codec fails will render plaintext.
+// Returns the HTTP status code of the error.
+func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int {
+ utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
+ status := ErrorToAPIStatus(err)
+ code := int(status.Code)
+ output, err := runtime.Encode(codec, status)
+ if err != nil {
+ w.WriteHeader(code)
+ fmt.Fprintf(w, "%s: %s", status.Reason, status.Message)
+ return code
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ w.Write(output)
+ return code
+}
+
+// WriteRawJSON writes a non-API object in JSON.
+func WriteRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
+ output, err := json.MarshalIndent(object, "", " ")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(statusCode)
+ w.Write(output)
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
new file mode 100644
index 0000000..d42c019
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/rest.go
@@ -0,0 +1,330 @@
+/*
+Copyright 2014 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "context"
+ "encoding/hex"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/golang/glog"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apiserver/pkg/admission"
+ "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
+ "k8s.io/apiserver/pkg/endpoints/metrics"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/registry/rest"
+ openapiproto "k8s.io/kube-openapi/pkg/util/proto"
+)
+
+// RequestScope encapsulates common fields across all RESTful handler methods.
+type RequestScope struct {
+ Namer ScopeNamer
+
+ Serializer runtime.NegotiatedSerializer
+ runtime.ParameterCodec
+
+ Creater runtime.ObjectCreater
+ Convertor runtime.ObjectConvertor
+ Defaulter runtime.ObjectDefaulter
+ Typer runtime.ObjectTyper
+ UnsafeConvertor runtime.ObjectConvertor
+
+ TableConvertor rest.TableConvertor
+ OpenAPISchema openapiproto.Schema
+
+ Resource schema.GroupVersionResource
+ Kind schema.GroupVersionKind
+ Subresource string
+
+ MetaGroupVersion schema.GroupVersion
+}
+
+func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Request) {
+ responsewriters.ErrorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req)
+}
+
+func (scope *RequestScope) AllowsConversion(gvk schema.GroupVersionKind) bool {
+ // TODO: this is temporary, replace with an abstraction calculated at endpoint installation time
+ if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion {
+ switch gvk.Kind {
+ case "Table":
+ return scope.TableConvertor != nil
+ case "PartialObjectMetadata", "PartialObjectMetadataList":
+ // TODO: should delineate between lists and non-list endpoints
+ return true
+ default:
+ return false
+ }
+ }
+ return false
+}
+
+func (scope *RequestScope) AllowsServerVersion(version string) bool {
+ return version == scope.MetaGroupVersion.Version
+}
+
+func (scope *RequestScope) AllowsStreamSchema(s string) bool {
+ return s == "watch"
+}
+
+// ConnectResource returns a function that handles a connect request on a rest.Storage object.
+func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ namespace, name, err := scope.Namer.Name(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+ ae := request.AuditEventFrom(ctx)
+ admit = admission.WithAudit(admit, ae)
+
+ opts, subpath, subpathKey := connecter.NewConnectOptions()
+ if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil {
+ err = errors.NewBadRequest(err.Error())
+ scope.err(err, w, req)
+ return
+ }
+ if admit != nil && admit.Handles(admission.Connect) {
+ connectRequest := &rest.ConnectRequest{
+ Name: name,
+ Options: opts,
+ ResourcePath: restPath,
+ }
+ userInfo, _ := request.UserFrom(ctx)
+ // TODO: remove the mutating admission here as soon as we have ported all plugin that handle CONNECT
+ if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
+ err = mutatingAdmission.Admit(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo))
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+ if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
+ err = validatingAdmission.Validate(admission.NewAttributesRecord(connectRequest, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo))
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ }
+ }
+ requestInfo, _ := request.RequestInfoFrom(ctx)
+ metrics.RecordLongRunning(req, requestInfo, func() {
+ handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, w: w})
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ handler.ServeHTTP(w, req)
+ })
+ }
+}
+
+// responder implements rest.Responder for assisting a connector in writing objects or errors.
+type responder struct {
+ scope RequestScope
+ req *http.Request
+ w http.ResponseWriter
+}
+
+func (r *responder) Object(statusCode int, obj runtime.Object) {
+ responsewriters.WriteObject(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.w, r.req)
+}
+
+func (r *responder) Error(err error) {
+ r.scope.err(err, r.w, r.req)
+}
+
+// resultFunc is a function that returns a rest result and can be run in a goroutine
+type resultFunc func() (runtime.Object, error)
+
+// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response.
+// An api.Status object with status != success is considered an "error", which interrupts the normal response flow.
+func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object, err error) {
+ // these channels need to be buffered to prevent the goroutine below from hanging indefinitely
+ // when the select statement reads something other than the one the goroutine sends on.
+ ch := make(chan runtime.Object, 1)
+ errCh := make(chan error, 1)
+ panicCh := make(chan interface{}, 1)
+ go func() {
+ // panics don't cross goroutine boundaries, so we have to handle ourselves
+ defer utilruntime.HandleCrash(func(panicReason interface{}) {
+ // Propagate to parent goroutine
+ panicCh <- panicReason
+ })
+
+ if result, err := fn(); err != nil {
+ errCh <- err
+ } else {
+ ch <- result
+ }
+ }()
+
+ select {
+ case result = <-ch:
+ if status, ok := result.(*metav1.Status); ok {
+ if status.Status != metav1.StatusSuccess {
+ return nil, errors.FromObject(status)
+ }
+ }
+ return result, nil
+ case err = <-errCh:
+ return nil, err
+ case p := <-panicCh:
+ panic(p)
+ case <-time.After(timeout):
+ return nil, errors.NewTimeoutError("request did not complete within allowed duration", 0)
+ }
+}
+
+// transformDecodeError adds additional information when a decode fails.
+func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, gvk *schema.GroupVersionKind, body []byte) error {
+ objGVKs, _, err := typer.ObjectKinds(into)
+ if err != nil {
+ return err
+ }
+ objGVK := objGVKs[0]
+ if gvk != nil && len(gvk.Kind) > 0 {
+ return errors.NewBadRequest(fmt.Sprintf("%s in version %q cannot be handled as a %s: %v", gvk.Kind, gvk.Version, objGVK.Kind, baseErr))
+ }
+ summary := summarizeData(body, 30)
+ return errors.NewBadRequest(fmt.Sprintf("the object provided is unrecognized (must be of type %s): %v (%s)", objGVK.Kind, baseErr, summary))
+}
+
+// setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request
+// plus the path and query generated by the provided linkFunc
+func setSelfLink(obj runtime.Object, requestInfo *request.RequestInfo, namer ScopeNamer) error {
+ // TODO: SelfLink generation should return a full URL?
+ uri, err := namer.GenerateLink(requestInfo, obj)
+ if err != nil {
+ return nil
+ }
+
+ return namer.SetSelfLink(obj, uri)
+}
+
+func hasUID(obj runtime.Object) (bool, error) {
+ if obj == nil {
+ return false, nil
+ }
+ accessor, err := meta.Accessor(obj)
+ if err != nil {
+ return false, errors.NewInternalError(err)
+ }
+ if len(accessor.GetUID()) == 0 {
+ return false, nil
+ }
+ return true, nil
+}
+
+// checkName checks the provided name against the request
+func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error {
+ objNamespace, objName, err := namer.ObjectName(obj)
+ if err != nil {
+ return errors.NewBadRequest(fmt.Sprintf(
+ "the name of the object (%s based on URL) was undeterminable: %v", name, err))
+ }
+ if objName != name {
+ return errors.NewBadRequest(fmt.Sprintf(
+ "the name of the object (%s) does not match the name on the URL (%s)", objName, name))
+ }
+ if len(namespace) > 0 {
+ if len(objNamespace) > 0 && objNamespace != namespace {
+ return errors.NewBadRequest(fmt.Sprintf(
+ "the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace))
+ }
+ }
+
+ return nil
+}
+
+// setListSelfLink sets the self link of a list to the base URL, then sets the self links
+// on all child objects returned. Returns the number of items in the list.
+func setListSelfLink(obj runtime.Object, ctx context.Context, req *http.Request, namer ScopeNamer) (int, error) {
+ if !meta.IsListType(obj) {
+ return 0, nil
+ }
+
+ uri, err := namer.GenerateListLink(req)
+ if err != nil {
+ return 0, err
+ }
+ if err := namer.SetSelfLink(obj, uri); err != nil {
+ glog.V(4).Infof("Unable to set self link on object: %v", err)
+ }
+ requestInfo, ok := request.RequestInfoFrom(ctx)
+ if !ok {
+ return 0, fmt.Errorf("missing requestInfo")
+ }
+
+ count := 0
+ err = meta.EachListItem(obj, func(obj runtime.Object) error {
+ count++
+ return setSelfLink(obj, requestInfo, namer)
+ })
+ return count, err
+}
+
+func summarizeData(data []byte, maxLength int) string {
+ switch {
+ case len(data) == 0:
+ return "<empty>"
+ case data[0] == '{':
+ if len(data) > maxLength {
+ return string(data[:maxLength]) + " ..."
+ }
+ return string(data)
+ default:
+ if len(data) > maxLength {
+ return hex.EncodeToString(data[:maxLength]) + " ..."
+ }
+ return hex.EncodeToString(data)
+ }
+}
+
+func readBody(req *http.Request) ([]byte, error) {
+ defer req.Body.Close()
+ return ioutil.ReadAll(req.Body)
+}
+
+func parseTimeout(str string) time.Duration {
+ if str != "" {
+ timeout, err := time.ParseDuration(str)
+ if err == nil {
+ return timeout
+ }
+ glog.Errorf("Failed to parse %q: %v", str, err)
+ }
+ return 30 * time.Second
+}
+
+func isDryRun(url *url.URL) bool {
+ return len(url.Query()["dryRun"]) != 0
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
new file mode 100644
index 0000000..de24277
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/update.go
@@ -0,0 +1,142 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "time"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apiserver/pkg/admission"
+ "k8s.io/apiserver/pkg/audit"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/registry/rest"
+ utiltrace "k8s.io/apiserver/pkg/util/trace"
+)
+
+// UpdateResource returns a function that will handle a resource update
+func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interface) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ // For performance tracking purposes.
+ trace := utiltrace.New("Update " + req.URL.Path)
+ defer trace.LogIfLong(500 * time.Millisecond)
+
+ if isDryRun(req.URL) {
+ scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
+ return
+ }
+
+ // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
+ timeout := parseTimeout(req.URL.Query().Get("timeout"))
+
+ namespace, name, err := scope.Namer.Name(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ ctx := req.Context()
+ ctx = request.WithNamespace(ctx, namespace)
+
+ body, err := readBody(req)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ defaultGVK := scope.Kind
+ original := r.New()
+ trace.Step("About to convert to expected version")
+ decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: defaultGVK.Group, Version: runtime.APIVersionInternal})
+ obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
+ if err != nil {
+ err = transformDecodeError(scope.Typer, err, original, gvk, body)
+ scope.err(err, w, req)
+ return
+ }
+ if gvk.GroupVersion() != defaultGVK.GroupVersion() {
+ err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", gvk.GroupVersion(), defaultGVK.GroupVersion()))
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Conversion done")
+
+ ae := request.AuditEventFrom(ctx)
+ audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
+ admit = admission.WithAudit(admit, ae)
+
+ if err := checkName(obj, name, namespace, scope.Namer); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+
+ userInfo, _ := request.UserFrom(ctx)
+ staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)
+ var transformers []rest.TransformFunc
+ if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Update) {
+ transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
+ return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
+ })
+ }
+
+ trace.Step("About to store object in database")
+ wasCreated := false
+ result, err := finishRequest(timeout, func() (runtime.Object, error) {
+ obj, created, err := r.Update(
+ ctx,
+ name,
+ rest.DefaultUpdatedObjectInfo(obj, transformers...),
+ rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
+ rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
+ )
+ wasCreated = created
+ return obj, err
+ })
+ if err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Object stored in database")
+
+ requestInfo, ok := request.RequestInfoFrom(ctx)
+ if !ok {
+ scope.err(fmt.Errorf("missing requestInfo"), w, req)
+ return
+ }
+ if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
+ scope.err(err, w, req)
+ return
+ }
+ trace.Step("Self-link added")
+
+ status := http.StatusOK
+ if wasCreated {
+ status = http.StatusCreated
+ }
+
+ transformResponseObject(ctx, scope, req, w, status, result)
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
new file mode 100755
index 0000000..c1bc984
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/handlers/watch.go
@@ -0,0 +1,324 @@
+/*
+Copyright 2014 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package handlers
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "reflect"
+ "time"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/serializer/streaming"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/metrics"
+ "k8s.io/apiserver/pkg/endpoints/request"
+ "k8s.io/apiserver/pkg/server/httplog"
+ "k8s.io/apiserver/pkg/util/wsstream"
+
+ "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
+}
+
+// TimeoutCh 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 *http.Request, w http.ResponseWriter, timeout time.Duration) {
+ // negotiate for the stream serializer
+ serializer, err := negotiation.NegotiateOutputStreamSerializer(req, scope.Serializer)
+ if err != nil {
+ scope.err(err, w, req)
+ 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), w, req)
+ 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"
+ }
+
+ ctx := req.Context()
+ requestInfo, ok := request.RequestInfoFrom(ctx)
+ if !ok {
+ scope.err(fmt.Errorf("missing requestInfo"), w, req)
+ return
+ }
+
+ 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, requestInfo, scope.Namer); err != nil {
+ utilruntime.HandleError(fmt.Errorf("failed to set link for object %v: %v", reflect.TypeOf(obj), err))
+ }
+ },
+
+ TimeoutFactory: &realTimeoutFactory{timeout},
+ }
+
+ server.ServeHTTP(w, req)
+}
+
+// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
+type WatchServer struct {
+ Watching watch.Interface
+ Scope RequestScope
+
+ // true if websocket messages should use text framing (as opposed to binary framing)
+ UseTextFraming bool
+ // the media type this watch is being served with
+ MediaType string
+ // used to frame the watch stream
+ Framer runtime.Framer
+ // used to encode the watch stream event itself
+ Encoder runtime.Encoder
+ // used to encode the nested object in the watch stream
+ EmbeddedEncoder runtime.Encoder
+ Fixup func(runtime.Object)
+
+ TimeoutFactory TimeoutFactory
+}
+
+// ServeHTTP serves a series of encoded events via HTTP with Transfer-Encoding: chunked
+// or over a websocket connection.
+func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ kind := s.Scope.Kind
+ metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc()
+ defer metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Dec()
+
+ w = httplog.Unlogged(w)
+
+ if wsstream.IsWebSocketRequest(req) {
+ w.Header().Set("Content-Type", s.MediaType)
+ websocket.Handler(s.HandleWS).ServeHTTP(w, req)
+ return
+ }
+
+ cn, ok := w.(http.CloseNotifier)
+ if !ok {
+ err := fmt.Errorf("unable to start watch - can't get http.CloseNotifier: %#v", w)
+ utilruntime.HandleError(err)
+ s.Scope.err(errors.NewInternalError(err), w, req)
+ return
+ }
+ flusher, ok := w.(http.Flusher)
+ if !ok {
+ err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w)
+ utilruntime.HandleError(err)
+ s.Scope.err(errors.NewInternalError(err), w, req)
+ return
+ }
+
+ framer := s.Framer.NewFrameWriter(w)
+ if framer == nil {
+ // programmer error
+ err := fmt.Errorf("no stream framing support is available for media type %q", s.MediaType)
+ utilruntime.HandleError(err)
+ s.Scope.err(errors.NewBadRequest(err.Error()), w, req)
+ return
+ }
+ e := streaming.NewEncoder(framer, s.Encoder)
+
+ // ensure the connection times out
+ timeoutCh, cleanup := s.TimeoutFactory.TimeoutCh()
+ defer cleanup()
+ defer s.Watching.Stop()
+
+ // begin the stream
+ w.Header().Set("Content-Type", s.MediaType)
+ w.Header().Set("Transfer-Encoding", "chunked")
+ w.WriteHeader(http.StatusOK)
+ flusher.Flush()
+
+ var unknown runtime.Unknown
+ internalEvent := &metav1.InternalEvent{}
+ buf := &bytes.Buffer{}
+ ch := s.Watching.ResultChan()
+ for {
+ select {
+ case <-cn.CloseNotify():
+ return
+ case <-timeoutCh:
+ return
+ case event, ok := <-ch:
+ if !ok {
+ // End of results.
+ return
+ }
+
+ obj := event.Object
+ s.Fixup(obj)
+ if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
+ // unexpected error
+ utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
+ return
+ }
+
+ // ContentType is not required here because we are defaulting to the serializer
+ // type
+ unknown.Raw = buf.Bytes()
+ event.Object = &unknown
+
+ // create the external type directly and encode it. Clients will only recognize the serialization we provide.
+ // The internal event is being reused, not reallocated so its just a few extra assignments to do it this way
+ // and we get the benefit of using conversion functions which already have to stay in sync
+ outEvent := &metav1.WatchEvent{}
+ *internalEvent = metav1.InternalEvent(event)
+ err := metav1.Convert_versioned_InternalEvent_to_versioned_Event(internalEvent, outEvent, nil)
+ if err != nil {
+ utilruntime.HandleError(fmt.Errorf("unable to convert watch object: %v", err))
+ // client disconnect.
+ return
+ }
+ if err := e.Encode(outEvent); err != nil {
+ utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v (%#v)", err, e))
+ // client disconnect.
+ return
+ }
+ if len(ch) == 0 {
+ flusher.Flush()
+ }
+
+ buf.Reset()
+ }
+ }
+}
+
+// HandleWS implements a websocket handler.
+func (s *WatchServer) HandleWS(ws *websocket.Conn) {
+ defer ws.Close()
+ done := make(chan struct{})
+
+ go func() {
+ defer utilruntime.HandleCrash()
+ // This blocks until the connection is closed.
+ // Client should not send anything.
+ wsstream.IgnoreReceives(ws, 0)
+ // Once the client closes, we should also close
+ close(done)
+ }()
+
+ var unknown runtime.Unknown
+ internalEvent := &metav1.InternalEvent{}
+ buf := &bytes.Buffer{}
+ streamBuf := &bytes.Buffer{}
+ ch := s.Watching.ResultChan()
+ for {
+ select {
+ case <-done:
+ s.Watching.Stop()
+ return
+ case event, ok := <-ch:
+ if !ok {
+ // End of results.
+ return
+ }
+ obj := event.Object
+ s.Fixup(obj)
+ if err := s.EmbeddedEncoder.Encode(obj, buf); err != nil {
+ // unexpected error
+ utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err))
+ return
+ }
+
+ // ContentType is not required here because we are defaulting to the serializer
+ // type
+ unknown.Raw = buf.Bytes()
+ event.Object = &unknown
+
+ // the internal event will be versioned by the encoder
+ // create the external type directly and encode it. Clients will only recognize the serialization we provide.
+ // The internal event is being reused, not reallocated so its just a few extra assignments to do it this way
+ // and we get the benefit of using conversion functions which already have to stay in sync
+ outEvent := &metav1.WatchEvent{}
+ *internalEvent = metav1.InternalEvent(event)
+ err := metav1.Convert_versioned_InternalEvent_to_versioned_Event(internalEvent, outEvent, nil)
+ if err != nil {
+ utilruntime.HandleError(fmt.Errorf("unable to convert watch object: %v", err))
+ // client disconnect.
+ s.Watching.Stop()
+ return
+ }
+ if err := s.Encoder.Encode(outEvent, 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()
+ }
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/installer.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/installer.go
new file mode 100644
index 0000000..3edd09d
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/installer.go
@@ -0,0 +1,1064 @@
+/*
+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 endpoints
+
+import (
+ "fmt"
+ "net/http"
+ gpath "path"
+ "reflect"
+ "sort"
+ "strings"
+ "time"
+ "unicode"
+
+ restful "github.com/emicklei/go-restful"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/conversion"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apiserver/pkg/admission"
+ "k8s.io/apiserver/pkg/endpoints/handlers"
+ "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
+ "k8s.io/apiserver/pkg/endpoints/metrics"
+ "k8s.io/apiserver/pkg/registry/rest"
+ genericfilters "k8s.io/apiserver/pkg/server/filters"
+ utilopenapi "k8s.io/apiserver/pkg/util/openapi"
+ openapibuilder "k8s.io/kube-openapi/pkg/builder"
+)
+
+const (
+ ROUTE_META_GVK = "x-kubernetes-group-version-kind"
+ ROUTE_META_ACTION = "x-kubernetes-action"
+)
+
+type APIInstaller struct {
+ group *APIGroupVersion
+ prefix string // Path prefix where API resources are to be registered.
+ minRequestTimeout time.Duration
+ enableAPIResponseCompression bool
+}
+
+// Struct capturing information about an action ("GET", "POST", "WATCH", "PROXY", etc).
+type action struct {
+ Verb string // Verb identifying the action ("GET", "POST", "WATCH", "PROXY", etc).
+ Path string // The path of the action
+ Params []*restful.Parameter // List of parameters associated with the action.
+ Namer handlers.ScopeNamer
+ AllNamespaces bool // true iff the action is namespaced but works on aggregate result for all namespaces
+}
+
+// An interface to see if one storage supports override its default verb for monitoring
+type StorageMetricsOverride interface {
+ // OverrideMetricsVerb gives a storage object an opportunity to override the verb reported to the metrics endpoint
+ OverrideMetricsVerb(oldVerb string) (newVerb string)
+}
+
+// An interface to see if an object supports swagger documentation as a method
+type documentable interface {
+ SwaggerDoc() map[string]string
+}
+
+// toDiscoveryKubeVerb maps an action.Verb to the logical kube verb, used for discovery
+var toDiscoveryKubeVerb = map[string]string{
+ "CONNECT": "", // do not list in discovery.
+ "DELETE": "delete",
+ "DELETECOLLECTION": "deletecollection",
+ "GET": "get",
+ "LIST": "list",
+ "PATCH": "patch",
+ "POST": "create",
+ "PROXY": "proxy",
+ "PUT": "update",
+ "WATCH": "watch",
+ "WATCHLIST": "watch",
+}
+
+// Install handlers for API resources.
+func (a *APIInstaller) Install() ([]metav1.APIResource, *restful.WebService, []error) {
+ var apiResources []metav1.APIResource
+ var errors []error
+ ws := a.newWebService()
+
+ // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
+ paths := make([]string, len(a.group.Storage))
+ var i int = 0
+ for path := range a.group.Storage {
+ paths[i] = path
+ i++
+ }
+ sort.Strings(paths)
+ for _, path := range paths {
+ apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
+ if err != nil {
+ errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
+ }
+ if apiResource != nil {
+ apiResources = append(apiResources, *apiResource)
+ }
+ }
+ return apiResources, ws, errors
+}
+
+// newWebService creates a new restful webservice with the api installer's prefix and version.
+func (a *APIInstaller) newWebService() *restful.WebService {
+ ws := new(restful.WebService)
+ ws.Path(a.prefix)
+ // a.prefix contains "prefix/group/version"
+ ws.Doc("API at " + a.prefix)
+ // Backwards compatibility, we accepted objects with empty content-type at V1.
+ // If we stop using go-restful, we can default empty content-type to application/json on an
+ // endpoint by endpoint basis
+ ws.Consumes("*/*")
+ mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
+ ws.Produces(append(mediaTypes, streamMediaTypes...)...)
+ ws.ApiVersion(a.group.GroupVersion.String())
+
+ return ws
+}
+
+// getResourceKind returns the external group version kind registered for the given storage
+// object. If the storage object is a subresource and has an override supplied for it, it returns
+// the group version kind supplied in the override.
+func (a *APIInstaller) getResourceKind(path string, storage rest.Storage) (schema.GroupVersionKind, error) {
+ // Let the storage tell us exactly what GVK it has
+ if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok {
+ return gvkProvider.GroupVersionKind(a.group.GroupVersion), nil
+ }
+
+ object := storage.New()
+ fqKinds, _, err := a.group.Typer.ObjectKinds(object)
+ if err != nil {
+ return schema.GroupVersionKind{}, err
+ }
+
+ // a given go type can have multiple potential fully qualified kinds. Find the one that corresponds with the group
+ // we're trying to register here
+ fqKindToRegister := schema.GroupVersionKind{}
+ for _, fqKind := range fqKinds {
+ if fqKind.Group == a.group.GroupVersion.Group {
+ fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
+ break
+ }
+ }
+ if fqKindToRegister.Empty() {
+ return schema.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion)
+ }
+
+ // group is guaranteed to match based on the check above
+ return fqKindToRegister, nil
+}
+
+func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, error) {
+ admit := a.group.Admit
+
+ optionsExternalVersion := a.group.GroupVersion
+ if a.group.OptionsExternalVersion != nil {
+ optionsExternalVersion = *a.group.OptionsExternalVersion
+ }
+
+ resource, subresource, err := splitSubresource(path)
+ if err != nil {
+ return nil, err
+ }
+
+ fqKindToRegister, err := a.getResourceKind(path, storage)
+ if err != nil {
+ return nil, err
+ }
+
+ versionedPtr, err := a.group.Creater.New(fqKindToRegister)
+ if err != nil {
+ return nil, err
+ }
+ defaultVersionedObject := indirectArbitraryPointer(versionedPtr)
+ kind := fqKindToRegister.Kind
+ isSubresource := len(subresource) > 0
+
+ // If there is a subresource, namespace scoping is defined by the parent resource
+ namespaceScoped := true
+ if isSubresource {
+ parentStorage, ok := a.group.Storage[resource]
+ if !ok {
+ return nil, fmt.Errorf("missing parent storage: %q", resource)
+ }
+ scoper, ok := parentStorage.(rest.Scoper)
+ if !ok {
+ return nil, fmt.Errorf("%q must implement scoper", resource)
+ }
+ namespaceScoped = scoper.NamespaceScoped()
+
+ } else {
+ scoper, ok := storage.(rest.Scoper)
+ if !ok {
+ return nil, fmt.Errorf("%q must implement scoper", resource)
+ }
+ namespaceScoped = scoper.NamespaceScoped()
+ }
+
+ // what verbs are supported by the storage, used to know what verbs we support per path
+ creater, isCreater := storage.(rest.Creater)
+ namedCreater, isNamedCreater := storage.(rest.NamedCreater)
+ lister, isLister := storage.(rest.Lister)
+ getter, isGetter := storage.(rest.Getter)
+ getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
+ gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
+ collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
+ updater, isUpdater := storage.(rest.Updater)
+ patcher, isPatcher := storage.(rest.Patcher)
+ watcher, isWatcher := storage.(rest.Watcher)
+ connecter, isConnecter := storage.(rest.Connecter)
+ storageMeta, isMetadata := storage.(rest.StorageMetadata)
+ if !isMetadata {
+ storageMeta = defaultStorageMetadata{}
+ }
+ exporter, isExporter := storage.(rest.Exporter)
+ if !isExporter {
+ exporter = nil
+ }
+
+ versionedExportOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ExportOptions"))
+ if err != nil {
+ return nil, err
+ }
+
+ if isNamedCreater {
+ isCreater = true
+ }
+
+ var versionedList interface{}
+ if isLister {
+ list := lister.NewList()
+ listGVKs, _, err := a.group.Typer.ObjectKinds(list)
+ if err != nil {
+ return nil, err
+ }
+ versionedListPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind(listGVKs[0].Kind))
+ if err != nil {
+ return nil, err
+ }
+ versionedList = indirectArbitraryPointer(versionedListPtr)
+ }
+
+ versionedListOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ListOptions"))
+ if err != nil {
+ return nil, err
+ }
+
+ var versionedDeleteOptions runtime.Object
+ var versionedDeleterObject interface{}
+ if isGracefulDeleter {
+ versionedDeleteOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind("DeleteOptions"))
+ if err != nil {
+ return nil, err
+ }
+ versionedDeleterObject = indirectArbitraryPointer(versionedDeleteOptions)
+ }
+
+ versionedStatusPtr, err := a.group.Creater.New(optionsExternalVersion.WithKind("Status"))
+ if err != nil {
+ return nil, err
+ }
+ versionedStatus := indirectArbitraryPointer(versionedStatusPtr)
+ var (
+ getOptions runtime.Object
+ versionedGetOptions runtime.Object
+ getOptionsInternalKind schema.GroupVersionKind
+ getSubpath bool
+ )
+ if isGetterWithOptions {
+ getOptions, getSubpath, _ = getterWithOptions.NewGetOptions()
+ getOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(getOptions)
+ if err != nil {
+ return nil, err
+ }
+ getOptionsInternalKind = getOptionsInternalKinds[0]
+ versionedGetOptions, err = a.group.Creater.New(a.group.GroupVersion.WithKind(getOptionsInternalKind.Kind))
+ if err != nil {
+ versionedGetOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(getOptionsInternalKind.Kind))
+ if err != nil {
+ return nil, err
+ }
+ }
+ isGetter = true
+ }
+
+ var versionedWatchEvent interface{}
+ if isWatcher {
+ versionedWatchEventPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind("WatchEvent"))
+ if err != nil {
+ return nil, err
+ }
+ versionedWatchEvent = indirectArbitraryPointer(versionedWatchEventPtr)
+ }
+
+ var (
+ connectOptions runtime.Object
+ versionedConnectOptions runtime.Object
+ connectOptionsInternalKind schema.GroupVersionKind
+ connectSubpath bool
+ )
+ if isConnecter {
+ connectOptions, connectSubpath, _ = connecter.NewConnectOptions()
+ if connectOptions != nil {
+ connectOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(connectOptions)
+ if err != nil {
+ return nil, err
+ }
+
+ connectOptionsInternalKind = connectOptionsInternalKinds[0]
+ versionedConnectOptions, err = a.group.Creater.New(a.group.GroupVersion.WithKind(connectOptionsInternalKind.Kind))
+ if err != nil {
+ versionedConnectOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(connectOptionsInternalKind.Kind))
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list.
+ nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
+ pathParam := ws.PathParameter("path", "path to the resource").DataType("string")
+
+ params := []*restful.Parameter{}
+ actions := []action{}
+
+ var resourceKind string
+ kindProvider, ok := storage.(rest.KindProvider)
+ if ok {
+ resourceKind = kindProvider.Kind()
+ } else {
+ resourceKind = kind
+ }
+
+ tableProvider, _ := storage.(rest.TableConvertor)
+
+ var apiResource metav1.APIResource
+ // Get the list of actions for the given scope.
+ switch {
+ case !namespaceScoped:
+ // Handle non-namespace scoped resources like nodes.
+ resourcePath := resource
+ resourceParams := params
+ itemPath := resourcePath + "/{name}"
+ nameParams := append(params, nameParam)
+ proxyParams := append(nameParams, pathParam)
+ suffix := ""
+ if isSubresource {
+ suffix = "/" + subresource
+ itemPath = itemPath + suffix
+ resourcePath = itemPath
+ resourceParams = nameParams
+ }
+ apiResource.Name = path
+ apiResource.Namespaced = false
+ apiResource.Kind = resourceKind
+ namer := handlers.ContextBasedNaming{
+ SelfLinker: a.group.Linker,
+ ClusterScoped: true,
+ SelfLinkPathPrefix: gpath.Join(a.prefix, resource) + "/",
+ SelfLinkPathSuffix: suffix,
+ }
+
+ // Handler for standard REST verbs (GET, PUT, POST and DELETE).
+ // Add actions at the resource path: /api/apiVersion/resource
+ actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
+ actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
+ actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
+ // DEPRECATED
+ actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList)
+
+ // Add actions at the item path: /api/apiVersion/resource/{name}
+ actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter)
+ if getSubpath {
+ actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter)
+ }
+ actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
+ actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
+ actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
+ actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher)
+ actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter)
+ actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath)
+ break
+ default:
+ namespaceParamName := "namespaces"
+ // Handler for standard REST verbs (GET, PUT, POST and DELETE).
+ namespaceParam := ws.PathParameter("namespace", "object name and auth scope, such as for teams and projects").DataType("string")
+ namespacedPath := namespaceParamName + "/{" + "namespace" + "}/" + resource
+ namespaceParams := []*restful.Parameter{namespaceParam}
+
+ resourcePath := namespacedPath
+ resourceParams := namespaceParams
+ itemPath := namespacedPath + "/{name}"
+ nameParams := append(namespaceParams, nameParam)
+ proxyParams := append(nameParams, pathParam)
+ itemPathSuffix := ""
+ if isSubresource {
+ itemPathSuffix = "/" + subresource
+ itemPath = itemPath + itemPathSuffix
+ resourcePath = itemPath
+ resourceParams = nameParams
+ }
+ apiResource.Name = path
+ apiResource.Namespaced = true
+ apiResource.Kind = resourceKind
+ namer := handlers.ContextBasedNaming{
+ SelfLinker: a.group.Linker,
+ ClusterScoped: false,
+ SelfLinkPathPrefix: gpath.Join(a.prefix, namespaceParamName) + "/",
+ SelfLinkPathSuffix: itemPathSuffix,
+ }
+
+ actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer, false}, isLister)
+ actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer, false}, isCreater)
+ actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer, false}, isCollectionDeleter)
+ // DEPRECATED
+ actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer, false}, allowWatchList)
+
+ actions = appendIf(actions, action{"GET", itemPath, nameParams, namer, false}, isGetter)
+ if getSubpath {
+ actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer, false}, isGetter)
+ }
+ actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer, false}, isUpdater)
+ actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer, false}, isPatcher)
+ actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer, false}, isGracefulDeleter)
+ actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer, false}, isWatcher)
+ actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer, false}, isConnecter)
+ actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer, false}, isConnecter && connectSubpath)
+
+ // list or post across namespace.
+ // For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods.
+ // TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete)
+ if !isSubresource {
+ actions = appendIf(actions, action{"LIST", resource, params, namer, true}, isLister)
+ actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer, true}, allowWatchList)
+ }
+ break
+ }
+
+ // Create Routes for the actions.
+ // TODO: Add status documentation using Returns()
+ // Errors (see api/errors/errors.go as well as go-restful router):
+ // http.StatusNotFound, http.StatusMethodNotAllowed,
+ // http.StatusUnsupportedMediaType, http.StatusNotAcceptable,
+ // http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden,
+ // http.StatusRequestTimeout, http.StatusConflict, http.StatusPreconditionFailed,
+ // http.StatusUnprocessableEntity, http.StatusInternalServerError,
+ // http.StatusServiceUnavailable
+ // and api error codes
+ // Note that if we specify a versioned Status object here, we may need to
+ // create one for the tests, also
+ // Success:
+ // http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent
+ //
+ // test/integration/auth_test.go is currently the most comprehensive status code test
+
+ mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
+ allMediaTypes := append(mediaTypes, streamMediaTypes...)
+ ws.Produces(allMediaTypes...)
+
+ kubeVerbs := map[string]struct{}{}
+ reqScope := handlers.RequestScope{
+ Serializer: a.group.Serializer,
+ ParameterCodec: a.group.ParameterCodec,
+ Creater: a.group.Creater,
+ Convertor: a.group.Convertor,
+ Defaulter: a.group.Defaulter,
+ Typer: a.group.Typer,
+ UnsafeConvertor: a.group.UnsafeConvertor,
+
+ // TODO: Check for the interface on storage
+ TableConvertor: tableProvider,
+
+ // TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this.
+ Resource: a.group.GroupVersion.WithResource(resource),
+ Subresource: subresource,
+ Kind: fqKindToRegister,
+
+ MetaGroupVersion: metav1.SchemeGroupVersion,
+ }
+ if a.group.MetaGroupVersion != nil {
+ reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
+ }
+ if a.group.OpenAPIConfig != nil {
+ openAPIDefinitions, err := openapibuilder.BuildOpenAPIDefinitionsForResource(defaultVersionedObject, a.group.OpenAPIConfig)
+ if err != nil {
+ return nil, fmt.Errorf("unable to build openapi definitions for %v: %v", fqKindToRegister, err)
+ }
+ reqScope.OpenAPISchema, err = utilopenapi.ToProtoSchema(openAPIDefinitions, fqKindToRegister)
+ if err != nil {
+ return nil, fmt.Errorf("unable to get openapi schema for %v: %v", fqKindToRegister, err)
+ }
+ }
+ for _, action := range actions {
+ producedObject := storageMeta.ProducesObject(action.Verb)
+ if producedObject == nil {
+ producedObject = defaultVersionedObject
+ }
+ reqScope.Namer = action.Namer
+
+ requestScope := "cluster"
+ var namespaced string
+ var operationSuffix string
+ if apiResource.Namespaced {
+ requestScope = "namespace"
+ namespaced = "Namespaced"
+ }
+ if strings.HasSuffix(action.Path, "/{path:*}") {
+ requestScope = "resource"
+ operationSuffix = operationSuffix + "WithPath"
+ }
+ if action.AllNamespaces {
+ requestScope = "cluster"
+ operationSuffix = operationSuffix + "ForAllNamespaces"
+ namespaced = ""
+ }
+
+ if kubeVerb, found := toDiscoveryKubeVerb[action.Verb]; found {
+ if len(kubeVerb) != 0 {
+ kubeVerbs[kubeVerb] = struct{}{}
+ }
+ } else {
+ return nil, fmt.Errorf("unknown action verb for discovery: %s", action.Verb)
+ }
+
+ routes := []*restful.RouteBuilder{}
+
+ // If there is a subresource, kind should be the parent's kind.
+ if isSubresource {
+ parentStorage, ok := a.group.Storage[resource]
+ if !ok {
+ return nil, fmt.Errorf("missing parent storage: %q", resource)
+ }
+
+ fqParentKind, err := a.getResourceKind(resource, parentStorage)
+ if err != nil {
+ return nil, err
+ }
+ kind = fqParentKind.Kind
+ }
+
+ verbOverrider, needOverride := storage.(StorageMetricsOverride)
+
+ switch action.Verb {
+ case "GET": // Get a resource.
+ var handler restful.RouteFunction
+ if isGetterWithOptions {
+ handler = restfulGetResourceWithOptions(getterWithOptions, reqScope, isSubresource)
+ } else {
+ handler = restfulGetResource(getter, exporter, reqScope)
+ }
+
+ if needOverride {
+ // need change the reported verb
+ handler = metrics.InstrumentRouteFunc(verbOverrider.OverrideMetricsVerb(action.Verb), resource, subresource, requestScope, handler)
+ } else {
+ handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, handler)
+ }
+
+ if a.enableAPIResponseCompression {
+ handler = genericfilters.RestfulWithCompression(handler)
+ }
+ doc := "read the specified " + kind
+ if isSubresource {
+ doc = "read " + subresource + " of the specified " + kind
+ }
+ route := ws.GET(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("read"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
+ Returns(http.StatusOK, "OK", producedObject).
+ Writes(producedObject)
+ if isGetterWithOptions {
+ if err := addObjectParams(ws, route, versionedGetOptions); err != nil {
+ return nil, err
+ }
+ }
+ if isExporter {
+ if err := addObjectParams(ws, route, versionedExportOptions); err != nil {
+ return nil, err
+ }
+ }
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ case "LIST": // List all resources of a kind.
+ doc := "list objects of kind " + kind
+ if isSubresource {
+ doc = "list " + subresource + " of objects of kind " + kind
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
+ if a.enableAPIResponseCompression {
+ handler = genericfilters.RestfulWithCompression(handler)
+ }
+ route := ws.GET(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("list"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(append(storageMeta.ProducesMIMETypes(action.Verb), allMediaTypes...)...).
+ Returns(http.StatusOK, "OK", versionedList).
+ Writes(versionedList)
+ if err := addObjectParams(ws, route, versionedListOptions); err != nil {
+ return nil, err
+ }
+ switch {
+ case isLister && isWatcher:
+ doc := "list or watch objects of kind " + kind
+ if isSubresource {
+ doc = "list or watch " + subresource + " of objects of kind " + kind
+ }
+ route.Doc(doc)
+ case isWatcher:
+ doc := "watch objects of kind " + kind
+ if isSubresource {
+ doc = "watch " + subresource + "of objects of kind " + kind
+ }
+ route.Doc(doc)
+ }
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ case "PUT": // Update a resource.
+ doc := "replace the specified " + kind
+ if isSubresource {
+ doc = "replace " + subresource + " of the specified " + kind
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulUpdateResource(updater, reqScope, admit))
+ route := ws.PUT(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("replace"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
+ Returns(http.StatusOK, "OK", producedObject).
+ // TODO: in some cases, the API may return a v1.Status instead of the versioned object
+ // but currently go-restful can't handle multiple different objects being returned.
+ Returns(http.StatusCreated, "Created", producedObject).
+ Reads(defaultVersionedObject).
+ Writes(producedObject)
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ case "PATCH": // Partially update a resource
+ doc := "partially update the specified " + kind
+ if isSubresource {
+ doc = "partially update " + subresource + " of the specified " + kind
+ }
+ supportedTypes := []string{
+ string(types.JSONPatchType),
+ string(types.MergePatchType),
+ string(types.StrategicMergePatchType),
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulPatchResource(patcher, reqScope, admit, supportedTypes))
+ route := ws.PATCH(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Consumes(string(types.JSONPatchType), string(types.MergePatchType), string(types.StrategicMergePatchType)).
+ Operation("patch"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
+ Returns(http.StatusOK, "OK", producedObject).
+ Reads(metav1.Patch{}).
+ Writes(producedObject)
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ case "POST": // Create a resource.
+ var handler restful.RouteFunction
+ if isNamedCreater {
+ handler = restfulCreateNamedResource(namedCreater, reqScope, admit)
+ } else {
+ handler = restfulCreateResource(creater, reqScope, admit)
+ }
+ handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, handler)
+ article := getArticleForNoun(kind, " ")
+ doc := "create" + article + kind
+ if isSubresource {
+ doc = "create " + subresource + " of" + article + kind
+ }
+ route := ws.POST(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("create"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
+ Returns(http.StatusOK, "OK", producedObject).
+ // TODO: in some cases, the API may return a v1.Status instead of the versioned object
+ // but currently go-restful can't handle multiple different objects being returned.
+ Returns(http.StatusCreated, "Created", producedObject).
+ Returns(http.StatusAccepted, "Accepted", producedObject).
+ Reads(defaultVersionedObject).
+ Writes(producedObject)
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ case "DELETE": // Delete a resource.
+ article := getArticleForNoun(kind, " ")
+ doc := "delete" + article + kind
+ if isSubresource {
+ doc = "delete " + subresource + " of" + article + kind
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulDeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
+ route := ws.DELETE(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("delete"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
+ Writes(versionedStatus).
+ Returns(http.StatusOK, "OK", versionedStatus)
+ if isGracefulDeleter {
+ route.Reads(versionedDeleterObject)
+ if err := addObjectParams(ws, route, versionedDeleteOptions); err != nil {
+ return nil, err
+ }
+ }
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ case "DELETECOLLECTION":
+ doc := "delete collection of " + kind
+ if isSubresource {
+ doc = "delete collection of " + subresource + " of a " + kind
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulDeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
+ route := ws.DELETE(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("deletecollection"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
+ Writes(versionedStatus).
+ Returns(http.StatusOK, "OK", versionedStatus)
+ if err := addObjectParams(ws, route, versionedListOptions); err != nil {
+ return nil, err
+ }
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ // TODO: deprecated
+ case "WATCH": // Watch a resource.
+ doc := "watch changes to an object of kind " + kind
+ if isSubresource {
+ doc = "watch changes to " + subresource + " of an object of kind " + kind
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
+ route := ws.GET(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("watch"+namespaced+kind+strings.Title(subresource)+operationSuffix).
+ Produces(allMediaTypes...).
+ Returns(http.StatusOK, "OK", versionedWatchEvent).
+ Writes(versionedWatchEvent)
+ if err := addObjectParams(ws, route, versionedListOptions); err != nil {
+ return nil, err
+ }
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ // TODO: deprecated
+ case "WATCHLIST": // Watch all resources of a kind.
+ doc := "watch individual changes to a list of " + kind
+ if isSubresource {
+ doc = "watch individual changes to a list of " + subresource + " of " + kind
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
+ route := ws.GET(action.Path).To(handler).
+ Doc(doc).
+ Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
+ Operation("watch"+namespaced+kind+strings.Title(subresource)+"List"+operationSuffix).
+ Produces(allMediaTypes...).
+ Returns(http.StatusOK, "OK", versionedWatchEvent).
+ Writes(versionedWatchEvent)
+ if err := addObjectParams(ws, route, versionedListOptions); err != nil {
+ return nil, err
+ }
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ case "CONNECT":
+ for _, method := range connecter.ConnectMethods() {
+ connectProducedObject := storageMeta.ProducesObject(method)
+ if connectProducedObject == nil {
+ connectProducedObject = "string"
+ }
+ doc := "connect " + method + " requests to " + kind
+ if isSubresource {
+ doc = "connect " + method + " requests to " + subresource + " of " + kind
+ }
+ handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulConnectResource(connecter, reqScope, admit, path, isSubresource))
+ route := ws.Method(method).Path(action.Path).
+ To(handler).
+ Doc(doc).
+ Operation("connect" + strings.Title(strings.ToLower(method)) + namespaced + kind + strings.Title(subresource) + operationSuffix).
+ Produces("*/*").
+ Consumes("*/*").
+ Writes(connectProducedObject)
+ if versionedConnectOptions != nil {
+ if err := addObjectParams(ws, route, versionedConnectOptions); err != nil {
+ return nil, err
+ }
+ }
+ addParams(route, action.Params)
+ routes = append(routes, route)
+ }
+ default:
+ return nil, fmt.Errorf("unrecognized action verb: %s", action.Verb)
+ }
+ for _, route := range routes {
+ route.Metadata(ROUTE_META_GVK, metav1.GroupVersionKind{
+ Group: reqScope.Kind.Group,
+ Version: reqScope.Kind.Version,
+ Kind: reqScope.Kind.Kind,
+ })
+ route.Metadata(ROUTE_META_ACTION, strings.ToLower(action.Verb))
+ ws.Route(route)
+ }
+ // Note: update GetAuthorizerAttributes() when adding a custom handler.
+ }
+
+ apiResource.Verbs = make([]string, 0, len(kubeVerbs))
+ for kubeVerb := range kubeVerbs {
+ apiResource.Verbs = append(apiResource.Verbs, kubeVerb)
+ }
+ sort.Strings(apiResource.Verbs)
+
+ if shortNamesProvider, ok := storage.(rest.ShortNamesProvider); ok {
+ apiResource.ShortNames = shortNamesProvider.ShortNames()
+ }
+ if categoriesProvider, ok := storage.(rest.CategoriesProvider); ok {
+ apiResource.Categories = categoriesProvider.Categories()
+ }
+ if gvkProvider, ok := storage.(rest.GroupVersionKindProvider); ok {
+ gvk := gvkProvider.GroupVersionKind(a.group.GroupVersion)
+ apiResource.Group = gvk.Group
+ apiResource.Version = gvk.Version
+ apiResource.Kind = gvk.Kind
+ }
+
+ return &apiResource, nil
+}
+
+// indirectArbitraryPointer returns *ptrToObject for an arbitrary pointer
+func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
+ return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
+}
+
+func appendIf(actions []action, a action, shouldAppend bool) []action {
+ if shouldAppend {
+ actions = append(actions, a)
+ }
+ return actions
+}
+
+// Wraps a http.Handler function inside a restful.RouteFunction
+func routeFunction(handler http.Handler) restful.RouteFunction {
+ return func(restReq *restful.Request, restResp *restful.Response) {
+ handler.ServeHTTP(restResp.ResponseWriter, restReq.Request)
+ }
+}
+
+func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
+ for _, param := range params {
+ route.Param(param)
+ }
+}
+
+// addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
+// The object must be a pointer to a struct; only fields at the top level of the struct that are not
+// themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard
+// Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is
+// the JSON field name. If a description struct tag is set on the field, that description is used on the
+// query parameter. In essence, it converts a standard JSON top level object into a query param schema.
+func addObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj interface{}) error {
+ sv, err := conversion.EnforcePtr(obj)
+ if err != nil {
+ return err
+ }
+ st := sv.Type()
+ switch st.Kind() {
+ case reflect.Struct:
+ for i := 0; i < st.NumField(); i++ {
+ name := st.Field(i).Name
+ sf, ok := st.FieldByName(name)
+ if !ok {
+ continue
+ }
+ switch sf.Type.Kind() {
+ case reflect.Interface, reflect.Struct:
+ case reflect.Ptr:
+ // TODO: This is a hack to let metav1.Time through. This needs to be fixed in a more generic way eventually. bug #36191
+ if (sf.Type.Elem().Kind() == reflect.Interface || sf.Type.Elem().Kind() == reflect.Struct) && strings.TrimPrefix(sf.Type.String(), "*") != "metav1.Time" {
+ continue
+ }
+ fallthrough
+ default:
+ jsonTag := sf.Tag.Get("json")
+ if len(jsonTag) == 0 {
+ continue
+ }
+ jsonName := strings.SplitN(jsonTag, ",", 2)[0]
+ if len(jsonName) == 0 {
+ continue
+ }
+
+ var desc string
+ if docable, ok := obj.(documentable); ok {
+ desc = docable.SwaggerDoc()[jsonName]
+ }
+ route.Param(ws.QueryParameter(jsonName, desc).DataType(typeToJSON(sf.Type.String())))
+ }
+ }
+ }
+ return nil
+}
+
+// TODO: this is incomplete, expand as needed.
+// Convert the name of a golang type to the name of a JSON type
+func typeToJSON(typeName string) string {
+ switch typeName {
+ case "bool", "*bool":
+ return "boolean"
+ case "uint8", "*uint8", "int", "*int", "int32", "*int32", "int64", "*int64", "uint32", "*uint32", "uint64", "*uint64":
+ return "integer"
+ case "float64", "*float64", "float32", "*float32":
+ return "number"
+ case "metav1.Time", "*metav1.Time":
+ return "string"
+ case "byte", "*byte":
+ return "string"
+ case "v1.DeletionPropagation", "*v1.DeletionPropagation":
+ return "string"
+
+ // TODO: Fix these when go-restful supports a way to specify an array query param:
+ // https://github.com/emicklei/go-restful/issues/225
+ case "[]string", "[]*string":
+ return "string"
+ case "[]int32", "[]*int32":
+ return "integer"
+
+ default:
+ return typeName
+ }
+}
+
+// defaultStorageMetadata provides default answers to rest.StorageMetadata.
+type defaultStorageMetadata struct{}
+
+// defaultStorageMetadata implements rest.StorageMetadata
+var _ rest.StorageMetadata = defaultStorageMetadata{}
+
+func (defaultStorageMetadata) ProducesMIMETypes(verb string) []string {
+ return nil
+}
+
+func (defaultStorageMetadata) ProducesObject(verb string) interface{} {
+ return nil
+}
+
+// splitSubresource checks if the given storage path is the path of a subresource and returns
+// the resource and subresource components.
+func splitSubresource(path string) (string, string, error) {
+ var resource, subresource string
+ switch parts := strings.Split(path, "/"); len(parts) {
+ case 2:
+ resource, subresource = parts[0], parts[1]
+ case 1:
+ resource = parts[0]
+ default:
+ // TODO: support deeper paths
+ return "", "", fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)")
+ }
+ return resource, subresource, nil
+}
+
+// getArticleForNoun returns the article needed for the given noun.
+func getArticleForNoun(noun string, padding string) string {
+ if noun[len(noun)-2:] != "ss" && noun[len(noun)-1:] == "s" {
+ // Plurals don't have an article.
+ // Don't catch words like class
+ return fmt.Sprintf("%v", padding)
+ }
+
+ article := "a"
+ if isVowel(rune(noun[0])) {
+ article = "an"
+ }
+
+ return fmt.Sprintf("%s%s%s", padding, article, padding)
+}
+
+// isVowel returns true if the rune is a vowel (case insensitive).
+func isVowel(c rune) bool {
+ vowels := []rune{'a', 'e', 'i', 'o', 'u'}
+ for _, value := range vowels {
+ if value == unicode.ToLower(c) {
+ return true
+ }
+ }
+ return false
+}
+
+func restfulListResource(r rest.Lister, rw rest.Watcher, scope handlers.RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.ListResource(r, rw, scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulCreateNamedResource(r rest.NamedCreater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.CreateNamedResource(r, scope, admit)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.CreateResource(r, scope, admit)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulDeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.DeleteResource(r, allowsOptions, scope, admit)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulDeleteCollection(r rest.CollectionDeleter, checkBody bool, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.DeleteCollection(r, checkBody, scope, admit)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulUpdateResource(r rest.Updater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.UpdateResource(r, scope, admit)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulPatchResource(r rest.Patcher, scope handlers.RequestScope, admit admission.Interface, supportedTypes []string) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.PatchResource(r, scope, admit, supportedTypes)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulGetResource(r rest.Getter, e rest.Exporter, scope handlers.RequestScope) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.GetResource(r, e, scope)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulGetResourceWithOptions(r rest.GetterWithOptions, scope handlers.RequestScope, isSubresource bool) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.GetResourceWithOptions(r, scope, isSubresource)(res.ResponseWriter, req.Request)
+ }
+}
+
+func restfulConnectResource(connecter rest.Connecter, scope handlers.RequestScope, admit admission.Interface, restPath string, isSubresource bool) restful.RouteFunction {
+ return func(req *restful.Request, res *restful.Response) {
+ handlers.ConnectResource(connecter, scope, admit, restPath, isSubresource)(res.ResponseWriter, req.Request)
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS
new file mode 100755
index 0000000..f0706b3
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/metrics/OWNERS
@@ -0,0 +1,3 @@
+reviewers:
+- wojtek-t
+- jimmidyson
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
new file mode 100644
index 0000000..516452e
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go
@@ -0,0 +1,443 @@
+/*
+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"
+ "regexp"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ utilnet "k8s.io/apimachinery/pkg/util/net"
+ "k8s.io/apiserver/pkg/endpoints/request"
+
+ "github.com/emicklei/go-restful"
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+// resettableCollector is the interface implemented by prometheus.MetricVec
+// that can be used by Prometheus to collect metrics and reset their values.
+type resettableCollector interface {
+ prometheus.Collector
+ Reset()
+}
+
+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", "subresource", "scope", "client", "contentType", "code"},
+ )
+ longRunningRequestGauge = prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Name: "apiserver_longrunning_gauge",
+ Help: "Gauge of all active long-running apiserver requests broken out by verb, API resource, and scope. Not all requests are tracked this way.",
+ },
+ []string{"verb", "resource", "subresource", "scope"},
+ )
+ requestLatencies = prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "apiserver_request_latencies",
+ Help: "Response latency distribution in microseconds for each verb, resource and subresource.",
+ // Use buckets ranging from 125 ms to 8 seconds.
+ Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
+ },
+ []string{"verb", "resource", "subresource", "scope"},
+ )
+ requestLatenciesSummary = prometheus.NewSummaryVec(
+ prometheus.SummaryOpts{
+ Name: "apiserver_request_latencies_summary",
+ Help: "Response latency summary in microseconds for each verb, resource and subresource.",
+ // Make the sliding window of 5h.
+ // TODO: The value for this should be based on our SLI definition (medium term).
+ MaxAge: 5 * time.Hour,
+ },
+ []string{"verb", "resource", "subresource", "scope"},
+ )
+ responseSizes = prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "apiserver_response_sizes",
+ Help: "Response size distribution in bytes for each verb, resource, subresource and scope (namespace/cluster).",
+ // Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB).
+ Buckets: prometheus.ExponentialBuckets(1000, 10.0, 7),
+ },
+ []string{"verb", "resource", "subresource", "scope"},
+ )
+ // DroppedRequests is a number of requests dropped with 'Try again later' response"
+ DroppedRequests = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "apiserver_dropped_requests",
+ Help: "Number of requests dropped with 'Try again later' response",
+ },
+ []string{"requestKind"},
+ )
+ // RegisteredWatchers is a number of currently registered watchers splitted by resource.
+ RegisteredWatchers = prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Name: "apiserver_registered_watchers",
+ Help: "Number of currently registered watchers for a given resources",
+ },
+ []string{"group", "version", "kind"},
+ )
+ // Because of volatality of the base metric this is pre-aggregated one. Instead of reporing current usage all the time
+ // it reports maximal usage during the last second.
+ currentInflightRequests = prometheus.NewGaugeVec(
+ prometheus.GaugeOpts{
+ Name: "apiserver_current_inflight_requests",
+ Help: "Maximal mumber of currently used inflight request limit of this apiserver per request kind in last second.",
+ },
+ []string{"requestKind"},
+ )
+ kubectlExeRegexp = regexp.MustCompile(`^.*((?i:kubectl\.exe))`)
+
+ metrics = []resettableCollector{
+ requestCounter,
+ longRunningRequestGauge,
+ requestLatencies,
+ requestLatenciesSummary,
+ responseSizes,
+ DroppedRequests,
+ RegisteredWatchers,
+ currentInflightRequests,
+ }
+)
+
+const (
+ // ReadOnlyKind is a string identifying read only request kind
+ ReadOnlyKind = "readOnly"
+ // MutatingKind is a string identifying mutating request kind
+ MutatingKind = "mutating"
+)
+
+var registerMetrics sync.Once
+
+// Register all metrics.
+func Register() {
+ registerMetrics.Do(func() {
+ for _, metric := range metrics {
+ prometheus.MustRegister(metric)
+ }
+ })
+}
+
+// Reset all metrics.
+func Reset() {
+ for _, metric := range metrics {
+ metric.Reset()
+ }
+}
+
+func UpdateInflightRequestMetrics(nonmutating, mutating int) {
+ currentInflightRequests.WithLabelValues(ReadOnlyKind).Set(float64(nonmutating))
+ currentInflightRequests.WithLabelValues(MutatingKind).Set(float64(mutating))
+}
+
+// Record records a single request to the standard metrics endpoints. For use by handlers that perform their own
+// processing. All API paths should use InstrumentRouteFunc implicitly. Use this instead of MonitorRequest if
+// you already have a RequestInfo object.
+func Record(req *http.Request, requestInfo *request.RequestInfo, contentType string, code int, responseSizeInBytes int, elapsed time.Duration) {
+ if requestInfo == nil {
+ requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
+ }
+ scope := CleanScope(requestInfo)
+ if requestInfo.IsResourceRequest {
+ MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, contentType, scope, code, responseSizeInBytes, elapsed)
+ } else {
+ MonitorRequest(req, strings.ToUpper(requestInfo.Verb), "", requestInfo.Path, contentType, scope, code, responseSizeInBytes, elapsed)
+ }
+}
+
+// RecordLongRunning tracks the execution of a long running request against the API server. It provides an accurate count
+// of the total number of open long running requests. requestInfo may be nil if the caller is not in the normal request flow.
+func RecordLongRunning(req *http.Request, requestInfo *request.RequestInfo, fn func()) {
+ if requestInfo == nil {
+ requestInfo = &request.RequestInfo{Verb: req.Method, Path: req.URL.Path}
+ }
+ var g prometheus.Gauge
+ scope := CleanScope(requestInfo)
+ reportedVerb := cleanVerb(strings.ToUpper(requestInfo.Verb), req)
+ if requestInfo.IsResourceRequest {
+ g = longRunningRequestGauge.WithLabelValues(reportedVerb, requestInfo.Resource, requestInfo.Subresource, scope)
+ } else {
+ g = longRunningRequestGauge.WithLabelValues(reportedVerb, "", requestInfo.Path, scope)
+ }
+ g.Inc()
+ defer g.Dec()
+ fn()
+}
+
+// MonitorRequest handles standard transformations for client and the reported verb and then invokes Monitor to record
+// a request. verb must be uppercase to be backwards compatible with existing monitoring tooling.
+func MonitorRequest(req *http.Request, verb, resource, subresource, scope, contentType string, httpCode, respSize int, elapsed time.Duration) {
+ reportedVerb := cleanVerb(verb, req)
+ client := cleanUserAgent(utilnet.GetHTTPClient(req))
+ elapsedMicroseconds := float64(elapsed / time.Microsecond)
+ requestCounter.WithLabelValues(reportedVerb, resource, subresource, scope, client, contentType, codeToString(httpCode)).Inc()
+ requestLatencies.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(elapsedMicroseconds)
+ requestLatenciesSummary.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(elapsedMicroseconds)
+ // We are only interested in response sizes of read requests.
+ if verb == "GET" || verb == "LIST" {
+ responseSizes.WithLabelValues(reportedVerb, resource, subresource, scope).Observe(float64(respSize))
+ }
+}
+
+// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
+// the go-restful RouteFunction instead of a HandlerFunc plus some Kubernetes endpoint specific information.
+func InstrumentRouteFunc(verb, resource, subresource, scope 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)
+
+ MonitorRequest(request.Request, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
+ })
+}
+
+// InstrumentHandlerFunc works like Prometheus' InstrumentHandlerFunc but adds some Kubernetes endpoint specific information.
+func InstrumentHandlerFunc(verb, resource, subresource, scope string, handler http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ now := time.Now()
+
+ delegate := &ResponseWriterDelegator{ResponseWriter: w}
+
+ _, cn := w.(http.CloseNotifier)
+ _, fl := w.(http.Flusher)
+ _, hj := w.(http.Hijacker)
+ if cn && fl && hj {
+ w = &fancyResponseWriterDelegator{delegate}
+ } else {
+ w = delegate
+ }
+
+ handler(w, req)
+
+ MonitorRequest(req, verb, resource, subresource, scope, delegate.Header().Get("Content-Type"), delegate.Status(), delegate.ContentLength(), time.Since(now))
+ }
+}
+
+// CleanScope returns the scope of the request.
+func CleanScope(requestInfo *request.RequestInfo) string {
+ if requestInfo.Namespace != "" {
+ return "namespace"
+ }
+ if requestInfo.Name != "" {
+ return "resource"
+ }
+ if requestInfo.IsResourceRequest {
+ return "cluster"
+ }
+ // this is the empty scope
+ return ""
+}
+
+func cleanVerb(verb string, request *http.Request) string {
+ reportedVerb := verb
+ if verb == "LIST" {
+ // see apimachinery/pkg/runtime/conversion.go Convert_Slice_string_To_bool
+ if values := request.URL.Query()["watch"]; len(values) > 0 {
+ if value := strings.ToLower(values[0]); value != "0" && value != "false" {
+ reportedVerb = "WATCH"
+ }
+ }
+ }
+ // normalize the legacy WATCHLIST to WATCH to ensure users aren't surprised by metrics
+ if verb == "WATCHLIST" {
+ reportedVerb = "WATCH"
+ }
+ return reportedVerb
+}
+
+func cleanUserAgent(ua string) string {
+ // We collapse all "web browser"-type user agents into one "browser" to reduce metric cardinality.
+ if strings.HasPrefix(ua, "Mozilla/") {
+ return "Browser"
+ }
+ // If an old "kubectl.exe" has passed us its full path, we discard the path portion.
+ ua = kubectlExeRegexp.ReplaceAllString(ua, "$1")
+ return ua
+}
+
+// ResponseWriterDelegator interface wraps http.ResponseWriter to additionally record content-length, status-code, etc.
+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
+}
+
+func (r *ResponseWriterDelegator) Status() int {
+ return r.status
+}
+
+func (r *ResponseWriterDelegator) ContentLength() int {
+ return int(r.written)
+}
+
+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)
+ }
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/openapi/OWNERS b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/openapi/OWNERS
new file mode 100755
index 0000000..4126a6b
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/openapi/OWNERS
@@ -0,0 +1,2 @@
+reviewers:
+- mbohlool
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go
new file mode 100644
index 0000000..e512f29
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go
@@ -0,0 +1,179 @@
+/*
+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"
+ "reflect"
+ "sort"
+ "strings"
+ "unicode"
+
+ restful "github.com/emicklei/go-restful"
+ "github.com/go-openapi/spec"
+
+ "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/kube-openapi/pkg/util"
+)
+
+var verbs = util.NewTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
+
+const (
+ extensionGVK = "x-kubernetes-group-version-kind"
+)
+
+// 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(r *restful.Route) (string, []string, error) {
+ op := r.Operation
+ path := r.Path
+ var tags []string
+ 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" {
+ trimmed := strings.TrimSuffix(parts[1], ".k8s.io")
+ prefix = prefix + ToValidOperationID(trimmed, prefix != "")
+ tag := ToValidOperationID(trimmed, 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
+}
+
+type groupVersionKinds []v1.GroupVersionKind
+
+func (s groupVersionKinds) Len() int {
+ return len(s)
+}
+
+func (s groupVersionKinds) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
+func (s groupVersionKinds) Less(i, j int) bool {
+ if s[i].Group == s[j].Group {
+ if s[i].Version == s[j].Version {
+ return s[i].Kind < s[j].Kind
+ }
+ return s[i].Version < s[j].Version
+ }
+ return s[i].Group < s[j].Group
+}
+
+// DefinitionNamer is the type to customize OpenAPI definition name.
+type DefinitionNamer struct {
+ typeGroupVersionKinds map[string]groupVersionKinds
+}
+
+func gvkConvert(gvk schema.GroupVersionKind) v1.GroupVersionKind {
+ return v1.GroupVersionKind{
+ Group: gvk.Group,
+ Version: gvk.Version,
+ Kind: gvk.Kind,
+ }
+}
+
+func friendlyName(name string) string {
+ nameParts := strings.Split(name, "/")
+ // Reverse first part. e.g., io.k8s... instead of k8s.io...
+ if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
+ parts := strings.Split(nameParts[0], ".")
+ for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
+ parts[i], parts[j] = parts[j], parts[i]
+ }
+ nameParts[0] = strings.Join(parts, ".")
+ }
+ return strings.Join(nameParts, ".")
+}
+
+func typeName(t reflect.Type) string {
+ path := t.PkgPath()
+ if strings.Contains(path, "/vendor/") {
+ path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
+ }
+ return fmt.Sprintf("%s.%s", path, t.Name())
+}
+
+// NewDefinitionNamer constructs a new DefinitionNamer to be used to customize OpenAPI spec.
+func NewDefinitionNamer(schemes ...*runtime.Scheme) *DefinitionNamer {
+ ret := &DefinitionNamer{
+ typeGroupVersionKinds: map[string]groupVersionKinds{},
+ }
+ for _, s := range schemes {
+ for gvk, rtype := range s.AllKnownTypes() {
+ newGVK := gvkConvert(gvk)
+ exists := false
+ for _, existingGVK := range ret.typeGroupVersionKinds[typeName(rtype)] {
+ if newGVK == existingGVK {
+ exists = true
+ break
+ }
+ }
+ if !exists {
+ ret.typeGroupVersionKinds[typeName(rtype)] = append(ret.typeGroupVersionKinds[typeName(rtype)], newGVK)
+ }
+ }
+ }
+ for _, gvk := range ret.typeGroupVersionKinds {
+ sort.Sort(gvk)
+ }
+ return ret
+}
+
+// GetDefinitionName returns the name and tags for a given definition
+func (d *DefinitionNamer) GetDefinitionName(name string) (string, spec.Extensions) {
+ if groupVersionKinds, ok := d.typeGroupVersionKinds[name]; ok {
+ return friendlyName(name), spec.Extensions{
+ extensionGVK: []v1.GroupVersionKind(groupVersionKinds),
+ }
+ }
+ return friendlyName(name), nil
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/OWNERS b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/OWNERS
new file mode 100755
index 0000000..9d268c4
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/OWNERS
@@ -0,0 +1,2 @@
+reviewers:
+- sttts
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/context.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/context.go
new file mode 100644
index 0000000..95166f5
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/context.go
@@ -0,0 +1,93 @@
+/*
+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 request
+
+import (
+ "context"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apiserver/pkg/apis/audit"
+ "k8s.io/apiserver/pkg/authentication/user"
+)
+
+// The key type is unexported to prevent collisions
+type key int
+
+const (
+ // namespaceKey is the context key for the request namespace.
+ namespaceKey key = iota
+
+ // userKey is the context key for the request user.
+ userKey
+
+ // auditKey is the context key for the audit event.
+ auditKey
+)
+
+// NewContext instantiates a base context object for request flows.
+func NewContext() context.Context {
+ return context.TODO()
+}
+
+// NewDefaultContext instantiates a base context object for request flows in the default namespace
+func NewDefaultContext() context.Context {
+ return WithNamespace(NewContext(), metav1.NamespaceDefault)
+}
+
+// WithValue returns a copy of parent in which the value associated with key is val.
+func WithValue(parent context.Context, key interface{}, val interface{}) context.Context {
+ return context.WithValue(parent, key, val)
+}
+
+// WithNamespace returns a copy of parent in which the namespace value is set
+func WithNamespace(parent context.Context, namespace string) context.Context {
+ return WithValue(parent, namespaceKey, namespace)
+}
+
+// NamespaceFrom returns the value of the namespace key on the ctx
+func NamespaceFrom(ctx context.Context) (string, bool) {
+ namespace, ok := ctx.Value(namespaceKey).(string)
+ return namespace, ok
+}
+
+// NamespaceValue returns the value of the namespace key on the ctx, or the empty string if none
+func NamespaceValue(ctx context.Context) string {
+ namespace, _ := NamespaceFrom(ctx)
+ return namespace
+}
+
+// WithUser returns a copy of parent in which the user value is set
+func WithUser(parent context.Context, user user.Info) context.Context {
+ return WithValue(parent, userKey, user)
+}
+
+// UserFrom returns the value of the user key on the ctx
+func UserFrom(ctx context.Context) (user.Info, bool) {
+ user, ok := ctx.Value(userKey).(user.Info)
+ return user, ok
+}
+
+// WithAuditEvent returns set audit event struct.
+func WithAuditEvent(parent context.Context, ev *audit.Event) context.Context {
+ return WithValue(parent, auditKey, ev)
+}
+
+// AuditEventFrom returns the audit event struct on the ctx
+func AuditEventFrom(ctx context.Context) *audit.Event {
+ ev, _ := ctx.Value(auditKey).(*audit.Event)
+ return ev
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/doc.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/doc.go
new file mode 100644
index 0000000..96da6f2
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/doc.go
@@ -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/apiserver/pkg/endpoints/request"
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go
new file mode 100644
index 0000000..1520bb3
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go
@@ -0,0 +1,273 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package request
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "k8s.io/apimachinery/pkg/api/validation/path"
+ metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/sets"
+
+ "github.com/golang/glog"
+)
+
+// LongRunningRequestCheck is a predicate which is true for long-running http requests.
+type LongRunningRequestCheck func(r *http.Request, requestInfo *RequestInfo) bool
+
+type RequestInfoResolver interface {
+ NewRequestInfo(req *http.Request) (*RequestInfo, error)
+}
+
+// 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", "watch")
+
+// specialVerbsNoSubresources contains root verbs which do not allow subresources
+var specialVerbsNoSubresources = sets.NewString("proxy")
+
+// 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}
+//
+// 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 = metav1.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" {
+ opts := metainternalversion.ListOptions{}
+ if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil {
+ // An error in parsing request will result in default to "list" and not setting "name" field.
+ glog.Errorf("Couldn't parse request %#v: %v", req.URL.Query(), err)
+ // Reset opts to not rely on partial results from parsing.
+ // However, if watch is set, let's report it.
+ opts = metainternalversion.ListOptions{}
+ if values := req.URL.Query()["watch"]; len(values) > 0 {
+ switch strings.ToLower(values[0]) {
+ case "false", "0":
+ default:
+ opts.Watch = true
+ }
+ }
+ }
+
+ if opts.Watch {
+ requestInfo.Verb = "watch"
+ } else {
+ requestInfo.Verb = "list"
+ }
+
+ if opts.FieldSelector != nil {
+ if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok {
+ if len(path.IsValidPathSegmentName(name)) == 0 {
+ requestInfo.Name = name
+ }
+ }
+ }
+ }
+ // 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 context.Context, info *RequestInfo) context.Context {
+ return WithValue(parent, requestInfoKey, info)
+}
+
+// RequestInfoFrom returns the value of the RequestInfo key on the ctx
+func RequestInfoFrom(ctx context.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, "/")
+}