Skip to content

Commit 6bebfc7

Browse files
committed
relation
On-behalf-of: @SAP [email protected] Signed-off-by: Artem Shcherbatiuk <[email protected]>
1 parent 24f0fc3 commit 6bebfc7

File tree

2 files changed

+457
-53
lines changed

2 files changed

+457
-53
lines changed

gateway/resolver/relationships.go

Lines changed: 169 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ type RelationshipRef struct {
3131
Namespace string `json:"namespace,omitempty"`
3232
}
3333

34+
// EnhancedRef represents our custom relationship reference structure
35+
type EnhancedRef struct {
36+
Kind string `json:"kind,omitempty"`
37+
GroupVersion string `json:"groupVersion,omitempty"`
38+
Name string `json:"name"`
39+
Namespace string `json:"namespace,omitempty"`
40+
}
41+
3442
// NewRelationshipResolver creates a new relationship resolver
3543
func NewRelationshipResolver(log *logger.Logger, runtimeClient client.WithWatch, groupNames map[string]string) *RelationshipResolver {
3644
return &RelationshipResolver{
@@ -93,7 +101,7 @@ func (r *RelationshipResolver) CreateRelationshipResolver(sourceGVK schema.Group
93101
}
94102

95103
// Parse the relationship reference
96-
ref, err := r.parseRelationshipRef(refMap)
104+
ref, err := r.parseRelationshipRef(refMap, sourceObj, sourceGVK, fieldName)
97105
if err != nil {
98106
return nil, fmt.Errorf("error parsing relationship ref: %v", err)
99107
}
@@ -110,12 +118,63 @@ func (r *RelationshipResolver) CreateRelationshipResolver(sourceGVK schema.Group
110118
}
111119

112120
// Fetch the referenced resource
113-
return r.fetchReferencedResource(ctx, ref, targetGVK)
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
114173
}
115174
}
116175

117176
// parseRelationshipRef parses a relationship reference from a map
118-
func (r *RelationshipResolver) parseRelationshipRef(refMap map[string]interface{}) (*RelationshipRef, error) {
177+
func (r *RelationshipResolver) parseRelationshipRef(refMap map[string]interface{}, sourceObj map[string]interface{}, sourceGVK schema.GroupVersionKind, fieldName string) (*RelationshipRef, error) {
119178
ref := &RelationshipRef{}
120179

121180
if kind, ok := refMap["kind"].(string); ok {
@@ -126,17 +185,17 @@ func (r *RelationshipResolver) parseRelationshipRef(refMap map[string]interface{
126185
ref.GroupVersion = groupVersion
127186
}
128187

188+
// Handle native Kubernetes apiGroup field (used in roleRef, clusterRoleRef, etc.)
129189
if apiGroup, ok := refMap["apiGroup"].(string); ok {
130-
// Handle RBAC-style references where apiGroup is specified separately
131-
if apiGroup != "" {
132-
// For RBAC, we need to construct the groupVersion
133-
// Default to v1 if no version specified in the groupVersion field
134-
if ref.GroupVersion == "" {
135-
ref.GroupVersion = apiGroup + "/v1"
136-
}
137-
} else {
190+
// For RBAC resources, apiGroup + kind determines the groupVersion
191+
if apiGroup == "rbac.authorization.k8s.io" {
192+
ref.GroupVersion = "rbac.authorization.k8s.io/v1"
193+
} else if apiGroup == "" {
138194
// Empty apiGroup means core/v1
139195
ref.GroupVersion = "v1"
196+
} else {
197+
// Default to v1 for other groups
198+
ref.GroupVersion = apiGroup + "/v1"
140199
}
141200
}
142201

@@ -148,11 +207,101 @@ func (r *RelationshipResolver) parseRelationshipRef(refMap map[string]interface{
148207

149208
if namespace, ok := refMap["namespace"].(string); ok {
150209
ref.Namespace = namespace
210+
} else {
211+
// For native Kubernetes references like roleRef, infer namespace from source
212+
ref.Namespace = r.inferNamespaceForReference(sourceObj, sourceGVK, fieldName, ref.Kind)
151213
}
152214

153215
return ref, nil
154216
}
155217

218+
// createEnhancedRef transforms a native Kubernetes reference to our enhanced structure
219+
func (r *RelationshipResolver) createEnhancedRef(nativeRefMap map[string]interface{}, sourceObj map[string]interface{}, sourceGVK schema.GroupVersionKind, fieldName string) (map[string]interface{}, error) {
220+
enhancedRef := make(map[string]interface{})
221+
222+
// Extract name (required)
223+
name, ok := nativeRefMap["name"].(string)
224+
if !ok {
225+
return nil, fmt.Errorf("name is required in relationship reference")
226+
}
227+
enhancedRef["name"] = name
228+
229+
// Extract or infer kind
230+
if kind, ok := nativeRefMap["kind"].(string); ok {
231+
enhancedRef["kind"] = kind
232+
} else {
233+
// Infer kind from field name
234+
targetKind := ExtractTargetKind(fieldName)
235+
if targetKind != "" {
236+
enhancedRef["kind"] = targetKind
237+
}
238+
}
239+
240+
// Extract or construct groupVersion
241+
if groupVersion, ok := nativeRefMap["groupVersion"].(string); ok {
242+
enhancedRef["groupVersion"] = groupVersion
243+
} else if apiGroup, ok := nativeRefMap["apiGroup"].(string); ok {
244+
// Construct groupVersion from apiGroup
245+
if apiGroup == "rbac.authorization.k8s.io" {
246+
enhancedRef["groupVersion"] = "rbac.authorization.k8s.io/v1"
247+
} else if apiGroup == "" {
248+
enhancedRef["groupVersion"] = "v1"
249+
} else {
250+
enhancedRef["groupVersion"] = apiGroup + "/v1"
251+
}
252+
}
253+
254+
// Extract or infer namespace
255+
if namespace, ok := nativeRefMap["namespace"].(string); ok {
256+
enhancedRef["namespace"] = namespace
257+
} else {
258+
// Infer namespace from source object and relationship context
259+
targetKind, _ := enhancedRef["kind"].(string)
260+
inferredNamespace := r.inferNamespaceForReference(sourceObj, sourceGVK, fieldName, targetKind)
261+
if inferredNamespace != "" {
262+
enhancedRef["namespace"] = inferredNamespace
263+
}
264+
}
265+
266+
r.log.Debug().
267+
Str("fieldName", fieldName).
268+
Str("name", name).
269+
Interface("enhancedRef", enhancedRef).
270+
Msg("Created enhanced reference")
271+
272+
return enhancedRef, nil
273+
}
274+
275+
// inferNamespaceForReference infers the namespace for a reference based on Kubernetes conventions
276+
func (r *RelationshipResolver) inferNamespaceForReference(sourceObj map[string]interface{}, sourceGVK schema.GroupVersionKind, fieldName string, targetKind string) string {
277+
// For roleRef in RoleBinding, the referenced Role is in the same namespace as the RoleBinding
278+
if fieldName == "roleRef" && sourceGVK.Kind == "RoleBinding" && targetKind == "Role" {
279+
if metadata, found := sourceObj["metadata"]; found {
280+
if metadataMap, ok := metadata.(map[string]interface{}); ok {
281+
if namespace, ok := metadataMap["namespace"].(string); ok {
282+
return namespace
283+
}
284+
}
285+
}
286+
}
287+
288+
// For clusterRoleRef or when targeting ClusterRole, no namespace needed (cluster-scoped)
289+
if fieldName == "roleRef" && targetKind == "ClusterRole" {
290+
return ""
291+
}
292+
293+
// Default: try to use source object's namespace for namespaced targets
294+
if metadata, found := sourceObj["metadata"]; found {
295+
if metadataMap, ok := metadata.(map[string]interface{}); ok {
296+
if namespace, ok := metadataMap["namespace"].(string); ok {
297+
return namespace
298+
}
299+
}
300+
}
301+
302+
return ""
303+
}
304+
156305
// resolveTargetGVK resolves the target GroupVersionKind from the relationship reference
157306
func (r *RelationshipResolver) resolveTargetGVK(ref *RelationshipRef, sourceGVK schema.GroupVersionKind) (schema.GroupVersionKind, error) {
158307
targetGVK := schema.GroupVersionKind{
@@ -187,7 +336,7 @@ func (r *RelationshipResolver) resolveTargetGVK(ref *RelationshipRef, sourceGVK
187336
}
188337

189338
// fetchReferencedResource fetches the referenced Kubernetes resource
190-
func (r *RelationshipResolver) fetchReferencedResource(ctx context.Context, ref *RelationshipRef, targetGVK schema.GroupVersionKind) (interface{}, error) {
339+
func (r *RelationshipResolver) fetchReferencedResource(ctx context.Context, ref *RelationshipRef, targetGVK schema.GroupVersionKind, sourceObj map[string]interface{}, sourceGVK schema.GroupVersionKind) (interface{}, error) {
191340
// Create an unstructured object to hold the result
192341
obj := &unstructured.Unstructured{}
193342
obj.SetGroupVersionKind(targetGVK)
@@ -196,11 +345,18 @@ func (r *RelationshipResolver) fetchReferencedResource(ctx context.Context, ref
196345
Name: ref.Name,
197346
}
198347

199-
// If namespace is specified in the ref, use it
348+
// Set namespace if specified and target is namespaced
200349
if ref.Namespace != "" {
201350
key.Namespace = ref.Namespace
202351
}
203352

353+
r.log.Debug().
354+
Str("refName", ref.Name).
355+
Str("refNamespace", ref.Namespace).
356+
Str("targetGVK", targetGVK.String()).
357+
Str("sourceKind", sourceGVK.Kind).
358+
Msg("Fetching referenced resource")
359+
204360
// Get the object using the runtime client
205361
if err := r.runtimeClient.Get(ctx, key, obj); err != nil {
206362
r.log.Error().Err(err).

0 commit comments

Comments
 (0)