package k8score import ( apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" apiruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/tools/cache" ) // OwnerUIDIndex indexes every cached object but Events by the UIDs its owner // references name, so the objects naming one owner are found without scanning // their kind. const OwnerUIDIndex = "ownerUID" var ownerUIDIndexers = cache.Indexers{OwnerUIDIndex: ownerUIDs} // ownerUIDs returns the UIDs obj's owner references name, whatever their other // fields say: the garbage collector treats an object as a dependent of every // UID it names, even when the reference's kind or name is wrong. func ownerUIDs(obj any) ([]string, error) { switch o := obj.(type) { case indexQuery: return o, nil case *unstructured.Unstructured: refs, _, _ := unstructured.NestedFieldNoCopy(o.Object, "metadata", "ownerReferences") list, _ := refs.([]any) var uids []string for _, r := range list { ref, _ := r.(map[string]any) if uid, _ := ref["uid"].(string); uid != "" { uids = append(uids, uid) } } return uids, nil case metav1.Object: refs := o.GetOwnerReferences() if len(refs) == 0 { return nil, nil } uids := make([]string, 0, len(refs)) for _, r := range refs { if r.UID != "" { uids = append(uids, string(r.UID)) } } return uids, nil } return nil, nil } // isEventResource reports either API group's Events, which are never indexed. func isEventResource(gvr schema.GroupVersionResource) bool { return gvr.Resource == "events" && (gvr.Group == "" || gvr.Group == "events.k8s.io") } // ReadableDependents returns the objects of resource in namespaces whose owner // references name uid, under ReadableObjects' rules: ok is false unless each // namespace's store is complete now, and nil namespaces means the kind's whole // scope. The objects are the cached ones: callers must not modify or keep // them. func (rc *ResourceCache) ReadableDependents(resource string, namespaces []string, uid string) ([]apiruntime.Object, bool) { if rc == nil || !rc.isEnabled(resource) || rc.kindStores[resource] == nil { return nil, false } var hits []any if namespaces == nil { for _, st := range rc.kindStores[resource] { if !st.synced() { return nil, false } items, err := st.indexer.ByIndex(OwnerUIDIndex, uid) if err != nil { return nil, false } hits = append(hits, items...) } } else { covered := rc.KindNamespaces(resource) isCovered := namespaceSet(covered) wanted := namespaceSet(namespaces) read := map[cache.Indexer]bool{} for _, namespace := range namespaces { if covered != nil && !isCovered[namespace] { return nil, false } st, ok := rc.storeFor(resource, namespace) if !ok || !st.synced() { return nil, false } if read[st.indexer] { continue } read[st.indexer] = true items, err := st.indexer.ByIndex(OwnerUIDIndex, uid) if err != nil { return nil, false } for _, item := range items { if m, err := apimeta.Accessor(item); err == nil && wanted[m.GetNamespace()] { hits = append(hits, item) } } } } out := make([]apiruntime.Object, 0, len(hits)) for _, item := range hits { if obj, ok := item.(apiruntime.Object); ok { out = append(out, obj) } } return out, true } // indexQuery is a read of keys from one of k8score's indexes: their index // functions answer it with the keys themselves, so Indexer.Index returns the // objects filed under any of them from one hold of the store's lock. type indexQuery []string // ReadableIndexed returns the objects of gvr in namespace that index holds // under any of keys — for OwnerUIDIndex, those whose owner references name one // of the UIDs — from the informers serving namespace now, under // ReadableObject's rules. Each informer's store is read once for all the keys, // so an object that moves from one key to another during the read is found // under one of them, and an object filed under several is returned once. // Several keys need one of k8score's own indexes. namespace "" reads only a // cluster-wide informer, which holds every namespace of a namespaced // resource. ok is also false when the serving informers lack the index. The // objects are the cached ones: callers must not modify or keep them. func (d *DynamicResourceCache) ReadableIndexed(gvr schema.GroupVersionResource, namespace, index string, keys ...string) (objs []*unstructured.Unstructured, ok bool) { entries, ok := d.servedEntries(gvr, namespace) if !ok { return nil, false } for _, e := range entries { var items []any var err error if len(keys) == 1 { items, err = e.informer.GetIndexer().ByIndex(index, keys[0]) } else { items, err = e.informer.GetIndexer().Index(index, indexQuery(keys)) } if err != nil { return nil, false } for _, item := range items { if u, isUnstructured := item.(*unstructured.Unstructured); isUnstructured && (namespace == "" || u.GetNamespace() == namespace) { objs = append(objs, u) } } } return objs, true }