@@ -2,6 +2,7 @@ package k8s
22
33import (
44 "fmt"
5+ "strings"
56
67 appsv1 "k8s.io/api/apps/v1"
78 autoscalingv2 "k8s.io/api/autoscaling/v2"
@@ -13,12 +14,102 @@ import (
1314 storagev1 "k8s.io/api/storage/v1"
1415 "k8s.io/apimachinery/pkg/labels"
1516 "k8s.io/apimachinery/pkg/runtime"
17+ "k8s.io/apimachinery/pkg/runtime/schema"
1618)
1719
1820// ErrUnknownKind is returned by FetchResourceList/FetchResource when the kind
1921// is not a built-in typed resource. The caller should fall through to the dynamic cache.
2022var ErrUnknownKind = fmt .Errorf ("unknown typed kind" )
2123
24+ // builtinGVRs maps every lowercase kind form the typed informer cache serves
25+ // (plural, singular, and abbreviations — the union of what
26+ // FetchResource/FetchResourceList and the REST switch in internal/server
27+ // accept) to its canonical GroupVersionResource. Versions are the GA
28+ // group/versions every supported cluster serves.
29+ //
30+ // One table, two jobs:
31+ // - TypedKindOwnsGroup decides typed-vs-dynamic routing for the resource
32+ // GET/LIST handlers (a built-in addressed by its own group must use the
33+ // typed cache, not the dynamic/CRD cache).
34+ // - BuiltinGVR is a static fallback for live/dynamic fetches when API
35+ // discovery can't resolve a built-in's GVR (partial discovery under
36+ // restricted RBAC, or a transient refresh miss). The drift/insights live
37+ // GET (GetDynamicWithGroupPreserveLastApplied) can't use the typed cache —
38+ // it needs last-applied, which the typed cache strips — so without this it
39+ // would silently return nil and drop drift for built-in managed resources.
40+ //
41+ // Keep in sync with the typed switches in this file and internal/server.
42+ var builtinGVRs = func () map [string ]schema.GroupVersionResource {
43+ defs := []struct {
44+ forms []string
45+ group string
46+ version string
47+ resource string
48+ }{
49+ {[]string {"pod" , "pods" }, "" , "v1" , "pods" },
50+ {[]string {"service" , "services" }, "" , "v1" , "services" },
51+ {[]string {"configmap" , "configmaps" }, "" , "v1" , "configmaps" },
52+ {[]string {"secret" , "secrets" }, "" , "v1" , "secrets" },
53+ {[]string {"event" , "events" }, "" , "v1" , "events" },
54+ {[]string {"persistentvolumeclaim" , "persistentvolumeclaims" , "pvc" , "pvcs" }, "" , "v1" , "persistentvolumeclaims" },
55+ {[]string {"node" , "nodes" }, "" , "v1" , "nodes" },
56+ {[]string {"namespace" , "namespaces" }, "" , "v1" , "namespaces" },
57+ {[]string {"persistentvolume" , "persistentvolumes" , "pv" , "pvs" }, "" , "v1" , "persistentvolumes" },
58+ {[]string {"serviceaccount" , "serviceaccounts" , "sa" }, "" , "v1" , "serviceaccounts" },
59+ {[]string {"limitrange" , "limitranges" }, "" , "v1" , "limitranges" },
60+ {[]string {"resourcequota" , "resourcequotas" }, "" , "v1" , "resourcequotas" },
61+ {[]string {"deployment" , "deployments" }, "apps" , "v1" , "deployments" },
62+ {[]string {"daemonset" , "daemonsets" }, "apps" , "v1" , "daemonsets" },
63+ {[]string {"statefulset" , "statefulsets" }, "apps" , "v1" , "statefulsets" },
64+ {[]string {"replicaset" , "replicasets" }, "apps" , "v1" , "replicasets" },
65+ {[]string {"job" , "jobs" }, "batch" , "v1" , "jobs" },
66+ {[]string {"cronjob" , "cronjobs" }, "batch" , "v1" , "cronjobs" },
67+ {[]string {"hpa" , "hpas" , "horizontalpodautoscaler" , "horizontalpodautoscalers" }, "autoscaling" , "v2" , "horizontalpodautoscalers" },
68+ {[]string {"ingress" , "ingresses" }, "networking.k8s.io" , "v1" , "ingresses" },
69+ {[]string {"networkpolicy" , "networkpolicies" , "netpol" , "netpols" }, "networking.k8s.io" , "v1" , "networkpolicies" },
70+ {[]string {"ingressclass" , "ingressclasses" }, "networking.k8s.io" , "v1" , "ingressclasses" },
71+ {[]string {"poddisruptionbudget" , "poddisruptionbudgets" , "pdb" , "pdbs" }, "policy" , "v1" , "poddisruptionbudgets" },
72+ {[]string {"storageclass" , "storageclasses" , "sc" }, "storage.k8s.io" , "v1" , "storageclasses" },
73+ {[]string {"role" , "roles" }, "rbac.authorization.k8s.io" , "v1" , "roles" },
74+ {[]string {"clusterrole" , "clusterroles" }, "rbac.authorization.k8s.io" , "v1" , "clusterroles" },
75+ {[]string {"rolebinding" , "rolebindings" }, "rbac.authorization.k8s.io" , "v1" , "rolebindings" },
76+ {[]string {"clusterrolebinding" , "clusterrolebindings" }, "rbac.authorization.k8s.io" , "v1" , "clusterrolebindings" },
77+ }
78+ m := make (map [string ]schema.GroupVersionResource )
79+ for _ , d := range defs {
80+ gvr := schema.GroupVersionResource {Group : d .group , Version : d .version , Resource : d .resource }
81+ for _ , f := range d .forms {
82+ m [f ] = gvr
83+ }
84+ }
85+ return m
86+ }()
87+
88+ // TypedKindOwnsGroup reports whether (kind, group) names a built-in kind
89+ // addressed by its own API group — i.e. it must resolve via the typed cache,
90+ // not the dynamic/CRD cache. `deployments`+`apps` is a typed lookup;
91+ // `services`+`serving.knative.dev` is a CRD (dynamic) lookup; `services` with
92+ // an empty group is the core typed Service. Handlers use this to gate the "explicit group ⇒
93+ // dynamic cache" dispatch so built-in workloads addressed with their real group
94+ // don't fall through to the dynamic cache (which has no informer for them).
95+ func TypedKindOwnsGroup (kind , group string ) bool {
96+ gvr , ok := builtinGVRs [strings .ToLower (kind )]
97+ return ok && gvr .Group == group
98+ }
99+
100+ // BuiltinGVR returns the canonical GroupVersionResource for a built-in kind in
101+ // the given group, for use as a static fallback when API discovery can't
102+ // resolve it. group must match the kind's canonical group ("" for core kinds);
103+ // a mismatch (e.g. a CRD whose plural shadows a built-in) returns ok=false so
104+ // the caller keeps treating it as unknown rather than mis-resolving.
105+ func BuiltinGVR (kind , group string ) (schema.GroupVersionResource , bool ) {
106+ gvr , ok := builtinGVRs [strings .ToLower (kind )]
107+ if ! ok || gvr .Group != group {
108+ return schema.GroupVersionResource {}, false
109+ }
110+ return gvr , true
111+ }
112+
22113// ToRuntimeObjects converts a typed slice to []runtime.Object using generics.
23114func ToRuntimeObjects [T runtime.Object ](items []T ) []runtime.Object {
24115 out := make ([]runtime.Object , len (items ))
0 commit comments