github.com/grahambrereton-form3/tilt@v0.10.18/internal/k8s/owner_fetcher.go (about) 1 package k8s 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "sync" 8 9 v1 "k8s.io/api/core/v1" 10 "k8s.io/apimachinery/pkg/api/errors" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 "k8s.io/apimachinery/pkg/runtime" 13 "k8s.io/apimachinery/pkg/types" 14 ) 15 16 // The ObjectRefTree only contains immutable properties 17 // of a Kubernetes object: the name, namespace, and UID 18 type ObjectRefTree struct { 19 Ref v1.ObjectReference 20 Owners []ObjectRefTree 21 } 22 23 func (t ObjectRefTree) UIDs() []types.UID { 24 result := []types.UID{t.Ref.UID} 25 for _, owner := range t.Owners { 26 result = append(result, owner.UIDs()...) 27 } 28 return result 29 } 30 31 func (t ObjectRefTree) stringLines() []string { 32 result := []string{fmt.Sprintf("%s:%s", t.Ref.Kind, t.Ref.Name)} 33 for _, owner := range t.Owners { 34 // indent each of the owners by two spaces 35 branchLines := owner.stringLines() 36 for _, branchLine := range branchLines { 37 result = append(result, fmt.Sprintf(" %s", branchLine)) 38 } 39 } 40 return result 41 } 42 43 func (t ObjectRefTree) String() string { 44 return strings.Join(t.stringLines(), "\n") 45 } 46 47 type OwnerFetcher struct { 48 kCli Client 49 cache map[types.UID]*objectTreePromise 50 mu *sync.Mutex 51 } 52 53 func ProvideOwnerFetcher(kCli Client) OwnerFetcher { 54 return OwnerFetcher{ 55 kCli: kCli, 56 cache: make(map[types.UID]*objectTreePromise), 57 mu: &sync.Mutex{}, 58 } 59 } 60 61 // Returns a promise and a boolean. The boolean is true if the promise is 62 // already in progress, and false if the caller is responsible for 63 // resolving/rejecting the promise. 64 func (v OwnerFetcher) getOrCreatePromise(id types.UID) (*objectTreePromise, bool) { 65 v.mu.Lock() 66 defer v.mu.Unlock() 67 promise, ok := v.cache[id] 68 if !ok { 69 promise = newObjectTreePromise() 70 v.cache[id] = promise 71 } 72 return promise, ok 73 } 74 75 func (v OwnerFetcher) OwnerTreeOfRef(ctx context.Context, ref v1.ObjectReference) (result ObjectRefTree, err error) { 76 uid := ref.UID 77 if uid == "" { 78 return ObjectRefTree{}, fmt.Errorf("Can only get owners of deployed entities") 79 } 80 81 promise, ok := v.getOrCreatePromise(uid) 82 if ok { 83 return promise.wait() 84 } 85 86 defer func() { 87 if err != nil { 88 promise.reject(err) 89 } else { 90 promise.resolve(result) 91 } 92 }() 93 94 entity, err := v.kCli.GetByReference(ctx, ref) 95 if err != nil { 96 if errors.IsNotFound(err) { 97 return ObjectRefTree{Ref: ref}, nil 98 } 99 return ObjectRefTree{}, err 100 } 101 return v.ownerTreeOfHelper(ctx, ref, entity.meta()) 102 } 103 104 func (v OwnerFetcher) OwnerTreeOf(ctx context.Context, entity K8sEntity) (result ObjectRefTree, err error) { 105 meta := entity.meta() 106 uid := meta.GetUID() 107 if uid == "" { 108 return ObjectRefTree{}, fmt.Errorf("Can only get owners of deployed entities") 109 } 110 111 promise, ok := v.getOrCreatePromise(uid) 112 if ok { 113 return promise.wait() 114 } 115 116 defer func() { 117 if err != nil { 118 promise.reject(err) 119 } else { 120 promise.resolve(result) 121 } 122 }() 123 124 ref := entity.ToObjectReference() 125 return v.ownerTreeOfHelper(ctx, ref, meta) 126 } 127 128 func (v OwnerFetcher) ownerTreeOfHelper(ctx context.Context, ref v1.ObjectReference, meta k8sMeta) (ObjectRefTree, error) { 129 tree := ObjectRefTree{Ref: ref} 130 owners, err := v.ownersOfMeta(ctx, meta) 131 if err != nil { 132 return ObjectRefTree{}, err 133 } 134 for _, owner := range owners { 135 ownerTree, err := v.OwnerTreeOf(ctx, owner) 136 if err != nil { 137 return ObjectRefTree{}, err 138 } 139 tree.Owners = append(tree.Owners, ownerTree) 140 } 141 return tree, nil 142 } 143 144 func (v OwnerFetcher) ownersOfMeta(ctx context.Context, meta k8sMeta) ([]K8sEntity, error) { 145 owners := meta.GetOwnerReferences() 146 result := make([]K8sEntity, 0, len(owners)) 147 for _, owner := range owners { 148 ref := OwnerRefToObjectRef(owner, meta.GetNamespace()) 149 owner, err := v.kCli.GetByReference(ctx, ref) 150 if err != nil { 151 if errors.IsNotFound(err) { 152 continue 153 } 154 return nil, err 155 } 156 result = append(result, owner) 157 } 158 159 return result, nil 160 } 161 162 func OwnerRefToObjectRef(owner metav1.OwnerReference, namespace string) v1.ObjectReference { 163 return v1.ObjectReference{ 164 APIVersion: owner.APIVersion, 165 Kind: owner.Kind, 166 Namespace: namespace, 167 Name: owner.Name, 168 UID: owner.UID, 169 } 170 } 171 172 func RuntimeObjToOwnerRef(obj runtime.Object) metav1.OwnerReference { 173 e := NewK8sEntity(obj) 174 ref := e.ToObjectReference() 175 return metav1.OwnerReference{ 176 APIVersion: ref.APIVersion, 177 Kind: ref.Kind, 178 Name: ref.Name, 179 UID: ref.UID, 180 } 181 } 182 183 type objectTreePromise struct { 184 tree ObjectRefTree 185 err error 186 done chan struct{} 187 } 188 189 func newObjectTreePromise() *objectTreePromise { 190 return &objectTreePromise{ 191 done: make(chan struct{}), 192 } 193 } 194 195 func (e *objectTreePromise) resolve(tree ObjectRefTree) { 196 e.tree = tree 197 close(e.done) 198 } 199 200 func (e *objectTreePromise) reject(err error) { 201 e.err = err 202 close(e.done) 203 } 204 205 func (e *objectTreePromise) wait() (ObjectRefTree, error) { 206 <-e.done 207 return e.tree, e.err 208 }