cluster/tools/kartongips: init
This forks bitnami/kubecfg into kartongips. The rationale is that we
want to implement hscloud-specific functionality that wouldn't really be
upstreamable into kubecfg (like secret support, mulit-cluster support).
We forked off from github.com/q3k/kubecfg at commit b6817a94492c561ed61a44eeea2d92dcf2e6b8c0.
Change-Id: If5ba513905e0a86f971576fe7061a471c1d8b398
diff --git a/cluster/tools/kartongips/utils/client.go b/cluster/tools/kartongips/utils/client.go
new file mode 100644
index 0000000..07cf254
--- /dev/null
+++ b/cluster/tools/kartongips/utils/client.go
@@ -0,0 +1,297 @@
+// Copyright 2017 The kubecfg 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.
+
+// Changes:
+// * Merged updates from https://github.com/kubernetes/client-go/blob/kubernetes-1.18.1/discovery/cached/memory/memcache.go
+// --jjo, 2020-04-09
+
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "net/url"
+ "sync"
+ "syscall"
+
+ openapi_v2 "github.com/googleapis/gnostic/openapiv2"
+
+ log "github.com/sirupsen/logrus"
+ errorsutil "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"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apimachinery/pkg/version"
+ "k8s.io/client-go/discovery"
+ "k8s.io/client-go/dynamic"
+ restclient "k8s.io/client-go/rest"
+)
+
+type cacheEntry struct {
+ resourceList *metav1.APIResourceList
+ err error
+}
+
+// memcachedDiscoveryClient can Invalidate() to stay up-to-date with discovery
+// information.
+//
+// TODO: Switch to a watch interface. Right now it will poll after each
+// Invalidate() call.
+type memcachedDiscoveryClient struct {
+ delegate discovery.DiscoveryInterface
+
+ lock sync.RWMutex
+ groupToServerResources map[string]*cacheEntry
+ groupList *metav1.APIGroupList
+ cacheValid bool
+}
+
+// Error Constants
+var (
+ ErrCacheNotFound = errors.New("not found")
+)
+
+var _ discovery.CachedDiscoveryInterface = &memcachedDiscoveryClient{}
+
+// isTransientConnectionError checks whether given error is "Connection refused" or
+// "Connection reset" error which usually means that apiserver is temporarily
+// unavailable.
+func isTransientConnectionError(err error) bool {
+ urlError, ok := err.(*url.Error)
+ if !ok {
+ return false
+ }
+ opError, ok := urlError.Err.(*net.OpError)
+ if !ok {
+ return false
+ }
+ errno, ok := opError.Err.(syscall.Errno)
+ if !ok {
+ return false
+ }
+ return errno == syscall.ECONNREFUSED || errno == syscall.ECONNRESET
+}
+
+func isTransientError(err error) bool {
+ if isTransientConnectionError(err) {
+ return true
+ }
+
+ if t, ok := err.(errorsutil.APIStatus); ok && t.Status().Code >= 500 {
+ return true
+ }
+
+ return errorsutil.IsTooManyRequests(err)
+}
+
+// ServerResourcesForGroupVersion returns the supported resources for a group and version.
+func (d *memcachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+ if !d.cacheValid {
+ if err := d.refreshLocked(); err != nil {
+ return nil, err
+ }
+ }
+ cachedVal, ok := d.groupToServerResources[groupVersion]
+ if !ok {
+ return nil, ErrCacheNotFound
+ }
+
+ if cachedVal.err != nil && isTransientError(cachedVal.err) {
+ r, err := d.serverResourcesForGroupVersion(groupVersion)
+ if err != nil {
+ utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", groupVersion, err))
+ }
+ cachedVal = &cacheEntry{r, err}
+ d.groupToServerResources[groupVersion] = cachedVal
+ }
+
+ return cachedVal.resourceList, cachedVal.err
+}
+
+// ServerResources returns the supported resources for all groups and versions.
+// Deprecated: use ServerGroupsAndResources instead.
+func (d *memcachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
+ return discovery.ServerResources(d)
+}
+
+// ServerGroupsAndResources returns the groups and supported resources for all groups and versions.
+func (d *memcachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
+ return discovery.ServerGroupsAndResources(d)
+}
+
+func (d *memcachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+ if !d.cacheValid {
+ if err := d.refreshLocked(); err != nil {
+ return nil, err
+ }
+ }
+ return d.groupList, nil
+}
+
+func (d *memcachedDiscoveryClient) RESTClient() restclient.Interface {
+ return d.delegate.RESTClient()
+}
+
+func (d *memcachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
+ return discovery.ServerPreferredResources(d)
+}
+
+func (d *memcachedDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
+ return discovery.ServerPreferredNamespacedResources(d)
+}
+
+func (d *memcachedDiscoveryClient) ServerVersion() (*version.Info, error) {
+ return d.delegate.ServerVersion()
+}
+
+func (d *memcachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
+ return d.delegate.OpenAPISchema()
+}
+
+func (d *memcachedDiscoveryClient) Fresh() bool {
+ d.lock.RLock()
+ defer d.lock.RUnlock()
+ // Return whether the cache is populated at all. It is still possible that
+ // a single entry is missing due to transient errors and the attempt to read
+ // that entry will trigger retry.
+ return d.cacheValid
+}
+
+// Invalidate enforces that no cached data that is older than the current time
+// is used.
+func (d *memcachedDiscoveryClient) Invalidate() {
+ d.lock.Lock()
+ defer d.lock.Unlock()
+ d.cacheValid = false
+ d.groupToServerResources = nil
+ d.groupList = nil
+}
+
+// refreshLocked refreshes the state of cache. The caller must hold d.lock for
+// writing.
+func (d *memcachedDiscoveryClient) refreshLocked() error {
+ // TODO: Could this multiplicative set of calls be replaced by a single call
+ // to ServerResources? If it's possible for more than one resulting
+ // APIResourceList to have the same GroupVersion, the lists would need merged.
+ gl, err := d.delegate.ServerGroups()
+ if err != nil || len(gl.Groups) == 0 {
+ utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list: %v", err))
+ return err
+ }
+
+ wg := &sync.WaitGroup{}
+ resultLock := &sync.Mutex{}
+ rl := map[string]*cacheEntry{}
+ for _, g := range gl.Groups {
+ for _, v := range g.Versions {
+ gv := v.GroupVersion
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer utilruntime.HandleCrash()
+
+ r, err := d.serverResourcesForGroupVersion(gv)
+ if err != nil {
+ utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", gv, err))
+ }
+
+ resultLock.Lock()
+ defer resultLock.Unlock()
+ rl[gv] = &cacheEntry{r, err}
+ }()
+ }
+ }
+ wg.Wait()
+
+ d.groupToServerResources, d.groupList = rl, gl
+ d.cacheValid = true
+ return nil
+}
+
+func (d *memcachedDiscoveryClient) serverResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
+ r, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
+ if err != nil {
+ return r, err
+ }
+ if len(r.APIResources) == 0 {
+ return r, fmt.Errorf("Got empty response for: %v", groupVersion)
+ }
+ return r, nil
+}
+
+var _ discovery.CachedDiscoveryInterface = &memcachedDiscoveryClient{}
+
+// MaybeMarkStale calls MarkStale on the discovery client, if the
+// client is a memcachedClient.
+func MaybeMarkStale(d discovery.DiscoveryInterface) {
+ if c, ok := d.(*memcachedDiscoveryClient); ok {
+ c.Invalidate()
+ }
+}
+
+func (c *memcachedDiscoveryClient) MarkStale() {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ log.Debug("Marking cached discovery info (potentially) stale")
+ c.cacheValid = false
+}
+
+// ClientForResource returns the ResourceClient for a given object
+func ClientForResource(client dynamic.Interface, mapper meta.RESTMapper, obj runtime.Object, defNs string) (dynamic.ResourceInterface, error) {
+ gvk := obj.GetObjectKind().GroupVersionKind()
+
+ mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
+ if err != nil {
+ return nil, err
+ }
+
+ rc := client.Resource(mapping.Resource)
+
+ switch mapping.Scope.Name() {
+ case meta.RESTScopeNameRoot:
+ return rc, nil
+ case meta.RESTScopeNameNamespace:
+ meta, err := meta.Accessor(obj)
+ if err != nil {
+ return nil, err
+ }
+ namespace := meta.GetNamespace()
+ if namespace == "" {
+ namespace = defNs
+ }
+ return rc.Namespace(namespace), nil
+ default:
+ return nil, fmt.Errorf("unexpected resource scope %q", mapping.Scope)
+ }
+}
+
+// NewmemcachedDiscoveryClient creates a new CachedDiscoveryInterface which caches
+// discovery information in memory and will stay up-to-date if Invalidate is
+// called with regularity.
+//
+// NOTE: The client will NOT resort to live lookups on cache misses.
+func NewMemcachedDiscoveryClient(delegate discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface {
+ return &memcachedDiscoveryClient{
+ delegate: delegate,
+ groupToServerResources: map[string]*cacheEntry{},
+ }
+}