Skip to content

Commit e769daa

Browse files
authored
feat: add-version-filtering-to-newest (#90)
* feat: add version filtering to schema processing Signed-off-by: Bastian Echterhölter <[email protected]> On-behalf-of: @SAP <[email protected]> * feat: enhance version filtering logic in schema processing Signed-off-by: Bastian Echterhölter <[email protected]> On-behalf-of: @SAP <[email protected]> * feat: improving reuse of gvk parsing Signed-off-by: Bastian Echterhölter <[email protected]> On-behalf-of: @SAP <[email protected]> --------- Signed-off-by: Bastian Echterhölter <[email protected]>
1 parent 86cdb96 commit e769daa

File tree

3 files changed

+680
-32
lines changed

3 files changed

+680
-32
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ go.work
4343
# binary files
4444
main
4545
kubernetes-graphql-gateway
46+
47+
# Claude Code files
48+
.claude/
49+
CLAUDE.md
50+
CLAUDE.local.md

gateway/schema/schema.go

Lines changed: 165 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import (
99
"github.com/gobuffalo/flect"
1010
"github.com/graphql-go/graphql"
1111
"github.com/platform-mesh/golang-commons/logger"
12+
1213
"github.com/platform-mesh/kubernetes-graphql-gateway/common"
1314
"github.com/platform-mesh/kubernetes-graphql-gateway/gateway/resolver"
1415

1516
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1617
"k8s.io/apimachinery/pkg/runtime/schema"
18+
"k8s.io/apimachinery/pkg/version"
1719
"k8s.io/kube-openapi/pkg/validation/spec"
1820
)
1921

@@ -63,9 +65,10 @@ func (g *Gateway) generateGraphqlSchema() error {
6365
rootSubscriptionFields := graphql.Fields{}
6466

6567
for group, groupedResources := range g.getDefinitionsByGroup(g.definitions) {
68+
filteredResources := g.filterOutNotActiveVersions(groupedResources)
6669
g.processGroupedResources(
6770
group,
68-
groupedResources,
71+
filteredResources,
6972
rootQueryFields,
7073
rootMutationFields,
7174
rootSubscriptionFields,
@@ -99,6 +102,120 @@ func (g *Gateway) generateGraphqlSchema() error {
99102
return nil
100103
}
101104

105+
func (g *Gateway) filterOutNotActiveVersions(allKinds map[string]*spec.Schema) map[string]*spec.Schema {
106+
filteredSchema := map[string]*spec.Schema{}
107+
processedResources := make(map[string]bool)
108+
109+
for resourceKey, resourceScheme := range allKinds {
110+
// Skip if we've already processed this resource as part of another version group
111+
if processedResources[resourceKey] {
112+
continue
113+
}
114+
115+
hasOtherVersions, otherVersions := hasAnotherVersion(resourceKey, allKinds, g.definitions)
116+
if hasOtherVersions {
117+
// Filter out non-active versions
118+
highestSemver := highestSemverVersion(resourceKey, otherVersions, g.definitions)
119+
filteredSchema[highestSemver] = allKinds[highestSemver]
120+
121+
if resourceKey != highestSemver {
122+
g.log.Debug().
123+
Str("filtered", resourceKey).
124+
Str("kept", highestSemver).
125+
Msg("Filtered out non-active version")
126+
}
127+
for otherVersion := range otherVersions {
128+
if otherVersion != highestSemver {
129+
g.log.Debug().
130+
Str("filtered", otherVersion).
131+
Str("kept", highestSemver).
132+
Msg("Filtered out non-active version")
133+
}
134+
}
135+
136+
// Mark the current resource and all other versions as processed
137+
processedResources[resourceKey] = true
138+
for otherVersion := range otherVersions {
139+
processedResources[otherVersion] = true
140+
}
141+
} else {
142+
// No other versions, keep this resource
143+
filteredSchema[resourceKey] = resourceScheme
144+
processedResources[resourceKey] = true
145+
}
146+
}
147+
return filteredSchema
148+
}
149+
150+
// highestSemverVersion finds the highest semantic version among a group of resources with the same Kind.
151+
// It extracts the version from each GroupVersionKind string and compares them using Kubernetes version priority.
152+
// Returns the full GroupVersionKind string of the resource with the highest version.
153+
func highestSemverVersion(currentKind string, otherVersions map[string]*spec.Schema, definitions map[string]*spec.Schema) string {
154+
highestKey := currentKind
155+
highestVersion := ""
156+
157+
// Extract version from current kind
158+
if gvk, err := getGroupVersionKindFromDefinitions(currentKind, definitions); err == nil {
159+
highestVersion = gvk.Version
160+
}
161+
162+
// Compare with other versions
163+
for versionKey := range otherVersions {
164+
gvk, err := getGroupVersionKindFromDefinitions(versionKey, definitions)
165+
if err != nil {
166+
continue
167+
}
168+
169+
// Compare versions using Kubernetes version comparison
170+
// CompareKubeAwareVersionStrings returns positive if v1 > v2, 0 if equal, negative if v1 < v2
171+
if version.CompareKubeAwareVersionStrings(gvk.Version, highestVersion) > 0 {
172+
highestVersion = gvk.Version
173+
highestKey = versionKey
174+
}
175+
}
176+
177+
return highestKey
178+
}
179+
180+
// hasAnotherVersion checks if there are other versions of the same resource (same Group and Kind, different Version).
181+
// It returns true if other versions exist, and a map of all other versions found.
182+
func hasAnotherVersion(groupVersionKind string, allKinds map[string]*spec.Schema, definitions map[string]*spec.Schema) (bool, map[string]*spec.Schema) {
183+
// Get the GVK for the current resource
184+
currentGVK, err := getGroupVersionKindFromDefinitions(groupVersionKind, definitions)
185+
if err != nil {
186+
// If we can't parse the current GVK, we can't determine if there are other versions
187+
return false, nil
188+
}
189+
190+
otherVersions := map[string]*spec.Schema{}
191+
hasOtherVersion := false
192+
193+
// Check all other resources to find ones with the same Group and Kind but different Version
194+
for otherResourceKey, otherSchema := range allKinds {
195+
// Skip the current resource
196+
if otherResourceKey == groupVersionKind {
197+
continue
198+
}
199+
200+
// Get the GVK for the other resource
201+
otherGVK, err := getGroupVersionKindFromDefinitions(otherResourceKey, definitions)
202+
if err != nil {
203+
// Skip resources we can't parse
204+
continue
205+
}
206+
207+
// Check if it's the same Group and Kind but different Version
208+
if otherGVK.Group == currentGVK.Group &&
209+
otherGVK.Kind == currentGVK.Kind &&
210+
otherGVK.Version != currentGVK.Version {
211+
hasOtherVersion = true
212+
otherVersions[otherResourceKey] = otherSchema
213+
}
214+
}
215+
216+
return hasOtherVersion, otherVersions
217+
}
218+
102219
func (g *Gateway) processGroupedResources(
103220
group string,
104221
groupedResources map[string]*spec.Schema,
@@ -468,44 +585,60 @@ func (g *Gateway) generateTypeName(typePrefix string, fieldPath []string) string
468585
return name
469586
}
470587

471-
// io.openmfp.core.v1alpha1.Account
588+
// parseGVKExtension parses the x-kubernetes-group-version-kind extension from a resource schema.
589+
func parseGVKExtension(extensions map[string]any, resourceKey string) (*schema.GroupVersionKind, error) {
590+
xkGvk, ok := extensions[common.GVKExtensionKey]
591+
if !ok {
592+
return nil, errors.New("x-kubernetes-group-version-kind extension not found")
593+
}
472594

473-
// getGroupVersionKind retrieves the GroupVersionKind for a given resourceKey and its OpenAPI schema.
474-
// It first checks for the 'x-kubernetes-group-version-kind' extension and uses it if available.
475-
// If not, it falls back to parsing the resourceKey.
476-
func (g *Gateway) getGroupVersionKind(resourceKey string) (*schema.GroupVersionKind, error) {
477-
// First, check if 'x-kubernetes-group-version-kind' extension is present
478-
resourceSpec, ok := g.definitions[resourceKey]
479-
if !ok || resourceSpec.Extensions == nil {
480-
return nil, errors.New("no resource extensions")
595+
gvkList, ok := xkGvk.([]any)
596+
if !ok || len(gvkList) == 0 {
597+
return nil, errors.New("invalid GVK extension format")
481598
}
482-
xkGvk, ok := resourceSpec.Extensions[common.GVKExtensionKey]
599+
600+
gvkMap, ok := gvkList[0].(map[string]any)
483601
if !ok {
484-
return nil, errors.New("x-kubernetes-group-version-kind extension not found")
602+
return nil, errors.New("invalid GVK map format")
485603
}
486-
// xkGvk should be an array of maps
487-
if gvkList, ok := xkGvk.([]any); ok && len(gvkList) > 0 {
488-
// Use the first item in the list
489-
if gvkMap, ok := gvkList[0].(map[string]any); ok {
490-
group, _ := gvkMap["group"].(string)
491-
version, _ := gvkMap["version"].(string)
492-
kind, _ := gvkMap["kind"].(string)
493-
494-
// Validate that kind is not empty - empty kinds cannot be used for GraphQL type names
495-
if kind == "" {
496-
return nil, fmt.Errorf("kind cannot be empty for resource %s", resourceKey)
497-
}
498604

499-
// Sanitize the group and kind names
500-
return &schema.GroupVersionKind{
501-
Group: g.resolver.SanitizeGroupName(group),
502-
Version: version,
503-
Kind: kind,
504-
}, nil
505-
}
605+
group, _ := gvkMap["group"].(string)
606+
versionStr, _ := gvkMap["version"].(string)
607+
kind, _ := gvkMap["kind"].(string)
608+
609+
if kind == "" {
610+
return nil, fmt.Errorf("kind cannot be empty for resource %s", resourceKey)
611+
}
612+
613+
return &schema.GroupVersionKind{
614+
Group: group,
615+
Version: versionStr,
616+
Kind: kind,
617+
}, nil
618+
}
619+
620+
// getGroupVersionKindFromDefinitions retrieves the GroupVersionKind for a given resourceKey from a definitions map.
621+
// This is a standalone function that doesn't require a Gateway receiver.
622+
func getGroupVersionKindFromDefinitions(resourceKey string, definitions map[string]*spec.Schema) (*schema.GroupVersionKind, error) {
623+
resourceSpec, ok := definitions[resourceKey]
624+
if !ok || resourceSpec.Extensions == nil {
625+
return nil, errors.New("no resource extensions")
626+
}
627+
628+
return parseGVKExtension(resourceSpec.Extensions, resourceKey)
629+
}
630+
631+
// getGroupVersionKind retrieves the GroupVersionKind for a given resourceKey and its OpenAPI schema.
632+
// It uses the standalone helper but applies group name sanitization for GraphQL compatibility.
633+
func (g *Gateway) getGroupVersionKind(resourceKey string) (*schema.GroupVersionKind, error) {
634+
gvk, err := getGroupVersionKindFromDefinitions(resourceKey, g.definitions)
635+
if err != nil {
636+
return nil, err
506637
}
507638

508-
return nil, errors.New("failed to parse x-kubernetes-group-version-kind extension")
639+
// Sanitize the group name for GraphQL compatibility
640+
gvk.Group = g.resolver.SanitizeGroupName(gvk.Group)
641+
return gvk, nil
509642
}
510643

511644
func (g *Gateway) storeCategory(

0 commit comments

Comments
 (0)