@@ -294,6 +294,11 @@ func handleAgenticSessionEvent(obj *unstructured.Unstructured) error {
294294 }
295295 }
296296
297+ // Ensure ServiceAccount exists in the target namespace
298+ if err := ensureServiceAccount (sessionNamespace ); err != nil {
299+ log .Printf ("Warning: Failed to ensure ServiceAccount in namespace %s: %v" , sessionNamespace , err )
300+ }
301+
297302 // Create the Job
298303 job := & batchv1.Job {
299304 ObjectMeta : v1.ObjectMeta {
@@ -343,7 +348,8 @@ func handleAgenticSessionEvent(obj *unstructured.Unstructured) error {
343348 },
344349 },
345350 },
346- RestartPolicy : corev1 .RestartPolicyNever ,
351+ RestartPolicy : corev1 .RestartPolicyNever ,
352+ ServiceAccountName : "claude-runner" ,
347353
348354 // ⚠️ Let OpenShift SCC choose UID/GID dynamically (restricted-v2 compatible)
349355 // SecurityContext omitted to allow SCC assignment
@@ -398,7 +404,7 @@ func handleAgenticSessionEvent(obj *unstructured.Unstructured) error {
398404 {Name : "LLM_TEMPERATURE" , Value : fmt .Sprintf ("%.2f" , temperature )},
399405 {Name : "LLM_MAX_TOKENS" , Value : fmt .Sprintf ("%d" , maxTokens )},
400406 {Name : "TIMEOUT" , Value : fmt .Sprintf ("%d" , timeout )},
401- {Name : "BACKEND_API_URL" , Value : getBackendAPIURL ( )},
407+ {Name : "BACKEND_API_URL" , Value : fmt . Sprintf ( "http://backend-service.%s.svc.cluster.local:8080/api" , backendNamespace )},
402408
403409 // 🔑 Git configuration environment variables
404410 {Name : "GIT_USER_NAME" , Value : gitUserName },
@@ -1010,18 +1016,60 @@ func updateProjectSettingsStatus(namespace, name string, statusUpdate map[string
10101016 return nil
10111017}
10121018
1013- // getBackendAPIURL returns the fully qualified backend API URL for cross-namespace communication
1014- func getBackendAPIURL () string {
1015- // Check if a custom backend API URL is provided
1016- if customURL := os .Getenv ("BACKEND_API_URL" ); customURL != "" {
1017- // If it already contains a fully qualified domain, use it as-is
1018- if strings .Contains (customURL , ".svc.cluster.local" ) || strings .Contains (customURL , "://" ) {
1019- return customURL
1019+
1020+ // ensureServiceAccount creates a ServiceAccount and RoleBinding for the runner in the target namespace
1021+ func ensureServiceAccount (targetNamespace string ) error {
1022+ // Create ServiceAccount if it doesn't exist
1023+ serviceAccount := & corev1.ServiceAccount {
1024+ ObjectMeta : v1.ObjectMeta {
1025+ Name : "claude-runner" ,
1026+ Namespace : targetNamespace ,
1027+ },
1028+ }
1029+
1030+ _ , err := k8sClient .CoreV1 ().ServiceAccounts (targetNamespace ).Get (context .TODO (), "claude-runner" , v1.GetOptions {})
1031+ if errors .IsNotFound (err ) {
1032+ _ , err = k8sClient .CoreV1 ().ServiceAccounts (targetNamespace ).Create (context .TODO (), serviceAccount , v1.CreateOptions {})
1033+ if err != nil {
1034+ return fmt .Errorf ("failed to create ServiceAccount: %v" , err )
10201035 }
1036+ log .Printf ("Created ServiceAccount claude-runner in namespace %s" , targetNamespace )
1037+ } else if err != nil {
1038+ return fmt .Errorf ("failed to get ServiceAccount: %v" , err )
10211039 }
10221040
1023- // Construct fully qualified service name for cross-namespace communication
1024- return fmt .Sprintf ("http://backend-service.%s.svc.cluster.local:8080/api" , backendNamespace )
1041+ // Create RoleBinding to give the ServiceAccount access to the project
1042+ roleBinding := & rbacv1.RoleBinding {
1043+ ObjectMeta : v1.ObjectMeta {
1044+ Name : "claude-runner-project-access" ,
1045+ Namespace : targetNamespace ,
1046+ },
1047+ Subjects : []rbacv1.Subject {
1048+ {
1049+ Kind : "ServiceAccount" ,
1050+ Name : "claude-runner" ,
1051+ Namespace : targetNamespace ,
1052+ },
1053+ },
1054+ RoleRef : rbacv1.RoleRef {
1055+ APIGroup : "rbac.authorization.k8s.io" ,
1056+ Kind : "ClusterRole" ,
1057+ Name : "edit" , // Standard Kubernetes edit role for project access
1058+ },
1059+ }
1060+
1061+ _ , err = k8sClient .RbacV1 ().RoleBindings (targetNamespace ).Get (context .TODO (), "claude-runner-project-access" , v1.GetOptions {})
1062+ if errors .IsNotFound (err ) {
1063+ _ , err = k8sClient .RbacV1 ().RoleBindings (targetNamespace ).Create (context .TODO (), roleBinding , v1.CreateOptions {})
1064+ if err != nil {
1065+ return fmt .Errorf ("failed to create RoleBinding: %v" , err )
1066+ }
1067+ log .Printf ("Created RoleBinding claude-runner-project-access in namespace %s" , targetNamespace )
1068+ } else if err != nil {
1069+ return fmt .Errorf ("failed to get RoleBinding: %v" , err )
1070+ }
1071+
1072+ return nil
10251073}
10261074
10271075var (
0 commit comments