Skip to content

Commit 1915e21

Browse files
committed
operator still isnt working
Signed-off-by: Ryan Cook <[email protected]>
1 parent 673f06d commit 1915e21

File tree

4 files changed

+65
-15
lines changed

4 files changed

+65
-15
lines changed

components/manifests/operator-deployment.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ spec:
2828
valueFrom:
2929
fieldRef:
3030
fieldPath: metadata.namespace
31-
- name: BACKEND_API_URL
32-
value: "http://backend-service:8080/api"
3331
- name: AMBIENT_CODE_RUNNER_IMAGE
3432
value: "quay.io/ambient_code/vteam_claude_runner:latest"
3533
- name: IMAGE_PULL_POLICY

components/manifests/rbac/operator-clusterrole.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ rules:
4444
- apiGroups: ["apps"]
4545
resources: ["deployments"]
4646
verbs: ["create"]
47-
# RoleBindings (create group access bindings)
47+
# ServiceAccounts (create runner ServiceAccounts in project namespaces)
48+
- apiGroups: [""]
49+
resources: ["serviceaccounts"]
50+
verbs: ["get", "create"]
51+
# RoleBindings (create group access bindings and runner access bindings)
4852
- apiGroups: ["rbac.authorization.k8s.io"]
4953
resources: ["rolebindings"]
5054
verbs: ["get", "create"]

components/operator/main.go

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

10271075
var (

components/runners/claude-code-runner/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ async def _generate_display_name(self) -> str:
179179
async def _update_display_name(self, display_name: str):
180180
"""Update the display name via backend API"""
181181
try:
182-
url = f"{self.backend_api_url}/agentic-sessions/{self.session_name}/displayname"
182+
url = f"{self.backend_api_url}/projects/{self.session_namespace}/agentic-sessions/{self.session_name}/displayname"
183183

184184
payload = {"displayName": display_name}
185185

0 commit comments

Comments
 (0)