11package resolver
22
33import (
4- "context"
54 "fmt"
65 "strings"
76
@@ -11,24 +10,14 @@ import (
1110 "go.opentelemetry.io/otel/trace"
1211 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1312 "k8s.io/apimachinery/pkg/runtime/schema"
14- "sigs.k8s.io/controller-runtime/pkg/client"
1513
1614 "github.com/openmfp/golang-commons/logger"
1715)
1816
1917// RelationshipResolver handles resolution of relationships between Kubernetes resources
2018type RelationshipResolver struct {
21- log * logger.Logger
22- runtimeClient client.WithWatch
23- groupNames map [string ]string
24- }
25-
26- // RelationshipRef represents a reference to another Kubernetes resource
27- type RelationshipRef struct {
28- Kind string `json:"kind,omitempty"`
29- GroupVersion string `json:"groupVersion,omitempty"`
30- Name string `json:"name"`
31- Namespace string `json:"namespace,omitempty"`
19+ log * logger.Logger
20+ groupNames map [string ]string
3221}
3322
3423// EnhancedRef represents our custom relationship reference structure
@@ -40,11 +29,10 @@ type EnhancedRef struct {
4029}
4130
4231// NewRelationshipResolver creates a new relationship resolver
43- func NewRelationshipResolver (log * logger.Logger , runtimeClient client. WithWatch , groupNames map [string ]string ) * RelationshipResolver {
32+ func NewRelationshipResolver (log * logger.Logger , groupNames map [string ]string ) * RelationshipResolver {
4433 return & RelationshipResolver {
45- log : log ,
46- runtimeClient : runtimeClient ,
47- groupNames : groupNames ,
34+ log : log ,
35+ groupNames : groupNames ,
4836 }
4937}
5038
@@ -69,110 +57,6 @@ func ExtractTargetKind(fieldName string) string {
6957 return strings .ToUpper (kindName [:1 ]) + kindName [1 :]
7058}
7159
72- // CreateRelationshipResolver creates a GraphQL field resolver for relationship fields
73- func (r * RelationshipResolver ) CreateRelationshipResolver (sourceGVK schema.GroupVersionKind , fieldName string , targetKind string ) graphql.FieldResolveFn {
74- return func (p graphql.ResolveParams ) (interface {}, error ) {
75- ctx , span := otel .Tracer ("" ).Start (p .Context , "ResolveRelationship" ,
76- trace .WithAttributes (
77- attribute .String ("sourceKind" , sourceGVK .Kind ),
78- attribute .String ("fieldName" , fieldName ),
79- attribute .String ("targetKind" , targetKind ),
80- ))
81- defer span .End ()
82-
83- // Get the source object from the parent resolver
84- sourceObj , ok := p .Source .(map [string ]interface {})
85- if ! ok {
86- return nil , fmt .Errorf ("expected source to be map[string]interface{}, got %T" , p .Source )
87- }
88-
89- // Extract the relationship reference from the source object
90- refValue , found , err := unstructured .NestedFieldNoCopy (sourceObj , fieldName )
91- if err != nil {
92- return nil , fmt .Errorf ("error accessing field %s: %v" , fieldName , err )
93- }
94- if ! found {
95- return nil , nil // Field not present
96- }
97-
98- refMap , ok := refValue .(map [string ]interface {})
99- if ! ok {
100- return nil , fmt .Errorf ("expected %s to be map[string]interface{}, got %T" , fieldName , refValue )
101- }
102-
103- // Parse the relationship reference
104- ref , err := r .parseRelationshipRef (refMap , sourceObj , sourceGVK , fieldName )
105- if err != nil {
106- return nil , fmt .Errorf ("error parsing relationship ref: %v" , err )
107- }
108-
109- // If no kind specified in ref, use the target kind from field name
110- if ref .Kind == "" {
111- ref .Kind = targetKind
112- }
113-
114- // Resolve the target GVK
115- targetGVK , err := r .resolveTargetGVK (ref , sourceGVK )
116- if err != nil {
117- return nil , fmt .Errorf ("error resolving target GVK: %v" , err )
118- }
119-
120- // Fetch the referenced resource
121- return r .fetchReferencedResource (ctx , ref , targetGVK , sourceObj , sourceGVK )
122- }
123- }
124-
125- // CreateRelationsResolver creates a GraphQL field resolver for the relations field
126- func (r * RelationshipResolver ) CreateRelationsResolver (sourceGVK schema.GroupVersionKind , relationshipFields []string ) graphql.FieldResolveFn {
127- return func (p graphql.ResolveParams ) (interface {}, error ) {
128- _ , span := otel .Tracer ("" ).Start (p .Context , "ResolveRelations" ,
129- trace .WithAttributes (
130- attribute .String ("sourceKind" , sourceGVK .Kind ),
131- attribute .Int ("relationshipCount" , len (relationshipFields )),
132- ))
133- defer span .End ()
134-
135- // Get the source object from the parent resolver
136- sourceObj , ok := p .Source .(map [string ]interface {})
137- if ! ok {
138- return nil , fmt .Errorf ("expected source to be map[string]interface{}, got %T" , p .Source )
139- }
140-
141- relations := make (map [string ]interface {})
142-
143- for _ , fieldName := range relationshipFields {
144- // Extract the relationship reference from the source object
145- refValue , found , err := unstructured .NestedFieldNoCopy (sourceObj , fieldName )
146- if err != nil {
147- r .log .Debug ().Err (err ).Str ("field" , fieldName ).Msg ("Error accessing relationship field" )
148- continue
149- }
150- if ! found {
151- continue // Field not present, skip
152- }
153-
154- refMap , ok := refValue .(map [string ]interface {})
155- if ! ok {
156- r .log .Debug ().Str ("field" , fieldName ).Msg ("Relationship field is not a map" )
157- continue
158- }
159-
160- // Create enhanced reference
161- enhancedRef , err := r .createEnhancedRef (refMap , sourceObj , sourceGVK , fieldName )
162- if err != nil {
163- r .log .Debug ().Err (err ).Str ("field" , fieldName ).Msg ("Error creating enhanced reference" )
164- continue
165- }
166-
167- // Add to relations without "Ref" suffix
168- relationName := strings .TrimSuffix (fieldName , "Ref" )
169- relations [relationName ] = enhancedRef
170- }
171-
172- return relations , nil
173- }
174- }
175-
17660// CreateSingleRelationResolver creates a GraphQL field resolver for a single relation field
17761func (r * RelationshipResolver ) CreateSingleRelationResolver (sourceGVK schema.GroupVersionKind , fieldName string ) graphql.FieldResolveFn {
17862 return func (p graphql.ResolveParams ) (interface {}, error ) {
@@ -216,48 +100,6 @@ func (r *RelationshipResolver) CreateSingleRelationResolver(sourceGVK schema.Gro
216100 }
217101}
218102
219- // parseRelationshipRef parses a relationship reference from a map
220- func (r * RelationshipResolver ) parseRelationshipRef (refMap map [string ]interface {}, sourceObj map [string ]interface {}, sourceGVK schema.GroupVersionKind , fieldName string ) (* RelationshipRef , error ) {
221- ref := & RelationshipRef {}
222-
223- if kind , ok := refMap ["kind" ].(string ); ok {
224- ref .Kind = kind
225- }
226-
227- if groupVersion , ok := refMap ["groupVersion" ].(string ); ok {
228- ref .GroupVersion = groupVersion
229- }
230-
231- // Handle native Kubernetes apiGroup field (used in roleRef, clusterRoleRef, etc.)
232- if apiGroup , ok := refMap ["apiGroup" ].(string ); ok {
233- // For RBAC resources, apiGroup + kind determines the groupVersion
234- if apiGroup == "rbac.authorization.k8s.io" {
235- ref .GroupVersion = "rbac.authorization.k8s.io/v1"
236- } else if apiGroup == "" {
237- // Empty apiGroup means core/v1
238- ref .GroupVersion = "v1"
239- } else {
240- // Default to v1 for other groups
241- ref .GroupVersion = apiGroup + "/v1"
242- }
243- }
244-
245- name , ok := refMap ["name" ].(string )
246- if ! ok {
247- return nil , fmt .Errorf ("name is required in relationship reference" )
248- }
249- ref .Name = name
250-
251- if namespace , ok := refMap ["namespace" ].(string ); ok {
252- ref .Namespace = namespace
253- } else {
254- // For native Kubernetes references like roleRef, infer namespace from source
255- ref .Namespace = r .inferNamespaceForReference (sourceObj , sourceGVK , fieldName , ref .Kind )
256- }
257-
258- return ref , nil
259- }
260-
261103// createEnhancedRef transforms a native Kubernetes reference to our enhanced structure
262104func (r * RelationshipResolver ) createEnhancedRef (nativeRefMap map [string ]interface {}, sourceObj map [string ]interface {}, sourceGVK schema.GroupVersionKind , fieldName string ) (map [string ]interface {}, error ) {
263105 enhancedRef := make (map [string ]interface {})
@@ -345,72 +187,4 @@ func (r *RelationshipResolver) inferNamespaceForReference(sourceObj map[string]i
345187 return ""
346188}
347189
348- // resolveTargetGVK resolves the target GroupVersionKind from the relationship reference
349- func (r * RelationshipResolver ) resolveTargetGVK (ref * RelationshipRef , sourceGVK schema.GroupVersionKind ) (schema.GroupVersionKind , error ) {
350- targetGVK := schema.GroupVersionKind {
351- Kind : ref .Kind ,
352- }
353-
354- if ref .GroupVersion != "" {
355- // Parse group and version from groupVersion
356- parts := strings .Split (ref .GroupVersion , "/" )
357- if len (parts ) == 2 {
358- targetGVK .Group = parts [0 ]
359- targetGVK .Version = parts [1 ]
360- } else if len (parts ) == 1 {
361- // Could be just version (for core resources) or just group
362- if ref .GroupVersion == "v1" || strings .HasPrefix (ref .GroupVersion , "v" ) {
363- targetGVK .Group = ""
364- targetGVK .Version = ref .GroupVersion
365- } else {
366- targetGVK .Group = ref .GroupVersion
367- targetGVK .Version = "v1" // Default version
368- }
369- } else {
370- return targetGVK , fmt .Errorf ("invalid groupVersion format: %s" , ref .GroupVersion )
371- }
372- } else {
373- // If no groupVersion specified, inherit from source or use defaults
374- targetGVK .Group = sourceGVK .Group
375- targetGVK .Version = sourceGVK .Version
376- }
377-
378- return targetGVK , nil
379- }
380-
381- // fetchReferencedResource fetches the referenced Kubernetes resource
382- func (r * RelationshipResolver ) fetchReferencedResource (ctx context.Context , ref * RelationshipRef , targetGVK schema.GroupVersionKind , sourceObj map [string ]interface {}, sourceGVK schema.GroupVersionKind ) (interface {}, error ) {
383- // Create an unstructured object to hold the result
384- obj := & unstructured.Unstructured {}
385- obj .SetGroupVersionKind (targetGVK )
386-
387- key := client.ObjectKey {
388- Name : ref .Name ,
389- }
390-
391- // Set namespace if specified and target is namespaced
392- if ref .Namespace != "" {
393- key .Namespace = ref .Namespace
394- }
395-
396- r .log .Debug ().
397- Str ("refName" , ref .Name ).
398- Str ("refNamespace" , ref .Namespace ).
399- Str ("targetGVK" , targetGVK .String ()).
400- Str ("sourceKind" , sourceGVK .Kind ).
401- Msg ("Fetching referenced resource" )
402-
403- // Get the object using the runtime client
404- if err := r .runtimeClient .Get (ctx , key , obj ); err != nil {
405- r .log .Error ().Err (err ).
406- Str ("name" , ref .Name ).
407- Str ("namespace" , ref .Namespace ).
408- Str ("gvk" , targetGVK .String ()).
409- Msg ("Unable to get referenced object" )
410- return nil , err
411- }
412-
413- return obj .Object , nil
414- }
415-
416190// getOriginalGroupName converts sanitized group name back to original
0 commit comments