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