mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			283 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| 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,
 | |
| 	}
 | |
| }
 | 
