@@ -18,6 +18,7 @@ package http
1818
1919import (
2020 "context"
21+ "encoding/base64"
2122 "encoding/json"
2223 "errors"
2324 "fmt"
@@ -167,6 +168,8 @@ func (h *handler) AddRoutes(mux *mux.Router) error {
167168 apiRouter .Handle ("/collections" , auth .RequireAuth (http .HandlerFunc (h .handleCollections ))).Methods (http .MethodGet )
168169 apiRouter .Handle ("/bind" , auth .RequireAuth (http .HandlerFunc (h .handleBind ))).Methods (http .MethodPost )
169170 apiRouter .Handle ("/ping" , auth .RequireAuth (http .HandlerFunc (h .handlePing ))).Methods (http .MethodGet )
171+ apiRouter .Handle ("/consumer-status" , auth .RequireAuth (http .HandlerFunc (h .handleConsumerStatus ))).Methods (http .MethodGet )
172+ apiRouter .Handle ("/apply-binding" , auth .RequireAuth (http .HandlerFunc (h .handleApplyBinding ))).Methods (http .MethodPost )
170173
171174 if h .oidcServer != nil {
172175 h .oidcServer .AddRoutes (mux )
@@ -555,7 +558,119 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
555558 w .Write (payload ) //nolint:errcheck
556559}
557560
558- // listTemplates fetches the list of APIServiceExportTemplates from the backend cluster without checking
561+ // handleConsumerStatus returns whether the authenticated user already has a consumer
562+ // namespace with existing APIServiceExports on the provider.
563+ func (h * handler ) handleConsumerStatus (w http.ResponseWriter , r * http.Request ) {
564+ logger := getLogger (r )
565+ params := client .GetQueryParams (r )
566+ prepareNoCache (w )
567+
568+ authCtx := auth .GetAuthContext (r .Context ())
569+ state := authCtx .SessionState
570+ identity := state .Token .Issuer + "/" + state .Token .Subject
571+
572+ status , err := h .kubeManager .GetConsumerStatus (r .Context (), identity , params .ClusterID )
573+ if err != nil {
574+ logger .Error (err , "failed to get consumer status" )
575+ writeErrorResponse (w , http .StatusInternalServerError , kubebindv1alpha2 .ErrorCodeInternalError , "Failed to get consumer status" , err .Error ())
576+ return
577+ }
578+
579+ payload , err := json .Marshal (status )
580+ if err != nil {
581+ logger .Error (err , "failed to marshal consumer status" )
582+ writeErrorResponse (w , http .StatusInternalServerError , kubebindv1alpha2 .ErrorCodeInternalError , "Failed to marshal consumer status" , err .Error ())
583+ return
584+ }
585+
586+ w .Header ().Set ("Content-Type" , "application/json" )
587+ w .Write (payload ) //nolint:errcheck
588+ }
589+
590+ // applyBindingRequest is the JSON body for the apply-binding endpoint.
591+ type applyBindingRequest struct {
592+ // ConsumerKubeconfig is the base64-encoded kubeconfig for the consumer cluster.
593+ ConsumerKubeconfig string `json:"consumerKubeconfig"`
594+ // BindingName is the name for the binding (used for secret and bundle naming).
595+ BindingName string `json:"bindingName"`
596+ }
597+
598+ // handleApplyBinding receives a consumer kubeconfig and applies the konnector + binding
599+ // bundle to the consumer cluster.
600+ func (h * handler ) handleApplyBinding (w http.ResponseWriter , r * http.Request ) {
601+ logger := getLogger (r )
602+ params := client .GetQueryParams (r )
603+ prepareNoCache (w )
604+
605+ authCtx := auth .GetAuthContext (r .Context ())
606+ state := authCtx .SessionState
607+ identity := state .Token .Issuer + "/" + state .Token .Subject
608+
609+ // Parse request body
610+ const maxBodySize = 1 << 20 // 1 MB
611+ r .Body = http .MaxBytesReader (w , r .Body , maxBodySize )
612+ var req applyBindingRequest
613+ if err := json .NewDecoder (r .Body ).Decode (& req ); err != nil {
614+ writeErrorResponse (w , http .StatusBadRequest , kubebindv1alpha2 .ErrorCodeBadRequest , "Invalid request body" , err .Error ())
615+ return
616+ }
617+
618+ if req .ConsumerKubeconfig == "" {
619+ writeErrorResponse (w , http .StatusBadRequest , kubebindv1alpha2 .ErrorCodeBadRequest , "Missing consumer kubeconfig" , "consumerKubeconfig is required" )
620+ return
621+ }
622+ if req .BindingName == "" {
623+ writeErrorResponse (w , http .StatusBadRequest , kubebindv1alpha2 .ErrorCodeBadRequest , "Missing binding name" , "bindingName is required" )
624+ return
625+ }
626+
627+ // Decode base64 consumer kubeconfig
628+ consumerKubeconfigData , err := base64 .StdEncoding .DecodeString (req .ConsumerKubeconfig )
629+ if err != nil {
630+ writeErrorResponse (w , http .StatusBadRequest , kubebindv1alpha2 .ErrorCodeBadRequest , "Invalid consumer kubeconfig encoding" , "consumerKubeconfig must be base64 encoded" )
631+ return
632+ }
633+
634+ // Get the provider kubeconfig for this user's namespace
635+ handleResult , err := h .kubeManager .HandleResources (r .Context (), state .Token .Subject , identity , params .ClusterID )
636+ if err != nil {
637+ logger .Error (err , "failed to handle resources for apply-binding" )
638+ statusCode , code , details := mapErrorToCode (err )
639+ writeErrorResponse (w , statusCode , code , "Failed to prepare provider resources" , details )
640+ return
641+ }
642+
643+ // Resolve konnector image
644+ konnectorVersion , err := bindversion .BinaryVersion (componentbaseversion .Get ().GitVersion )
645+ if err != nil {
646+ konnectorVersion = "latest"
647+ }
648+ konnectorImage := fmt .Sprintf ("ghcr.io/kube-bind/konnector:%s" , konnectorVersion )
649+
650+ // Apply to consumer cluster
651+ result , err := h .kubeManager .ApplyToConsumer (
652+ r .Context (),
653+ consumerKubeconfigData ,
654+ handleResult .Kubeconfig ,
655+ req .BindingName ,
656+ konnectorImage ,
657+ )
658+ if err != nil {
659+ logger .Error (err , "failed to apply binding to consumer cluster" )
660+ writeErrorResponse (w , http .StatusInternalServerError , kubebindv1alpha2 .ErrorCodeInternalError , "Failed to apply binding to consumer cluster" , err .Error ())
661+ return
662+ }
663+
664+ payload , err := json .Marshal (result )
665+ if err != nil {
666+ logger .Error (err , "failed to marshal apply result" )
667+ writeErrorResponse (w , http .StatusInternalServerError , kubebindv1alpha2 .ErrorCodeInternalError , "Failed to marshal result" , err .Error ())
668+ return
669+ }
670+
671+ w .Header ().Set ("Content-Type" , "application/json" )
672+ w .Write (payload ) //nolint:errcheck
673+ }
559674// if they are part of a Collection or not.
560675func (h * handler ) listTemplates (ctx context.Context , cluster string ) (* kubebindv1alpha2.APIServiceExportTemplateList , error ) {
561676 templates , err := h .kubeManager .ListTemplates (ctx , cluster )
0 commit comments