Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config/components/evalhub/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ resources:
- crd/trustyai.opendatahub.io_evalhubs.yaml
- rbac/evalhub_auth_reviewer_role.yaml
- rbac/evalhub_collections_access_binding.yaml
- rbac/evalhub_events_binding.yaml
- rbac/evalhub_events_role.yaml
- rbac/evalhub_collections_access_role.yaml
- rbac/evalhub_hardware_profiles_reader_role.yaml
Expand Down
15 changes: 0 additions & 15 deletions config/components/evalhub/rbac/evalhub_events_binding.yaml

This file was deleted.

21 changes: 21 additions & 0 deletions controllers/evalhub/service_accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ func (r *EvalHubReconciler) createServiceAccount(ctx context.Context, instance *
return err
}

err = r.createEventsRoleBinding(ctx, instance, serviceAccountName)
if err != nil {
return err
}

// Always grant the API SA hardwareprofiles get/list in the applications
// namespace (r.Namespace: opendatahub on ODH, redhat-ods-applications on RHOAI),
// regardless of where this EvalHub instance is deployed. EvalHub reads profiles
Expand Down Expand Up @@ -267,6 +272,10 @@ const (
jobsWriterClusterRoleName = "trustyai-service-operator-evalhub-jobs-writer"
jobConfigClusterRoleName = "trustyai-service-operator-evalhub-job-config"
hardwareProfilesReaderClusterRoleName = "trustyai-service-operator-evalhub-hardware-profiles-reader"
// evalhubEventsClusterRoleName grants events create/patch in tenant namespaces so the
// EvalHub server SA can emit Kubernetes Events on evaluation lifecycle transitions.
// Bound per-tenant-namespace via a RoleBinding created by reconcileTenantNamespaces.
evalhubEventsClusterRoleName = "trustyai-service-operator-evalhub-events"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)

// EvalHub API access ClusterRoles for SAR-protected endpoints.
Expand Down Expand Up @@ -627,6 +636,18 @@ func (r *EvalHubReconciler) createHardwareProfilesReaderRoleBinding(ctx context.
})
}

// createEventsRoleBinding creates a RoleBinding for the API SA to the evalhub-events
// ClusterRole (events create,patch) in the instance namespace. This covers single-tenant
// mode and any transitions where resolveNamespace falls back to the server's own namespace.
// Tenant namespaces receive a separate binding via reconcileTenantNamespaces.
func (r *EvalHubReconciler) createEventsRoleBinding(ctx context.Context, instance *evalhubv1.EvalHub, serviceAccountName string) error {
return r.createGenericRoleBinding(ctx, instance, instance.Name+"-events-rb", serviceAccountName, rbacv1.RoleRef{
Kind: "ClusterRole",
Name: evalhubEventsClusterRoleName,
APIGroup: rbacv1.GroupName,
})
}

// generateApplicationsHardwareProfilesReaderRBName returns the RoleBinding name used to
// grant the API SA hardwareprofiles get/list in the applications namespace (r.Namespace).
func generateApplicationsHardwareProfilesReaderRBName(instance *evalhubv1.EvalHub) string {
Expand Down
12 changes: 12 additions & 0 deletions controllers/evalhub/tenant_namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ func (r *EvalHubReconciler) reconcileTenantNamespaces(ctx context.Context, insta
return err
}

// Create events RoleBinding for the API SA in the tenant namespace so the
// EvalHub server can emit Kubernetes Events on evaluation lifecycle transitions.
eventsRBName := normalizeDNS1123LabelValue(instance.Name + "-" + ns + "-events-rb")
if err := r.createJobRoleBinding(ctx, instance, eventsRBName, serviceAccountName, ns, rbacv1.RoleRef{
Kind: "ClusterRole",
Name: evalhubEventsClusterRoleName,
APIGroup: rbacv1.GroupName,
}, instance.Namespace); err != nil {
log.Error(err, "Failed to create events RoleBinding in tenant namespace", "namespace", ns)
return err
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Create pod-logs Role and RoleBinding so the EvalHub service SA can read job pod logs.
if err := r.createServicePodLogsRole(ctx, instance, ns); err != nil {
log.Error(err, "Failed to create service pod-logs Role in tenant namespace", "namespace", ns)
Expand Down
16 changes: 16 additions & 0 deletions controllers/evalhub/unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,22 @@ func TestEvalHubReconciler_reconcileTenantNamespaces(t *testing.T) {
}, cm)
require.NoError(t, err)
assert.Equal(t, "true", cm.Annotations["service.beta.openshift.io/inject-cabundle"])

// Verify events RoleBinding was created in the tenant namespace so the
// EvalHub server SA can emit Kubernetes Events on lifecycle transitions.
eventsRBName := normalizeDNS1123LabelValue(evalHubName + "-" + tenantNamespace + "-events-rb")
eventsRB := &rbacv1.RoleBinding{}
err = fakeClient.Get(ctx, types.NamespacedName{
Name: eventsRBName,
Namespace: tenantNamespace,
}, eventsRB)
require.NoError(t, err, "events RoleBinding should exist in tenant namespace")
assert.Equal(t, "ClusterRole", eventsRB.RoleRef.Kind)
assert.Equal(t, evalhubEventsClusterRoleName, eventsRB.RoleRef.Name)
require.Len(t, eventsRB.Subjects, 1)
assert.Equal(t, "ServiceAccount", eventsRB.Subjects[0].Kind)
assert.Equal(t, evalHubName+"-service", eventsRB.Subjects[0].Name)
assert.Equal(t, instanceNamespace, eventsRB.Subjects[0].Namespace)
})

t.Run("should skip terminating tenant namespace", func(t *testing.T) {
Expand Down
2 changes: 0 additions & 2 deletions policy/rbac.rego
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ expected_crbs := {

# --- component: evalhub (prefixed overlays: odh, rhoai, dev, testing) ---
"trustyai-service-operator-evalhub-manager-rolebinding": "trustyai-service-operator-evalhub-manager-role",
"trustyai-service-operator-evalhub-events-binding": "trustyai-service-operator-evalhub-events",
"trustyai-service-operator-evalhub-collections-access-binding": "trustyai-service-operator-evalhub-collections-access",
"trustyai-service-operator-evalhub-providers-access-binding": "trustyai-service-operator-evalhub-providers-access",
"trustyai-service-operator-evalhub-mlflow-access-binding": "trustyai-service-operator-evalhub-mlflow-access",
Expand All @@ -41,7 +40,6 @@ expected_crbs := {

# --- component: evalhub (un-prefixed overlay: evalhub-only) ---
"evalhub-manager-rolebinding": "trustyai-service-operator-evalhub-manager-role",
"evalhub-events-binding": "evalhub-events",
"evalhub-collections-access-binding": "evalhub-collections-access",
"evalhub-providers-access-binding": "evalhub-providers-access",
"evalhub-mlflow-access-binding": "evalhub-mlflow-access",
Expand Down
Loading