@@ -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+
102219func (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
511644func (g * Gateway ) storeCategory (
0 commit comments