@@ -150,6 +150,7 @@ func (h *handler) AddRoutes(mux *mux.Router) error {
150150 // Public API routes (no authentication required)
151151 mux .HandleFunc ("/api/healthz" , h .handleHealthz ).Methods (http .MethodGet )
152152 mux .HandleFunc ("/api/bindable-resources" , h .handleBindableResources ).Methods (http .MethodGet )
153+ mux .HandleFunc ("/api/konnector-manifests" , h .handleKonnectorManifests ).Methods (http .MethodGet )
153154
154155 // Generic authentication routes (support both UI and CLI)
155156 mux .HandleFunc ("/api/authorize" , h .authHandler .HandleAuthorize ).Methods (http .MethodGet , http .MethodPost )
@@ -198,6 +199,92 @@ func (h *handler) handlePing(w http.ResponseWriter, r *http.Request) {
198199 w .Write ([]byte ("pong" )) //nolint:errcheck
199200}
200201
202+ // handleKonnectorManifests returns the pre-rendered konnector YAML manifests
203+ // that a consumer cluster needs to apply to deploy the konnector agent.
204+ func (h * handler ) handleKonnectorManifests (w http.ResponseWriter , r * http.Request ) {
205+ prepareNoCache (w )
206+
207+ konnectorVersion , err := bindversion .BinaryVersion (componentbaseversion .Get ().GitVersion )
208+ if err != nil {
209+ konnectorVersion = "latest"
210+ }
211+ konnectorImage := fmt .Sprintf ("ghcr.io/kube-bind/konnector:%s" , konnectorVersion )
212+
213+ manifests := fmt .Sprintf (`apiVersion: v1
214+ kind: Namespace
215+ metadata:
216+ name: kube-bind
217+ ---
218+ apiVersion: v1
219+ kind: ServiceAccount
220+ metadata:
221+ name: konnector
222+ namespace: kube-bind
223+ ---
224+ apiVersion: rbac.authorization.k8s.io/v1
225+ kind: ClusterRole
226+ metadata:
227+ name: kube-bind-konnector
228+ rules:
229+ - apiGroups: ["*"]
230+ resources: ["*"]
231+ verbs: ["*"]
232+ ---
233+ apiVersion: rbac.authorization.k8s.io/v1
234+ kind: ClusterRoleBinding
235+ metadata:
236+ name: kube-bind-konnector
237+ roleRef:
238+ apiGroup: rbac.authorization.k8s.io
239+ kind: ClusterRole
240+ name: kube-bind-konnector
241+ subjects:
242+ - kind: ServiceAccount
243+ name: konnector
244+ namespace: kube-bind
245+ ---
246+ apiVersion: apps/v1
247+ kind: Deployment
248+ metadata:
249+ name: konnector
250+ namespace: kube-bind
251+ labels:
252+ app: konnector
253+ spec:
254+ replicas: 2
255+ selector:
256+ matchLabels:
257+ app: konnector
258+ template:
259+ metadata:
260+ labels:
261+ app: konnector
262+ spec:
263+ restartPolicy: Always
264+ serviceAccountName: konnector
265+ containers:
266+ - name: konnector
267+ image: %s
268+ env:
269+ - name: POD_NAME
270+ valueFrom:
271+ fieldRef:
272+ fieldPath: metadata.name
273+ - name: POD_NAMESPACE
274+ valueFrom:
275+ fieldRef:
276+ fieldPath: metadata.namespace
277+ readinessProbe:
278+ httpGet:
279+ path: /healthz
280+ port: 8090
281+ ` , konnectorImage )
282+
283+ w .Header ().Set ("Content-Type" , "text/yaml" )
284+ w .Header ().Set ("Content-Disposition" , "attachment; filename=konnector.yaml" )
285+ w .Write ([]byte (manifests )) //nolint:errcheck
286+ }
287+
201288func (h * handler ) handleLogout (w http.ResponseWriter , r * http.Request ) {
202289 prepareNoCache (w )
203290
@@ -369,7 +456,8 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
369456 }
370457
371458 // Resolve the UI sentinel to a real identity derived from the authenticated session.
372- if identity == auth .UIIdentity {
459+ isUIFlow := identity == auth .UIIdentity
460+ if isUIFlow {
373461 identity = state .Token .Issuer + "/" + state .Token .Subject
374462 logger .Info ("Resolved ui-identity from session" , "identity" , identity )
375463 }
@@ -412,6 +500,26 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
412500 },
413501 }
414502
503+ // For UI-only flow, create the APIServiceExportRequest on the provider cluster
504+ // and wait for reconciliation. In CLI flow the konnector handles this instead.
505+ var exportRequestName string
506+ if isUIFlow {
507+ exportRequest , err := h .kubeManager .CreateAPIServiceExportRequest (
508+ r .Context (),
509+ params .ClusterID ,
510+ handleResult .Namespace ,
511+ bindRequest .Name ,
512+ request .Spec ,
513+ )
514+ if err != nil {
515+ logger .Error (err , "failed to create APIServiceExportRequest" )
516+ statusCode , code , details := mapErrorToCode (err )
517+ writeErrorResponse (w , statusCode , code , "Failed to create API service export request" , details )
518+ return
519+ }
520+ exportRequestName = exportRequest .Name
521+ }
522+
415523 // callback response
416524 requestBytes , err := json .Marshal (& request )
417525 if err != nil {
@@ -431,8 +539,10 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
431539 ID : state .Token .Issuer + "/" + state .Token .Subject ,
432540 },
433541 },
434- Kubeconfig : handleResult .Kubeconfig ,
435- Requests : []runtime.RawExtension {{Raw : requestBytes }},
542+ Kubeconfig : handleResult .Kubeconfig ,
543+ Requests : []runtime.RawExtension {{Raw : requestBytes }},
544+ ProviderNamespace : handleResult .Namespace ,
545+ BindingName : exportRequestName ,
436546 }
437547
438548 payload , err := json .Marshal (& response )
0 commit comments