@@ -18,6 +18,7 @@ package http
1818
1919import (
2020 "bytes"
21+ "context"
2122 "encoding/base64"
2223 "encoding/json"
2324 "errors"
@@ -33,9 +34,12 @@ import (
3334 "github.com/gorilla/securecookie"
3435 "golang.org/x/oauth2"
3536 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
37+ apierrors "k8s.io/apimachinery/pkg/api/errors"
3638 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3740 "k8s.io/apimachinery/pkg/labels"
3841 "k8s.io/apimachinery/pkg/runtime"
42+ "k8s.io/apimachinery/pkg/runtime/schema"
3943 componentbaseversion "k8s.io/component-base/version"
4044 "k8s.io/klog/v2"
4145
@@ -66,6 +70,7 @@ type handler struct {
6670 backendCallbackURL string
6771 providerPrettyName string
6872 testingAutoSelect string
73+ schemaSource string
6974
7075 cookieEncryptionKey []byte
7176 cookieSigningKey []byte
@@ -78,6 +83,7 @@ func NewHandler(
7883 provider * OIDCServiceProvider ,
7984 oidcAuthorizeURL , backendCallbackURL , providerPrettyName , testingAutoSelect string ,
8085 cookieSigningKey , cookieEncryptionKey []byte ,
86+ schemaSource string ,
8187 scope kubebindv1alpha2.InformerScope ,
8288 mgr * kubernetes.Manager ,
8389) (* handler , error ) {
@@ -87,6 +93,7 @@ func NewHandler(
8793 backendCallbackURL : backendCallbackURL ,
8894 providerPrettyName : providerPrettyName ,
8995 testingAutoSelect : testingAutoSelect ,
96+ schemaSource : schemaSource ,
9097 scope : scope ,
9198 client : http .DefaultClient ,
9299 kubeManager : mgr ,
@@ -328,6 +335,15 @@ func createSessionState(authCode *AuthCode, token *oauth2.Token) (*session.State
328335 }, nil
329336}
330337
338+ type UISchema struct {
339+ Name string
340+ Version string
341+ Group string
342+ Kind string
343+ Scope string // "Namespaced" or "Cluster"
344+ Resource string
345+ }
346+
331347func (h * handler ) handleResources (w http.ResponseWriter , r * http.Request ) {
332348 logger := getLogger (r )
333349
@@ -346,41 +362,74 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
346362 return
347363 }
348364
349- labelSelector := labels.Set {
350- resources .ExportedCRDsLabel : "true" ,
351- }
352-
353- crds , err := h .kubeManager .ListCustomResourceDefinitions (r .Context (), cluster , labelSelector .AsSelector ())
365+ apiResourceSchemas , err := h .getBackendDynamicResource (r .Context (), cluster )
354366 if err != nil {
355- logger .Error (err , "failed to list crds " )
367+ logger .Error (err , "failed to get dynamic resources " )
356368 http .Error (w , "internal error" , http .StatusInternalServerError )
357369 return
358370 }
359- sort .SliceStable (crds .Items , func (i , j int ) bool {
360- return crds .Items [i ].Name < crds .Items [j ].Name
361- })
362- rightScopedCRDs := []* apiextensionsv1.CustomResourceDefinition {}
363- for _ , crd := range crds .Items {
364- if h .scope == kubebindv1alpha2 .ClusterScope || crd .Spec .Scope == apiextensionsv1 .NamespaceScoped {
365- rightScopedCRDs = append (rightScopedCRDs , & crd )
371+
372+ var result []UISchema
373+ for _ , item := range apiResourceSchemas .Items {
374+ scope := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["scope" ]
375+ if scope == nil {
376+ scope = "-"
366377 }
367- }
368378
369- apiResourceSchemas , err := h .kubeManager .ListAPIResourceSchemas (r .Context (), cluster )
370- if err != nil {
371- logger .Error (err , "failed to get api resource schemas" )
372- http .Error (w , "internal error" , http .StatusInternalServerError )
373- return
379+ // Convert scope to string for comparison
380+ scopeStr := scope .(string )
381+ handlerScopeStr := string (h .scope )
382+
383+ // Skip resources that don't match the handler's scope configuration
384+ // If handler is configured for ClusterScope, only show cluster-scoped resources
385+ // If handler is configured for NamespacedScope, only show namespaced resources
386+ if (handlerScopeStr == string (kubebindv1alpha2 .ClusterScope ) && scopeStr == string (apiextensionsv1 .NamespaceScoped )) ||
387+ (handlerScopeStr == string (kubebindv1alpha2 .NamespacedScope ) && scopeStr == string (apiextensionsv1 .ClusterScoped )) {
388+ continue
389+ }
390+
391+ group := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["group" ]
392+ if group == nil {
393+ group = "-"
394+ }
395+ resource := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["names" ].(map [string ]interface {})["plural" ]
396+ if resource == nil {
397+ resource = "-"
398+ }
399+
400+ kind := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["names" ].(map [string ]interface {})["kind" ]
401+ if kind == nil {
402+ kind = "-"
403+ }
404+ namespace := item .GetNamespace ()
405+ if namespace == "" {
406+ namespace = "-"
407+ }
408+
409+ versions := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["versions" ]
410+ if versions == nil {
411+ versions = []interface {}{"" }
412+ }
413+ for _ , v := range versions .([]interface {}) {
414+ version := v .(map [string ]interface {})["name" ]
415+ result = append (result , UISchema {
416+ Name : item .GetName (),
417+ Kind : kind .(string ),
418+ Scope : scope .(string ),
419+ Version : version .(string ),
420+ Group : group .(string ),
421+ Resource : resource .(string ),
422+ })
423+ }
374424 }
425+
375426 bs := bytes.Buffer {}
376427 if err := resourcesTemplate .Execute (& bs , struct {
377- Cluster string
378- CRDs []* apiextensionsv1.CustomResourceDefinition
379- APIResourceSchemas []kubebindv1alpha2.APIResourceSchema
428+ Cluster string
429+ Schemas []UISchema
380430 }{
381- Cluster : cluster ,
382- CRDs : rightScopedCRDs ,
383- APIResourceSchemas : apiResourceSchemas .Items ,
431+ Cluster : cluster ,
432+ Schemas : result ,
384433 }); err != nil {
385434 logger .Error (err , "failed to execute template" )
386435 http .Error (w , "internal error" , http .StatusInternalServerError )
@@ -393,8 +442,10 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
393442
394443func (h * handler ) handleBind (w http.ResponseWriter , r * http.Request ) {
395444 logger := getLogger (r )
445+ name := r .URL .Query ().Get ("name" )
396446 group := r .URL .Query ().Get ("group" )
397447 resource := r .URL .Query ().Get ("resource" )
448+ version := r .URL .Query ().Get ("version" )
398449 cluster := mux .Vars (r )["cluster" ]
399450
400451 prepareNoCache (w )
@@ -415,7 +466,38 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
415466 return
416467 }
417468
418- kfg , err := h .kubeManager .HandleResources (r .Context (), state .Token .Subject + "#" + state .ClusterID , cluster , resource , group )
469+ // There is an intent to bind. We need to create APIResourceSchema if one does not exists.
470+ {
471+ apiResourceSchemas , err := h .getBackendDynamicResource (r .Context (), cluster )
472+ if err != nil {
473+ logger .Error (err , "failed to get dynamic resources" )
474+ http .Error (w , "internal error" , http .StatusInternalServerError )
475+ return
476+ }
477+
478+ schema := & unstructured.Unstructured {}
479+ for _ , item := range apiResourceSchemas .Items {
480+ if item .GetName () == name {
481+ schema = & item
482+ break
483+ }
484+ }
485+ if schema == nil || schema .GetName () != name {
486+ logger .Error (nil , "no APIResourceSchema found" , "name" , name , "group" , group , "resource" , resource , "version" , version )
487+ http .Error (w , fmt .Sprintf ("no APIResourceSchema found for %s.%s.%s/%s" , group , resource , version , name ), http .StatusNotFound )
488+ return
489+ }
490+
491+ // create apiResourceSchema if not exists
492+ err = h .kubeManager .CreateAPIResourceSchema (r .Context (), cluster , name , schema )
493+ if err != nil && ! apierrors .IsAlreadyExists (err ) {
494+ logger .Error (err , "failed to create APIResourceSchema" )
495+ http .Error (w , "internal error" , http .StatusInternalServerError )
496+ return
497+ }
498+ }
499+
500+ kfg , err := h .kubeManager .HandleResources (r .Context (), state .Token .Subject + "#" + state .ClusterID , cluster )
419501 if err != nil {
420502 logger .Error (err , "failed to handle resources" )
421503 http .Error (w , "internal error" , http .StatusInternalServerError )
@@ -435,7 +517,10 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
435517 },
436518 Spec : kubebindv1alpha2.APIServiceExportRequestSpec {
437519 Resources : []kubebindv1alpha2.APIServiceExportRequestResource {
438- {GroupResource : kubebindv1alpha2.GroupResource {Group : group , Resource : resource }},
520+ {
521+ GroupResource : kubebindv1alpha2.GroupResource {Group : group , Resource : resource },
522+ Versions : []string {version },
523+ },
439524 },
440525 },
441526 }
@@ -495,3 +580,28 @@ func mustRead(f func(name string) ([]byte, error), name string) string {
495580 }
496581 return string (bs )
497582}
583+
584+ func (h * handler ) getBackendDynamicResource (ctx context.Context , cluster string ) (* unstructured.UnstructuredList , error ) {
585+ labelSelector := labels.Set {
586+ resources .ExportedCRDsLabel : "true" ,
587+ }
588+
589+ parts := strings .SplitN (h .schemaSource , "." , 3 )
590+ if len (parts ) != 3 {
591+ return nil , fmt .Errorf ("invalid schema source: %q" , h .schemaSource )
592+ }
593+
594+ gvk := schema.GroupVersionKind {
595+ Kind : parts [0 ],
596+ Version : parts [1 ],
597+ Group : parts [2 ],
598+ }
599+ apiResourceSchemas , err := h .kubeManager .ListDynamicResources (ctx , cluster , gvk , labelSelector .AsSelector ())
600+ if err != nil {
601+ return nil , fmt .Errorf ("failed to list crds: %w" , err )
602+ }
603+ sort .SliceStable (apiResourceSchemas .Items , func (i , j int ) bool {
604+ return apiResourceSchemas .Items [i ].GetName () < apiResourceSchemas .Items [j ].GetName ()
605+ })
606+ return apiResourceSchemas , nil
607+ }
0 commit comments