Skip to content

Commit 7219225

Browse files
committed
OLS-3236: Reconcile agentic console plugin in lightspeed-operator
1 parent fb5c715 commit 7219225

36 files changed

Lines changed: 3139 additions & 712 deletions

.ai/spec/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ AI agents (Claude). Content is optimized for precision and machine consumption o
3535
| Add a new managed component | `what/system-overview.md` + `how/project-structure.md` |
3636
| Understand the CRD | `what/crd-api.md` |
3737
| Navigate the codebase | `how/project-structure.md` |
38+
| Understand console plugins | `what/console-ui.md` (chat), `what/agentic-console-ui.md` (agentic) |
3839
| Understand TLS configuration | `what/tls.md` |
3940
| Understand security constraints | `what/security.md` |
4041
| Debug external resource watching | `what/resource-lifecycle.md` + `how/reconciliation.md` |
@@ -48,7 +49,8 @@ When what/ and how/ file names don't match 1:1, this table maps behavioral specs
4849
| what/ | how/ |
4950
|---|---|
5051
| `reconciliation.md` | `how/reconciliation.md` -- implementation patterns, code locations, task registration |
51-
| `app-server.md`, `postgres.md`, `console-ui.md` | `how/deployment-generation.md` -- how deployments/services/configmaps are generated |
52+
| `app-server.md`, `postgres.md` | `how/deployment-generation.md` -- how deployments/services/configmaps are generated |
53+
| `console-ui.md`, `agentic-console-ui.md` | `how/deployment-generation.md` -- deployment/service/configmap generation; `how/reconciliation.md` -- ConsolePlugin lifecycle, activation, and cleanup |
5254
| `crd-api.md` | `how/config-generation.md` -- how CRD fields map to generated configuration |
5355
| `system-overview.md` | `how/project-structure.md` -- codebase layout, package responsibilities |
5456

.ai/spec/how/project-structure.md

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
| `api/v1alpha1/zz_generated.deepcopy.go` | Generated `DeepCopyObject()` methods | Auto-generated deep copy |
1010
| `cmd/main.go` | `main()`, `overrideImages()` | Operator entry point, flag parsing, manager setup |
1111
| `internal/controller/olsconfig_controller.go` | `OLSConfigReconciler`, `Reconcile()`, `SetupWithManager()` | Main reconciler, orchestration, watcher registration |
12-
| `internal/controller/olsconfig_helpers.go` | `UpdateStatusCondition()`, `checkDeploymentStatus()`, `annotateExternalResources()`, `shouldWatchSecret()` | Status management, diagnostics, annotation, watcher predicates |
12+
| `internal/controller/olsconfig_helpers.go` | `UpdateStatusCondition()`, `checkDeploymentStatus()`, `annotateExternalResources()`, `shouldWatchSecret()`, `GetAgenticConsoleImage()` | Status management, diagnostics, annotation, watcher predicates, image getter for agentic console |
1313
| `internal/controller/operator_assets.go` | `ReconcileServiceMonitorForOperator()`, `ReconcileNetworkPolicyForOperator()` | Operator-level resources |
1414
| `internal/controller/appserver/reconciler.go` | `ReconcileAppServerResources()`, `ReconcileAppServerDeployment()` | AppServer Phase 1 + Phase 2 orchestration |
1515
| `internal/controller/appserver/deployment.go` | `GenerateOLSDeployment()`, `updateOLSDeployment()` | AppServer deployment generation, update detection |
@@ -18,9 +18,13 @@
1818
| `internal/controller/postgres/reconciler.go` | `ReconcilePostgresResources()`, `ReconcilePostgresDeployment()` | PostgreSQL Phase 1 + Phase 2 |
1919
| `internal/controller/postgres/deployment.go` | `GeneratePostgresDeployment()` | PostgreSQL deployment generation |
2020
| `internal/controller/postgres/assets.go` | `GeneratePostgresConfigMap()`, `GeneratePostgresBootstrapSecret()`, `GeneratePostgresSecret()` | PostgreSQL config, bootstrap script, credentials |
21-
| `internal/controller/console/reconciler.go` | `ReconcileConsoleUIResources()`, `ReconcileConsoleUIDeploymentAndPlugin()`, `RemoveConsoleUI()` | Console UI Phase 1 + Phase 2 + cleanup |
22-
| `internal/controller/console/deployment.go` | `GenerateConsoleUIDeployment()` | Console UI deployment generation |
23-
| `internal/controller/console/assets.go` | ConsolePlugin CR generator, nginx config, service, network policy | Console UI resource generation |
21+
| `internal/controller/console/reconciler.go` | `ReconcileConsoleUIResources()`, `ReconcileConsoleUIDeploymentAndPlugin()`, `RemoveConsoleUI()` | Chat console plugin Phase 1 + Phase 2 + cleanup |
22+
| `internal/controller/console/deployment.go` | `GenerateConsoleUIDeployment()` | Chat console plugin deployment generation |
23+
| `internal/controller/console/assets.go` | ConsolePlugin CR generator, nginx config, service, network policy | Chat console plugin resource generation |
24+
| `internal/controller/agenticconsole/reconciler.go` | `ReconcileAgenticConsoleUIResources()`, `ReconcileAgenticConsoleUIDeploymentAndPlugin()`, `RemoveAgenticConsole()` | Agentic console plugin Phase 1 + Phase 2 + cleanup |
25+
| `internal/controller/agenticconsole/deployment.go` | `GenerateAgenticConsoleUIDeployment()` | Agentic console plugin deployment generation |
26+
| `internal/controller/agenticconsole/assets.go` | ConsolePlugin CR generator, nginx config, service, network policy | Agentic console plugin resource generation |
27+
| `internal/controller/utils/console_plugin_reconciler.go` | Shared ConsolePlugin reconcile helpers | Used by `console/` and `agenticconsole/` |
2428
| `internal/controller/reconciler/interface.go` | `Reconciler` interface | Dependency injection interface for component packages |
2529
| `internal/controller/utils/constants.go` | ~200 constants | Resource names, ports, paths, annotation keys, defaults |
2630
| `internal/controller/utils/errors.go` | ~80 error message constants | Structured error messages for all operations |
@@ -67,10 +71,12 @@ OLSConfigReconciler.Reconcile()
6771
4. annotateExternalResources() -- Mark external secrets/configmaps for watching
6872
5. reconcileIndependentResources() -- Phase 1: ConfigMaps, Secrets, ServiceAccounts, RBAC, NetworkPolicies
6973
+-- console.ReconcileConsoleUIResources()
74+
+-- agenticconsole.ReconcileAgenticConsoleUIResources()
7075
+-- postgres.ReconcilePostgresResources()
7176
+-- appserver.ReconcileAppServerResources()
7277
6. reconcileDeploymentsAndStatus() -- Phase 2: Deployments, Services, TLS certs, status
7378
+-- console.ReconcileConsoleUIDeploymentAndPlugin()
79+
+-- agenticconsole.ReconcileAgenticConsoleUIDeploymentAndPlugin()
7480
+-- postgres.ReconcilePostgresDeployment()
7581
+-- appserver.ReconcileAppServerDeployment()
7682
+-- checkDeploymentStatus() per deployment -> build newStatus
@@ -90,25 +96,31 @@ External secret/configmap changes
9096
-> Match against SystemResources list (by name+namespace)
9197
-> OR match against WatcherAnnotationKey annotation
9298
-> Resolve "ACTIVE_BACKEND" to appserver deployment name
93-
-> Call RestartAppServer() / RestartPostgres() / RestartConsoleUI()
99+
-> Call RestartAppServer() / RestartPostgres() / RestartConsoleUI() / RestartAgenticConsoleUI()
94100
-> Set force-reload annotation with current timestamp
95101
```
96102

97103
## Key Abstractions
98104

99105
### Image Management
100-
Default images are stored in a `defaultImages` map in `cmd/main.go` keyed by logical name (e.g., `"lightspeed-service"`, `"postgres-image"`, `"console-plugin"`). Default values come from `internal/relatedimages/` which reads `related_images.json` at build time. Command-line flags override individual images. The map is passed to the reconciler via `OLSConfigReconcilerOptions` as individual named fields (e.g., `LightspeedServiceImage`, `ConsoleUIImage`).
106+
Default images are stored in a `defaultImages` map in `cmd/main.go` keyed by logical name (e.g., `"lightspeed-service"`, `"postgres-image"`, `"console-plugin"`, `"agentic-console-plugin"`). Default values come from `internal/relatedimages/` which reads `related_images.json` at build time. Command-line flags override individual images (`--console-image`, `--agentic-console-image`, etc.). The map is passed to the reconciler via `OLSConfigReconcilerOptions` as individual named fields (e.g., `LightspeedServiceImage`, `ConsoleUIImage`, `AgenticConsoleUIImage`).
101107

102108
### WatcherConfig
103-
Declarative configuration for external resource watching. Contains:
104-
- `Secrets.SystemResources`: Fixed list of system secrets with affected deployment names (telemetry pull secret, console TLS cert, postgres TLS cert)
109+
Declarative configuration for external resource watching. Built in `cmd/main.go` and passed via `OLSConfigReconcilerOptions.WatcherConfig`. Contains:
110+
- `Secrets.SystemResources`: Fixed list of system secrets with affected deployment names:
111+
- Telemetry pull secret → app server (`ACTIVE_BACKEND`)
112+
- `lightspeed-console-plugin-cert` → chat console deployment
113+
- `lightspeed-agentic-console-plugin-cert` → agentic console deployment (`AgenticConsoleUIDeploymentName`)
114+
- Postgres TLS cert → postgres + app server
105115
- `ConfigMaps.SystemResources`: Fixed list of system configmaps (kube-root-ca.crt, service-ca bundle)
106116
- `AnnotatedSecretMapping`: Dynamic map populated from CR spec at runtime (maps secret name to deployment names)
107117
- `AnnotatedConfigMapMapping`: Dynamic map populated from CR spec at runtime (maps configmap name to deployment names)
108118
The special deployment name `"ACTIVE_BACKEND"` resolves to the AppServer deployment name (`lightspeed-app-server`).
109119

120+
When the service-ca operator rotates or populates a watched TLS secret, `SecretUpdateHandler` restarts the mapped deployment via `RestartConsoleUI()` or `RestartAgenticConsoleUI()` (registered in `watchers/watchers.go`).
121+
110122
### Component Package Pattern
111-
Each component (appserver, postgres, console) follows the same package structure:
123+
Each component (appserver, postgres, console, agenticconsole) follows the same package structure:
112124
- `reconciler.go`: Phase 1 (resources) and Phase 2 (deployment) entry points
113125
- `deployment.go`: Deployment spec generation and update detection
114126
- `assets.go` and/or `config.go`: Resource and config generation
@@ -117,17 +129,20 @@ The packages receive `reconciler.Reconciler` interface, never import the control
117129
### Reconciler Interface (`internal/controller/reconciler/interface.go`)
118130
Embeds `client.Client` and adds getter methods for:
119131
- `GetScheme()`, `GetLogger()`, `GetNamespace()`
120-
- Image getters: `GetAppServerImage()`, `GetPostgresImage()`, `GetConsoleUIImage()`, `GetOpenShiftMCPServerImage()`, `GetDataverseExporterImage()`
132+
- Image getters: `GetAppServerImage()`, `GetPostgresImage()`, `GetConsoleUIImage()`, `GetAgenticConsoleImage()`, `GetOpenShiftMCPServerImage()`, `GetDataverseExporterImage()`
121133
- Version getters: `GetOpenShiftMajor()`, `GetOpenshiftMinor()`
122134
- Config getters: `IsPrometheusAvailable()`, `GetWatcherConfig()`
123135

136+
`OLSConfigReconciler` implements the interface in `olsconfig_helpers.go`. Component packages call `r.GetAgenticConsoleImage()` when generating the agentic console deployment; the value comes from `OLSConfigReconcilerOptions.AgenticConsoleUIImage`, set in `cmd/main.go` from `--agentic-console-image` (with default from `defaultImages["agentic-console-plugin"]`).
137+
124138
### Finalizer Pattern
125139
The OLSConfig CR uses finalizer `ols.openshift.io/finalizer` (defined in `utils.OLSConfigFinalizer`). On deletion:
126-
1. Remove Console UI (deactivate plugin, delete ConsolePlugin CR)
127-
2. List all owned resources via owner references
128-
3. Explicitly delete owned resources
129-
4. Wait up to 3 minutes for deletion (poll every 5 seconds)
130-
5. Remove finalizer (proceeds even if cleanup times out)
140+
1. Remove chat console UI (deactivate plugin, delete ConsolePlugin CR)
141+
2. Remove agentic console UI (deactivate plugin, delete ConsolePlugin CR)
142+
3. List all owned resources via owner references
143+
4. Explicitly delete owned resources
144+
5. Wait up to 3 minutes for deletion (poll every 5 seconds)
145+
6. Remove finalizer (proceeds even if cleanup times out)
131146

132147
## Integration Points
133148

@@ -210,6 +225,20 @@ E2E tests live in `test/e2e/` and run against a real OpenShift cluster with the
210225
| `CONDITION_TIMEOUT` | No | Custom timeout in seconds for condition checks |
211226
| `ARTIFACT_DIR` | No | Directory for must-gather diagnostics output |
212227

228+
## Local Development
229+
230+
`make run` sets `LOCAL_DEV_MODE=true` and runs the operator on the host against the cluster kubeconfig.
231+
232+
| Behavior | When `LOCAL_DEV_MODE=true` |
233+
|---|---|
234+
| Operator ServiceMonitor | Skipped in `reconcileOperatorResources()` |
235+
| App-server metrics reader secret | Skipped in `appserver.reconcileMetricsReaderSecret()` |
236+
| App-server ServiceMonitor / PrometheusRule | Still reconciled if Prometheus Operator CRDs exist |
237+
238+
Skipping metrics reader secret reconciliation avoids a local reconcile loop: creating the token secret triggers `Owns(Secret)` and immediate requeue.
239+
240+
`make run` also runs `dev-setup` (namespace, metrics RBAC, user-access). Image overrides: `--console-image`, `--agentic-console-image`, and other flags in `cmd/main.go`.
241+
213242
## Implementation Notes
214243

215244
- The operator uses kubebuilder v3 markers for CRD generation and RBAC.
@@ -218,4 +247,3 @@ E2E tests live in `test/e2e/` and run against a real OpenShift cluster with the
218247
- The OLSConfig CRD is cluster-scoped and validated to require `.metadata.name == "cluster"`.
219248
- `SetupWithManager()` registers `Owns()` watches for: Deployment, ServiceAccount, ClusterRole, ClusterRoleBinding, Service, ConfigMap, Secret, PersistentVolumeClaim, ConsolePlugin, ServiceMonitor, PrometheusRule, ImageStream.
220249
- Controller-runtime handles retry with exponential backoff; the operator does not use periodic reconciliation.
221-
- `LOCAL_DEV_MODE=true` env var skips ServiceMonitor creation for local development with `make run-local`.

.ai/spec/how/reconciliation.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ Reconcile(ctx, req)
1818
-> handleFinalizer() # Add/remove finalizer, run cleanup
1919
-> reconcileOperatorResources() # ServiceMonitor, NetworkPolicy (operator-level)
2020
-> annotateExternalResources() # Validate secrets, annotate for watching
21-
-> reconcileIndependentResources() # Phase 1: console, postgres, backend resources
21+
-> reconcileIndependentResources() # Phase 1: console, agentic console, postgres, backend resources
2222
| |-- console.ReconcileConsoleUIResources()
23+
| |-- agenticconsole.ReconcileAgenticConsoleUIResources()
2324
| |-- postgres.ReconcilePostgresResources()
2425
| +-- appserver.ReconcileAppServerResources()
2526
-> reconcileDeploymentsAndStatus() # Phase 2: deployments + status update
2627
|-- console.ReconcileConsoleUIDeploymentAndPlugin()
28+
|-- agenticconsole.ReconcileAgenticConsoleUIDeploymentAndPlugin()
2729
|-- postgres.ReconcilePostgresDeployment()
2830
|-- appserver.ReconcileAppServerDeployment()
2931
|-- checkDeploymentStatus() for each # Collect diagnostics
@@ -33,7 +35,7 @@ Reconcile(ctx, req)
3335
## Key Abstractions
3436

3537
### Reconciler Interface
36-
The `reconciler.Reconciler` interface breaks the circular dependency between the main controller and component packages. Component packages (appserver, postgres, console) receive this interface instead of importing the controller package directly. It embeds `client.Client` and adds getter methods for images, namespace, and OpenShift version.
38+
The `reconciler.Reconciler` interface breaks the circular dependency between the main controller and component packages. Component packages (appserver, postgres, console, agenticconsole) receive this interface instead of importing the controller package directly. It embeds `client.Client` and adds getter methods for images, namespace, and OpenShift version.
3739

3840
### ReconcileSteps Pattern
3941
Both phases use a slice of `ReconcileSteps` structs, each containing a Name, reconcile function, and (for Phase 2) a ConditionType and Deployment name. Phase 1 iterates with continue-on-error; Phase 2 iterates but tracks all conditions and diagnostics.
@@ -77,5 +79,5 @@ The `finalizeOLSConfig()` method uses `listOwnedResources()` which queries every
7779
- `SetupWithManager()` registers Owns() for 12 resource types and Watches() for Secrets and ConfigMaps with custom predicates.
7880
- Secret watch predicates: Create events allowed for all secrets in operator namespace (handles recreated secrets); Update events filtered by watcher annotation; Delete events ignored.
7981
- ConfigMap watch predicates: Same pattern as secrets.
80-
- The `LOCAL_DEV_MODE` environment variable skips ServiceMonitor creation when running locally.
82+
- The `LOCAL_DEV_MODE` environment variable skips operator ServiceMonitor creation and app-server metrics reader secret reconciliation when running locally (`make run`).
8183
- Phase 1 failures update status with `ResourceReconciliation` condition type (not the component-specific types used in Phase 2).

0 commit comments

Comments
 (0)