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 }