diff --git a/deploy/openshift/Chart.yaml b/deploy/openshift/Chart.yaml new file mode 100644 index 0000000..eb20923 --- /dev/null +++ b/deploy/openshift/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: lightspeed-agent +description: Red Hat Lightspeed Agent for OpenShift +type: application +version: 0.1.0 +appVersion: "latest" +keywords: + - lightspeed + - agent + - a2a + - redhat + - gemini diff --git a/deploy/openshift/README.md b/deploy/openshift/README.md new file mode 100644 index 0000000..f078dba --- /dev/null +++ b/deploy/openshift/README.md @@ -0,0 +1,458 @@ +# Red Hat Lightspeed Agent - OpenShift Deployment (Helm) + +This guide covers deploying the Red Hat Lightspeed Agent on OpenShift using Helm. + +By default, the OpenShift deployment does **not** include the Google Cloud +Marketplace handler. Order-id validation is skipped +(`SKIP_ORDER_VALIDATION=true`), while JWT token introspection against Red Hat SSO +is still enforced. + +For OpenShift clusters running **inside Google Cloud**, you can optionally enable +the marketplace handler to support the full Gemini Enterprise integration flow +(Pub/Sub events, Procurement API approvals, and DCR). See +[Marketplace Handler (optional)](#marketplace-handler-optional) below. + +## Architecture + +### Default (without marketplace handler) + +``` + +----------------------------------+ + | OpenShift Route | + | (TLS edge termination) | + +---------------+------------------+ + | + +---------------v------------------+ + | lightspeed-agent (Pod) | + | | + | +---------------------------+ | + | | lightspeed-agent | | + | | (port 8000) |----------> console.redhat.com + | | A2A / JSON-RPC 2.0 | | (via MCP) + | | OAuth 2.0 (Red Hat SSO) | | + | +-----------+---------------+ | + | | localhost:8081 | + | +-----------v---------------+ | + | | lightspeed-mcp | | + | | (sidecar) | | + | | Red Hat Lightspeed MCP | | + | +---------------------------+ | + +-----------------------------------+ + | | + +--------------+--+ +------------+----------+ + | PostgreSQL | | Redis | + | (sessions) | | (rate limiting) | + | Port 5432 | | Port 6379 | + +-----------------+ +------------------------+ +``` + +### With marketplace handler (`handler.enabled=true`) + +``` + Google Cloud OpenShift Cluster + Marketplace +---------------------------------+ + (Pub/Sub) | | + | | +---------------------------+ | + +------ push ----------------------------+->| handler Route | | + | +------------+--------------+ | + | | | + Gemini | +------------v--------------+ | + Enterprise | | marketplace-handler | | + | | | (port 8001) | | + +------ DCR -----------------------------+->| Pub/Sub + DCR endpoint | | + | +---+-----------+-----------+ | + | | | | + | v v | + | Procurement Keycloak | + | API (GCP) (Red Hat SSO) | + | | + | +---------------------------+ | + | | agent Route | | + | +------------+--------------+ | + | | | + | +------------v--------------+ | + | | lightspeed-agent (Pod) | | + | | agent + MCP sidecar | | + | +---+-----------+-----------+ | + | | | | + | v v | + | PostgreSQL Redis | + | (shared) (rate limiting) | + +---------------------------------+ +``` + +## Components + +| Component | Description | +|---|---| +| **lightspeed-agent** | Main A2A agent (Gemini + Google ADK) | +| **lightspeed-mcp** | Red Hat Lightspeed MCP server (sidecar in agent pod) providing tools for console.redhat.com APIs | +| **postgresql** | PostgreSQL 16 for ADK session persistence (and marketplace data when handler is enabled) | +| **redis** | Redis 7 for distributed rate limiting | +| **marketplace-handler** | *(optional)* Marketplace handler for Pub/Sub events and DCR from Gemini Enterprise | + +## Prerequisites + +- OpenShift 4.x cluster with `oc` and `helm` CLIs configured +- Access to pull container images from: + - `quay.io/ecosystem-appeng/lightspeed-agent` (or your own registry) + - `quay.io/redhat-services-prod/insights-management-tenant/insights-mcp/red-hat-lightspeed-mcp` + - `registry.redhat.io/rhel9/postgresql-16` + - `quay.io/fedora/redis-7` +- A Google AI Studio API key or Vertex AI project +- Red Hat SSO OAuth credentials (client ID and secret) + +**Additional prerequisites when enabling the marketplace handler:** +- OpenShift cluster running inside Google Cloud (for Pub/Sub push delivery) +- A GCP service account with the following roles: + - `roles/cloudcommerceprocurement.admin` (Procurement API access) + - `roles/servicecontrol.reporter` (if Service Control is enabled) +- A Keycloak Initial Access Token for DCR +- A Fernet encryption key (generate with `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`) + +## Deployment Steps + +### 1. Create a project (namespace) + +```bash +oc new-project lightspeed-agent +``` + +### 2. Build and push the agent image + +Build the container image from the repository root and push it to an accessible +registry: + +```bash +podman build -t quay.io//lightspeed-agent:latest -f Containerfile . +podman push quay.io//lightspeed-agent:latest +``` + +If enabling the marketplace handler, also build its image: + +```bash +podman build -t quay.io//lightspeed-agent-handler:latest -f Containerfile.marketplace-handler . +podman push quay.io//lightspeed-agent-handler:latest +``` + +If using a different registry or tag, set the image in `values.yaml` or pass it +as an override (see step 4). + +### 3. Configure values + +Copy `values.yaml` and edit it with your settings: + +```bash +cp deploy/openshift/values.yaml deploy/openshift/my-values.yaml +``` + +At minimum, update the `secrets` section with real credentials: + +```yaml +secrets: + create: true + googleApiKey: "your-real-api-key" + redHatSsoClientId: "your-real-client-id" + redHatSsoClientSecret: "your-real-client-secret" + sessionDbPassword: "a-strong-password" + sessionDatabaseUrl: "postgresql+asyncpg://sessions:a-strong-password@lightspeed-agent-postgresql:5432/agent_sessions" +``` + +> **Note**: If you prefer to manage the Secret externally (e.g., via Vault or +> sealed-secrets), set `secrets.create: false` and create a Secret named +> `-lightspeed-agent-secrets` with the same keys. + +### Key configurable values + +| Value | Description | Default | +|---|---|---| +| `agent.image.repository` | Agent container image | `quay.io/ecosystem-appeng/lightspeed-agent` | +| `agent.image.tag` | Agent image tag | `latest` | +| `agent.replicas` | Number of agent replicas | `1` | +| `mcp.image.repository` | Lightspeed MCP server image | `quay.io/.../red-hat-lightspeed-mcp` | +| `google.geminiModel` | Gemini model name | `gemini-2.5-flash` | +| `google.useVertexAI` | Use Vertex AI instead of AI Studio | `false` | +| `postgresql.storage.size` | PostgreSQL PVC size | `1Gi` | +| `redis.storage.size` | Redis PVC size | `1Gi` | +| `route.enabled` | Create an OpenShift Route | `true` | +| `auth.skipOrderValidation` | Skip marketplace order checks | `true` | +| `handler.enabled` | Deploy the marketplace handler | `false` | +| `handler.serviceControlServiceName` | Marketplace product identifier | `""` | +| `serviceControl.enabled` | Enable Service Control usage reporting | `false` | + +See `values.yaml` for the full list of configurable options. + +### 4. Install the chart + +```bash +helm install lightspeed-agent deploy/openshift/ \ + -f deploy/openshift/my-values.yaml \ + -n lightspeed-agent +``` + +Or override individual values directly: + +```bash +helm install lightspeed-agent deploy/openshift/ \ + -f deploy/openshift/my-values.yaml \ + --set agent.image.repository=my-registry.example.com/lightspeed-agent \ + --set agent.image.tag=v1.0.0 \ + --set google.geminiModel=gemini-2.5-pro \ + --set postgresql.storage.size=5Gi \ + -n lightspeed-agent +``` + +### 5. Update the agent provider URL + +After the Route is created, update `AGENT_PROVIDER_URL` to match the route +hostname: + +```bash +ROUTE_HOST=$(oc get route lightspeed-agent -n lightspeed-agent -o jsonpath='{.spec.host}') +helm upgrade lightspeed-agent deploy/openshift/ \ + -f deploy/openshift/my-values.yaml \ + --set agent.providerUrl=https://${ROUTE_HOST} \ + -n lightspeed-agent +``` + +### 6. Verify the deployment + +```bash +# Check all pods are running +oc get pods -n lightspeed-agent + +# Check the agent health endpoint +ROUTE_HOST=$(oc get route lightspeed-agent -n lightspeed-agent -o jsonpath='{.spec.host}') +curl -s https://${ROUTE_HOST}/health + +# Check the agent card +curl -s https://${ROUTE_HOST}/.well-known/agent.json | python -m json.tool +``` + +## Authentication + +The agent authenticates requests via Red Hat SSO token introspection: + +1. Clients obtain a Bearer token from Red Hat SSO +2. The agent validates the token via the SSO introspection endpoint +3. The required scope (`agent:insights` by default) is checked + +When there is no marketplace handler (`handler.enabled=false`), order-id +validation is disabled (`SKIP_ORDER_VALIDATION=true`). The agent does not need a +marketplace database or DCR client registrations. + +When the handler is enabled, order-id validation should be turned on +(`auth.skipOrderValidation=false`) so the agent verifies that each request is +associated with an active marketplace entitlement. + +## Marketplace Handler (optional) + +For OpenShift clusters running **inside Google Cloud**, you can enable the +marketplace handler to support the full Google Cloud Marketplace integration: + +- Receives Pub/Sub push events from Google Cloud Marketplace +- Approves accounts and entitlements via the Procurement API +- Handles Dynamic Client Registration (DCR) from Gemini Enterprise +- Stores entitlements in the shared PostgreSQL database + +### 1. Create a GCP service account + +Create a service account in your GCP project and grant it the required roles: + +```bash +PROJECT_ID="your-gcp-project" +SA_NAME="lightspeed-handler" + +gcloud iam service-accounts create $SA_NAME \ + --project=$PROJECT_ID \ + --display-name="Lightspeed Marketplace Handler" + +# Grant Procurement API access +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member="serviceAccount:${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role="roles/cloudcommerceprocurement.admin" + +# Grant Service Control access (if enabling usage reporting) +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member="serviceAccount:${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role="roles/servicemanagement.serviceController" +``` + +### 2. Download and encode the service account key + +```bash +gcloud iam service-accounts keys create sa-key.json \ + --iam-account="${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" + +# Base64-encode for the Helm values +GCP_SA_KEY_B64=$(base64 -w0 sa-key.json) +``` + +### 3. Configure handler values + +Add the following to your `my-values.yaml`: + +```yaml +handler: + enabled: true + image: + repository: quay.io//lightspeed-agent-handler + serviceControlServiceName: "your-product.gcpmarketplace.example.com" + +auth: + skipOrderValidation: false + +secrets: + databaseUrl: "postgresql+asyncpg://sessions:a-strong-password@lightspeed-agent-postgresql:5432/agent_sessions" + dcrInitialAccessToken: "your-keycloak-initial-access-token" + dcrEncryptionKey: "your-fernet-encryption-key" + gcpServiceAccountKey: "" +``` + +### 4. Deploy or upgrade + +```bash +helm upgrade --install lightspeed-agent deploy/openshift/ \ + -f deploy/openshift/my-values.yaml \ + -n lightspeed-agent +``` + +### 5. Configure Pub/Sub push subscription + +After the handler Route is created, configure a Pub/Sub push subscription to +send events to the handler: + +```bash +HANDLER_HOST=$(oc get route lightspeed-agent-handler -n lightspeed-agent -o jsonpath='{.spec.host}') + +gcloud pubsub subscriptions create marketplace-events-sub \ + --topic="$PUBSUB_TOPIC" \ + --push-endpoint="https://${HANDLER_HOST}/dcr" \ + --push-auth-service-account="$PUBSUB_INVOKER_SA" \ + --ack-deadline=60 \ + --project="$PROJECT_ID" +``` + +> **Note**: The handler Route must be reachable from Google Cloud Pub/Sub. For +> OpenShift clusters on GCP, this is typically the case as long as the Route has +> a public hostname. The Pub/Sub push subscription authenticates itself with an +> OIDC token signed by the push auth service account. + +### Handler configuration values + +| Value | Description | Default | +|---|---|---| +| `handler.enabled` | Enable the marketplace handler | `false` | +| `handler.image.repository` | Handler container image | `quay.io/ecosystem-appeng/lightspeed-agent-handler` | +| `handler.image.tag` | Handler image tag | `latest` | +| `handler.replicas` | Number of handler replicas | `1` | +| `handler.port` | Handler listen port | `8001` | +| `handler.serviceControlServiceName` | Marketplace product identifier | `""` | +| `handler.dcr.enabled` | Enable DCR with Keycloak | `true` | +| `handler.dcr.clientNamePrefix` | Prefix for created OAuth client names | `gemini-order-` | +| `handler.route.enabled` | Create a Route for the handler | `true` | +| `serviceControl.enabled` | Enable Service Control usage reporting | `false` | +| `secrets.dcrInitialAccessToken` | Keycloak Initial Access Token | `""` | +| `secrets.dcrEncryptionKey` | Fernet key for encrypting client secrets | `""` | +| `secrets.databaseUrl` | Marketplace database URL (shared PostgreSQL) | *(see values.yaml)* | +| `secrets.gcpServiceAccountKey` | Base64-encoded GCP SA key JSON | `""` | + +## Scaling + +To scale the agent horizontally: + +```bash +oc scale deployment/lightspeed-agent --replicas=3 -n lightspeed-agent +``` + +Rate limiting state is shared across replicas through Redis. + +For automatic scaling, create a HorizontalPodAutoscaler: + +```bash +oc autoscale deployment/lightspeed-agent --min=1 --max=5 --cpu-percent=80 -n lightspeed-agent +``` + +> **Note**: The marketplace handler should typically run with a single replica +> to avoid processing duplicate Pub/Sub events. + +## Upgrading + +```bash +helm upgrade lightspeed-agent deploy/openshift/ \ + -f deploy/openshift/my-values.yaml \ + -n lightspeed-agent +``` + +## Troubleshooting + +### View logs + +```bash +# Agent logs +oc logs deployment/lightspeed-agent -c lightspeed-agent -n lightspeed-agent + +# Lightspeed MCP server logs +oc logs deployment/lightspeed-agent -c lightspeed-mcp -n lightspeed-agent + +# Marketplace handler logs (if enabled) +oc logs deployment/lightspeed-agent-handler -n lightspeed-agent + +# PostgreSQL logs +oc logs deployment/lightspeed-agent-postgresql -n lightspeed-agent + +# Redis logs +oc logs deployment/lightspeed-agent-redis -n lightspeed-agent +``` + +### Common issues + +**Pod stuck in `ImagePullBackOff`**: Verify the image registry is accessible and +credentials are configured if pulling from a private registry: + +```bash +oc create secret docker-registry my-registry-secret \ + --docker-server=quay.io \ + --docker-username= \ + --docker-password= \ + -n lightspeed-agent +oc secrets link default my-registry-secret --for=pull -n lightspeed-agent +``` + +**Agent cannot connect to PostgreSQL**: Verify the PostgreSQL pod is running and +the `SESSION_DATABASE_URL` in the secret matches the service name and port. + +**Agent cannot connect to Redis**: Verify the Redis pod is running and the +`RATE_LIMIT_REDIS_URL` in the ConfigMap points to the correct Redis service. + +**Health check failing**: Check agent logs for startup errors. Common causes +include missing secrets or unreachable database/Redis services. + +**Handler cannot reach Procurement API**: Verify the GCP service account key is +correctly base64-encoded in `secrets.gcpServiceAccountKey` and that the service +account has the required IAM roles. + +**Pub/Sub events not arriving**: Verify the handler Route is externally +accessible and the Pub/Sub push subscription is configured with the correct +endpoint URL (`https:///dcr`). + +## Cleanup + +Uninstall the Helm release to remove all deployed resources: + +```bash +helm uninstall lightspeed-agent -n lightspeed-agent +``` + +PersistentVolumeClaims are not deleted by `helm uninstall`. Remove them manually +if needed: + +```bash +oc delete pvc -l app.kubernetes.io/part-of=lightspeed-agent -n lightspeed-agent +``` + +Or delete the entire project: + +```bash +oc delete project lightspeed-agent +``` diff --git a/deploy/openshift/templates/_helpers.tpl b/deploy/openshift/templates/_helpers.tpl new file mode 100644 index 0000000..42c71d6 --- /dev/null +++ b/deploy/openshift/templates/_helpers.tpl @@ -0,0 +1,87 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "lightspeed-agent.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "lightspeed-agent.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "lightspeed-agent.labels" -}} +app.kubernetes.io/name: {{ include "lightspeed-agent.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Values.agent.image.tag | default .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: lightspeed-agent +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Selector labels for the agent +*/}} +{{- define "lightspeed-agent.agentSelectorLabels" -}} +app.kubernetes.io/name: {{ include "lightspeed-agent.fullname" . }} +app.kubernetes.io/component: agent +{{- end }} + +{{/* +Selector labels for PostgreSQL +*/}} +{{- define "lightspeed-agent.postgresqlSelectorLabels" -}} +app.kubernetes.io/name: {{ include "lightspeed-agent.fullname" . }}-postgresql +app.kubernetes.io/component: database +{{- end }} + +{{/* +Selector labels for Redis +*/}} +{{- define "lightspeed-agent.redisSelectorLabels" -}} +app.kubernetes.io/name: {{ include "lightspeed-agent.fullname" . }}-redis +app.kubernetes.io/component: ratelimit +{{- end }} + +{{/* +PostgreSQL service name +*/}} +{{- define "lightspeed-agent.postgresqlServiceName" -}} +{{- include "lightspeed-agent.fullname" . }}-postgresql +{{- end }} + +{{/* +Redis service name +*/}} +{{- define "lightspeed-agent.redisServiceName" -}} +{{- include "lightspeed-agent.fullname" . }}-redis +{{- end }} + +{{/* +Selector labels for the marketplace handler +*/}} +{{- define "lightspeed-agent.handlerSelectorLabels" -}} +app.kubernetes.io/name: {{ include "lightspeed-agent.fullname" . }}-handler +app.kubernetes.io/component: handler +{{- end }} + +{{/* +Handler service name +*/}} +{{- define "lightspeed-agent.handlerServiceName" -}} +{{- include "lightspeed-agent.fullname" . }}-handler +{{- end }} diff --git a/deploy/openshift/templates/agent-deployment.yaml b/deploy/openshift/templates/agent-deployment.yaml new file mode 100644 index 0000000..0b90c04 --- /dev/null +++ b/deploy/openshift/templates/agent-deployment.yaml @@ -0,0 +1,109 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "lightspeed-agent.fullname" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: agent +spec: + replicas: {{ .Values.agent.replicas }} + selector: + matchLabels: + {{- include "lightspeed-agent.agentSelectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "lightspeed-agent.agentSelectorLabels" . | nindent 8 }} + spec: + containers: + # =================================================================== + # Lightspeed Agent + # =================================================================== + - name: lightspeed-agent + image: {{ printf "%s:%s" .Values.agent.image.repository .Values.agent.image.tag }} + imagePullPolicy: {{ .Values.agent.image.pullPolicy }} + ports: + - containerPort: {{ .Values.agent.port }} + protocol: TCP + name: http + envFrom: + - configMapRef: + name: {{ include "lightspeed-agent.fullname" . }}-config + env: + - name: GOOGLE_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: GOOGLE_API_KEY + - name: GOOGLE_CLOUD_PROJECT + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: GOOGLE_CLOUD_PROJECT + optional: true + - name: RED_HAT_SSO_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: RED_HAT_SSO_CLIENT_ID + - name: RED_HAT_SSO_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: RED_HAT_SSO_CLIENT_SECRET + - name: SESSION_DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: SESSION_DATABASE_URL + optional: true + {{- if .Values.handler.enabled }} + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: DATABASE_URL + {{- end }} + resources: + {{- toYaml .Values.agent.resources | nindent 12 }} + startupProbe: + httpGet: + path: /health + port: {{ .Values.agent.port }} + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 6 + readinessProbe: + httpGet: + path: /ready + port: {{ .Values.agent.port }} + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /health + port: {{ .Values.agent.port }} + periodSeconds: 30 + failureThreshold: 3 + + # =================================================================== + # Red Hat Lightspeed MCP Server (Sidecar) + # =================================================================== + - name: lightspeed-mcp + image: {{ printf "%s:%s" .Values.mcp.image.repository .Values.mcp.image.tag }} + imagePullPolicy: {{ .Values.mcp.image.pullPolicy }} + envFrom: + - configMapRef: + name: {{ include "lightspeed-agent.fullname" . }}-config + args: + - "--debug" + - "$(MCP_SERVER_MODE)" + - "--port" + - "$(MCP_SERVER_PORT)" + - "--host" + - "$(MCP_SERVER_HOST)" + ports: + - containerPort: {{ .Values.mcp.port }} + protocol: TCP + resources: + {{- toYaml .Values.mcp.resources | nindent 12 }} diff --git a/deploy/openshift/templates/agent-route.yaml b/deploy/openshift/templates/agent-route.yaml new file mode 100644 index 0000000..9dc84a3 --- /dev/null +++ b/deploy/openshift/templates/agent-route.yaml @@ -0,0 +1,20 @@ +{{- if .Values.route.enabled }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "lightspeed-agent.fullname" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: agent +spec: + to: + kind: Service + name: {{ include "lightspeed-agent.fullname" . }} + weight: 100 + port: + targetPort: http + tls: + termination: {{ .Values.route.tls.termination }} + insecureEdgeTerminationPolicy: {{ .Values.route.tls.insecureEdgeTerminationPolicy }} + wildcardPolicy: None +{{- end }} diff --git a/deploy/openshift/templates/agent-service.yaml b/deploy/openshift/templates/agent-service.yaml new file mode 100644 index 0000000..a9f6586 --- /dev/null +++ b/deploy/openshift/templates/agent-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "lightspeed-agent.fullname" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: agent +spec: + selector: + {{- include "lightspeed-agent.agentSelectorLabels" . | nindent 4 }} + ports: + - port: {{ .Values.agent.port }} + targetPort: {{ .Values.agent.port }} + protocol: TCP + name: http + type: ClusterIP diff --git a/deploy/openshift/templates/configmap.yaml b/deploy/openshift/templates/configmap.yaml new file mode 100644 index 0000000..bb7d11d --- /dev/null +++ b/deploy/openshift/templates/configmap.yaml @@ -0,0 +1,71 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "lightspeed-agent.fullname" . }}-config + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} +data: + # Google AI Configuration + GOOGLE_GENAI_USE_VERTEXAI: {{ .Values.google.useVertexAI | ternary "TRUE" "FALSE" | quote }} + GOOGLE_CLOUD_LOCATION: {{ .Values.google.cloudLocation | quote }} + GEMINI_MODEL: {{ .Values.google.geminiModel | quote }} + + # Red Hat SSO Configuration + RED_HAT_SSO_ISSUER: {{ .Values.sso.issuer | quote }} + AGENT_REQUIRED_SCOPE: {{ .Values.sso.requiredScope | quote }} + + # MCP Server Configuration (agent -> MCP sidecar) + MCP_TRANSPORT_MODE: {{ .Values.mcp.transport | quote }} + MCP_SERVER_URL: {{ printf "http://localhost:%d" (int .Values.mcp.port) | quote }} + MCP_READ_ONLY: {{ .Values.mcp.readOnly | quote }} + + # MCP Server Configuration (container startup args) + MCP_SERVER_MODE: {{ .Values.mcp.transport | quote }} + MCP_SERVER_PORT: {{ .Values.mcp.port | quote }} + MCP_SERVER_HOST: {{ .Values.mcp.host | quote }} + + # Agent Configuration + AGENT_PROVIDER_URL: {{ .Values.agent.providerUrl | quote }} + AGENT_NAME: {{ .Values.agent.name | quote }} + AGENT_DESCRIPTION: {{ .Values.agent.description | quote }} + AGENT_HOST: {{ .Values.agent.host | quote }} + AGENT_PORT: {{ .Values.agent.port | quote }} + + # Session PostgreSQL Configuration + SESSION_BACKEND: {{ .Values.postgresql.sessionBackend | quote }} + SESSION_DB_USER: {{ .Values.postgresql.user | quote }} + SESSION_DB_NAME: {{ .Values.postgresql.database | quote }} + + # Rate Limiting Configuration + RATE_LIMIT_REDIS_URL: {{ printf "redis://%s-redis:6379/0" (include "lightspeed-agent.fullname" .) | quote }} + RATE_LIMIT_REDIS_TIMEOUT_MS: {{ .Values.rateLimit.redisTimeoutMs | quote }} + RATE_LIMIT_KEY_PREFIX: {{ .Values.rateLimit.keyPrefix | quote }} + RATE_LIMIT_REQUESTS_PER_MINUTE: {{ .Values.rateLimit.requestsPerMinute | quote }} + RATE_LIMIT_REQUESTS_PER_HOUR: {{ .Values.rateLimit.requestsPerHour | quote }} + + # Google Cloud Service Control + SERVICE_CONTROL_ENABLED: {{ .Values.serviceControl.enabled | quote }} + + # Marketplace Handler + HANDLER_HOST: {{ .Values.handler.host | quote }} + HANDLER_PORT: {{ .Values.handler.port | quote }} + DCR_ENABLED: {{ .Values.handler.dcr.enabled | quote }} + DCR_CLIENT_NAME_PREFIX: {{ .Values.handler.dcr.clientNamePrefix | quote }} + SERVICE_CONTROL_SERVICE_NAME: {{ .Values.handler.serviceControlServiceName | quote }} + + # Authentication + SKIP_JWT_VALIDATION: {{ .Values.auth.skipJwtValidation | quote }} + SKIP_ORDER_VALIDATION: {{ .Values.auth.skipOrderValidation | quote }} + + # Logging + LOG_LEVEL: {{ .Values.logging.level | quote }} + LOG_FORMAT: {{ .Values.logging.format | quote }} + + # OpenTelemetry + OTEL_ENABLED: {{ .Values.otel.enabled | quote }} + OTEL_SERVICE_NAME: {{ .Values.otel.serviceName | quote }} + OTEL_EXPORTER_TYPE: {{ .Values.otel.exporterType | quote }} + OTEL_EXPORTER_OTLP_ENDPOINT: {{ .Values.otel.otlpEndpoint | quote }} + OTEL_EXPORTER_OTLP_HTTP_ENDPOINT: {{ .Values.otel.otlpHttpEndpoint | quote }} + OTEL_TRACES_SAMPLER: {{ .Values.otel.tracesSampler | quote }} + OTEL_TRACES_SAMPLER_ARG: {{ .Values.otel.tracesSamplerArg | quote }} diff --git a/deploy/openshift/templates/gcp-sa-secret.yaml b/deploy/openshift/templates/gcp-sa-secret.yaml new file mode 100644 index 0000000..59bccc7 --- /dev/null +++ b/deploy/openshift/templates/gcp-sa-secret.yaml @@ -0,0 +1,11 @@ +{{- if .Values.handler.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "lightspeed-agent.fullname" . }}-gcp-sa-key + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} +type: Opaque +data: + sa-key.json: {{ .Values.secrets.gcpServiceAccountKey }} +{{- end }} diff --git a/deploy/openshift/templates/handler-deployment.yaml b/deploy/openshift/templates/handler-deployment.yaml new file mode 100644 index 0000000..4a55e96 --- /dev/null +++ b/deploy/openshift/templates/handler-deployment.yaml @@ -0,0 +1,108 @@ +{{- if .Values.handler.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "lightspeed-agent.handlerServiceName" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: handler +spec: + replicas: {{ .Values.handler.replicas }} + selector: + matchLabels: + {{- include "lightspeed-agent.handlerSelectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "lightspeed-agent.handlerSelectorLabels" . | nindent 8 }} + spec: + containers: + - name: marketplace-handler + image: {{ printf "%s:%s" .Values.handler.image.repository .Values.handler.image.tag }} + imagePullPolicy: {{ .Values.handler.image.pullPolicy }} + ports: + - containerPort: {{ .Values.handler.port }} + protocol: TCP + name: http + envFrom: + - configMapRef: + name: {{ include "lightspeed-agent.fullname" . }}-config + env: + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /var/run/secrets/gcp/sa-key.json + - name: GOOGLE_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: GOOGLE_API_KEY + - name: GOOGLE_CLOUD_PROJECT + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: GOOGLE_CLOUD_PROJECT + optional: true + - name: RED_HAT_SSO_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: RED_HAT_SSO_CLIENT_ID + - name: RED_HAT_SSO_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: RED_HAT_SSO_CLIENT_SECRET + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: DATABASE_URL + - name: DCR_INITIAL_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: DCR_INITIAL_ACCESS_TOKEN + - name: DCR_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: DCR_ENCRYPTION_KEY + - name: GMA_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: GMA_CLIENT_ID + - name: GMA_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: GMA_CLIENT_SECRET + volumeMounts: + - name: gcp-sa-key + mountPath: /var/run/secrets/gcp + readOnly: true + resources: + {{- toYaml .Values.handler.resources | nindent 12 }} + startupProbe: + httpGet: + path: /health + port: {{ .Values.handler.port }} + initialDelaySeconds: 5 + periodSeconds: 10 + failureThreshold: 6 + readinessProbe: + httpGet: + path: /ready + port: {{ .Values.handler.port }} + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /health + port: {{ .Values.handler.port }} + periodSeconds: 30 + failureThreshold: 3 + volumes: + - name: gcp-sa-key + secret: + secretName: {{ include "lightspeed-agent.fullname" . }}-gcp-sa-key +{{- end }} diff --git a/deploy/openshift/templates/handler-route.yaml b/deploy/openshift/templates/handler-route.yaml new file mode 100644 index 0000000..9bbb8b9 --- /dev/null +++ b/deploy/openshift/templates/handler-route.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.handler.enabled .Values.handler.route.enabled }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ include "lightspeed-agent.handlerServiceName" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: handler +spec: + to: + kind: Service + name: {{ include "lightspeed-agent.handlerServiceName" . }} + weight: 100 + port: + targetPort: http + tls: + termination: {{ .Values.route.tls.termination }} + insecureEdgeTerminationPolicy: {{ .Values.route.tls.insecureEdgeTerminationPolicy }} + wildcardPolicy: None +{{- end }} diff --git a/deploy/openshift/templates/handler-service.yaml b/deploy/openshift/templates/handler-service.yaml new file mode 100644 index 0000000..d1989ec --- /dev/null +++ b/deploy/openshift/templates/handler-service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.handler.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "lightspeed-agent.handlerServiceName" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: handler +spec: + selector: + {{- include "lightspeed-agent.handlerSelectorLabels" . | nindent 4 }} + ports: + - port: {{ .Values.handler.port }} + targetPort: {{ .Values.handler.port }} + protocol: TCP + name: http + type: ClusterIP +{{- end }} diff --git a/deploy/openshift/templates/postgresql-deployment.yaml b/deploy/openshift/templates/postgresql-deployment.yaml new file mode 100644 index 0000000..6f05119 --- /dev/null +++ b/deploy/openshift/templates/postgresql-deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "lightspeed-agent.postgresqlServiceName" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + {{- include "lightspeed-agent.postgresqlSelectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "lightspeed-agent.postgresqlSelectorLabels" . | nindent 8 }} + spec: + containers: + - name: postgresql + image: {{ printf "%s:%s" .Values.postgresql.image.repository .Values.postgresql.image.tag }} + imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }} + ports: + - containerPort: 5432 + protocol: TCP + env: + - name: POSTGRESQL_USER + valueFrom: + configMapKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-config + key: SESSION_DB_USER + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + key: SESSION_DB_PASSWORD + - name: POSTGRESQL_DATABASE + valueFrom: + configMapKeyRef: + name: {{ include "lightspeed-agent.fullname" . }}-config + key: SESSION_DB_NAME + volumeMounts: + - name: postgresql-data + mountPath: /var/lib/pgsql/data + resources: + {{- toYaml .Values.postgresql.resources | nindent 12 }} + readinessProbe: + exec: + command: + - /usr/libexec/check-container + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + exec: + command: + - /usr/libexec/check-container + - --live + initialDelaySeconds: 120 + periodSeconds: 10 + volumes: + - name: postgresql-data + persistentVolumeClaim: + claimName: {{ include "lightspeed-agent.fullname" . }}-postgresql-data +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "lightspeed-agent.postgresqlServiceName" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + selector: + {{- include "lightspeed-agent.postgresqlSelectorLabels" . | nindent 4 }} + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + type: ClusterIP diff --git a/deploy/openshift/templates/postgresql-pvc.yaml b/deploy/openshift/templates/postgresql-pvc.yaml new file mode 100644 index 0000000..5756bb6 --- /dev/null +++ b/deploy/openshift/templates/postgresql-pvc.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "lightspeed-agent.fullname" . }}-postgresql-data + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.postgresql.storage.size }} + {{- if .Values.postgresql.storage.storageClassName }} + storageClassName: {{ .Values.postgresql.storage.storageClassName | quote }} + {{- end }} diff --git a/deploy/openshift/templates/redis-deployment.yaml b/deploy/openshift/templates/redis-deployment.yaml new file mode 100644 index 0000000..e108173 --- /dev/null +++ b/deploy/openshift/templates/redis-deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "lightspeed-agent.redisServiceName" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: ratelimit +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + {{- include "lightspeed-agent.redisSelectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "lightspeed-agent.redisSelectorLabels" . | nindent 8 }} + spec: + containers: + - name: redis + image: {{ printf "%s:%s" .Values.redis.image.repository .Values.redis.image.tag }} + imagePullPolicy: {{ .Values.redis.image.pullPolicy }} + args: + - "--appendonly" + - "yes" + ports: + - containerPort: 6379 + protocol: TCP + volumeMounts: + - name: redis-data + mountPath: /var/lib/redis/data + resources: + {{- toYaml .Values.redis.resources | nindent 12 }} + readinessProbe: + exec: + command: + - redis-cli + - ping + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + exec: + command: + - redis-cli + - ping + initialDelaySeconds: 30 + periodSeconds: 10 + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: {{ include "lightspeed-agent.fullname" . }}-redis-data +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "lightspeed-agent.redisServiceName" . }} + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: ratelimit +spec: + selector: + {{- include "lightspeed-agent.redisSelectorLabels" . | nindent 4 }} + ports: + - port: 6379 + targetPort: 6379 + protocol: TCP + type: ClusterIP diff --git a/deploy/openshift/templates/redis-pvc.yaml b/deploy/openshift/templates/redis-pvc.yaml new file mode 100644 index 0000000..68a5c91 --- /dev/null +++ b/deploy/openshift/templates/redis-pvc.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "lightspeed-agent.fullname" . }}-redis-data + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} + app.kubernetes.io/component: ratelimit +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.redis.storage.size }} + {{- if .Values.redis.storage.storageClassName }} + storageClassName: {{ .Values.redis.storage.storageClassName | quote }} + {{- end }} diff --git a/deploy/openshift/templates/secret.yaml b/deploy/openshift/templates/secret.yaml new file mode 100644 index 0000000..2d32bbb --- /dev/null +++ b/deploy/openshift/templates/secret.yaml @@ -0,0 +1,21 @@ +{{- if .Values.secrets.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "lightspeed-agent.fullname" . }}-secrets + labels: + {{- include "lightspeed-agent.labels" . | nindent 4 }} +type: Opaque +stringData: + GOOGLE_API_KEY: {{ .Values.secrets.googleApiKey | quote }} + GOOGLE_CLOUD_PROJECT: {{ .Values.secrets.googleCloudProject | quote }} + RED_HAT_SSO_CLIENT_ID: {{ .Values.secrets.redHatSsoClientId | quote }} + RED_HAT_SSO_CLIENT_SECRET: {{ .Values.secrets.redHatSsoClientSecret | quote }} + SESSION_DB_PASSWORD: {{ .Values.secrets.sessionDbPassword | quote }} + SESSION_DATABASE_URL: {{ .Values.secrets.sessionDatabaseUrl | quote }} + DATABASE_URL: {{ .Values.secrets.databaseUrl | quote }} + DCR_INITIAL_ACCESS_TOKEN: {{ .Values.secrets.dcrInitialAccessToken | quote }} + DCR_ENCRYPTION_KEY: {{ .Values.secrets.dcrEncryptionKey | quote }} + GMA_CLIENT_ID: {{ .Values.secrets.gmaClientId | quote }} + GMA_CLIENT_SECRET: {{ .Values.secrets.gmaClientSecret | quote }} +{{- end }} diff --git a/deploy/openshift/values.yaml b/deploy/openshift/values.yaml new file mode 100644 index 0000000..e9f8489 --- /dev/null +++ b/deploy/openshift/values.yaml @@ -0,0 +1,218 @@ +# ============================================================================= +# Red Hat Lightspeed Agent - Helm Values +# ============================================================================= + +# --------------------------------------------------------------------------- +# Agent configuration +# --------------------------------------------------------------------------- +agent: + image: + repository: quay.io/ecosystem-appeng/lightspeed-agent + tag: latest + pullPolicy: Always + replicas: 1 + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: "2" + memory: 2Gi + # Agent provider URL — override after the Route is created, or set a known hostname + providerUrl: "https://lightspeed-agent.apps.example.com" + name: lightspeed_agent + description: "Red Hat Lightspeed Agent" + host: "0.0.0.0" + port: 8000 + +# --------------------------------------------------------------------------- +# Red Hat Lightspeed MCP server (runs as sidecar in the agent pod) +# --------------------------------------------------------------------------- +mcp: + image: + repository: quay.io/redhat-services-prod/insights-management-tenant/insights-mcp/red-hat-lightspeed-mcp + tag: latest + pullPolicy: Always + transport: http + port: 8081 + host: "0.0.0.0" + readOnly: true + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + +# --------------------------------------------------------------------------- +# Google AI / Gemini +# --------------------------------------------------------------------------- +google: + # Set to true to use Vertex AI instead of Google AI Studio + useVertexAI: false + cloudLocation: us-central1 + geminiModel: gemini-2.5-flash + +# --------------------------------------------------------------------------- +# Red Hat SSO / OAuth 2.0 +# --------------------------------------------------------------------------- +sso: + issuer: "https://sso.redhat.com/auth/realms/redhat-external" + requiredScope: "agent:insights" + +# --------------------------------------------------------------------------- +# Authentication +# --------------------------------------------------------------------------- +auth: + # Skip JWT validation entirely (development only — do NOT enable in production) + skipJwtValidation: false + # Skip marketplace order-id validation while keeping JWT introspection. + # When the marketplace handler is disabled, order validation code is not + # executed regardless of this setting. + skipOrderValidation: false + +# --------------------------------------------------------------------------- +# Rate limiting (Redis-backed) +# --------------------------------------------------------------------------- +rateLimit: + requestsPerMinute: 60 + requestsPerHour: 1000 + redisTimeoutMs: 200 + keyPrefix: "lightspeed:ratelimit" + +# --------------------------------------------------------------------------- +# Logging +# --------------------------------------------------------------------------- +logging: + level: INFO + format: json + +# --------------------------------------------------------------------------- +# OpenTelemetry +# --------------------------------------------------------------------------- +otel: + enabled: false + serviceName: lightspeed_agent + exporterType: otlp + otlpEndpoint: "http://localhost:4317" + otlpHttpEndpoint: "http://localhost:4318" + tracesSampler: always_on + tracesSamplerArg: "1.0" + +# --------------------------------------------------------------------------- +# Session PostgreSQL database +# --------------------------------------------------------------------------- +postgresql: + image: + repository: registry.redhat.io/rhel9/postgresql-16 + tag: latest + pullPolicy: IfNotPresent + # Session storage backend: "database" persists sessions in PostgreSQL, + # "memory" uses in-memory sessions (lost on pod restart). + sessionBackend: database + user: sessions + database: agent_sessions + storage: + size: 1Gi + # storageClassName: "" # uncomment to use a specific storage class + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +# --------------------------------------------------------------------------- +# Redis (rate limiting backend) +# --------------------------------------------------------------------------- +redis: + image: + repository: quay.io/fedora/redis-7 + tag: latest + pullPolicy: IfNotPresent + storage: + size: 1Gi + # storageClassName: "" # uncomment to use a specific storage class + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + +# --------------------------------------------------------------------------- +# Marketplace Handler (optional — enable for GCP deployments with +# Google Cloud Marketplace integration) +# --------------------------------------------------------------------------- +handler: + enabled: false + image: + repository: quay.io/ecosystem-appeng/lightspeed-agent-handler + tag: latest + pullPolicy: Always + replicas: 1 + host: "0.0.0.0" + port: 8001 + # Marketplace product identifier (managed service name from Producer Portal). + # Required for entitlement approval and product-level Pub/Sub event filtering. + serviceControlServiceName: "" + dcr: + enabled: true + clientNamePrefix: "gemini-order-" + route: + enabled: true + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + +# --------------------------------------------------------------------------- +# Google Cloud Service Control (usage reporting) +# --------------------------------------------------------------------------- +serviceControl: + enabled: false + +# --------------------------------------------------------------------------- +# Route +# --------------------------------------------------------------------------- +route: + enabled: true + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + +# --------------------------------------------------------------------------- +# Secrets — these are references only. +# Create the Secret separately from the template (see secret.yaml) +# or supply values here for the template to generate it. +# --------------------------------------------------------------------------- +secrets: + # Set to true to have the chart create the Secret from the values below. + # Set to false if you manage the Secret externally. + create: true + googleApiKey: "your-google-api-key" + googleCloudProject: "" + redHatSsoClientId: "your-sso-client-id" + redHatSsoClientSecret: "your-sso-client-secret" + sessionDbPassword: "sessions" + # Full connection URL (contains credentials, stored in the Secret) + sessionDatabaseUrl: "postgresql+asyncpg://sessions:sessions@lightspeed-agent-postgresql:5432/agent_sessions" + # --- Handler secrets (only needed when handler.enabled=true) --- + # Marketplace database URL (shared with agent for order validation). + # Points to the same PostgreSQL instance by default. + databaseUrl: "postgresql+asyncpg://sessions:sessions@lightspeed-agent-postgresql:5432/agent_sessions" + # Keycloak Initial Access Token for Dynamic Client Registration + dcrInitialAccessToken: "" + # Fernet key for encrypting stored client secrets + dcrEncryptionKey: "" + # GMA SSO API credentials for DCR tenant creation (handler only) + gmaClientId: "" + gmaClientSecret: "" + # Base64-encoded GCP service account key JSON (for ADC outside Cloud Run) + gcpServiceAccountKey: "" diff --git a/src/lightspeed_agent/auth/middleware.py b/src/lightspeed_agent/auth/middleware.py index d776033..7706d16 100644 --- a/src/lightspeed_agent/auth/middleware.py +++ b/src/lightspeed_agent/auth/middleware.py @@ -110,11 +110,19 @@ async def dispatch( introspector = get_token_introspector() user = await introspector.validate_token(token) - order_id = await self._resolve_and_validate_order(client_id=user.client_id) - if not order_id: - return self._forbidden_response( - "No active order found for this client" + order_id: str | None = None + if self._settings.skip_order_validation: + logger.debug( + "Skipping order validation (skip_order_validation=true)" ) + else: + order_id = await self._resolve_and_validate_order( + client_id=user.client_id + ) + if not order_id: + return self._forbidden_response( + "No active order found for this client" + ) # Store user in request state for access in handlers request.state.user = user diff --git a/src/lightspeed_agent/config/settings.py b/src/lightspeed_agent/config/settings.py index 841f58c..e62a698 100644 --- a/src/lightspeed_agent/config/settings.py +++ b/src/lightspeed_agent/config/settings.py @@ -299,6 +299,12 @@ def sso_token_endpoint(self) -> str: default=False, description="Skip JWT validation (development only)", ) + skip_order_validation: bool = Field( + default=False, + description="Skip marketplace order-id validation. When enabled, JWT token " + "introspection still occurs but the order/entitlement check is skipped. " + "Use for deployments without the Google Cloud Marketplace handler (e.g., OpenShift).", + ) @model_validator(mode="after") def _block_skip_jwt_in_production(self) -> "Settings":