github.com/docker/compose-on-kubernetes@v0.5.0/internal/controller/stackownercache.go (about)

     1  package controller
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/docker/compose-on-kubernetes/api/client/clientset"
     8  	"github.com/docker/compose-on-kubernetes/api/client/clientset/scheme"
     9  	"github.com/docker/compose-on-kubernetes/api/compose/latest"
    10  	"github.com/docker/compose-on-kubernetes/internal/stackresources"
    11  	log "github.com/sirupsen/logrus"
    12  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/util/wait"
    15  	"k8s.io/client-go/rest"
    16  )
    17  
    18  // StackOwnerCacher describes a component capable of caching stack ownership data
    19  type StackOwnerCacher interface {
    20  	remove(key string)
    21  	setDirty(key string)
    22  	getWithRetries(stack *latest.Stack, acceptDirty bool) (rest.ImpersonationConfig, error)
    23  }
    24  
    25  type ownerGetter interface {
    26  	get(*latest.Stack) (*latest.Owner, error)
    27  }
    28  
    29  type stackOwnerCacheEntry struct {
    30  	config rest.ImpersonationConfig
    31  	dirty  bool
    32  }
    33  
    34  type stackOwnerCache struct {
    35  	mut    sync.Mutex
    36  	data   map[string]stackOwnerCacheEntry
    37  	getter ownerGetter
    38  }
    39  
    40  type apiOwnerGetter struct {
    41  	rest.Interface
    42  }
    43  
    44  func (g *apiOwnerGetter) get(stack *latest.Stack) (*latest.Owner, error) {
    45  	var owner latest.Owner
    46  	if err := g.Get().Namespace(stack.Namespace).Name(stack.Name).
    47  		Resource("stacks").
    48  		SubResource("owner").
    49  		VersionedParams(&metav1.GetOptions{}, scheme.ParameterCodec).
    50  		Do().
    51  		Into(&owner); err != nil {
    52  		return nil, err
    53  	}
    54  	return &owner, nil
    55  }
    56  
    57  // NewStackOwnerCache creates a stackOwnerCache
    58  func NewStackOwnerCache(config *rest.Config) (StackOwnerCacher, error) {
    59  	cs, err := clientset.NewForConfig(config)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	return &stackOwnerCache{
    64  		data:   make(map[string]stackOwnerCacheEntry),
    65  		getter: &apiOwnerGetter{cs.ComposeLatest().RESTClient()},
    66  	}, nil
    67  }
    68  
    69  func (s *stackOwnerCache) remove(key string) {
    70  	s.mut.Lock()
    71  	defer s.mut.Unlock()
    72  	delete(s.data, key)
    73  }
    74  
    75  func (s *stackOwnerCache) setDirty(key string) {
    76  	s.mut.Lock()
    77  	defer s.mut.Unlock()
    78  	if entry, ok := s.data[key]; ok {
    79  		entry.dirty = true
    80  		s.data[key] = entry
    81  	}
    82  }
    83  
    84  func (s *stackOwnerCache) getWithError(stack *latest.Stack, acceptDirty bool) (rest.ImpersonationConfig, error) {
    85  	s.mut.Lock()
    86  	defer s.mut.Unlock()
    87  	objKey := stackresources.ObjKey(stack.Namespace, stack.Name)
    88  	if v, ok := s.data[objKey]; ok {
    89  		if !v.dirty || acceptDirty {
    90  			return v.config, nil
    91  		}
    92  	}
    93  	owner, err := s.getter.get(stack)
    94  	if err != nil {
    95  		log.Errorf("Unable to get stack %q owner: %s", objKey, err)
    96  		if kerrors.IsNotFound(err) {
    97  			if v, ok := s.data[objKey]; ok {
    98  				log.Infof("Stack %q seem to have been deleted. Fallback to dirty impersonation config", objKey)
    99  				return v.config, nil
   100  
   101  			}
   102  		}
   103  		return rest.ImpersonationConfig{}, err
   104  	}
   105  	log.Debugf("Stack %s/%s owner is %s", stack.Namespace, stack.Name, owner.Owner.UserName)
   106  	ic := rest.ImpersonationConfig{
   107  		UserName: owner.Owner.UserName,
   108  		Groups:   owner.Owner.Groups,
   109  		Extra:    owner.Owner.Extra,
   110  	}
   111  	s.data[objKey] = stackOwnerCacheEntry{dirty: false, config: ic}
   112  	return ic, nil
   113  }
   114  
   115  func (s *stackOwnerCache) getWithRetries(stack *latest.Stack, acceptDirty bool) (rest.ImpersonationConfig, error) {
   116  	var ic rest.ImpersonationConfig
   117  	err := wait.ExponentialBackoff(wait.Backoff{
   118  		Duration: time.Second,
   119  		Factor:   2,
   120  		Jitter:   float64(100 * time.Millisecond),
   121  		Steps:    4,
   122  	}, func() (done bool, err error) {
   123  		res, err := s.getWithError(stack, acceptDirty)
   124  		if err == nil {
   125  			ic = res
   126  			return true, nil
   127  		}
   128  		if kerrors.IsNotFound(err) {
   129  			// stack has been removed and we don't have anything in cache, but still the reconciler wants to update
   130  			// this can happen when a stack is removed while the controller is starting, or when an informer
   131  			// somehow fails to watch an event (which seems to be possible on Docker Desktop after a machine gets to sleep and a compaction
   132  			// occurs immediately after)
   133  			// So here we pass the error to the caller to let the process crash and be re-scheduled
   134  			return false, err
   135  		}
   136  		return false, nil
   137  	})
   138  	return ic, err
   139  }