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/client-go/discovery/cached_discovery.go b/metrics-server/vendor/k8s.io/client-go/discovery/cached_discovery.go
new file mode 100644
index 0000000..aca4654
--- /dev/null
+++ b/metrics-server/vendor/k8s.io/client-go/discovery/cached_discovery.go
@@ -0,0 +1,282 @@
+/*
+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 (
+ "errors"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+
+ "github.com/golang/glog"
+ "github.com/googleapis/gnostic/OpenAPIv2"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/version"
+ "k8s.io/client-go/kubernetes/scheme"
+ restclient "k8s.io/client-go/rest"
+)
+
+// CachedDiscoveryClient implements the functions that discovery server-supported API groups,
+// versions and resources.
+type CachedDiscoveryClient struct {
+ delegate DiscoveryInterface
+
+ // cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
+ cacheDirectory string
+
+ // ttl is how long the cache should be considered valid
+ ttl time.Duration
+
+ // mutex protects the variables below
+ mutex sync.Mutex
+
+ // ourFiles are all filenames of cache files created by this process
+ ourFiles map[string]struct{}
+ // invalidated is true if all cache files should be ignored that are not ours (e.g. after Invalidate() was called)
+ invalidated bool
+ // fresh is true if all used cache files were ours
+ fresh bool
+}
+
+var _ CachedDiscoveryInterface = &CachedDiscoveryClient{}
+
+// ServerResourcesForGroupVersion returns the supported resources for a group and version.
+func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
+ filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
+ cachedBytes, err := d.getCachedFile(filename)
+ // don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
+ if err == nil {
+ cachedResources := &metav1.APIResourceList{}
+ if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
+ glog.V(10).Infof("returning cached discovery info from %v", filename)
+ return cachedResources, nil
+ }
+ }
+
+ liveResources, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
+ if err != nil {
+ glog.V(3).Infof("skipped caching discovery info due to %v", err)
+ return liveResources, err
+ }
+ if liveResources == nil || len(liveResources.APIResources) == 0 {
+ glog.V(3).Infof("skipped caching discovery info, no resources found")
+ return liveResources, err
+ }
+
+ if err := d.writeCachedFile(filename, liveResources); err != nil {
+ glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
+ }
+
+ return liveResources, nil
+}
+
+// ServerResources returns the supported resources for all groups and versions.
+func (d *CachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
+ return ServerResources(d)
+}
+
+func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
+ filename := filepath.Join(d.cacheDirectory, "servergroups.json")
+ cachedBytes, err := d.getCachedFile(filename)
+ // don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
+ if err == nil {
+ cachedGroups := &metav1.APIGroupList{}
+ if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
+ glog.V(10).Infof("returning cached discovery info from %v", filename)
+ return cachedGroups, nil
+ }
+ }
+
+ liveGroups, err := d.delegate.ServerGroups()
+ if err != nil {
+ glog.V(3).Infof("skipped caching discovery info due to %v", err)
+ return liveGroups, err
+ }
+ if liveGroups == nil || len(liveGroups.Groups) == 0 {
+ glog.V(3).Infof("skipped caching discovery info, no groups found")
+ return liveGroups, err
+ }
+
+ if err := d.writeCachedFile(filename, liveGroups); err != nil {
+ glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
+ }
+
+ return liveGroups, nil
+}
+
+func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
+ // after invalidation ignore cache files not created by this process
+ d.mutex.Lock()
+ _, ourFile := d.ourFiles[filename]
+ if d.invalidated && !ourFile {
+ d.mutex.Unlock()
+ return nil, errors.New("cache invalidated")
+ }
+ d.mutex.Unlock()
+
+ file, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ fileInfo, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ if time.Now().After(fileInfo.ModTime().Add(d.ttl)) {
+ return nil, errors.New("cache expired")
+ }
+
+ // the cache is present and its valid. Try to read and use it.
+ cachedBytes, err := ioutil.ReadAll(file)
+ if err != nil {
+ return nil, err
+ }
+
+ d.mutex.Lock()
+ defer d.mutex.Unlock()
+ d.fresh = d.fresh && ourFile
+
+ return cachedBytes, nil
+}
+
+func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
+ if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
+ return err
+ }
+
+ bytes, err := runtime.Encode(scheme.Codecs.LegacyCodec(), obj)
+ if err != nil {
+ return err
+ }
+
+ f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".")
+ if err != nil {
+ return err
+ }
+ defer os.Remove(f.Name())
+ _, err = f.Write(bytes)
+ if err != nil {
+ return err
+ }
+
+ err = os.Chmod(f.Name(), 0755)
+ if err != nil {
+ return err
+ }
+
+ name := f.Name()
+ err = f.Close()
+ if err != nil {
+ return err
+ }
+
+ // atomic rename
+ d.mutex.Lock()
+ defer d.mutex.Unlock()
+ err = os.Rename(name, filename)
+ if err == nil {
+ d.ourFiles[filename] = struct{}{}
+ }
+ return err
+}
+
+func (d *CachedDiscoveryClient) RESTClient() restclient.Interface {
+ return d.delegate.RESTClient()
+}
+
+func (d *CachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
+ return ServerPreferredResources(d)
+}
+
+func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
+ return ServerPreferredNamespacedResources(d)
+}
+
+func (d *CachedDiscoveryClient) ServerVersion() (*version.Info, error) {
+ return d.delegate.ServerVersion()
+}
+
+func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
+ return d.delegate.OpenAPISchema()
+}
+
+func (d *CachedDiscoveryClient) Fresh() bool {
+ d.mutex.Lock()
+ defer d.mutex.Unlock()
+
+ return d.fresh
+}
+
+func (d *CachedDiscoveryClient) Invalidate() {
+ d.mutex.Lock()
+ defer d.mutex.Unlock()
+
+ d.ourFiles = map[string]struct{}{}
+ d.fresh = true
+ d.invalidated = true
+}
+
+// NewCachedDiscoveryClientForConfig creates a new DiscoveryClient for the given config, and wraps
+// the created client in a CachedDiscoveryClient. The provided configuration is updated with a
+// custom transport that understands cache responses.
+// We receive two distinct cache directories for now, in order to preserve old behavior
+// which makes use of the --cache-dir flag value for storing cache data from the CacheRoundTripper,
+// and makes use of the hardcoded destination (~/.kube/cache/discovery/...) for storing
+// CachedDiscoveryClient cache data. If httpCacheDir is empty, the restconfig's transport will not
+// be updated with a roundtripper that understands cache responses.
+// If discoveryCacheDir is empty, cached server resource data will be looked up in the current directory.
+// TODO(juanvallejo): the value of "--cache-dir" should be honored. Consolidate discoveryCacheDir with httpCacheDir
+// so that server resources and http-cache data are stored in the same location, provided via config flags.
+func NewCachedDiscoveryClientForConfig(config *restclient.Config, discoveryCacheDir, httpCacheDir string, ttl time.Duration) (*CachedDiscoveryClient, error) {
+ if len(httpCacheDir) > 0 {
+ // update the given restconfig with a custom roundtripper that
+ // understands how to handle cache responses.
+ wt := config.WrapTransport
+ config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
+ if wt != nil {
+ rt = wt(rt)
+ }
+ return newCacheRoundTripper(httpCacheDir, rt)
+ }
+ }
+
+ discoveryClient, err := NewDiscoveryClientForConfig(config)
+ if err != nil {
+ return nil, err
+ }
+
+ return newCachedDiscoveryClient(discoveryClient, discoveryCacheDir, ttl), nil
+}
+
+// NewCachedDiscoveryClient creates a new DiscoveryClient. cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
+func newCachedDiscoveryClient(delegate DiscoveryInterface, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
+ return &CachedDiscoveryClient{
+ delegate: delegate,
+ cacheDirectory: cacheDirectory,
+ ttl: ttl,
+ ourFiles: map[string]struct{}{},
+ fresh: true,
+ }
+}