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/audit/format.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/format.go
new file mode 100644
index 0000000..bf805f5
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/format.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 audit
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	auditinternal "k8s.io/apiserver/pkg/apis/audit"
+)
+
+// EventString creates a 1-line text representation of an audit event, using a subset of the
+// information in the event struct.
+func EventString(ev *auditinternal.Event) string {
+	username := "<none>"
+	groups := "<none>"
+	if len(ev.User.Username) > 0 {
+		username = ev.User.Username
+		if len(ev.User.Groups) > 0 {
+			groups = auditStringSlice(ev.User.Groups)
+		}
+	}
+	asuser := "<self>"
+	asgroups := "<lookup>"
+	if ev.ImpersonatedUser != nil {
+		asuser = ev.ImpersonatedUser.Username
+		if ev.ImpersonatedUser.Groups != nil {
+			asgroups = auditStringSlice(ev.ImpersonatedUser.Groups)
+		}
+	}
+
+	namespace := "<none>"
+	if ev.ObjectRef != nil && len(ev.ObjectRef.Namespace) != 0 {
+		namespace = ev.ObjectRef.Namespace
+	}
+
+	response := "<deferred>"
+	if ev.ResponseStatus != nil {
+		response = strconv.Itoa(int(ev.ResponseStatus.Code))
+	}
+
+	ip := "<unknown>"
+	if len(ev.SourceIPs) > 0 {
+		ip = ev.SourceIPs[0]
+	}
+
+	return fmt.Sprintf("%s AUDIT: id=%q stage=%q ip=%q method=%q user=%q groups=%q as=%q asgroups=%q namespace=%q uri=%q response=\"%s\"",
+		ev.RequestReceivedTimestamp.Format(time.RFC3339Nano), ev.AuditID, ev.Stage, ip, ev.Verb, username, groups, asuser, asgroups, namespace, ev.RequestURI, response)
+}
+
+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, ",")
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/audit/metrics.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/metrics.go
new file mode 100644
index 0000000..10280e0
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/metrics.go
@@ -0,0 +1,87 @@
+/*
+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 audit
+
+import (
+	"fmt"
+
+	"github.com/golang/glog"
+	"github.com/prometheus/client_golang/prometheus"
+	auditinternal "k8s.io/apiserver/pkg/apis/audit"
+)
+
+const (
+	subsystem = "apiserver_audit"
+)
+
+var (
+	eventCounter = prometheus.NewCounter(
+		prometheus.CounterOpts{
+			Subsystem: subsystem,
+			Name:      "event_total",
+			Help:      "Counter of audit events generated and sent to the audit backend.",
+		})
+	errorCounter = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Subsystem: subsystem,
+			Name:      "error_total",
+			Help: "Counter of audit events that failed to be audited properly. " +
+				"Plugin identifies the plugin affected by the error.",
+		},
+		[]string{"plugin"},
+	)
+	levelCounter = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Subsystem: subsystem,
+			Name:      "level_total",
+			Help:      "Counter of policy levels for audit events (1 per request).",
+		},
+		[]string{"level"},
+	)
+)
+
+func init() {
+	prometheus.MustRegister(eventCounter)
+	prometheus.MustRegister(errorCounter)
+	prometheus.MustRegister(levelCounter)
+}
+
+// ObserveEvent updates the relevant prometheus metrics for the generated audit event.
+func ObserveEvent() {
+	eventCounter.Inc()
+}
+
+// ObservePolicyLevel updates the relevant prometheus metrics with the audit level for a request.
+func ObservePolicyLevel(level auditinternal.Level) {
+	levelCounter.WithLabelValues(string(level)).Inc()
+}
+
+// HandlePluginError handles an error that occurred in an audit plugin. This method should only be
+// used if the error may have prevented the audit event from being properly recorded. The events are
+// logged to the debug log.
+func HandlePluginError(plugin string, err error, impacted ...*auditinternal.Event) {
+	// Count the error.
+	errorCounter.WithLabelValues(plugin).Add(float64(len(impacted)))
+
+	// Log the audit events to the debug log.
+	msg := fmt.Sprintf("Error in audit plugin '%s' affecting %d audit events: %v\nImpacted events:\n",
+		plugin, len(impacted), err)
+	for _, ev := range impacted {
+		msg = msg + EventString(ev) + "\n"
+	}
+	glog.Error(msg)
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/audit/policy/checker.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/policy/checker.go
new file mode 100644
index 0000000..41c6b1a
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/policy/checker.go
@@ -0,0 +1,219 @@
+/*
+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 policy
+
+import (
+	"strings"
+
+	"k8s.io/apiserver/pkg/apis/audit"
+	"k8s.io/apiserver/pkg/authorization/authorizer"
+)
+
+const (
+	// DefaultAuditLevel is the default level to audit at, if no policy rules are matched.
+	DefaultAuditLevel = audit.LevelNone
+)
+
+// Checker exposes methods for checking the policy rules.
+type Checker interface {
+	// Check the audit level for a request with the given authorizer attributes.
+	LevelAndStages(authorizer.Attributes) (audit.Level, []audit.Stage)
+}
+
+// NewChecker creates a new policy checker.
+func NewChecker(policy *audit.Policy) Checker {
+	for i, rule := range policy.Rules {
+		policy.Rules[i].OmitStages = unionStages(policy.OmitStages, rule.OmitStages)
+	}
+	return &policyChecker{*policy}
+}
+
+func unionStages(stageLists ...[]audit.Stage) []audit.Stage {
+	m := make(map[audit.Stage]bool)
+	for _, sl := range stageLists {
+		for _, s := range sl {
+			m[s] = true
+		}
+	}
+	result := make([]audit.Stage, 0, len(m))
+	for key := range m {
+		result = append(result, key)
+	}
+	return result
+}
+
+// FakeChecker creates a checker that returns a constant level for all requests (for testing).
+func FakeChecker(level audit.Level, stage []audit.Stage) Checker {
+	return &fakeChecker{level, stage}
+}
+
+type policyChecker struct {
+	audit.Policy
+}
+
+func (p *policyChecker) LevelAndStages(attrs authorizer.Attributes) (audit.Level, []audit.Stage) {
+	for _, rule := range p.Rules {
+		if ruleMatches(&rule, attrs) {
+			return rule.Level, rule.OmitStages
+		}
+	}
+	return DefaultAuditLevel, p.OmitStages
+}
+
+// Check whether the rule matches the request attrs.
+func ruleMatches(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
+	user := attrs.GetUser()
+	if len(r.Users) > 0 {
+		if user == nil || !hasString(r.Users, user.GetName()) {
+			return false
+		}
+	}
+	if len(r.UserGroups) > 0 {
+		if user == nil {
+			return false
+		}
+		matched := false
+		for _, group := range user.GetGroups() {
+			if hasString(r.UserGroups, group) {
+				matched = true
+				break
+			}
+		}
+		if !matched {
+			return false
+		}
+	}
+	if len(r.Verbs) > 0 {
+		if !hasString(r.Verbs, attrs.GetVerb()) {
+			return false
+		}
+	}
+
+	if len(r.Namespaces) > 0 || len(r.Resources) > 0 {
+		return ruleMatchesResource(r, attrs)
+	}
+
+	if len(r.NonResourceURLs) > 0 {
+		return ruleMatchesNonResource(r, attrs)
+	}
+
+	return true
+}
+
+// Check whether the rule's non-resource URLs match the request attrs.
+func ruleMatchesNonResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
+	if attrs.IsResourceRequest() {
+		return false
+	}
+
+	path := attrs.GetPath()
+	for _, spec := range r.NonResourceURLs {
+		if pathMatches(path, spec) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Check whether the path matches the path specification.
+func pathMatches(path, spec string) bool {
+	// Allow wildcard match
+	if spec == "*" {
+		return true
+	}
+	// Allow exact match
+	if spec == path {
+		return true
+	}
+	// Allow a trailing * subpath match
+	if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
+		return true
+	}
+	return false
+}
+
+// Check whether the rule's resource fields match the request attrs.
+func ruleMatchesResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
+	if !attrs.IsResourceRequest() {
+		return false
+	}
+
+	if len(r.Namespaces) > 0 {
+		if !hasString(r.Namespaces, attrs.GetNamespace()) { // Non-namespaced resources use the empty string.
+			return false
+		}
+	}
+	if len(r.Resources) == 0 {
+		return true
+	}
+
+	apiGroup := attrs.GetAPIGroup()
+	resource := attrs.GetResource()
+	subresource := attrs.GetSubresource()
+	combinedResource := resource
+	// If subresource, the resource in the policy must match "(resource)/(subresource)"
+	if subresource != "" {
+		combinedResource = resource + "/" + subresource
+	}
+
+	name := attrs.GetName()
+
+	for _, gr := range r.Resources {
+		if gr.Group == apiGroup {
+			if len(gr.Resources) == 0 {
+				return true
+			}
+			for _, res := range gr.Resources {
+				if len(gr.ResourceNames) == 0 || hasString(gr.ResourceNames, name) {
+					// match "*"
+					if res == combinedResource || res == "*" {
+						return true
+					}
+					// match "*/subresource"
+					if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimLeft(res, "*/") {
+						return true
+					}
+					// match "resource/*"
+					if strings.HasSuffix(res, "/*") && resource == strings.TrimRight(res, "/*") {
+						return true
+					}
+				}
+			}
+		}
+	}
+	return false
+}
+
+// Utility function to check whether a string slice contains a string.
+func hasString(slice []string, value string) bool {
+	for _, s := range slice {
+		if s == value {
+			return true
+		}
+	}
+	return false
+}
+
+type fakeChecker struct {
+	level audit.Level
+	stage []audit.Stage
+}
+
+func (f *fakeChecker) LevelAndStages(_ authorizer.Attributes) (audit.Level, []audit.Stage) {
+	return f.level, f.stage
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/audit/policy/reader.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/policy/reader.go
new file mode 100644
index 0000000..1d02e1a
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/policy/reader.go
@@ -0,0 +1,79 @@
+/*
+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 policy
+
+import (
+	"fmt"
+	"io/ioutil"
+
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	auditinternal "k8s.io/apiserver/pkg/apis/audit"
+	auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1"
+	auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
+	"k8s.io/apiserver/pkg/apis/audit/validation"
+	"k8s.io/apiserver/pkg/audit"
+
+	"github.com/golang/glog"
+)
+
+var (
+	apiGroupVersions = []schema.GroupVersion{
+		auditv1beta1.SchemeGroupVersion,
+		auditv1alpha1.SchemeGroupVersion,
+	}
+	apiGroupVersionSet = map[schema.GroupVersion]bool{}
+)
+
+func init() {
+	for _, gv := range apiGroupVersions {
+		apiGroupVersionSet[gv] = true
+	}
+}
+
+func LoadPolicyFromFile(filePath string) (*auditinternal.Policy, error) {
+	if filePath == "" {
+		return nil, fmt.Errorf("file path not specified")
+	}
+	policyDef, err := ioutil.ReadFile(filePath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read file path %q: %+v", filePath, err)
+	}
+
+	policy := &auditinternal.Policy{}
+	decoder := audit.Codecs.UniversalDecoder(apiGroupVersions...)
+
+	_, gvk, err := decoder.Decode(policyDef, nil, policy)
+	if err != nil {
+		return nil, fmt.Errorf("failed decoding file %q: %v", filePath, err)
+	}
+
+	// Ensure the policy file contained an apiVersion and kind.
+	if !apiGroupVersionSet[schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}] {
+		return nil, fmt.Errorf("unknown group version field %v in policy file %s", gvk, filePath)
+	}
+
+	if err := validation.ValidatePolicy(policy); err != nil {
+		return nil, err.ToAggregate()
+	}
+
+	policyCnt := len(policy.Rules)
+	if policyCnt == 0 {
+		return nil, fmt.Errorf("loaded illegal policy with 0 rules from file %s", filePath)
+	}
+	glog.V(4).Infof("Loaded %d audit policy rules from file %s", policyCnt, filePath)
+	return policy, nil
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/audit/request.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/request.go
new file mode 100644
index 0000000..25d6c33
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/request.go
@@ -0,0 +1,235 @@
+/*
+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 audit
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/golang/glog"
+	"github.com/pborman/uuid"
+
+	"reflect"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	utilnet "k8s.io/apimachinery/pkg/util/net"
+	auditinternal "k8s.io/apiserver/pkg/apis/audit"
+	"k8s.io/apiserver/pkg/authentication/user"
+	"k8s.io/apiserver/pkg/authorization/authorizer"
+)
+
+func NewEventFromRequest(req *http.Request, level auditinternal.Level, attribs authorizer.Attributes) (*auditinternal.Event, error) {
+	ev := &auditinternal.Event{
+		RequestReceivedTimestamp: metav1.NewMicroTime(time.Now()),
+		Verb:       attribs.GetVerb(),
+		RequestURI: req.URL.RequestURI(),
+	}
+
+	ev.Level = level
+
+	// prefer the id from the headers. If not available, create a new one.
+	// TODO(audit): do we want to forbid the header for non-front-proxy users?
+	ids := req.Header.Get(auditinternal.HeaderAuditID)
+	if ids != "" {
+		ev.AuditID = types.UID(ids)
+	} else {
+		ev.AuditID = types.UID(uuid.NewRandom().String())
+	}
+
+	ips := utilnet.SourceIPs(req)
+	ev.SourceIPs = make([]string, len(ips))
+	for i := range ips {
+		ev.SourceIPs[i] = ips[i].String()
+	}
+
+	if user := attribs.GetUser(); user != nil {
+		ev.User.Username = user.GetName()
+		ev.User.Extra = map[string]auditinternal.ExtraValue{}
+		for k, v := range user.GetExtra() {
+			ev.User.Extra[k] = auditinternal.ExtraValue(v)
+		}
+		ev.User.Groups = user.GetGroups()
+		ev.User.UID = user.GetUID()
+	}
+
+	if attribs.IsResourceRequest() {
+		ev.ObjectRef = &auditinternal.ObjectReference{
+			Namespace:   attribs.GetNamespace(),
+			Name:        attribs.GetName(),
+			Resource:    attribs.GetResource(),
+			Subresource: attribs.GetSubresource(),
+			APIGroup:    attribs.GetAPIGroup(),
+			APIVersion:  attribs.GetAPIVersion(),
+		}
+	}
+
+	return ev, nil
+}
+
+// LogImpersonatedUser fills in the impersonated user attributes into an audit event.
+func LogImpersonatedUser(ae *auditinternal.Event, user user.Info) {
+	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
+		return
+	}
+	ae.ImpersonatedUser = &auditinternal.UserInfo{
+		Username: user.GetName(),
+	}
+	ae.ImpersonatedUser.Groups = user.GetGroups()
+	ae.ImpersonatedUser.UID = user.GetUID()
+	ae.ImpersonatedUser.Extra = map[string]auditinternal.ExtraValue{}
+	for k, v := range user.GetExtra() {
+		ae.ImpersonatedUser.Extra[k] = auditinternal.ExtraValue(v)
+	}
+}
+
+// LogRequestObject fills in the request object into an audit event. The passed runtime.Object
+// will be converted to the given gv.
+func LogRequestObject(ae *auditinternal.Event, obj runtime.Object, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) {
+	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
+		return
+	}
+
+	// complete ObjectRef
+	if ae.ObjectRef == nil {
+		ae.ObjectRef = &auditinternal.ObjectReference{}
+	}
+	if acc, ok := obj.(metav1.ObjectMetaAccessor); ok {
+		meta := acc.GetObjectMeta()
+		if len(ae.ObjectRef.Namespace) == 0 {
+			ae.ObjectRef.Namespace = meta.GetNamespace()
+		}
+		if len(ae.ObjectRef.Name) == 0 {
+			ae.ObjectRef.Name = meta.GetName()
+		}
+		if len(ae.ObjectRef.UID) == 0 {
+			ae.ObjectRef.UID = meta.GetUID()
+		}
+		if len(ae.ObjectRef.ResourceVersion) == 0 {
+			ae.ObjectRef.ResourceVersion = meta.GetResourceVersion()
+		}
+	}
+	if len(ae.ObjectRef.APIVersion) == 0 {
+		ae.ObjectRef.APIGroup = gvr.Group
+		ae.ObjectRef.APIVersion = gvr.Version
+	}
+	if len(ae.ObjectRef.Resource) == 0 {
+		ae.ObjectRef.Resource = gvr.Resource
+	}
+	if len(ae.ObjectRef.Subresource) == 0 {
+		ae.ObjectRef.Subresource = subresource
+	}
+
+	if ae.Level.Less(auditinternal.LevelRequest) {
+		return
+	}
+
+	// TODO(audit): hook into the serializer to avoid double conversion
+	var err error
+	ae.RequestObject, err = encodeObject(obj, gvr.GroupVersion(), s)
+	if err != nil {
+		// TODO(audit): add error slice to audit event struct
+		glog.Warningf("Auditing failed of %v request: %v", reflect.TypeOf(obj).Name(), err)
+		return
+	}
+}
+
+// LogRequestPatch fills in the given patch as the request object into an audit event.
+func LogRequestPatch(ae *auditinternal.Event, patch []byte) {
+	if ae == nil || ae.Level.Less(auditinternal.LevelRequest) {
+		return
+	}
+
+	ae.RequestObject = &runtime.Unknown{
+		Raw:         patch,
+		ContentType: runtime.ContentTypeJSON,
+	}
+}
+
+// LogResponseObject fills in the response object into an audit event. The passed runtime.Object
+// will be converted to the given gv.
+func LogResponseObject(ae *auditinternal.Event, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) {
+	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
+		return
+	}
+	if status, ok := obj.(*metav1.Status); ok {
+		// selectively copy the bounded fields.
+		ae.ResponseStatus = &metav1.Status{
+			Status: status.Status,
+			Reason: status.Reason,
+			Code:   status.Code,
+		}
+	}
+
+	if ae.Level.Less(auditinternal.LevelRequestResponse) {
+		return
+	}
+	// TODO(audit): hook into the serializer to avoid double conversion
+	var err error
+	ae.ResponseObject, err = encodeObject(obj, gv, s)
+	if err != nil {
+		glog.Warningf("Audit failed for %q response: %v", reflect.TypeOf(obj).Name(), err)
+	}
+}
+
+func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime.NegotiatedSerializer) (*runtime.Unknown, error) {
+	supported := serializer.SupportedMediaTypes()
+	for i := range supported {
+		if supported[i].MediaType == "application/json" {
+			enc := serializer.EncoderForVersion(supported[i].Serializer, gv)
+			var buf bytes.Buffer
+			if err := enc.Encode(obj, &buf); err != nil {
+				return nil, fmt.Errorf("encoding failed: %v", err)
+			}
+
+			return &runtime.Unknown{
+				Raw:         buf.Bytes(),
+				ContentType: runtime.ContentTypeJSON,
+			}, nil
+		}
+	}
+	return nil, fmt.Errorf("no json encoder found")
+}
+
+// LogAnnotation fills in the Annotations according to the key value pair.
+func LogAnnotation(ae *auditinternal.Event, key, value string) {
+	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
+		return
+	}
+	if ae.Annotations == nil {
+		ae.Annotations = make(map[string]string)
+	}
+	if v, ok := ae.Annotations[key]; ok && v != value {
+		glog.Warningf("Failed to set annotations[%q] to %q for audit:%q, it has already been set to %q", key, value, ae.AuditID, ae.Annotations[key])
+		return
+	}
+	ae.Annotations[key] = value
+}
+
+// LogAnnotations fills in the Annotations according to the annotations map.
+func LogAnnotations(ae *auditinternal.Event, annotations map[string]string) {
+	if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
+		return
+	}
+	for key, value := range annotations {
+		LogAnnotation(ae, key, value)
+	}
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/audit/scheme.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/scheme.go
new file mode 100644
index 0000000..61c32f5
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/scheme.go
@@ -0,0 +1,36 @@
+/*
+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.
+*/
+
+// TODO: Delete this file if we generate a clientset.
+package audit
+
+import (
+	"k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/apiserver/pkg/apis/audit/v1alpha1"
+	"k8s.io/apiserver/pkg/apis/audit/v1beta1"
+)
+
+var Scheme = runtime.NewScheme()
+var Codecs = serializer.NewCodecFactory(Scheme)
+
+func init() {
+	v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
+	v1alpha1.AddToScheme(Scheme)
+	v1beta1.AddToScheme(Scheme)
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/audit/types.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/types.go
new file mode 100644
index 0000000..f1b7cef
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/types.go
@@ -0,0 +1,42 @@
+/*
+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 audit
+
+import (
+	auditinternal "k8s.io/apiserver/pkg/apis/audit"
+)
+
+type Sink interface {
+	// ProcessEvents handles events. Per audit ID it might be that ProcessEvents is called up to three times.
+	// Errors might be logged by the sink itself. If an error should be fatal, leading to an internal
+	// error, ProcessEvents is supposed to panic. The event must not be mutated and is reused by the caller
+	// after the call returns, i.e. the sink has to make a deepcopy to keep a copy around if necessary.
+	ProcessEvents(events ...*auditinternal.Event)
+}
+
+type Backend interface {
+	Sink
+
+	// Run will initialize the backend. It must not block, but may run go routines in the background. If
+	// stopCh is closed, it is supposed to stop them. Run will be called before the first call to ProcessEvents.
+	Run(stopCh <-chan struct{}) error
+
+	// Shutdown will synchronously shut down the backend while making sure that all pending
+	// events are delivered. It can be assumed that this method is called after
+	// the stopCh channel passed to the Run method has been closed.
+	Shutdown()
+}
diff --git a/metrics-server/vendor/k8s.io/apiserver/pkg/audit/union.go b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/union.go
new file mode 100644
index 0000000..6ee4415
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/apiserver/pkg/audit/union.go
@@ -0,0 +1,68 @@
+/*
+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 audit
+
+import (
+	"fmt"
+	"strings"
+
+	"k8s.io/apimachinery/pkg/util/errors"
+	auditinternal "k8s.io/apiserver/pkg/apis/audit"
+)
+
+// Union returns an audit Backend which logs events to a set of backends. The returned
+// Sink implementation blocks in turn for each call to ProcessEvents.
+func Union(backends ...Backend) Backend {
+	if len(backends) == 1 {
+		return backends[0]
+	}
+	return union{backends}
+}
+
+type union struct {
+	backends []Backend
+}
+
+func (u union) ProcessEvents(events ...*auditinternal.Event) {
+	for _, backend := range u.backends {
+		backend.ProcessEvents(events...)
+	}
+}
+
+func (u union) Run(stopCh <-chan struct{}) error {
+	var funcs []func() error
+	for _, backend := range u.backends {
+		funcs = append(funcs, func() error {
+			return backend.Run(stopCh)
+		})
+	}
+	return errors.AggregateGoroutines(funcs...)
+}
+
+func (u union) Shutdown() {
+	for _, backend := range u.backends {
+		backend.Shutdown()
+	}
+}
+
+func (u union) String() string {
+	var backendStrings []string
+	for _, backend := range u.backends {
+		backendStrings = append(backendStrings, fmt.Sprintf("%s", backend))
+	}
+	return fmt.Sprintf("union[%s]", strings.Join(backendStrings, ","))
+}