Skip to content

feat: relations #303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

feat: relations #303

wants to merge 13 commits into from

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.
  3. In case of the same kind but different group graphql schema while generated track those conflicts and mark apiGroup and kind fields from user input as required. It is done during startup, so no unnecessary calculation during runtime.

Testing

Tested 15.08 at 44afd8b commit in local-setup

Demo

Screenshot 2025-08-07 at 16 52 27 and CRD: Screenshot 2025-08-08 at 16 47 08

@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 [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
@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 [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
@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 [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
On-behalf-of: @SAP [email protected]
Signed-off-by: Artem Shcherbatiuk <[email protected]>
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