@@ -18,6 +18,7 @@ package http
1818
1919import (
2020 "bytes"
21+ "context"
2122 "encoding/base64"
2223 "encoding/json"
2324 "errors"
@@ -32,10 +33,12 @@ 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 "
36+ apierrors "k8s.io/apimachinery /pkg/api/errors "
3637 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
38+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3739 "k8s.io/apimachinery/pkg/labels"
3840 "k8s.io/apimachinery/pkg/runtime"
41+ "k8s.io/apimachinery/pkg/runtime/schema"
3942 componentbaseversion "k8s.io/component-base/version"
4043 "k8s.io/klog/v2"
4144
@@ -66,6 +69,7 @@ type handler struct {
6669 backendCallbackURL string
6770 providerPrettyName string
6871 testingAutoSelect string
72+ schemaSource string
6973
7074 cookieEncryptionKey []byte
7175 cookieSigningKey []byte
@@ -78,6 +82,7 @@ func NewHandler(
7882 provider * OIDCServiceProvider ,
7983 oidcAuthorizeURL , backendCallbackURL , providerPrettyName , testingAutoSelect string ,
8084 cookieSigningKey , cookieEncryptionKey []byte ,
85+ schemaSource string ,
8186 scope kubebindv1alpha2.InformerScope ,
8287 mgr * kubernetes.Manager ,
8388) (* handler , error ) {
@@ -87,6 +92,7 @@ func NewHandler(
8792 backendCallbackURL : backendCallbackURL ,
8893 providerPrettyName : providerPrettyName ,
8994 testingAutoSelect : testingAutoSelect ,
95+ schemaSource : schemaSource ,
9096 scope : scope ,
9197 client : http .DefaultClient ,
9298 kubeManager : mgr ,
@@ -328,6 +334,15 @@ func createSessionState(authCode *AuthCode, token *oauth2.Token) (*session.State
328334 }, nil
329335}
330336
337+ type UISchema struct {
338+ Name string
339+ Version string
340+ Group string
341+ Kind string
342+ Scope string // "Namespaced" or "Cluster"
343+ Resource string
344+ }
345+
331346func (h * handler ) handleResources (w http.ResponseWriter , r * http.Request ) {
332347 logger := getLogger (r )
333348
@@ -346,41 +361,59 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
346361 return
347362 }
348363
349- labelSelector := labels.Set {
350- resources .ExportedCRDsLabel : "true" ,
351- }
352-
353- crds , err := h .kubeManager .ListCustomResourceDefinitions (r .Context (), cluster , labelSelector .AsSelector ())
364+ apiResourceSchemas , err := h .getBackendDynamicResource (r .Context (), cluster )
354365 if err != nil {
355- logger .Error (err , "failed to list crds " )
366+ logger .Error (err , "failed to get dynamic resources " )
356367 http .Error (w , "internal error" , http .StatusInternalServerError )
357368 return
358369 }
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 )
370+
371+ var result []UISchema
372+ for _ , item := range apiResourceSchemas .Items {
373+ scope := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["scope" ]
374+ if scope == nil {
375+ scope = "-"
366376 }
367- }
368377
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
378+ group := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["group" ]
379+ if group == nil {
380+ group = "-"
381+ }
382+ resource := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["names" ].(map [string ]interface {})["plural" ]
383+ if resource == nil {
384+ resource = "-"
385+ }
386+
387+ kind := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["names" ].(map [string ]interface {})["kind" ]
388+ if kind == nil {
389+ kind = "-"
390+ }
391+
392+ versions := item .UnstructuredContent ()["spec" ].(map [string ]interface {})["versions" ]
393+ if versions == nil {
394+ versions = []interface {}{"" }
395+ }
396+ for _ , v := range versions .([]interface {}) {
397+ version := v .(map [string ]interface {})["name" ]
398+ result = append (result , UISchema {
399+ Name : item .GetName (),
400+ Kind : kind .(string ),
401+ Scope : scope .(string ),
402+ Version : version .(string ),
403+ Group : group .(string ),
404+ // Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it.
405+ Resource : resource .(string ),
406+ })
407+ }
374408 }
409+
375410 bs := bytes.Buffer {}
376411 if err := resourcesTemplate .Execute (& bs , struct {
377- Cluster string
378- CRDs []* apiextensionsv1.CustomResourceDefinition
379- APIResourceSchemas []kubebindv1alpha2.APIResourceSchema
412+ Cluster string
413+ Schemas []UISchema
380414 }{
381- Cluster : cluster ,
382- CRDs : rightScopedCRDs ,
383- APIResourceSchemas : apiResourceSchemas .Items ,
415+ Cluster : cluster ,
416+ Schemas : result ,
384417 }); err != nil {
385418 logger .Error (err , "failed to execute template" )
386419 http .Error (w , "internal error" , http .StatusInternalServerError )
@@ -393,8 +426,10 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
393426
394427func (h * handler ) handleBind (w http.ResponseWriter , r * http.Request ) {
395428 logger := getLogger (r )
429+ name := r .URL .Query ().Get ("name" )
396430 group := r .URL .Query ().Get ("group" )
397431 resource := r .URL .Query ().Get ("resource" )
432+ version := r .URL .Query ().Get ("version" )
398433 cluster := mux .Vars (r )["cluster" ]
399434
400435 prepareNoCache (w )
@@ -415,7 +450,38 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
415450 return
416451 }
417452
418- kfg , err := h .kubeManager .HandleResources (r .Context (), state .Token .Subject + "#" + state .ClusterID , cluster , resource , group )
453+ // There is an intent to bind. We need to create APIResourceSchema if one does not exists.
454+ {
455+ apiResourceSchemas , err := h .getBackendDynamicResource (r .Context (), cluster )
456+ if err != nil {
457+ logger .Error (err , "failed to get dynamic resources" )
458+ http .Error (w , "internal error" , http .StatusInternalServerError )
459+ return
460+ }
461+
462+ schema := & unstructured.Unstructured {}
463+ for _ , item := range apiResourceSchemas .Items {
464+ if item .GetName () == name {
465+ schema = & item
466+ break
467+ }
468+ }
469+ if schema == nil || schema .GetName () != name {
470+ logger .Error (nil , "no APIResourceSchema found" , "name" , name , "group" , group , "resource" , resource , "version" , version )
471+ http .Error (w , fmt .Sprintf ("no APIResourceSchema found for %s.%s.%s/%s" , group , resource , version , name ), http .StatusNotFound )
472+ return
473+ }
474+
475+ // create apiResourceSchema if not exists
476+ err = h .kubeManager .CreateAPIResourceSchema (r .Context (), cluster , name , schema )
477+ if err != nil && ! apierrors .IsAlreadyExists (err ) {
478+ logger .Error (err , "failed to create APIResourceSchema" )
479+ http .Error (w , "internal error" , http .StatusInternalServerError )
480+ return
481+ }
482+ }
483+
484+ kfg , err := h .kubeManager .HandleResources (r .Context (), state .Token .Subject + "#" + state .ClusterID , cluster )
419485 if err != nil {
420486 logger .Error (err , "failed to handle resources" )
421487 http .Error (w , "internal error" , http .StatusInternalServerError )
@@ -435,7 +501,10 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
435501 },
436502 Spec : kubebindv1alpha2.APIServiceExportRequestSpec {
437503 Resources : []kubebindv1alpha2.APIServiceExportRequestResource {
438- {GroupResource : kubebindv1alpha2.GroupResource {Group : group , Resource : resource }},
504+ {
505+ GroupResource : kubebindv1alpha2.GroupResource {Group : group , Resource : resource },
506+ Versions : []string {version },
507+ },
439508 },
440509 },
441510 }
@@ -495,3 +564,28 @@ func mustRead(f func(name string) ([]byte, error), name string) string {
495564 }
496565 return string (bs )
497566}
567+
568+ func (h * handler ) getBackendDynamicResource (ctx context.Context , cluster string ) (* unstructured.UnstructuredList , error ) {
569+ labelSelector := labels.Set {
570+ resources .ExportedCRDsLabel : "true" ,
571+ }
572+
573+ parts := strings .SplitN (h .schemaSource , "." , 3 )
574+ if len (parts ) != 3 {
575+ return nil , fmt .Errorf ("invalid schema source: %q" , h .schemaSource )
576+ }
577+
578+ gvk := schema.GroupVersionKind {
579+ Kind : parts [0 ],
580+ Version : parts [1 ],
581+ Group : parts [2 ],
582+ }
583+ apiResourceSchemas , err := h .kubeManager .ListDynamicResources (ctx , cluster , gvk , labelSelector .AsSelector ())
584+ if err != nil {
585+ return nil , fmt .Errorf ("failed to list crds: %w" , err )
586+ }
587+ sort .SliceStable (apiResourceSchemas .Items , func (i , j int ) bool {
588+ return apiResourceSchemas .Items [i ].GetName () < apiResourceSchemas .Items [j ].GetName ()
589+ })
590+ return apiResourceSchemas , nil
591+ }
0 commit comments