@@ -18,6 +18,7 @@ package http
1818
1919import (
2020 "bytes"
21+ "context"
2122 "encoding/base64"
2223 "encoding/json"
2324 "errors"
@@ -32,10 +33,11 @@ import (
3233 "github.com/gorilla/mux"
3334 "github.com/gorilla/securecookie"
3435 "golang.org/x/oauth2"
35- apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3636 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3738 "k8s.io/apimachinery/pkg/labels"
3839 "k8s.io/apimachinery/pkg/runtime"
40+ "k8s.io/apimachinery/pkg/runtime/schema"
3941 componentbaseversion "k8s.io/component-base/version"
4042 "k8s.io/klog/v2"
4143
@@ -66,6 +68,7 @@ type handler struct {
6668 backendCallbackURL string
6769 providerPrettyName string
6870 testingAutoSelect string
71+ schemaSource string
6972
7073 cookieEncryptionKey []byte
7174 cookieSigningKey []byte
@@ -78,6 +81,7 @@ func NewHandler(
7881 provider * OIDCServiceProvider ,
7982 oidcAuthorizeURL , backendCallbackURL , providerPrettyName , testingAutoSelect string ,
8083 cookieSigningKey , cookieEncryptionKey []byte ,
84+ schemaSource string ,
8185 scope kubebindv1alpha2.InformerScope ,
8286 mgr * kubernetes.Manager ,
8387) (* handler , error ) {
@@ -87,6 +91,7 @@ func NewHandler(
8791 backendCallbackURL : backendCallbackURL ,
8892 providerPrettyName : providerPrettyName ,
8993 testingAutoSelect : testingAutoSelect ,
94+ schemaSource : schemaSource ,
9095 scope : scope ,
9196 client : http .DefaultClient ,
9297 kubeManager : mgr ,
@@ -328,6 +333,15 @@ func createSessionState(authCode *AuthCode, token *oauth2.Token) (*session.State
328333 }, nil
329334}
330335
336+ type UISchema struct {
337+ Name string
338+ Version string
339+ Group string
340+ Kind string
341+ Scope string // "Namespaced" or "Cluster"
342+ Resource string
343+ }
344+
331345func (h * handler ) handleResources (w http.ResponseWriter , r * http.Request ) {
332346 logger := getLogger (r )
333347
@@ -346,41 +360,69 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
346360 return
347361 }
348362
349- labelSelector := labels.Set {
350- resources .ExportedCRDsLabel : "true" ,
351- }
352-
353- crds , err := h .kubeManager .ListCustomResourceDefinitions (r .Context (), cluster , labelSelector .AsSelector ())
363+ apiResourceSchemas , err := h .getBackendDynamicResource (r .Context (), cluster )
354364 if err != nil {
355- logger .Error (err , "failed to list crds " )
365+ logger .Error (err , "failed to get dynamic resources " )
356366 http .Error (w , "internal error" , http .StatusInternalServerError )
357367 return
358368 }
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 )
369+
370+ var result []UISchema
371+ for _ , item := range apiResourceSchemas .Items {
372+ scope := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["scope" ]
373+ if scope == nil {
374+ scope = "-"
375+ }
376+ group := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["group" ]
377+ if group == nil {
378+ group = "-"
379+ }
380+ resource := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["names" ].(map [string ]interface {})["plural" ]
381+ if resource == nil {
382+ resource = "-"
366383 }
367- }
368384
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
385+ kind := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["names" ].(map [string ]interface {})["kind" ]
386+ if kind == nil {
387+ kind = "-"
388+ }
389+ namespace := item .GetNamespace ()
390+ if namespace == "" {
391+ namespace = "-"
392+ }
393+
394+ versions := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["versions" ]
395+ if versions == nil {
396+ versions = []interface {}{"" }
397+ result = append (result , UISchema {
398+ Name : item .GetName (),
399+ Scope : scope .(string ),
400+ Version : "-" , // we can't parse it.
401+ Group : group .(string ),
402+ Resource : resource .(string ),
403+ })
404+ continue
405+ }
406+ for _ , v := range versions .([]interface {}) {
407+ version := v .(map [string ]interface {})["name" ]
408+ result = append (result , UISchema {
409+ Name : item .GetName (),
410+ Kind : kind .(string ),
411+ Scope : scope .(string ),
412+ Version : version .(string ),
413+ Group : group .(string ),
414+ Resource : resource .(string ),
415+ })
416+ }
374417 }
418+
375419 bs := bytes.Buffer {}
376420 if err := resourcesTemplate .Execute (& bs , struct {
377- Cluster string
378- CRDs []* apiextensionsv1.CustomResourceDefinition
379- APIResourceSchemas []kubebindv1alpha2.APIResourceSchema
421+ Cluster string
422+ Schemas []UISchema
380423 }{
381- Cluster : cluster ,
382- CRDs : rightScopedCRDs ,
383- APIResourceSchemas : apiResourceSchemas .Items ,
424+ Cluster : cluster ,
425+ Schemas : result ,
384426 }); err != nil {
385427 logger .Error (err , "failed to execute template" )
386428 http .Error (w , "internal error" , http .StatusInternalServerError )
@@ -393,8 +435,10 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
393435
394436func (h * handler ) handleBind (w http.ResponseWriter , r * http.Request ) {
395437 logger := getLogger (r )
438+ name := r .URL .Query ().Get ("name" )
396439 group := r .URL .Query ().Get ("group" )
397440 resource := r .URL .Query ().Get ("resource" )
441+ version := r .URL .Query ().Get ("version" )
398442 cluster := mux .Vars (r )["cluster" ]
399443
400444 prepareNoCache (w )
@@ -415,7 +459,38 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
415459 return
416460 }
417461
418- kfg , err := h .kubeManager .HandleResources (r .Context (), state .Token .Subject + "#" + state .ClusterID , cluster , resource , group )
462+ // There is an intent to bind. We need to create APIResourceSchema if one does not exists.
463+ {
464+ apiResourceSchemas , err := h .getBackendDynamicResource (r .Context (), cluster )
465+ if err != nil {
466+ logger .Error (err , "failed to get dynamic resources" )
467+ http .Error (w , "internal error" , http .StatusInternalServerError )
468+ return
469+ }
470+
471+ schema := & unstructured.Unstructured {}
472+ for _ , item := range apiResourceSchemas .Items {
473+ if item .GetName () == name {
474+ schema = & item
475+ break
476+ }
477+ }
478+ if schema == nil || schema .GetName () != name {
479+ logger .Error (nil , "no APIResourceSchema found" , "name" , name , "group" , group , "resource" , resource , "version" , version )
480+ http .Error (w , fmt .Sprintf ("no APIResourceSchema found for %s.%s.%s/%s" , group , resource , version , name ), http .StatusNotFound )
481+ return
482+ }
483+
484+ // create apiResourceSchema if not exists
485+ err = h .kubeManager .CreateAPIResourceSchema (r .Context (), cluster , name , schema )
486+ if err != nil {
487+ logger .Error (err , "failed to create APIResourceSchema" )
488+ http .Error (w , "internal error" , http .StatusInternalServerError )
489+ return
490+ }
491+ }
492+
493+ kfg , err := h .kubeManager .HandleResources (r .Context (), state .Token .Subject + "#" + state .ClusterID , cluster )
419494 if err != nil {
420495 logger .Error (err , "failed to handle resources" )
421496 http .Error (w , "internal error" , http .StatusInternalServerError )
@@ -435,7 +510,10 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
435510 },
436511 Spec : kubebindv1alpha2.APIServiceExportRequestSpec {
437512 Resources : []kubebindv1alpha2.APIServiceExportRequestResource {
438- {GroupResource : kubebindv1alpha2.GroupResource {Group : group , Resource : resource }},
513+ {
514+ GroupResource : kubebindv1alpha2.GroupResource {Group : group , Resource : resource },
515+ Versions : []string {version },
516+ },
439517 },
440518 },
441519 }
@@ -495,3 +573,28 @@ func mustRead(f func(name string) ([]byte, error), name string) string {
495573 }
496574 return string (bs )
497575}
576+
577+ func (h * handler ) getBackendDynamicResource (ctx context.Context , cluster string ) (* unstructured.UnstructuredList , error ) {
578+ labelSelector := labels.Set {
579+ resources .ExportedCRDsLabel : "true" ,
580+ }
581+
582+ parts := strings .SplitN (h .schemaSource , "." , 3 )
583+ if len (parts ) != 3 {
584+ return nil , fmt .Errorf ("invalid schema source: %q" , h .schemaSource )
585+ }
586+
587+ gvk := schema.GroupVersionKind {
588+ Kind : parts [0 ],
589+ Version : parts [1 ],
590+ Group : parts [2 ],
591+ }
592+ apiResourceSchemas , err := h .kubeManager .ListDynamicResources (ctx , cluster , gvk , labelSelector .AsSelector ())
593+ if err != nil {
594+ return nil , fmt .Errorf ("failed to list crds: %w" , err )
595+ }
596+ sort .SliceStable (apiResourceSchemas .Items , func (i , j int ) bool {
597+ return apiResourceSchemas .Items [i ].GetName () < apiResourceSchemas .Items [j ].GetName ()
598+ })
599+ return apiResourceSchemas , nil
600+ }
0 commit comments