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  }