Skip to content
Open
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 .cursor/skills/find-duplication/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ For each duplicate found, classify:
|----------|--------|
| **Extract** — identical logic in 3+ places | Recommend a shared helper in utils |
| **Parameterize** — same structure, different values | Recommend a common function with parameters |
| **Acceptable** — similar but serving different domains (app server vs lcore) | Note it, no action needed |
| **Acceptable** — similar but serving different domains (e.g. appserver vs postgres) | Note it, no action needed |
| **Boilerplate** — kubebuilder/controller-runtime patterns | Skip, this is framework convention |
| **Test-only** — repeated test setup/fixtures | Recommend shared test fixture (only if user asked) |

Expand Down
11 changes: 6 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ OLSConfigReconciler.Reconcile() →
├── reconcileLLMSecrets()
├── reconcileConsoleUI()
├── reconcilePostgresServer()
└── reconcileAppServer() OR reconcileLCore() [MUTUALLY EXCLUSIVE via --enable-lcore flag]
└── reconcileAppServer() (application server via `appserver` package)
└── [12+ sub-tasks via ReconcileTask pattern]
```

Expand Down Expand Up @@ -73,8 +73,7 @@ make test-e2e # E2E tests (requires cluster)

### Controllers
- `internal/controller/olsconfig_controller.go` - Main reconciler with finalizer logic
- `internal/controller/appserver/` - App server (LEGACY)
- `internal/controller/lcore/` - Lightspeed Core (NEW)
- `internal/controller/appserver/` - App server
- `internal/controller/postgres/` - PostgreSQL
- `internal/controller/console/` - Console UI
- `internal/controller/watchers/` - External resource watching
Expand All @@ -83,8 +82,10 @@ make test-e2e # E2E tests (requires cluster)

### Tests
- `*_test.go` - Unit tests (co-located)
- `test/e2e/` - E2E tests
- `internal/controller/utils/testing.go` - Test utilities
- `test/e2e/` - E2E tests (shared helpers in `assets.go`, `utils.go`, `client.go`, and related files)
- `internal/controller/utils/test_fixtures.go` - Shared controller **unit-test** fixtures (default `OLSConfig` CR, random secret/configmap/TLS helpers, telemetry pull-secret create/delete, shared `With*` provider mutators). Add here when the same shape is reused across tests or packages.
- For **one-off** CR or spec fragments used only in one file, **inline** next to the test (or use a file-local unexported helper in that `*_test.go`) instead of `test_fixtures.go`, so refactors do not leave unused exported helpers.
- `internal/controller/utils/testing.go` - `TestReconciler`, `NewTestReconciler`, and `StatusHasCondition` for envtest-based suites
- `internal/controller/suite_test.go` - Test suite setup, shared helpers
- `cleanupOLSConfig()` - Reusable CR cleanup helper (removes finalizers, waits for deletion)

Expand Down
25 changes: 6 additions & 19 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document describes the internal architecture of the OpenShift Lightspeed Op

## Overview

The operator follows a modular, component-based architecture where each major component (application server, Lightspeed Core/Llama Stack, PostgreSQL, Console UI) is managed by its own dedicated package with independent reconciliation logic.
The operator follows a modular, component-based architecture where each major component (application server, PostgreSQL, Console UI) is managed by its own dedicated package with independent reconciliation logic.

## Key Design Decisions

Expand Down Expand Up @@ -37,7 +37,7 @@ The operator follows a modular, component-based architecture where each major co
**Core Orchestration:**
- Main `Reconcile()` method coordinates all reconciliation phases
- `SetupWithManager()` configures controller watches and event handlers
- Selects backend: calls either `appserver.ReconcileAppServer()` OR `lcore.ReconcileLCore()` based on `--enable-lcore` flag
- Reconciles the application server via `appserver` (see `ReconcileAppServerResources` / `ReconcileAppServerDeployment` in `olsconfig_controller.go`)

**Support Functions:**
- Implements `reconciler.Reconciler` interface (provides config/images to components)
Expand All @@ -58,7 +58,7 @@ The operator follows a modular, component-based architecture where each major co
- Detect OpenShift version and select appropriate images
- Start controller and handle graceful shutdown

**Key Flags:** `--enable-lcore` (backend selection), `--controller-namespace`. See `cmd/main.go` for complete list.
**Key Flags:** Image URLs, `--controller-namespace`, reconcile interval, and related runtime options. See `cmd/main.go` for the complete list.

### Reconciler Interface (`internal/controller/reconciler`)

Expand All @@ -70,22 +70,9 @@ Provides clean contract between main controller and component packages:

### Application Server Package (`internal/controller/appserver`)

**Purpose:** Manages OpenShift Lightspeed application server (LEGACY backend - LLM API proxy)
**Purpose:** Manages the OpenShift Lightspeed application server (LLM API, RAG, optional MCP, metrics).

**Entry Point:** `ReconcileAppServer(reconciler.Reconciler, context, *OLSConfig)`

### Lightspeed Core Package (`internal/controller/lcore`)

**Purpose:** Manages Lightspeed Core + Llama Stack server (NEW backend - agent-based with MCP support)

**Entry Point:** `ReconcileLCore(reconciler.Reconciler, context, *OLSConfig)`

**Key Features:**
- Dynamic LLM configuration (supports OpenAI, Azure OpenAI, others)
- CA certificate support for custom TLS
- RAG support with vector database
- MCP (Model Context Protocol) integration
- Metrics with K8s authentication
**Entry Points:** `ReconcileAppServerResources` and `ReconcileAppServerDeployment` (invoked from `olsconfig_controller.go`).

### PostgreSQL Package (`internal/controller/postgres`)

Expand Down Expand Up @@ -138,7 +125,7 @@ High-level reconciliation sequence:
6. Reconcile Components:
- Console UI (if enabled)
- PostgreSQL (if conversation cache enabled)
- Backend (AppServer OR LCore - mutually exclusive, controlled by --enable-lcore flag)
- Application server (`appserver` package)
7. Update Status Conditions based on deployment readiness
```

Expand Down
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ This guide provides instructions for contributing to the OpenShift Lightspeed Op
## Quick Context

The operator uses a **modular, component-based architecture**:
- Each component (appserver, lcore, postgres, console) is a self-contained package
- Each component (`appserver`, `postgres`, `console`) is a self-contained package
- Components use the `reconciler.Reconciler` interface (no circular dependencies)
- Task-based reconciliation pattern (list of tasks executed sequentially)
- Two resource approaches: **Owned** (operator-created, ResourceVersion tracking) vs **External** (user-provided, data comparison)

**Best way to learn**: Read existing component code (`appserver/`, `postgres/`, `console/`, `lcore/`)
**Best way to learn**: Read existing component code (`appserver/`, `postgres/`, `console/`)

---

Expand Down Expand Up @@ -58,7 +58,7 @@ func ReconcileMyComponent(r reconciler.Reconciler, ctx context.Context, cr *olsv
}
```

**Reference**: See `appserver/reconciler.go`, `postgres/reconciler.go`, or `lcore/reconciler.go`
**Reference**: See `appserver/reconciler.go` or `postgres/reconciler.go`

### Step 3: Implement Asset Generation

Expand All @@ -70,7 +70,7 @@ func ReconcileMyComponent(r reconciler.Reconciler, ctx context.Context, cr *olsv
- Always set owner references with `controllerutil.SetControllerReference()`
- Use `utils.DefaultLabels()` for consistent labeling

**Reference**: See `appserver/assets.go`, `postgres/assets.go`, or `lcore/assets.go`
**Reference**: See `appserver/assets.go` or `postgres/assets.go`

### Step 4: Add Constants

Expand Down Expand Up @@ -139,7 +139,7 @@ The OLM bundle needs regeneration when changes affect how the operator is deploy
**Bundle Update Required:**
- **RBAC Changes**: Modified `//+kubebuilder:rbac` markers in Go code OR changed files in `config/rbac/`
- **CRD Changes**: Modified API types in `api/v1alpha1/olsconfig_types.go`
- **Image Changes**: New operator image, new operand images (appserver, lcore, postgres, console), or image version changes
- **Image Changes**: New operator image, new operand images (appserver, postgres, console), or image version changes
- **CSV Metadata**: Changed operator description, keywords, maintainers, links, or other metadata

**Bundle Update NOT Required:**
Expand Down
28 changes: 3 additions & 25 deletions api/v1alpha1/olsconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
corev1 "k8s.io/api/core/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)

// OLSConfigSpec defines the desired state of OLSConfig
Expand Down Expand Up @@ -343,9 +342,6 @@ type DeploymentConfig struct {
// MCP server container settings.
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="MCP Server Container"
MCPServerContainer ContainerConfig `json:"mcpServer,omitempty"`
// Llama Stack container settings.
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Llama Stack Container"
LlamaStackContainer ContainerConfig `json:"llamaStack,omitempty"`
// Console container settings.
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Console Deployment"
ConsoleContainer Config `json:"console,omitempty"`
Expand Down Expand Up @@ -482,10 +478,6 @@ type ModelSpec struct {
// ProviderSpec defines the desired state of LLM provider.
// +kubebuilder:validation:XValidation:message="'deploymentName' must be specified for 'azure_openai' provider",rule="self.type != \"azure_openai\" || self.deploymentName != \"\""
// +kubebuilder:validation:XValidation:message="'projectID' must be specified for 'watsonx' provider",rule="self.type != \"watsonx\" || self.projectID != \"\""
// +kubebuilder:validation:XValidation:message="'providerType' and 'config' must be used together in llamaStackGeneric mode",rule="!has(self.providerType) || has(self.config)"
// +kubebuilder:validation:XValidation:message="'config' requires 'providerType' to be set",rule="!has(self.config) || has(self.providerType)"
// +kubebuilder:validation:XValidation:message="Llama Stack Generic mode (providerType set) requires type='llamaStackGeneric'",rule="!has(self.providerType) || self.type == \"llamaStackGeneric\""
// +kubebuilder:validation:XValidation:message="Llama Stack Generic mode cannot use legacy provider-specific fields",rule="self.type != \"llamaStackGeneric\" || (!has(self.deploymentName) && !has(self.projectID) && !has(self.url) && !has(self.apiVersion))"
// +kubebuilder:validation:XValidation:message="credentialKey must not be empty or whitespace",rule="!has(self.credentialKey) || !self.credentialKey.matches('^[ \\t\\n\\r\\v\\f]*$')"
// +kubebuilder:validation:XValidation:message="googleVertexConfig is required for google_vertex provider",rule="self.type != \"google_vertex\" || has(self.googleVertexConfig)"
// +kubebuilder:validation:XValidation:message="googleVertexAnthropicConfig is required for google_vertex_anthropic provider",rule="self.type != \"google_vertex_anthropic\" || has(self.googleVertexAnthropicConfig)"
Expand Down Expand Up @@ -515,7 +507,7 @@ type ProviderSpec struct {
// Provider type
// +kubebuilder:validation:Required
// +required
// +kubebuilder:validation:Enum=azure_openai;bam;openai;watsonx;rhoai_vllm;rhelai_vllm;fake_provider;llamaStackGeneric;google_vertex;google_vertex_anthropic
// +kubebuilder:validation:Enum=azure_openai;bam;openai;watsonx;rhoai_vllm;rhelai_vllm;fake_provider;google_vertex;google_vertex_anthropic
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Provider Type"
Type string `json:"type"`
// Deployment name for Azure OpenAI provider
Expand All @@ -540,24 +532,10 @@ type ProviderSpec struct {
// +kubebuilder:validation:Optional
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="TLS Security Profile",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:advanced"}
TLSSecurityProfile *configv1.TLSSecurityProfile `json:"tlsSecurityProfile,omitempty"`
// Llama Stack Generic provider type for provider configuration (e.g., "remote::openai", "inline::sentence-transformers")
// When set, this provider uses Llama Stack Generic mode instead of legacy mode.
// Must follow pattern: (inline|remote)::<provider-name>
// +kubebuilder:validation:Pattern=`^(inline|remote)::[a-z0-9][a-z0-9_-]*$`
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Llama Stack Provider Type"
ProviderType string `json:"providerType,omitempty"`
// Arbitrary configuration for the provider (Llama Stack Generic mode only)
// This map is passed directly to Llama Stack provider configuration.
// Credentials are automatically injected as environment variable substitutions.
// Example: {"url": "https://...", "custom_field": "value"}
// +kubebuilder:pruning:PreserveUnknownFields
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Llama Stack Provider Config"
Config *runtime.RawExtension `json:"config,omitempty"`
// Secret key name for provider credentials (defaults to "apitoken" if not set).
// Specifies which key inside credentialsSecretRef to read the credential value from.
// The credential value is always exposed to the container as env var {PROVIDER_NAME}_API_KEY
// (derived from the provider name, not this field), and referenced in the Llama Stack config
// YAML as ${env.PROVIDER_NAME_API_KEY}. This field only controls which secret data key is read.
// The credential value is exposed to the app server container as env var {PROVIDER_NAME}_API_KEY
// (derived from the provider name, not this field). This field only controls which secret data key is read.
// +kubebuilder:validation:Optional
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Credential Key Name"
CredentialKey string `json:"credentialKey,omitempty"`
Expand Down
5 changes: 0 additions & 5 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 2 additions & 22 deletions bundle/manifests/lightspeed-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,11 @@ spec:
- description: API Version for Azure OpenAI provider
displayName: Azure OpenAI API Version
path: llm.providers[0].apiVersion
- description: |-
Arbitrary configuration for the provider (Llama Stack Generic mode only)
This map is passed directly to Llama Stack provider configuration.
Credentials are automatically injected as environment variable substitutions.
Example: {"url": "https://...", "custom_field": "value"}
displayName: Llama Stack Provider Config
path: llm.providers[0].config
- description: |-
Secret key name for provider credentials (defaults to "apitoken" if not set).
Specifies which key inside credentialsSecretRef to read the credential value from.
The credential value is always exposed to the container as env var {PROVIDER_NAME}_API_KEY
(derived from the provider name, not this field), and referenced in the Llama Stack config
YAML as ${env.PROVIDER_NAME_API_KEY}. This field only controls which secret data key is read.
The credential value is exposed to the app server container as env var {PROVIDER_NAME}_API_KEY
(derived from the provider name, not this field). This field only controls which secret data key is read.
displayName: Credential Key Name
path: llm.providers[0].credentialKey
- description: Deployment name for Azure OpenAI provider
Expand Down Expand Up @@ -156,12 +148,6 @@ spec:
- description: Watsonx Project ID
displayName: Watsonx Project ID
path: llm.providers[0].projectID
- description: |-
Llama Stack Generic provider type for provider configuration (e.g., "remote::openai", "inline::sentence-transformers")
When set, this provider uses Llama Stack Generic mode instead of legacy mode.
Must follow pattern: (inline|remote)::<provider-name>
displayName: Llama Stack Provider Type
path: llm.providers[0].providerType
- description: TLS Security Profile used by connection to provider
displayName: TLS Security Profile
path: llm.providers[0].tlsSecurityProfile
Expand Down Expand Up @@ -271,9 +257,6 @@ spec:
path: ols.deployment.database.replicas
x-descriptors:
- urn:alm:descriptor:com.tectonic.ui:podCount
- description: Llama Stack container settings.
displayName: Llama Stack Container
path: ols.deployment.llamaStack
- description: MCP server container settings.
displayName: MCP Server Container
path: ols.deployment.mcpServer
Expand Down Expand Up @@ -891,9 +874,6 @@ spec:
- --metrics-bind-address=:8443
- --secure-metrics-server
- --cert-dir=/etc/tls/private
- --lcore-image=quay.io/lightspeed-core/lightspeed-stack:dev-latest
- --use-lcore=false
- --lcore-server=true
- --service-image=registry.redhat.io/openshift-lightspeed/lightspeed-service-api-rhel9@sha256:5287134ee84c4837e74db54a36f9abe71dcbd46d067b04d99bfaa972a4f149cd
- --console-image=registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-rhel9@sha256:c613e471cd3b77fb85dacb337b5b9c6b9fddd06563485b0189809690b142dd4f
- --console-image-pf5=registry.redhat.io/openshift-lightspeed/lightspeed-console-plugin-pf5-rhel9@sha256:86ac43e54a7d121762265cfbb9b32d3b75226e75ef25abaff2adb22c1171f00a
Expand Down
Loading