Skip to content

Conversation

vertex451
Copy link
Member

@vertex451 vertex451 commented Aug 7, 2025

Resolves #254

Features

  1. Relations are resolved in GetItem only. For ListItem and both subscriptions it is disabled for now. When user try to query related field, he will receive null.
  2. Relation nesting is limited by 1, but in future we can change it to bigger number. Also, nesting level is hardcoded for now, later we can pass it as a user input.

TBD

  1. Handle relation resolution when the same kind from different groups is referenced.

Testing

Tested 21.08 at b6d83af commit in local-setup by creating an account via portal UI and querying related object(check attachments)

Demo

Screenshot 2025-08-21 at 14 18 25 Screenshot 2025-08-21 at 14 18 36

@github-actions github-actions bot removed the feature label Aug 14, 2025
@vertex451 vertex451 marked this pull request as ready for review August 14, 2025 12:03
@vertex451 vertex451 requested a review from a team as a code owner August 14, 2025 12:03
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
@vertex451 vertex451 self-assigned this Aug 15, 2025
@vertex451 vertex451 added this to the 2025.Q3 milestone Aug 15, 2025
Copy link

@pteich pteich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments and suggestions.

}

// Initialize the relation enhancer after gateway is created
g.relationEnhancer = NewRelationEnhancer(g)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the same circular dependency as above. I don't really see a need to separate it in another struct. You don't have an possibility to add a different RelationEnhancer so it makes no sense. I would move the methods to the Gateway. You can however keep it in a separate file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not going to believe - I seen those deps, but decided to go with them since it is in the same package.
But eventually you have the point - no need to move it under a different struct.

On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
@vertex451 vertex451 requested a review from pteich August 15, 2025 15:48
Comment on lines 126 to 134
if schema.VendorExtensible.Extensions == nil {
schema.VendorExtensible.Extensions = map[string]any{}
}
schema.VendorExtensible.Extensions[common.ScopeExtensionKey] = apiextensionsv1.NamespaceScoped
} else {
schema.VendorExtensible.AddExtension(common.ScopeExtensionKey, apiextensionsv1.ClusterScoped)
if schema.VendorExtensible.Extensions == nil {
schema.VendorExtensible.Extensions = map[string]any{}
}
schema.VendorExtensible.Extensions[common.ScopeExtensionKey] = apiextensionsv1.ClusterScoped
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this changed, was therer a deprecation somewhere? The old code looked cleaner

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overlooked this comment, now I noticed it, returned AddExtension method.

Copy link
Member

@aaronschweig aaronschweig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few questions:

  1. How does it verify, that none of the "deep" nesting is provided, as we wanted it to be limited to one level deep?

  2. How is prevented for watches, as with the field resolver we have the ability to execute this query on everything.

  3. How would you solve the multiple kinds for different group errors? E.g. imagine a resource that has a ThingRef but Thing is provied via group A and group B - how would you know which would be the right kind to use for the relation?

Comment on lines 141 to 145
if crd == nil {
return b
}

schema, ok := b.schemas[getOpenAPISchemaKey(*gvk)]
if !ok {
gkv, err := getCRDGroupVersionKind(crd.Spec)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was it intentional, that the short path with the categories got removed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this change having in mind different versions of CRD.

Before Categories were applied to the "preferred" version of a CRD
After Categories are applied to ALL versions of a CRD

It is not directly related to the relations feature, but it is needed for consistency.

Let me know if it makes sense for you.

Comment on lines 192 to 195
if resourceSchema.VendorExtensible.Extensions == nil {
resourceSchema.VendorExtensible.Extensions = map[string]any{}
}
resourceSchema.VendorExtensible.Extensions[common.CategoriesExtensionKey] = apiResource.Categories
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I touched this code as well for the same reason, but behavior remains the same compared to main branch - the change is in refactoring, less parsing and early returns.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was asking more about why the easily readale AddExtension was removed with this custom logic

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was not necessary, returned AddExtension methods instead of manual manipulation.

Comment on lines 257 to 258
key := strings.ToLower(gvk.Kind)
b.kindRegistry[key] = append(b.kindRegistry[key], resourceInfo)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we have the same kind provided by different groups? We have a bug here. In go map keys can be arbitrary, so it would be more benefitial to use the GroupVersionKind struct as an index here, rather than only the kind, the circumvent future name collisions with the kind.

Especially in the platform-mesh usecase this might be very common, that the same kind is provided form x different apiGroups

Copy link
Member Author

@vertex451 vertex451 Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right - we may have silent overrides here, fixing it.
Fixed

Comment on lines 61 to 68
// Two schemas with same Kind different groups/versions - first should win
first := schemaWithGVK("a.example", "v1", "Thing")
second := schemaWithGVK("b.example", "v1", "Thing")
b.SetSchemas(map[string]*spec.Schema{
"a.example.v1.Thing": first,
"b.example.v1.Thing": second,
"c.other.v1.Other": schemaWithGVK("c.other", "v1", "Other"),
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is basically testing the exact wrong thing that I mentioned in the other comment - this would in my view be a bug, not intended behaviour

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I change a logic a bit - now server preferred resources has priority first, if no, we put in the first place resources from core group, then by version stability (v1 > v1beta1 > v1alpha1).
Logic is preserved in findBestResourceForKind.

And I added a test that checks if server preferred resources comes first.

On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
@vertex451 vertex451 requested a review from aaronschweig August 21, 2025 12:14
@vertex451
Copy link
Member Author

  • How does it verify, that none of the "deep" nesting is provided, as we wanted it to be limited to one level deep?
  • How is prevented for watches, as with the field resolver we have the ability to execute this query on everything.
  • How would you solve the multiple kinds for different group errors? E.g. imagine a resource that has a ThingRef but Thing is provied via group A and group B - how would you know which would be the right kind to use for the relation?
  1. Deep Nesting Prevention:
    The schema builder's expandWithSimpleDepthControl() function only processes *Ref fields at the top level and doesn't recursively add relationship fields to the generated relationship fields themselves, limiting nesting to exactly one level.
  2. Watches Prevention:
    The RelationResolver uses detectOperationFromGraphQLInfo() to analyze the GraphQL path and isRelationResolutionAllowedForOperation() to only enable relationships for GET_ITEM and GET_ITEM_AS_YAML operations.
  3. Multiple Groups Resolution(not tested yet):
    I used kubectl-style priority resolution (preferred versions → core groups → alphabetical order) at schema build time to automatically select the "winner" group.

On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
@@ -117,16 +130,16 @@ func (r *Service) ListItems(gvk schema.GroupVersionKind, scope v1.ResourceScope)
return nil, err
}

err = validateSortBy(list.Items, sortBy)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to this issue, but we should sort only in case of sortBy param only

On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
On-behalf-of: @SAP a.shcherbatiuk@sap.com
Signed-off-by: Artem Shcherbatiuk <vertex451@gmail.com>
@vertex451 vertex451 merged commit 4019c9d into main Aug 21, 2025
12 checks passed
@vertex451 vertex451 deleted the relations-new branch August 21, 2025 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Resource relations in schema generation
3 participants