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(`&`, "&amp;", `<`, "&lt;", `>`, "&gt;")
+
+// 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, "/")
+}