Skip to content

Commit 4e69530

Browse files
committed
Add few more options for the UI flow, refactor the code
Signed-off-by: Karol Szwaj <karol.szwaj@gmail.com> On-behalf-of: @SAP karol.szwaj@sap.com
1 parent a6a2d85 commit 4e69530

6 files changed

Lines changed: 1208 additions & 94 deletions

File tree

backend/http/handler.go

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package http
1818

1919
import (
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.
560675
func (h *handler) listTemplates(ctx context.Context, cluster string) (*kubebindv1alpha2.APIServiceExportTemplateList, error) {
561676
templates, err := h.kubeManager.ListTemplates(ctx, cluster)

0 commit comments

Comments
 (0)