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
2 changes: 1 addition & 1 deletion .ai/spec/how/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Embeds `client.Client` and adds getter methods for:
The OLSConfig CR uses finalizer `ols.openshift.io/finalizer` (defined in `utils.OLSConfigFinalizer`). On deletion:
1. Remove chat console UI (deactivate plugin, delete ConsolePlugin CR)
2. Remove agentic console UI (deactivate plugin, delete ConsolePlugin CR)
3. Remove alerts adapter operand resources (`alertsadapter.RemoveAlertsAdapter()`: deployment, namespaced RBAC, SA, NetworkPolicy, monitoring RoleBinding; proposals ClusterRole/ClusterRoleBinding when the platform permits delete)
3. Remove alerts adapter operand resources (`alertsadapter.RemoveAlertsAdapter()`: deployment, namespaced RBAC, SA, NetworkPolicy, monitoring RoleBinding; AgenticRun ClusterRole/ClusterRoleBinding when the platform permits delete)
3. List all owned resources via owner references
4. Explicitly delete owned resources
5. Wait up to 3 minutes for deletion (poll every 5 seconds)
Expand Down
2 changes: 1 addition & 1 deletion .ai/spec/how/reconciliation.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Two ownership models:
2. **External resources**: Watches() with custom predicates. Annotation-based filtering. Secret/ConfigMap handlers compare data and trigger deployment restarts.

### Finalizer Cleanup
The `finalizeOLSConfig()` method removes Console UI, deletes alerts adapter operand resources via `alertsadapter.RemoveAlertsAdapter()` (deployment, namespaced RBAC, SA, NetworkPolicy, cross-namespace monitoring RoleBinding; proposals ClusterRole/ClusterRoleBinding when permitted—may remain on managed OpenShift if admission webhook blocks delete), then uses `listOwnedResources()` which queries every resource type by owner reference UID (not labels). This is more reliable than label-based cleanup. The wait loop polls with a fixed interval and timeout, using `wait.PollUntilContextTimeout`.
The `finalizeOLSConfig()` method removes Console UI, deletes alerts adapter operand resources via `alertsadapter.RemoveAlertsAdapter()` (deployment, namespaced RBAC, SA, NetworkPolicy, cross-namespace monitoring RoleBinding; AgenticRun ClusterRole/ClusterRoleBinding when permitted—may remain on managed OpenShift if admission webhook blocks delete), then uses `listOwnedResources()` which queries every resource type by owner reference UID (not labels). This is more reliable than label-based cleanup. The wait loop polls with a fixed interval and timeout, using `wait.PollUntilContextTimeout`.

### Status Update Mechanics
`UpdateStatusCondition()` uses `retry.RetryOnConflict` with `client.MergeFrom` patch. It preserves `LastTransitionTime` for conditions whose status hasn't changed. It re-fetches the CR before each update attempt to get the latest ResourceVersion.
Expand Down
2 changes: 1 addition & 1 deletion .ai/spec/what/bundle-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The lightspeed-operator OLM bundle installs both the lightspeed-operator control

11. The two controllers share no runtime state. They reconcile different CRDs (`ols.openshift.io` vs `agentic.openshift.io`).
12. Feature gates on `OLSConfig` (`MCPServer`, `ToolFiltering`) have no effect on the agentic controller.
13. The agentic controller is effectively inert until its CRs are created — it watches for `AgenticOLSConfig`, `Proposal`, and related CRs, but takes no action without them.
13. The agentic controller is effectively inert until its CRs are created — it watches for `AgenticOLSConfig`, `AgenticRun`, and related CRs, but takes no action without them.

### Console Plugins

Expand Down
6 changes: 3 additions & 3 deletions .ai/spec/what/reconciliation.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ The operator reconciles the OLSConfig CR into Kubernetes resources through a two
9. Five component groups are reconciled in Phase 1: chat Console UI, agentic console plugin, PostgreSQL, the application server, and (when enabled) the agentic alerts adapter.
10. All Phase 1 resource groups are independent and can be reconciled in any order.
11. If any Phase 1 resource fails, the operator continues reconciling the remaining resources, then reports all failures in the CR status with ResourceReconciliation conditions.
11a. Alerts adapter (OLS-3348) is **opt-in** via `spec.ols.deployment.alertsAdapter.configMapRef`. When unset, `ReconcileAlertsAdapterResources()` calls `RemoveAlertsAdapter()` to delete operator-managed operand resources (deployment, SA, namespaced RBAC, NetworkPolicy, monitoring RoleBinding; proposals ClusterRole/ClusterRoleBinding when the platform allows delete) and Phase 2 is skipped with `AlertsAdapterReady=True`, `Reason=NotConfigured`.
11b. When `configMapRef` is set, Phase 1 reconciles: ServiceAccount, ClusterRole (`agentic.openshift.io/proposals`: create, list, get), ClusterRoleBinding, legacy config Role/RoleBinding cleanup, RoleBinding in `openshift-monitoring` (binds SA to `monitoring-alertmanager-view`), NetworkPolicy. The operator does not create, update, or validate ConfigMap data. When the referenced ConfigMap exists, Phase 2 mounts it at `/etc/alerts-adapter`; when absent, no config volume is mounted. The adapter reads `config.yaml` and uses built-in defaults when the file is missing or invalid.
11a. Alerts adapter (OLS-3348) is **opt-in** via `spec.ols.deployment.alertsAdapter.configMapRef`. When unset, `ReconcileAlertsAdapterResources()` calls `RemoveAlertsAdapter()` to delete operator-managed operand resources (deployment, SA, namespaced RBAC, NetworkPolicy, monitoring RoleBinding; AgenticRun ClusterRole/ClusterRoleBinding when the platform allows delete) and Phase 2 is skipped with `AlertsAdapterReady=True`, `Reason=NotConfigured`.
11b. When `configMapRef` is set, Phase 1 reconciles: ServiceAccount, ClusterRole (`agentic.openshift.io/agenticruns`: create, list, get), ClusterRoleBinding, legacy config Role/RoleBinding cleanup, RoleBinding in `openshift-monitoring` (binds SA to `monitoring-alertmanager-view`), NetworkPolicy. The operator does not create, update, or validate ConfigMap data. When the referenced ConfigMap exists, Phase 2 mounts it at `/etc/alerts-adapter`; when absent, no config volume is mounted. The adapter reads `config.yaml` and uses built-in defaults when the file is missing or invalid.
11c. Agentic console Phase 1 resources: ServiceAccount, ConfigMap (nginx.conf), NetworkPolicy.

### Phase 2: Deployments and Status
Expand All @@ -38,7 +38,7 @@ The operator reconciles the OLSConfig CR into Kubernetes resources through a two
### Finalizer Lifecycle
19. On CR creation: add finalizer, return immediately (controller-runtime auto-requeues).
20. On CR deletion: run finalizer cleanup before removing finalizer.
21. Finalizer cleanup sequence: remove chat Console UI from Console CR, delete chat ConsolePlugin CR, remove agentic console plugin from Console CR, delete agentic ConsolePlugin CR (`agenticconsole.RemoveAgenticConsole()`), delete alerts adapter operand resources via `alertsadapter.RemoveAlertsAdapter()` (deployment, namespaced RBAC, SA, NetworkPolicy, monitoring RoleBinding; proposals ClusterRole/ClusterRoleBinding when permitted), list all owned resources by owner reference, explicitly delete them, wait for deletion (polling with timeout).
21. Finalizer cleanup sequence: remove chat Console UI from Console CR, delete chat ConsolePlugin CR, remove agentic console plugin from Console CR, delete agentic ConsolePlugin CR (`agenticconsole.RemoveAgenticConsole()`), delete alerts adapter operand resources via `alertsadapter.RemoveAlertsAdapter()` (deployment, namespaced RBAC, SA, NetworkPolicy, monitoring RoleBinding; AgenticRun ClusterRole/ClusterRoleBinding when permitted), list all owned resources by owner reference, explicitly delete them, wait for deletion (polling with timeout).
22. If cleanup times out, the finalizer is removed anyway to prevent the CR from being stuck in Terminating state.
23. Console UI and agentic component removal errors during finalization are logged but do not block finalization.

Expand Down
8 changes: 4 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ Provides clean contract between main controller and component packages:

### Alerts Adapter Package (`internal/controller/alertsadapter`)

**Purpose:** Manages the agentic alerts adapter that polls Alertmanager and creates `Proposal` CRs for firing alerts.
**Purpose:** Manages the agentic alerts adapter that polls Alertmanager and creates `AgenticRun` CRs for firing alerts.

**Opt-in:** Enabled only when `spec.ols.deployment.alertsAdapter.configMapRef` is set (non-empty name). When unset, Phase 1 calls `RemoveAlertsAdapter()` to tear down operand resources and Phase 2 sets `AlertsAdapterReady=True` with `Reason=NotConfigured`.

**Entry Points:** `ReconcileAlertsAdapterResources()` (Phase 1), `ReconcileAlertsAdapterDeployment()` (Phase 2), `RemoveAlertsAdapter()` (operand teardown on disable and during finalization), `RestartAlertsAdapter()` (rolling restart on deployment spec or runtime ConfigMap changes).

**Phase 1 resources (when enabled):** ServiceAccount, ClusterRole/ClusterRoleBinding for `agentic.openshift.io/proposals`, legacy config Role/RoleBinding cleanup (removed from reconcile; deleted if still present), RoleBinding in `openshift-monitoring` to `monitoring-alertmanager-view`, NetworkPolicy. The operator does not create, update, or validate user ConfigMap data.
**Phase 1 resources (when enabled):** ServiceAccount, ClusterRole/ClusterRoleBinding for `agentic.openshift.io/agenticruns`, legacy config Role/RoleBinding cleanup (removed from reconcile; deleted if still present), RoleBinding in `openshift-monitoring` to `monitoring-alertmanager-view`, NetworkPolicy. The operator does not create, update, or validate user ConfigMap data.

**Runtime config:** User creates the ConfigMap (see [adapter manifests](https://github.com/openshift/lightspeed-agentic-alerts-adapter/tree/main/manifests)). When the referenced ConfigMap exists, it is mounted read-only at `/etc/alerts-adapter`; when absent, no config volume is mounted. The adapter reads `config.yaml` from that path and uses built-in defaults when the file is missing or invalid. ConfigMap data changes trigger a deployment restart via the external ConfigMap watcher (`RestartAlertsAdapter`).

Expand Down Expand Up @@ -167,7 +167,7 @@ The operator uses a finalizer (`ols.openshift.io/finalizer`) to ensure proper cl

**Why Needed:**
- **Console plugin cleanup**: `ConsolePlugin` is cluster-scoped and not cascade-deleted by owner references; chat and agentic plugins must be deactivated in the Console CR
- **Alerts adapter cleanup**: RoleBinding in `openshift-monitoring` is outside the operator namespace; `RemoveAlertsAdapter()` also deletes deployment, namespaced RBAC, SA, and NetworkPolicy when the operand is disabled or during finalization. Proposals ClusterRole/ClusterRoleBinding deletion may be blocked on managed OpenShift clusters (admission webhook); the operator logs and continues.
- **Alerts adapter cleanup**: RoleBinding in `openshift-monitoring` is outside the operator namespace; `RemoveAlertsAdapter()` also deletes deployment, namespaced RBAC, SA, and NetworkPolicy when the operand is disabled or during finalization. AgenticRun ClusterRole/ClusterRoleBinding deletion may be blocked on managed OpenShift clusters (admission webhook); the operator logs and continues.
- **PVC cleanup**: PersistentVolumeClaims can block deletion if not properly released
- **Race condition prevention**: Ensures complete cleanup before CR can be recreated (important for tests and sequential deployments)

Expand Down Expand Up @@ -195,7 +195,7 @@ if !olsconfig.DeletionTimestamp.IsZero() {
**Cleanup Sequence** (`finalizeOLSConfig`):
1. **Remove chat console UI**: Deactivate plugin from Console CR, delete ConsolePlugin CR
2. **Remove agentic console UI**: Deactivate plugin from Console CR, delete ConsolePlugin CR
3. **Remove alerts adapter operand**: Delete deployment, namespaced RBAC, SA, NetworkPolicy, monitoring RoleBinding, and attempt proposals ClusterRoleBinding/ClusterRole deletion (`alertsadapter.RemoveAlertsAdapter()`). ClusterRoleBinding deletion may be blocked on managed OpenShift; remaining cluster RBAC is harmless when the operand is disabled.
3. **Remove alerts adapter operand**: Delete deployment, namespaced RBAC, SA, NetworkPolicy, monitoring RoleBinding, and attempt AgenticRun ClusterRoleBinding/ClusterRole deletion (`alertsadapter.RemoveAlertsAdapter()`). ClusterRoleBinding deletion may be blocked on managed OpenShift; remaining cluster RBAC is harmless when the operand is disabled.
4. **Wait for owned resources**: Poll for up to 3 minutes until deployments, services, PVCs are deleted (cascade deletion)
5. **Remove finalizer**: Allows Kubernetes to remove CR from etcd

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,6 @@ spec:
- tls.key: Private key (PEM format) - REQUIRED
- ca.crt: CA certificate for console proxy trust (PEM format) - OPTIONAL


If ca.crt is not provided, the OpenShift Console proxy will use the default system trust store.
displayName: TLS Certificate Secret Reference
path: ols.tlsConfig.keyCertSecretRef
Expand Down Expand Up @@ -743,6 +742,14 @@ spec:
- get
- list
- watch
- apiGroups:
- agentic.openshift.io
resources:
- agenticruns
verbs:
- create
- get
- list
- apiGroups:
- apps
resources:
Expand Down
8 changes: 8 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ rules:
- get
- list
- watch
- apiGroups:
- agentic.openshift.io
resources:
- agenticruns
verbs:
- create
- get
- list
- apiGroups:
- apps
resources:
Expand Down
16 changes: 8 additions & 8 deletions internal/controller/alertsadapter/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ func GenerateServiceAccount(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig)
return utils.GenerateServiceAccount(r, cr, utils.AlertsAdapterServiceAccountName)
}

// GenerateProposalsClusterRole generates the ClusterRole granting proposal create/list/get access.
func GenerateProposalsClusterRole(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) (*rbacv1.ClusterRole, error) {
// GenerateAgenticRunsClusterRole generates the ClusterRole granting AgenticRun create/list/get access.
func GenerateAgenticRunsClusterRole(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) (*rbacv1.ClusterRole, error) {
role := rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: utils.AlertsAdapterProposalsClusterRoleName,
Name: utils.AlertsAdapterAgenticRunsClusterRoleName,
Labels: utils.GenerateAlertsAdapterSelectorLabels(),
},
Rules: []rbacv1.PolicyRule{
{
APIGroups: []string{"agentic.openshift.io"},
Resources: []string{"proposals"},
Resources: []string{"agenticruns"},
Verbs: []string{"create", "list", "get"},
},
},
Expand All @@ -45,11 +45,11 @@ func GenerateProposalsClusterRole(r reconciler.Reconciler, cr *olsv1alpha1.OLSCo
return &role, nil
}

// GenerateProposalsClusterRoleBinding binds the alerts adapter ServiceAccount to the proposals ClusterRole.
func GenerateProposalsClusterRoleBinding(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) (*rbacv1.ClusterRoleBinding, error) {
// GenerateAgenticRunsClusterRoleBinding binds the alerts adapter ServiceAccount to the AgenticRun ClusterRole.
func GenerateAgenticRunsClusterRoleBinding(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) (*rbacv1.ClusterRoleBinding, error) {
rb := rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: utils.AlertsAdapterProposalsClusterRoleBindingName,
Name: utils.AlertsAdapterAgenticRunsClusterRoleBindingName,
Labels: utils.GenerateAlertsAdapterSelectorLabels(),
},
Subjects: []rbacv1.Subject{
Expand All @@ -62,7 +62,7 @@ func GenerateProposalsClusterRoleBinding(r reconciler.Reconciler, cr *olsv1alpha
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: utils.AlertsAdapterProposalsClusterRoleName,
Name: utils.AlertsAdapterAgenticRunsClusterRoleName,
},
}

Expand Down
8 changes: 4 additions & 4 deletions internal/controller/alertsadapter/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ var _ = Describe("Alerts adapter assets", func() {
Expect(sa.Namespace).To(Equal(utils.OLSNamespaceDefault))
})

It("should generate the proposals ClusterRole", func() {
role, err := GenerateProposalsClusterRole(testReconcilerInstance, cr)
It("should generate the agenticruns ClusterRole", func() {
role, err := GenerateAgenticRunsClusterRole(testReconcilerInstance, cr)
Expect(err).NotTo(HaveOccurred())
Expect(role.Name).To(Equal(utils.AlertsAdapterProposalsClusterRoleName))
Expect(role.Name).To(Equal(utils.AlertsAdapterAgenticRunsClusterRoleName))
Expect(role.Rules).To(ContainElement(rbacv1.PolicyRule{
APIGroups: []string{"agentic.openshift.io"},
Resources: []string{"proposals"},
Resources: []string{"agenticruns"},
Verbs: []string{"create", "list", "get"},
}))
})
Expand Down
Loading