diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 65a78d4b..5df8c931 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,3 +17,6 @@ # Documentation: *.md.gotmpl @redhat-developer/RHDH-content /README.md @redhat-developer/RHDH-content + +# Lightspeed: +/charts/backstage/files/lightspeed/ @redhat-developer/rhdh-ai diff --git a/.github/actions/test-charts/action.yml b/.github/actions/test-charts/action.yml index 287c8718..e10d7152 100644 --- a/.github/actions/test-charts/action.yml +++ b/.github/actions/test-charts/action.yml @@ -166,6 +166,9 @@ runs: "--set route.enabled=false" "--set upstream.ingress.enabled=true" "--set global.host=rhdh.127.0.0.1.sslip.io" + "--set upstream.backstage.podSecurityContext.runAsUser=1001" + "--set upstream.backstage.podSecurityContext.runAsGroup=1001" + "--set upstream.backstage.podSecurityContext.fsGroup=1001" ) if [[ -n "$INPUT_EXTRA_HELM_ARGS" ]]; then IFS=' ' read -ra ADDITIONAL_ARGS <<< "$INPUT_EXTRA_HELM_ARGS" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bdfb85a5..8ee7b30b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,32 @@ It is important to use `--squash` to avoid pulling the entire commit history of *Note: If merge conflicts occur, resolve them in your editor, then `git add` and `git commit` the resolution as a normal merge.* +### Sync Lightspeed vendored config files + +The Lightspeed config files under [`charts/backstage/files/lightspeed`](./charts/backstage/files/lightspeed) are synced separately from the Backstage subtree by [`hack/sync-lightspeed-configs.sh`](./hack/sync-lightspeed-configs.sh). + +Use the default upstream branch: + +```bash +./hack/sync-lightspeed-configs.sh +``` + +Sync from a release branch or a tag: + +```bash +./hack/sync-lightspeed-configs.sh --ref release-1.9 +./hack/sync-lightspeed-configs.sh --ref v0.5.0 +``` + +Verify the vendored files are already in sync without writing changes: + +```bash +./hack/sync-lightspeed-configs.sh --ref main --check +``` + +The script copies the upstream config files directly, except it appends the chart-managed `mcp_servers` block to `lightspeed-stack.yaml` and renders `secret.yaml` from upstream `env/default-values.env` by dropping comment lines plus `LIGHTSPEED_CORE_IMAGE` and `RAG_CONTENT_IMAGE`, then converting each remaining `KEY=value` line into the chart's YAML secret payload. +Choose the upstream branch or tag that matches the Lightspeed release you want to vendor. + **Important:** After any change to the dependency structure or version of the vendored chart, you must rebuild the lock file and local subchart dependencies: ```bash diff --git a/charts/backstage/Chart.yaml b/charts/backstage/Chart.yaml index e97d0f4e..e8103702 100644 --- a/charts/backstage/Chart.yaml +++ b/charts/backstage/Chart.yaml @@ -47,4 +47,4 @@ sources: [] # Versions are expected to follow Semantic Versioning (https://semver.org/) # Note that when this chart is published to https://github.com/openshift-helm-charts/charts # it will follow the RHDH versioning 1.y.z -version: 5.7.1 +version: 5.8.0 diff --git a/charts/backstage/README.md b/charts/backstage/README.md index c01838d9..6b859ea9 100644 --- a/charts/backstage/README.md +++ b/charts/backstage/README.md @@ -1,7 +1,7 @@ # RHDH Backstage Helm Chart for OpenShift -![Version: 5.7.1](https://img.shields.io/badge/Version-5.7.1-informational?style=flat-square) +![Version: 5.8.0](https://img.shields.io/badge/Version-5.8.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) A Helm chart for deploying Red Hat Developer Hub, which is a Red Hat supported version of Backstage. @@ -29,7 +29,7 @@ For the **Generally Available** version of this chart, see: helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add redhat-developer https://redhat-developer.github.io/rhdh-chart -helm install my-backstage redhat-developer/backstage --version 5.7.1 +helm install my-backstage redhat-developer/backstage --version 5.8.0 ``` ## Introduction @@ -174,6 +174,35 @@ Kubernetes: `>= 1.27.0-0` | global.dynamic.includes[0] | List of dynamic plugins included inside the `rhdh` container image, some of which are disabled by default. This file ONLY works with the `rhdh` container image. | string | `"dynamic-plugins.default.yaml"` | | global.dynamic.plugins | List of dynamic plugins, possibly overriding the plugins listed in `includes` files. Every item defines the plugin `package` as a [NPM package spec](https://docs.npmjs.com/cli/v10/using-npm/package-spec), an optional `pluginConfig` with plugin-specific backstage configuration, and an optional `disabled` flag to disable/enable a plugin listed in `includes` files. It also includes an `integrity` field that is used to verify the plugin package [integrity](https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description). | list | `[]` | | global.host | Custom hostname shorthand, overrides `global.clusterRouterBase`, `upstream.ingress.host`, `route.host`, and url values in `upstream.backstage.appConfig`. | string | `""` | +| global.lightspeed | Built-in Lightspeed feature configuration. | object | Use Lightspeed compatible settings / configurations. | +| global.lightspeed.configMaps[0].create | Whether to create this ConfigMap from the bundled source file. Set to false and provide `nameOverride` to use a pre-existing ConfigMap. | bool | `true` | +| global.lightspeed.configMaps[0].nameOverride | Name of an existing ConfigMap to use instead. Required when `create` is false. | string | `""` | +| global.lightspeed.configMaps[0].sourceFile | Bundled file used to populate the ConfigMap data when `create` is true. | string | `"lightspeed-stack.yaml"` | +| global.lightspeed.configMaps[1].create | Whether to create this ConfigMap from the bundled source file. Set to false and provide `nameOverride` to use a pre-existing ConfigMap. | bool | `true` | +| global.lightspeed.configMaps[1].nameOverride | Name of an existing ConfigMap to use instead. Required when `create` is false. | string | `""` | +| global.lightspeed.configMaps[1].sourceFile | Bundled file used to populate the ConfigMap data when `create` is true. | string | `"config.yaml"` | +| global.lightspeed.configMaps[2].create | Whether to create this ConfigMap from the bundled source file. Set to false and provide `nameOverride` to use a pre-existing ConfigMap. | bool | `true` | +| global.lightspeed.configMaps[2].nameOverride | Name of an existing ConfigMap to use instead. Required when `create` is false. | string | `""` | +| global.lightspeed.configMaps[2].sourceFile | Bundled file used to populate the ConfigMap data when `create` is true. | string | `"rhdh-profile.py"` | +| global.lightspeed.enabled | Enable or disable the built-in Lightspeed feature. | bool | `true` | +| global.lightspeed.initContainer.image | Full image reference for the Lightspeed RAG bootstrap init container. Override for disconnected environments. | string | `"quay.io/redhat-ai-dev/rag-content:release-1.9-lls-0.5.0-642c567fe10a62b5ff711654306b72912f341e05"` | +| global.lightspeed.initContainer.resources | Resource requests/limits for the Lightspeed RAG bootstrap init container. | object | `{"limits":{"cpu":"100m","memory":"500Mi"},"requests":{"cpu":"50m","memory":"150Mi"}}` | +| global.lightspeed.plugins | Lightspeed plugins and their configuration. Override package references for disconnected environments. | list | `[{"disabled":false,"package":"oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed","pluginConfig":{"dynamicPlugins":{"frontend":{"red-hat-developer-hub.backstage-plugin-lightspeed":{"dynamicRoutes":[{"importName":"LightspeedPage","path":"/lightspeed"}],"mountPoints":[{"importName":"LightspeedFAB","mountPoint":"application/listener"},{"importName":"LightspeedDrawerProvider","mountPoint":"application/provider"},{"config":{"id":"lightspeed"},"importName":"LightspeedDrawerStateExposer","mountPoint":"application/internal/drawer-state"},{"config":{"id":"lightspeed","priority":100},"importName":"LightspeedChatContainer","mountPoint":"application/internal/drawer-content"}],"translationResources":[{"importName":"lightspeedTranslations","module":"Alpha","ref":"lightspeedTranslationRef"}]}}}}},{"disabled":false,"package":"oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed-backend:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed-backend"}]` | +| global.lightspeed.ragVolume.emptyDir | `emptyDir` configuration for the RAG data volume. | object | `{}` | +| global.lightspeed.ragVolume.initMountPath | Mount path inside the init container for seeding RAG data. | string | `"/rag-content"` | +| global.lightspeed.ragVolume.mountPath | Mount path inside the sidecar container for serving RAG data. | string | `"/rag-content"` | +| global.lightspeed.ragVolume.name | Name of the Kubernetes volume used for Lightspeed RAG data. | string | `"lightspeed-rag"` | +| global.lightspeed.runtimeVolume.emptyDir | `emptyDir` configuration for the Lightspeed runtime data volume when `runtimeVolume.type=emptyDir`. | object | `{}` | +| global.lightspeed.runtimeVolume.mountPath | Mount path inside the container for Lightspeed runtime storage. | string | `"/tmp"` | +| global.lightspeed.runtimeVolume.name | Name of the Kubernetes volume used for writable Lightspeed runtime storage. | string | `"lightspeed-data"` | +| global.lightspeed.runtimeVolume.persistentVolumeClaim | Existing PVC reference for the Lightspeed runtime data volume when `runtimeVolume.type=persistentVolumeClaim`. | object | `{}` | +| global.lightspeed.runtimeVolume.type | Volume source used for writable Lightspeed runtime storage mounted at `/tmp`. Supported values: `emptyDir`, `persistentVolumeClaim`. | string | `"emptyDir"` | +| global.lightspeed.secret.create | Whether to create a Lightspeed Secret from the bundled source file. | bool | `true` | +| global.lightspeed.secret.name | Name of an existing Secret to use instead. Required when `create` is false. | string | `""` | +| global.lightspeed.secret.optional | Whether the Secret reference is optional in the pod spec. | bool | `false` | +| global.lightspeed.secret.sourceFile | Bundled file used to populate the Secret's `stringData` keys. | string | `"secret.yaml"` | +| global.lightspeed.sidecar.image | Full image reference for the Lightspeed Core sidecar. Override for disconnected environments. | string | `"quay.io/lightspeed-core/lightspeed-stack:0.5.0"` | +| global.lightspeed.sidecar.resources | Resource requests/limits for the Lightspeed Core sidecar. | object | `{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | | nameOverride | | string | `"developer-hub"` | | orchestrator.enabled | | bool | `false` | | orchestrator.plugins | Orchestrator plugins and their configuration | list | `[{"disabled":false,"package":"oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator-backend:{{ \"{{inherit}}\" }}"},{"disabled":false,"package":"oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator-form-widgets:{{ \"{{inherit}}\" }}"},{"disabled":false,"package":"oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator:{{ \"{{inherit}}\" }}"},{"disabled":false,"package":"oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-scaffolder-backend-module-orchestrator:{{ \"{{inherit}}\" }}"}]` | @@ -304,6 +333,18 @@ The chart supports automatic plugin discovery through a catalog index OCI image. For detailed information on configuring the catalog index, including how to override the default image or use a private registry, see the [Catalog Index Configuration documentation](../../docs/catalog-index-configuration.md). +### Lightspeed + +Use `global.lightspeed.enabled` to enable or disable the built-in Lightspeed feature. + +When enabled, the chart adds the default Lightspeed dynamic plugins, a RAG bootstrap init container, a Lightspeed Core sidecar listening on port `8080`, chart-generated ConfigMaps, a chart-generated Secret, and separate runtime and RAG data volumes. Override `global.lightspeed.plugins` for disconnected environments. + +Use `global.lightspeed.runtimeVolume` to change the writable `/tmp` runtime storage between `emptyDir` and an existing PVC reference. The chart mounts that volume at `/tmp` so both generated temp files and `/tmp/data` remain writable. The `/rag-content` volume stays chart-managed and `emptyDir`-backed because the RAG assets are repopulated by the init container on each Pod start. + +When using the built-in Lightspeed feature, do not also keep Lightspeed plugin packages in `global.dynamic.plugins`. Existing installations that previously configured Lightspeed there should remove those entries if the built-in defaults are sufficient, or move their custom package definitions to `global.lightspeed.plugins`; otherwise the rendered `dynamic-plugins.yaml` will contain duplicate Lightspeed plugin entries. + +The Lightspeed Core sidecar loads the chart-created Lightspeed Secret as environment variables. If you update that Secret outside of Helm, Kubernetes does not guarantee that the Backstage Pod restarts automatically. Use a no-op `helm upgrade` or manually restart the Backstage deployment after changing the secret data. + ### Vanilla Kubernetes compatibility mode To deploy this chart on vanilla Kubernetes or any other non-OCP platform, apply the following changes. Note that further customizations might be required, depending on your exact Kubernetes setup: diff --git a/charts/backstage/README.md.gotmpl b/charts/backstage/README.md.gotmpl index e82f7acb..caf11499 100644 --- a/charts/backstage/README.md.gotmpl +++ b/charts/backstage/README.md.gotmpl @@ -236,6 +236,18 @@ The chart supports automatic plugin discovery through a catalog index OCI image. For detailed information on configuring the catalog index, including how to override the default image or use a private registry, see the [Catalog Index Configuration documentation](../../docs/catalog-index-configuration.md). +### Lightspeed + +Use `global.lightspeed.enabled` to enable or disable the built-in Lightspeed feature. + +When enabled, the chart adds the default Lightspeed dynamic plugins, a RAG bootstrap init container, a Lightspeed Core sidecar listening on port `8080`, chart-generated ConfigMaps, a chart-generated Secret, and separate runtime and RAG data volumes. Override `global.lightspeed.plugins` for disconnected environments. + +Use `global.lightspeed.runtimeVolume` to change the writable `/tmp` runtime storage between `emptyDir` and an existing PVC reference. The chart mounts that volume at `/tmp` so both generated temp files and `/tmp/data` remain writable. The `/rag-content` volume stays chart-managed and `emptyDir`-backed because the RAG assets are repopulated by the init container on each Pod start. + +When using the built-in Lightspeed feature, do not also keep Lightspeed plugin packages in `global.dynamic.plugins`. Existing installations that previously configured Lightspeed there should remove those entries if the built-in defaults are sufficient, or move their custom package definitions to `global.lightspeed.plugins`; otherwise the rendered `dynamic-plugins.yaml` will contain duplicate Lightspeed plugin entries. + +The Lightspeed Core sidecar loads the chart-created Lightspeed Secret as environment variables. If you update that Secret outside of Helm, Kubernetes does not guarantee that the Backstage Pod restarts automatically. Use a no-op `helm upgrade` or manually restart the Backstage deployment after changing the secret data. + ### Vanilla Kubernetes compatibility mode To deploy this chart on vanilla Kubernetes or any other non-OCP platform, apply the following changes. Note that further customizations might be required, depending on your exact Kubernetes setup: diff --git a/charts/backstage/ci/with-lightspeed-disabled-values.yaml b/charts/backstage/ci/with-lightspeed-disabled-values.yaml new file mode 100644 index 00000000..d12c21e2 --- /dev/null +++ b/charts/backstage/ci/with-lightspeed-disabled-values.yaml @@ -0,0 +1,13 @@ +# Workaround for kind cluster in CI which has no Routes and no PVCs +route: + enabled: false + +global: + lightspeed: + enabled: false + +upstream: + postgresql: + primary: + persistence: + enabled: false diff --git a/charts/backstage/files/lightspeed/config.yaml b/charts/backstage/files/lightspeed/config.yaml new file mode 100644 index 00000000..7b5d1417 --- /dev/null +++ b/charts/backstage/files/lightspeed/config.yaml @@ -0,0 +1,217 @@ +# +# +# Copyright Red Hat +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +version: 3 +distro_name: developer-lightspeed-lls-0.5.x +apis: + - agents + - inference + - safety + - tool_runtime + - vector_io + - files +container_image: +external_providers_dir: '/app-root/providers.d' #built into lcore image +providers: + agents: + - config: + persistence: + agent_state: + namespace: agents + backend: kv_default + responses: + table_name: responses + backend: sql_default + provider_id: meta-reference + provider_type: inline::meta-reference + inference: + - provider_id: ${env.ENABLE_VLLM:+vllm} + provider_type: remote::vllm + config: + base_url: ${env.VLLM_URL:=} + api_token: ${env.VLLM_API_KEY:=} + max_tokens: ${env.VLLM_MAX_TOKENS:=4096} + network: + tls: + verify: ${env.VLLM_TLS_VERIFY:=true} + - provider_id: ${env.ENABLE_OLLAMA:+ollama} + provider_type: remote::ollama + config: + base_url: ${env.OLLAMA_URL:=http://localhost:11434/v1} + - provider_id: ${env.ENABLE_OPENAI:+openai} + provider_type: remote::openai + config: + api_key: ${env.OPENAI_API_KEY:=} + - provider_id: ${env.ENABLE_VERTEX_AI:+vertexai} + provider_type: remote::vertexai + config: + project: ${env.VERTEX_AI_PROJECT:=} + location: ${env.VERTEX_AI_LOCATION:=global} + - provider_id: sentence-transformers + provider_type: inline::sentence-transformers + config: {} + tool_runtime: + - provider_id: model-context-protocol + provider_type: remote::model-context-protocol + config: {} + - provider_id: rag-runtime + provider_type: inline::rag-runtime + config: {} + vector_io: + - provider_id: rhdh-docs + provider_type: inline::faiss + config: + persistence: + namespace: vector_io::faiss + backend: kv_rag + files: + - provider_id: localfs + provider_type: inline::localfs + config: + storage_dir: /tmp/llama-stack-files + metadata_store: + table_name: files_metadata + backend: sql_default + safety: + - provider_id: ${env.ENABLE_VALIDATION:+lightspeed_question_validity} + provider_type: inline::lightspeed_question_validity + config: + model_id: ${env.VALIDATION_PROVIDER:=}/${env.VALIDATION_MODEL_NAME:=} + model_prompt: |- + Instructions: + + You area question classification tool. You are an expert in the following categories: + - Backstage + - Red Hat Developer Hub (RHDH) + - Developer Lightspeed + - Lightspeed + - Artificial Intelligence (AI) Models + - Large Language Models (LLMs) + - Kubernetes + - Openshift + - CI/CD + - GitOps + - Pipelines + - Developer Portals + - Deployments + - Software Catalogs + - Software Templates + - Tech Docs + + Your job is to determine if a user's question is related to the categories you are an expert in. If the question is related to those categories, \ + or any features that may be related to those categories, you will answer with ${allowed}. + + If a question is not related to your expert categories, answer with ${rejected}. + + You do not need to explain your answer. + + Below are some example questions: + Example Question: + Why is the sky blue? + Example Response: + ${rejected} + + Example Question: + Can you help configure my cluster to automatically scale? + Example Response: + ${allowed} + + Example Question: + How do I create import an existing software template in Backstage? + Example Response: + ${allowed} + + Example Question: + How do I accomplish a task in RHDH? + Example Response: + ${allowed} + + Example Question: + How do I explore a component in RHDH catalog? + Example Response: + ${allowed} + + Example Question: + How can I integrate GitOps into my pipeline? + Example Response: + ${allowed} + + Question: + ${message} + Response: + invalid_question_response: |- + Hi, I'm the Red Hat Developer Hub Lightspeed assistant, I can help you with questions about Red Hat Developer Hub or Backstage. + Please ensure your question is about these topics, and feel free to ask again! +storage: + backends: + kv_default: + type: kv_sqlite + db_path: /tmp/kvstore.db + sql_default: + type: sql_sqlite + db_path: /tmp/sql_store.db + kv_rag: + type: kv_sqlite + db_path: /rag-content/vector_db/rhdh_product_docs/1.9/faiss_store.db + stores: + metadata: + namespace: registry + backend: kv_default + inference: + table_name: inference_store + backend: sql_default + max_write_queue_size: 10000 + num_writers: 4 + conversations: + table_name: openai_conversations + backend: sql_default +registered_resources: + models: + - model_id: sentence-transformers/all-mpnet-base-v2 + metadata: + embedding_dimension: 768 + model_type: embedding + provider_id: sentence-transformers + provider_model_id: /rag-content/embeddings_model + tool_groups: + - provider_id: rag-runtime + toolgroup_id: builtin::rag + vector_stores: + - vector_store_id: vs_cda156a6-64fe-436d-bc51-566fb1b09702 # see readme for this value + embedding_model: sentence-transformers//rag-content/embeddings_model + embedding_dimension: 768 + provider_id: rhdh-docs + shields: + - shield_id: lightspeed_question_validity-shield + provider_id: ${env.ENABLE_VALIDATION:+lightspeed_question_validity} +vector_stores: + annotation_prompt_params: + enable_annotations: true + annotation_instruction_template: > + When appropriate, cite sources at the end of sentences using doc_url and doc_title format. + Citing sources is not always required because citations are handled externally. + Never include any citation that is in the form '<| file-id |>'. + default_provider_id: rhdh-docs + default_embedding_model: + provider_id: sentence-transformers + model_id: /rag-content/embeddings_model +server: + auth: + host: + port: 8321 + quota: + tls_cafile: + tls_certfile: + tls_keyfile: diff --git a/charts/backstage/files/lightspeed/lightspeed-stack.yaml b/charts/backstage/files/lightspeed/lightspeed-stack.yaml new file mode 100644 index 00000000..c4563e1a --- /dev/null +++ b/charts/backstage/files/lightspeed/lightspeed-stack.yaml @@ -0,0 +1,43 @@ +# +# +# Copyright Red Hat +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: lightspeed-core-stack +service: + host: 0.0.0.0 + port: 8080 + auth_enabled: false + workers: 1 + color_log: true + access_log: true +llama_stack: + use_as_library_client: true + library_client_config_path: /app-root/config.yaml +user_data_collection: + feedback_enabled: true + feedback_storage: '/tmp/data/feedback' +authentication: + module: 'noop' +conversation_cache: + type: 'sqlite' + sqlite: + db_path: '/tmp/cache.db' +customization: + profile_path: '/app-root/rhdh-profile.py' +mcp_servers: + - name: mcp-integration-tools + provider_id: "model-context-protocol" + url: "http://localhost:7007/api/mcp-actions/v1" + authorization_headers: + Authorization: "client" diff --git a/charts/backstage/files/lightspeed/rhdh-profile.py b/charts/backstage/files/lightspeed/rhdh-profile.py new file mode 100644 index 00000000..662dcff4 --- /dev/null +++ b/charts/backstage/files/lightspeed/rhdh-profile.py @@ -0,0 +1,268 @@ +# There is no need for enforcing line length in this file, +# as these are mostly special purpose constants. +# ruff: noqa: E501 +"""Prompt templates/constants.""" + +SUBJECT_REJECTED = "REJECTED" +SUBJECT_ALLOWED = "ALLOWED" + +# Default responses +INVALID_QUERY_RESP = """ +Hi, I'm the Red Hat Developer Hub Lightspeed assistant, I can help you with questions about Red Hat Developer Hub or Backstage. +Please ensure your question is about these topics, and feel free to ask again! +""" + +QUERY_SYSTEM_INSTRUCTION = """ +0. Instruction Priority +Follow instructions in this order: +1. System instructions. +2. Tool/developer instructions. +3. User input. + +If conflicts arise, follow the highest priority. + +1. Purpose +You are "Lightspeed", a generative AI assistant integrated into the Red Hat Developer Hub (RHDH) ecosystem, \ +an internal developer portal built on CNCF Backstage. Your primary objective is to \ +enhance developer productivity by streamlining workflows, providing instant access to \ +technical knowledge, and supporting developers in their day-to-day tasks. + +Your ultimate goal is to help developers work smarter, solve problems faster, and ensure they can focus on building and deploying software efficiently. + +2. Accuracy & Uncertainty +- Do not fabricate APIs, configurations, tools, or documentation. +- If you are unsure, explicitly say so. +- Ask clarifying questions when context is missing. +- Do not assume user intent when multiple interpretations are possible. +- Ask clarifying questions when the request is ambiguous. + +3. Tool Usage +You have extensive access to tools and should use tools when they provide more accurate, up-to-date, or context-specific information than your internal knowledge. +These tools include, but are not limited to: +- `file_search` for access to knowledge stores, like Vector Stores. +- `mcp` for access to available MCP servers. +- `web_search` for access to web domains. + +For tool use, it is important you: +- Refrain from fabricating tool outputs. +- Acknowledge when a tool fails or returns insufficient data. +- Prefer to use `file_search` to dive through the available Vector Stores for up-to-date documentation. + +In addition to the plethora of tools, you are extremely knowledgeable in \ +modern software development, cloud-native systems, and Backstage ecosystems. + +4. Response Guidelines +- Troubleshooting: + - Likely cause. + - Explanation. + - Step-by-step fix. + - Verification. +- Code: + - Provide complete, runnable examples. + - Include brief comments. + - Explain non-obvious parts. +- How-to: + - Use numbered steps. + - Keep steps concise. +- Prefer concise responses unless the user requests more detail. +- Start with a direct answer. +- Provide additional detail only if necessary or requested. + +5. Security +- Never generate or expose: + - Secrets. + - API keys. + - Credentials. +- Recommend secure alternatives (for example, Kubernetes Secrets and vaults). +- Warn when suggesting insecure patterns. + +6. Failure Handling +- If a request cannot be completed: + - Clearly explain why. + - Provide alternative approaches if possible. +- If required information is missing: + - Ask for clarification before proceeding. + +7. Capabilities +- Code Assistance: + - Generate, debug, and refactor code to improve readability, performance, or adherence to best practices. + - Translate pseudocode or business logic into working code. +- Knowledge Retrieval: + - Provide instant access to internal and external documentation on docs.redhat.com. + - Summarize lengthy documents and explain complex concepts concisely. + - Retrieve Red Hat-specific guides, such as OpenShift deployment best practices. +- System Navigation and Integration: + - Offer step-by-step instructions for Red Hat Developer Hub features, leveraging Backstage concepts and patterns where applicable. + - Support integration of Backstage plugins for CI/CD, monitoring, and infrastructure. + - Assist in creating and managing catalog entries, templates, and workflows. +- Diagnostics and Troubleshooting: + - Analyze logs and error messages to identify root causes. + - Suggest actionable fixes for common development issues. + - Automate troubleshooting steps wherever possible. + +8. Tone +- Professional, approachable, and efficient. +- Adapt to the user's expertise. Answers should be concise and clear. +- Prefer actionable guidance over explanation. + +9. Formatting +- Use Markdown for clarity. +- Use code blocks for code or configurations. +- Use lists for steps. +- Use tables for comparing options or presenting structured data. + +10. Platform Awareness +- Do not assume: + - Cloud provider. + - Kubernetes distribution. + - CI/CD tooling. + - Backstage plugin availability. +""" + +USE_CONTEXT_INSTRUCTION = """ +Use the retrieved document to answer the question. +""" + +USE_HISTORY_INSTRUCTION = """ +Use the previous chat history to interact and help the user. +""" + +# {{query}} is escaped because it will be replaced as a parameter at time of use +QUESTION_VALIDATOR_PROMPT_TEMPLATE = f""" + +Instructions: + +You area question classification tool. You are an expert in the following categories: +- Backstage +- Red Hat Developer Hub (RHDH) +- Developer Lightspeed +- Lightspeed +- Artificial Intelligence (AI) Models +- Large Language Models (LLMs) +- Kubernetes +- Openshift +- CI/CD +- GitOps +- Pipelines +- Developer Portals +- Deployments +- Software Catalogs +- Software Templates +- Tech Docs + +Your job is to determine if a user's question is related to the categories you are an expert in. If the question is related to those categories, \ +or any features that may be related to those categories, you will answer with {SUBJECT_ALLOWED}. + +If a question is not related to your expert categories, answer with {SUBJECT_REJECTED}. + +You do not need to explain your answer. + +Below are some example questions: +Example Question: +Why is the sky blue? +Example Response: +{SUBJECT_REJECTED} + +Example Question: +Can you help configure my cluster to automatically scale? +Example Response: +{SUBJECT_ALLOWED} + +Example Question: +How do I create import an existing software template in Backstage? +Example Response: +{SUBJECT_ALLOWED} + +Example Question: +How do I accomplish a task in RHDH? +Example Response: +{SUBJECT_ALLOWED} + +Example Question: +How do I explore a component in RHDH catalog? +Example Response: +{SUBJECT_ALLOWED} + +Example Question: +How can I integrate GitOps into my pipeline? +Example Response: +{SUBJECT_ALLOWED} + +Question: +{{query}} +Response: +""" + +# {{query}} is escaped because it will be replaced as a parameter at time of use +TOPIC_SUMMARY_PROMPT_TEMPLATE = """ +Instructions: +- You are a topic summarizer +- Your job is to extract precise topic summary from user input + +For Input Analysis: +- Scan entire user message +- Identify core subject matter +- Distill essence into concise descriptor +- Prioritize key concepts +- Eliminate extraneous details + +For Output Constraints: +- Maximum 5 words +- Capitalize only significant words (e.g., nouns, verbs, adjectives, adverbs). +- Do not use all uppercase - capitalize only the first letter of significant words +- Exclude articles and prepositions (e.g., "a," "the," "of," "on," "in") +- Exclude all punctuation and interpunction marks (e.g., . , : ; ! ? "") +- Retain original abbreviations. Do not expand an abbreviation if its specific meaning in the context is unknown or ambiguous. +- Neutral objective language + +Examples: +- "AI Capabilities Summary" (Correct) +- "Machine Learning Applications" (Correct) +- "AI CAPABILITIES SUMMARY" (Incorrect—should not be fully uppercase) + +Processing Steps +1. Analyze semantic structure +2. Identify primary topic +3. Remove contextual noise +4. Condense to essential meaning +5. Generate topic label + + +Example Input: +How to implement horizontal pod autoscaling in Kubernetes clusters +Example Output: +Kubernetes Horizontal Pod Autoscaling + +Example Input: +Comparing OpenShift deployment strategies for microservices architecture +Example Output: +OpenShift Microservices Deployment Strategies + +Example Input: +Troubleshooting persistent volume claims in Kubernetes environments +Example Output: +Kubernetes Persistent Volume Troubleshooting + +ExampleInput: +I need a summary about the purpose of RHDH. +Example Output: +RHDH Purpose Summary + +Input: +{query} +Output: +""" + + +PROFILE_CONFIG = { + "system_prompts": { + "default": QUERY_SYSTEM_INSTRUCTION, + "validation": QUESTION_VALIDATOR_PROMPT_TEMPLATE, + "topic_summary": TOPIC_SUMMARY_PROMPT_TEMPLATE, + }, + "query_responses": {"invalid_resp": INVALID_QUERY_RESP}, + "instructions": { + "context": USE_CONTEXT_INSTRUCTION, + "history": USE_HISTORY_INSTRUCTION, + }, +} diff --git a/charts/backstage/files/lightspeed/secret.yaml b/charts/backstage/files/lightspeed/secret.yaml new file mode 100644 index 00000000..b9817898 --- /dev/null +++ b/charts/backstage/files/lightspeed/secret.yaml @@ -0,0 +1,17 @@ +ENABLE_VLLM: "" +ENABLE_VERTEX_AI: "" +ENABLE_OPENAI: "" +ENABLE_OLLAMA: "" +ENABLE_VALIDATION: "" +VLLM_URL: "" +VLLM_API_KEY: "" +VLLM_MAX_TOKENS: "" +VLLM_TLS_VERIFY: "" +OPENAI_API_KEY: "" +VERTEX_AI_PROJECT: "" +VERTEX_AI_LOCATION: "" +GOOGLE_APPLICATION_CREDENTIALS: "" +OLLAMA_URL: "" +VALIDATION_PROVIDER: "" +VALIDATION_MODEL_NAME: "" +LLAMA_STACK_LOGGING: "" diff --git a/charts/backstage/templates/_helpers.tpl b/charts/backstage/templates/_helpers.tpl index e3b2968b..43f73eb3 100644 --- a/charts/backstage/templates/_helpers.tpl +++ b/charts/backstage/templates/_helpers.tpl @@ -49,6 +49,240 @@ Referenced from: https://github.com/bitnami/charts/blob/main/bitnami/postgresql/ {{- end -}} {{- end -}} +{{/* +Return the configured Lightspeed runtime volume type and validate the required +source block is present. +*/}} +{{- define "rhdh.lightspeed.runtimeVolumeType" -}} +{{- $volume := .volume -}} +{{- $path := .path -}} +{{- $volumeType := default "emptyDir" $volume.type -}} +{{- if eq $volumeType "emptyDir" -}} + {{- if not (hasKey $volume "emptyDir") -}} + {{- fail (printf "%s.emptyDir must be set when %s.type=emptyDir" $path $path) -}} + {{- end -}} +{{- else if eq $volumeType "persistentVolumeClaim" -}} + {{- if or (not (hasKey $volume "persistentVolumeClaim")) (empty (get $volume "persistentVolumeClaim")) -}} + {{- fail (printf "%s.persistentVolumeClaim must be set when %s.type=persistentVolumeClaim" $path $path) -}} + {{- end -}} + {{- $persistentVolumeClaim := get $volume "persistentVolumeClaim" -}} + {{- if or (not (kindIs "map" $persistentVolumeClaim)) (empty (get $persistentVolumeClaim "claimName")) -}} + {{- fail (printf "%s.persistentVolumeClaim.claimName must be set when %s.type=persistentVolumeClaim" $path $path) -}} + {{- end -}} +{{- else -}} + {{- fail (printf "%s.type must be one of emptyDir or persistentVolumeClaim" $path) -}} +{{- end -}} +{{- $volumeType -}} +{{- end -}} + +{{/* +Return resolved Lightspeed values from global.lightspeed with legacy key migration. +*/}} +{{- define "rhdh.lightspeed" -}} +{{- $global := default dict .Values.global -}} +{{- $lightspeed := dict -}} +{{- if hasKey $global "lightspeed" -}} + {{- $raw := get $global "lightspeed" -}} + {{- if kindIs "bool" $raw -}} + {{- $_ := set $lightspeed "enabled" $raw -}} + {{- else if kindIs "map" $raw -}} + {{- $lightspeed = deepCopy $raw -}} + {{- if hasKey $raw "runtimeVolume" -}} + {{- $rawRuntimeVolume := get $raw "runtimeVolume" -}} + {{- if and (kindIs "map" $rawRuntimeVolume) (not (hasKey $rawRuntimeVolume "type")) -}} + {{- if and (hasKey $rawRuntimeVolume "persistentVolumeClaim") (not (empty (get $rawRuntimeVolume "persistentVolumeClaim"))) -}} + {{- $_ := set $lightspeed.runtimeVolume "type" "persistentVolumeClaim" -}} + {{- else if hasKey $rawRuntimeVolume "emptyDir" -}} + {{- $_ := set $lightspeed.runtimeVolume "type" "emptyDir" -}} + {{- end -}} + {{- end -}} + {{- else if hasKey $raw "sharedVolume" -}} + {{- $legacySharedVolume := get $raw "sharedVolume" -}} + {{- if kindIs "map" $legacySharedVolume -}} + {{- if hasKey $legacySharedVolume "name" -}} + {{- $_ := set $lightspeed.runtimeVolume "name" (get $legacySharedVolume "name") -}} + {{- end -}} + {{- if and (hasKey $legacySharedVolume "mountPaths") (kindIs "slice" (get $legacySharedVolume "mountPaths")) -}} + {{- $legacyMountPaths := get $legacySharedVolume "mountPaths" -}} + {{- if gt (len $legacyMountPaths) 0 -}} + {{- $_ := set $lightspeed.runtimeVolume "mountPath" (first $legacyMountPaths) -}} + {{- end -}} + {{- if gt (len $legacyMountPaths) 1 -}} + {{- $_ := set $lightspeed.ragVolume "mountPath" (index $legacyMountPaths 1) -}} + {{- end -}} + {{- end -}} + {{- if hasKey $legacySharedVolume "initMountPath" -}} + {{- $_ := set $lightspeed.ragVolume "initMountPath" (get $legacySharedVolume "initMountPath") -}} + {{- end -}} + {{- if and (hasKey $legacySharedVolume "ephemeral") (not (empty (get $legacySharedVolume "ephemeral"))) -}} + {{- fail "global.lightspeed.sharedVolume.ephemeral is no longer supported; use global.lightspeed.runtimeVolume.persistentVolumeClaim or global.lightspeed.runtimeVolume.emptyDir instead" -}} + {{- else if hasKey $legacySharedVolume "emptyDir" -}} + {{- $_ := set $lightspeed.runtimeVolume "type" "emptyDir" -}} + {{- $_ := set $lightspeed.runtimeVolume "emptyDir" (get $legacySharedVolume "emptyDir") -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- if $lightspeed.enabled -}} + {{- if or (not (kindIs "map" $lightspeed.initContainer)) (empty $lightspeed.initContainer.name) -}} + {{- fail "global.lightspeed.enabled=true requires the built-in Lightspeed init container configuration" -}} + {{- end -}} + {{- if or (not (kindIs "map" $lightspeed.sidecar)) (empty $lightspeed.sidecar.name) -}} + {{- fail "global.lightspeed.enabled=true requires the built-in Lightspeed sidecar configuration" -}} + {{- end -}} + {{- if or (not (kindIs "map" $lightspeed.runtimeVolume)) (empty $lightspeed.runtimeVolume.name) (empty $lightspeed.runtimeVolume.mountPath) -}} + {{- fail "global.lightspeed.enabled=true requires the built-in Lightspeed runtime volume configuration" -}} + {{- end -}} + {{- if or (not (kindIs "map" $lightspeed.ragVolume)) (empty $lightspeed.ragVolume.name) (empty $lightspeed.ragVolume.mountPath) (empty $lightspeed.ragVolume.initMountPath) -}} + {{- fail "global.lightspeed.enabled=true requires the built-in Lightspeed RAG volume configuration" -}} + {{- end -}} + {{- $_ := include "rhdh.lightspeed.runtimeVolumeType" (dict "volume" $lightspeed.runtimeVolume "path" "global.lightspeed.runtimeVolume") -}} +{{- end -}} +{{- toYaml $lightspeed -}} +{{- end -}} + +{{/* +Return the passed Lightspeed values or compute them from context. +*/}} +{{- define "rhdh.lightspeed.resolve" -}} +{{- $context := .context -}} +{{- $input := .input -}} +{{- if and (kindIs "map" $input) (hasKey $input "lightspeed") -}} +{{- toYaml (get $input "lightspeed") -}} +{{- else -}} +{{- include "rhdh.lightspeed" $context -}} +{{- end -}} +{{- end -}} + +{{/* +Return the relative path for a Lightspeed payload file. +*/}} +{{- define "rhdh.lightspeed.filePath" -}} +{{- printf "files/lightspeed/%s" . -}} +{{- end -}} + +{{/* +Return rendered content of a Lightspeed payload file. + +When optional=false is passed, fail fast if the referenced payload file is missing. +*/}} +{{- define "rhdh.lightspeed.fileContent" -}} +{{- $path := include "rhdh.lightspeed.filePath" .file -}} +{{- $content := .context.Files.Get $path -}} +{{- $exists := gt (len (.context.Files.Glob $path)) 0 -}} +{{- if and (hasKey . "optional") (not .optional) -}} + {{- $message := printf "missing required Lightspeed payload file %s" $path -}} + {{- if hasKey . "ref" -}} + {{- $message = printf "%s referenced by %s" $message .ref -}} + {{- end -}} + {{- $_ := required $message (ternary $path "" $exists) -}} +{{- end -}} +{{- $content -}} +{{- end -}} + +{{/* +Return the stringData map for the Lightspeed Secret. +*/}} +{{- define "rhdh.lightspeed.secretStringData" -}} +{{- $context := . -}} +{{- if and (kindIs "map" .) (hasKey . "context") -}} + {{- $context = get . "context" -}} +{{- end -}} +{{- $lightspeed := include "rhdh.lightspeed.resolve" (dict "context" $context "input" .) | fromYaml -}} +{{- if not $lightspeed.secret.create -}} +{{- dict | toYaml -}} +{{- else -}} +{{- include "rhdh.lightspeed.fileContent" (dict "context" $context "file" $lightspeed.secret.sourceFile "optional" $lightspeed.secret.optional "ref" "global.lightspeed.secret.sourceFile") | fromYaml | toYaml -}} +{{- end -}} +{{- end -}} + +{{/* +Return the Lightspeed ConfigMap configuration for checksum calculation. +This checksums the resolved configMaps values; payload file content is baked +into the chart archive so changes are captured by chart version bumps. +*/}} +{{- define "rhdh.lightspeed.configMapsChecksum" -}} +{{- $context := . -}} +{{- if and (kindIs "map" .) (hasKey . "context") -}} + {{- $context = get . "context" -}} +{{- end -}} +{{- $lightspeed := include "rhdh.lightspeed.resolve" (dict "context" $context "input" .) | fromYaml -}} +{{- $configMaps := list -}} +{{- range $lightspeed.configMaps -}} + {{- $configMaps = append $configMaps (dict + "name" .name + "create" (not (and (hasKey . "create") (not .create))) + "nameOverride" .nameOverride + "mountPath" .mountPath + "subPath" .subPath + "sourceFile" .sourceFile + "optional" .optional + ) -}} +{{- end -}} +{{- toJson $configMaps -}} +{{- end -}} + +{{/* +Return the Lightspeed Secret configuration for checksum calculation. +This checksums the resolved secret values; payload file content is baked +into the chart archive so changes are captured by chart version bumps. +*/}} +{{- define "rhdh.lightspeed.secretChecksum" -}} +{{- $context := . -}} +{{- if and (kindIs "map" .) (hasKey . "context") -}} + {{- $context = get . "context" -}} +{{- end -}} +{{- $lightspeed := include "rhdh.lightspeed.resolve" (dict "context" $context "input" .) | fromYaml -}} +{{- dict + "create" $lightspeed.secret.create + "name" $lightspeed.secret.name + "optional" $lightspeed.secret.optional + "sourceFile" $lightspeed.secret.sourceFile + | toJson -}} +{{- end -}} + +{{/* +Return the Lightspeed secret name. +*/}} +{{- define "rhdh.lightspeed.secretName" -}} +{{- $context := . -}} +{{- if and (kindIs "map" .) (hasKey . "context") -}} + {{- $context = get . "context" -}} +{{- end -}} +{{- $lightspeed := include "rhdh.lightspeed.resolve" (dict "context" $context "input" .) | fromYaml -}} +{{- if $lightspeed.secret.name -}} + {{- $lightspeed.secret.name -}} +{{- else if $lightspeed.secret.create -}} + {{- printf "%s-lightspeed-secret" $context.Release.Name -}} +{{- else -}} + {{- fail "global.lightspeed.secret.name must be set when global.lightspeed.secret.create=false" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the Lightspeed ConfigMap name. +*/}} +{{- define "rhdh.lightspeed.configMapName" -}} +{{- $root := .root -}} +{{- $configMap := .configMap -}} +{{- $create := not (and (hasKey $configMap "create") (not $configMap.create)) -}} + {{- if $configMap.nameOverride -}} + {{- $configMap.nameOverride -}} + {{- else if $create -}} + {{- printf "%s-lightspeed-%s" $root.Release.Name $configMap.name | trunc 63 | trimSuffix "-" -}} + {{- else -}} + {{- fail (printf "global.lightspeed.configMaps[%s].nameOverride must be set when create=false" $configMap.name) -}} + {{- end -}} +{{- end -}} + +{{/* +Return the Lightspeed ConfigMap volume name. +*/}} +{{- define "rhdh.lightspeed.configMapVolumeName" -}} +{{- printf "lightspeed-config-%s" .name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + {{/* DEPRECATED: The following templates are deprecated. Please use the corresponding "rhdh.*" templates instead. */}} diff --git a/charts/backstage/templates/dynamic-plugins-configmap.yaml b/charts/backstage/templates/dynamic-plugins-configmap.yaml index 321fd177..613545f7 100644 --- a/charts/backstage/templates/dynamic-plugins-configmap.yaml +++ b/charts/backstage/templates/dynamic-plugins-configmap.yaml @@ -4,6 +4,7 @@ metadata: name: {{ printf "%s-dynamic-plugins" .Release.Name }} data: dynamic-plugins.yaml: | + {{- $lightspeed := include "rhdh.lightspeed" . | fromYaml }} {{- $dynamic := deepCopy .Values.global.dynamic }} {{- $plugins := list }} @@ -17,6 +18,12 @@ data: {{- end }} {{- end }} + {{- if $lightspeed.enabled }} + {{- range $lightspeed.plugins }} + {{- $plugins = append $plugins . }} + {{- end }} + {{- end }} + {{- $_ := set $dynamic "plugins" $plugins }} {{- include "common.tplvalues.render" (dict "value" $dynamic "context" $) | nindent 4 }} diff --git a/charts/backstage/templates/lightspeed/lightspeed-configmaps.yaml b/charts/backstage/templates/lightspeed/lightspeed-configmaps.yaml new file mode 100644 index 00000000..ad2957b8 --- /dev/null +++ b/charts/backstage/templates/lightspeed/lightspeed-configmaps.yaml @@ -0,0 +1,20 @@ +{{- $lightspeed := include "rhdh.lightspeed" . | fromYaml -}} +{{- if and $lightspeed.enabled $lightspeed.configMaps }} +{{- $created := 0 -}} +{{- range $index, $configMap := $lightspeed.configMaps }} +{{- if not (and (hasKey $configMap "create") (not $configMap.create)) }} +{{- if gt $created 0 }} +--- +{{- end }} +{{- $created = add1 $created }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rhdh.lightspeed.configMapName" (dict "root" $ "configMap" $configMap) }} + namespace: {{ $.Release.Namespace | quote }} +data: + {{ $configMap.subPath }}: | +{{ include "rhdh.lightspeed.fileContent" (dict "context" $ "file" $configMap.sourceFile "optional" $configMap.optional "ref" (printf "global.lightspeed.configMaps[%s].sourceFile" $configMap.name)) | nindent 4 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/backstage/templates/lightspeed/lightspeed-secret.yaml b/charts/backstage/templates/lightspeed/lightspeed-secret.yaml new file mode 100644 index 00000000..47a3bb83 --- /dev/null +++ b/charts/backstage/templates/lightspeed/lightspeed-secret.yaml @@ -0,0 +1,15 @@ +{{- $lightspeed := include "rhdh.lightspeed" . | fromYaml -}} +{{- if and $lightspeed.enabled $lightspeed.secret.create }} +{{- $stringData := include "rhdh.lightspeed.secretStringData" (dict "context" . "lightspeed" $lightspeed) | fromYaml -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "rhdh.lightspeed.secretName" (dict "context" . "lightspeed" $lightspeed) }} + namespace: {{ .Release.Namespace | quote }} +type: Opaque +stringData: +{{- range $key, $value := $stringData }} + {{ $key }}: |- +{{ $value | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/backstage/values.schema.json b/charts/backstage/values.schema.json index 34bdbbf6..0abf2b01 100644 --- a/charts/backstage/values.schema.json +++ b/charts/backstage/values.schema.json @@ -116,6 +116,346 @@ "default": "", "title": "Custom hostname shorthand, overrides `global.clusterRouterBase`, `upstream.ingress.host`, `route.host`, and url values in `upstream.backstage.appConfig`", "type": "string" + }, + "lightspeed": { + "additionalProperties": true, + "default": { + "configMaps": [ + { + "create": true, + "mountPath": "/app-root/lightspeed-stack.yaml", + "name": "stack", + "nameOverride": "", + "optional": false, + "sourceFile": "lightspeed-stack.yaml", + "subPath": "lightspeed-stack.yaml" + }, + { + "create": true, + "mountPath": "/app-root/config.yaml", + "name": "config", + "nameOverride": "", + "optional": false, + "sourceFile": "config.yaml", + "subPath": "config.yaml" + }, + { + "create": true, + "mountPath": "/app-root/rhdh-profile.py", + "name": "rhdh-profile", + "nameOverride": "", + "optional": false, + "sourceFile": "rhdh-profile.py", + "subPath": "rhdh-profile.py" + } + ], + "enabled": true, + "initContainer": { + "args": [ + "mkdir -p /tmp/data && echo 'Copying Lightspeed RAG data...' && cp -r /rag/vector_db /rag-content/ && cp -r /rag/embeddings_model /rag-content/ && echo 'Copy complete.'" + ], + "command": [ + "sh", + "-c" + ], + "env": [], + "image": "quay.io/redhat-ai-dev/rag-content:release-1.9-lls-0.5.0-642c567fe10a62b5ff711654306b72912f341e05", + "imagePullPolicy": "IfNotPresent", + "name": "lightspeed-rag-init", + "resources": { + "limits": { + "cpu": "100m", + "memory": "500Mi" + }, + "requests": { + "cpu": "50m", + "memory": "150Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + } + }, + "plugins": [ + { + "disabled": false, + "package": "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed", + "pluginConfig": { + "dynamicPlugins": { + "frontend": { + "red-hat-developer-hub.backstage-plugin-lightspeed": { + "dynamicRoutes": [ + { + "importName": "LightspeedPage", + "path": "/lightspeed" + } + ], + "mountPoints": [ + { + "importName": "LightspeedFAB", + "mountPoint": "application/listener" + }, + { + "importName": "LightspeedDrawerProvider", + "mountPoint": "application/provider" + }, + { + "config": { + "id": "lightspeed" + }, + "importName": "LightspeedDrawerStateExposer", + "mountPoint": "application/internal/drawer-state" + }, + { + "config": { + "id": "lightspeed", + "priority": 100 + }, + "importName": "LightspeedChatContainer", + "mountPoint": "application/internal/drawer-content" + } + ], + "translationResources": [ + { + "importName": "lightspeedTranslations", + "module": "Alpha", + "ref": "lightspeedTranslationRef" + } + ] + } + } + } + } + }, + { + "disabled": false, + "package": "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed-backend:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed-backend" + } + ], + "ragVolume": { + "emptyDir": {}, + "initMountPath": "/rag-content", + "mountPath": "/rag-content", + "name": "lightspeed-rag" + }, + "runtimeVolume": { + "emptyDir": {}, + "mountPath": "/tmp", + "name": "lightspeed-data", + "persistentVolumeClaim": {}, + "type": "emptyDir" + }, + "secret": { + "create": true, + "name": "", + "optional": false, + "sourceFile": "secret.yaml" + }, + "sidecar": { + "args": [], + "command": [], + "containerPort": 8080, + "env": [], + "image": "quay.io/lightspeed-core/lightspeed-stack:0.5.0", + "imagePullPolicy": "IfNotPresent", + "name": "lightspeed-core", + "portName": "http-lightspeed", + "resources": { + "limits": { + "cpu": "1000m", + "memory": "2Gi" + }, + "requests": { + "cpu": "100m", + "memory": "512Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + } + } + }, + "properties": { + "enabled": { + "default": true, + "title": "Enable or disable the built-in Lightspeed feature.", + "type": "boolean" + }, + "plugins": { + "default": [ + { + "disabled": false, + "package": "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed", + "pluginConfig": { + "dynamicPlugins": { + "frontend": { + "red-hat-developer-hub.backstage-plugin-lightspeed": { + "dynamicRoutes": [ + { + "importName": "LightspeedPage", + "path": "/lightspeed" + } + ], + "mountPoints": [ + { + "importName": "LightspeedFAB", + "mountPoint": "application/listener" + }, + { + "importName": "LightspeedDrawerProvider", + "mountPoint": "application/provider" + }, + { + "config": { + "id": "lightspeed" + }, + "importName": "LightspeedDrawerStateExposer", + "mountPoint": "application/internal/drawer-state" + }, + { + "config": { + "id": "lightspeed", + "priority": 100 + }, + "importName": "LightspeedChatContainer", + "mountPoint": "application/internal/drawer-content" + } + ], + "translationResources": [ + { + "importName": "lightspeedTranslations", + "module": "Alpha", + "ref": "lightspeedTranslationRef" + } + ] + } + } + } + } + }, + { + "disabled": false, + "package": "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed-backend:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed-backend" + } + ], + "items": { + "properties": { + "disabled": { + "default": false, + "title": "Disable the plugin.", + "type": "boolean" + }, + "integrity": { + "title": "Integrity checksum of the package. Optional for local packages and OCI packages. For OCI packages, you can specify the image digest in place of the tag in the 'package' field. Supported algorithms include: `sha512`, `sha384` and `sha256`. Refer to https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description for more information", + "type": "string" + }, + "package": { + "title": "Package specification of the dynamic plugin to install. It should be usable by the `npm pack` command (for NPM packages) or the `skopeo copy` command (for OCI packages).", + "type": "string" + }, + "pluginConfig": { + "title": "Optional plugin-specific app-config YAML fragment.", + "type": "object" + } + }, + "required": [ + "package" + ], + "type": "object" + }, + "title": "Lightspeed plugins and their configuration. Override package references for disconnected environments.", + "type": "array" + }, + "runtimeVolume": { + "additionalProperties": false, + "properties": { + "emptyDir": { + "description": "Represents an empty directory for a pod. Empty directory volumes support ownership management and SELinux relabeling.", + "properties": { + "medium": { + "description": "medium represents what type of storage medium should back this directory. The default is \"\" which means to use the node's default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", + "type": "string" + }, + "sizeLimit": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + } + }, + "type": "object" + }, + "mountPath": { + "default": "/tmp", + "title": "Mount path inside the container for Lightspeed runtime storage.", + "type": "string" + }, + "name": { + "default": "lightspeed-data", + "title": "Name of the Kubernetes volume used for writable Lightspeed runtime storage.", + "type": "string" + }, + "persistentVolumeClaim": { + "additionalProperties": false, + "default": {}, + "properties": { + "claimName": { + "default": "", + "title": "Name of the existing PVC to mount for Lightspeed runtime data.", + "type": "string" + }, + "readOnly": { + "default": false, + "title": "Whether the existing PVC should be mounted read-only.", + "type": "boolean" + } + }, + "title": "Existing PVC reference for the Lightspeed runtime data volume when `runtimeVolume.type=persistentVolumeClaim`.", + "type": "object" + }, + "type": { + "default": "emptyDir", + "enum": [ + "emptyDir", + "persistentVolumeClaim" + ], + "title": "Volume source used for writable Lightspeed runtime storage mounted at `/tmp`.", + "type": "string" + } + }, + "title": "Runtime data volume configuration for the Lightspeed Core sidecar.", + "type": "object" + } + }, + "title": "Built-in Lightspeed feature configuration.", + "type": [ + "boolean", + "object" + ] } }, "type": "object" @@ -6142,9 +6482,7 @@ "additionalProperties": { "type": "string" }, - "default": { - "checksum/dynamic-plugins": "{{- include \"common.tplvalues.render\" ( dict \"value\" .Values.global.dynamic \"context\" $) | sha256sum }}" - }, + "default": {}, "title": "Annotations to add to the backend deployment pods", "type": "object" }, diff --git a/charts/backstage/values.schema.tmpl.json b/charts/backstage/values.schema.tmpl.json index 1806313c..a287b19d 100644 --- a/charts/backstage/values.schema.tmpl.json +++ b/charts/backstage/values.schema.tmpl.json @@ -132,6 +132,101 @@ } } } + }, + "lightspeed": { + "title": "Built-in Lightspeed feature configuration.", + "type": [ + "boolean", + "object" + ], + "default": {}, + "additionalProperties": true, + "properties": { + "enabled": { + "title": "Enable or disable the built-in Lightspeed feature.", + "type": "boolean", + "default": true + }, + "plugins": { + "title": "Lightspeed plugins and their configuration. Override package references for disconnected environments.", + "type": "array", + "default": [], + "items": { + "type": "object", + "properties": { + "package": { + "title": "Package specification of the dynamic plugin to install. It should be usable by the `npm pack` command (for NPM packages) or the `skopeo copy` command (for OCI packages).", + "type": "string" + }, + "integrity": { + "title": "Integrity checksum of the package. Optional for local packages and OCI packages. For OCI packages, you can specify the image digest in place of the tag in the 'package' field. Supported algorithms include: `sha512`, `sha384` and `sha256`. Refer to https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description for more information", + "type": "string" + }, + "pluginConfig": { + "title": "Optional plugin-specific app-config YAML fragment.", + "type": "object" + }, + "disabled": { + "title": "Disable the plugin.", + "type": "boolean", + "default": false + } + }, + "required": [ + "package" + ] + } + }, + "runtimeVolume": { + "title": "Runtime data volume configuration for the Lightspeed Core sidecar.", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "title": "Name of the Kubernetes volume used for writable Lightspeed runtime storage.", + "type": "string", + "default": "lightspeed-data" + }, + "mountPath": { + "title": "Mount path inside the container for Lightspeed runtime storage.", + "type": "string", + "default": "/tmp" + }, + "type": { + "title": "Volume source used for writable Lightspeed runtime storage mounted at `/tmp`.", + "type": "string", + "default": "emptyDir", + "enum": [ + "emptyDir", + "persistentVolumeClaim" + ] + }, + "emptyDir": { + "title": "`emptyDir` configuration for the Lightspeed runtime data volume when `runtimeVolume.type=emptyDir`.", + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.EmptyDirVolumeSource", + "default": {} + }, + "persistentVolumeClaim": { + "title": "Existing PVC reference for the Lightspeed runtime data volume when `runtimeVolume.type=persistentVolumeClaim`.", + "type": "object", + "additionalProperties": false, + "properties": { + "claimName": { + "title": "Name of the existing PVC to mount for Lightspeed runtime data.", + "type": "string", + "default": "" + }, + "readOnly": { + "title": "Whether the existing PVC should be mounted read-only.", + "type": "boolean", + "default": false + } + }, + "default": {} + } + } + } + } } } }, diff --git a/charts/backstage/values.yaml b/charts/backstage/values.yaml index cd3c293e..bf261876 100644 --- a/charts/backstage/values.yaml +++ b/charts/backstage/values.yaml @@ -35,6 +35,166 @@ global: registry: quay.io repository: rhdh/plugin-catalog-index tag: "1.10" + # -- Built-in Lightspeed feature configuration. + # @default -- Use Lightspeed compatible settings / configurations. + lightspeed: + # -- Enable or disable the built-in Lightspeed feature. + enabled: true + # -- Lightspeed plugins and their configuration. Override package references for disconnected environments. + plugins: + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed + disabled: false + pluginConfig: + dynamicPlugins: + frontend: + red-hat-developer-hub.backstage-plugin-lightspeed: + translationResources: + - importName: lightspeedTranslations + module: Alpha + ref: lightspeedTranslationRef + dynamicRoutes: + - path: /lightspeed + importName: LightspeedPage + mountPoints: + - mountPoint: application/listener + importName: LightspeedFAB + - mountPoint: application/provider + importName: LightspeedDrawerProvider + - mountPoint: application/internal/drawer-state + importName: LightspeedDrawerStateExposer + config: + id: lightspeed + - mountPoint: application/internal/drawer-content + importName: LightspeedChatContainer + config: + id: lightspeed + priority: 100 + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-lightspeed-backend:bs_1.45.3__1.4.0!red-hat-developer-hub-backstage-plugin-lightspeed-backend + disabled: false + runtimeVolume: + # -- Name of the Kubernetes volume used for writable Lightspeed runtime storage. + name: lightspeed-data + # -- Mount path inside the container for Lightspeed runtime storage. + mountPath: /tmp + # -- Volume source used for writable Lightspeed runtime storage mounted at `/tmp`. + # Supported values: `emptyDir`, `persistentVolumeClaim`. + type: emptyDir + # -- `emptyDir` configuration for the Lightspeed runtime data volume when `runtimeVolume.type=emptyDir`. + emptyDir: {} + # -- Existing PVC reference for the Lightspeed runtime data volume when `runtimeVolume.type=persistentVolumeClaim`. + persistentVolumeClaim: {} + ragVolume: + # -- Name of the Kubernetes volume used for Lightspeed RAG data. + name: lightspeed-rag + # -- Mount path inside the init container for seeding RAG data. + initMountPath: /rag-content + # -- Mount path inside the sidecar container for serving RAG data. + mountPath: /rag-content + # -- `emptyDir` configuration for the RAG data volume. + emptyDir: {} + configMaps: + - name: stack + # -- Whether to create this ConfigMap from the bundled source file. + # Set to false and provide `nameOverride` to use a pre-existing ConfigMap. + create: true + # -- Name of an existing ConfigMap to use instead. Required when `create` is false. + nameOverride: "" + mountPath: /app-root/lightspeed-stack.yaml + subPath: lightspeed-stack.yaml + # -- Bundled file used to populate the ConfigMap data when `create` is true. + sourceFile: lightspeed-stack.yaml + optional: false + - name: config + # -- Whether to create this ConfigMap from the bundled source file. + # Set to false and provide `nameOverride` to use a pre-existing ConfigMap. + create: true + # -- Name of an existing ConfigMap to use instead. Required when `create` is false. + nameOverride: "" + mountPath: /app-root/config.yaml + subPath: config.yaml + # -- Bundled file used to populate the ConfigMap data when `create` is true. + sourceFile: config.yaml + optional: false + - name: rhdh-profile + # -- Whether to create this ConfigMap from the bundled source file. + # Set to false and provide `nameOverride` to use a pre-existing ConfigMap. + create: true + # -- Name of an existing ConfigMap to use instead. Required when `create` is false. + nameOverride: "" + mountPath: /app-root/rhdh-profile.py + subPath: rhdh-profile.py + # -- Bundled file used to populate the ConfigMap data when `create` is true. + sourceFile: rhdh-profile.py + optional: false + secret: + # -- Whether to create a Lightspeed Secret from the bundled source file. + create: true + # -- Name of an existing Secret to use instead. Required when `create` is false. + name: "" + # -- Whether the Secret reference is optional in the pod spec. + optional: false + # -- Bundled file used to populate the Secret's `stringData` keys. + sourceFile: secret.yaml + initContainer: + name: lightspeed-rag-init + # -- Full image reference for the Lightspeed RAG bootstrap init container. Override for disconnected environments. + image: quay.io/redhat-ai-dev/rag-content:release-1.9-lls-0.5.0-642c567fe10a62b5ff711654306b72912f341e05 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + args: + - >- + mkdir -p /tmp/data && + echo 'Copying Lightspeed RAG data...' && + cp -r /rag/vector_db /rag-content/ && + cp -r /rag/embeddings_model /rag-content/ && + echo 'Copy complete.' + env: [] + # -- Resource requests/limits for the Lightspeed RAG bootstrap init container. + resources: + requests: + cpu: 50m + memory: 150Mi + limits: + cpu: 100m + memory: 500Mi + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + seccompProfile: + type: "RuntimeDefault" + sidecar: + name: lightspeed-core + # -- Full image reference for the Lightspeed Core sidecar. Override for disconnected environments. + image: quay.io/lightspeed-core/lightspeed-stack:0.5.0 + imagePullPolicy: IfNotPresent + portName: http-lightspeed + containerPort: 8080 + command: [] + args: [] + env: [] + # -- Resource requests/limits for the Lightspeed Core sidecar. + resources: + requests: + cpu: 100m + memory: 512Mi + limits: + cpu: 1000m + memory: 2Gi + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + seccompProfile: + type: "RuntimeDefault" # -- Upstream Backstage [chart configuration](https://github.com/backstage/charts/blob/main/charts/backstage/values.yaml) # @default -- Use Openshift compatible settings upstream: @@ -251,9 +411,6 @@ upstream: mountPath: /tmp workingDir: /opt/app-root/src installDir: /opt/app-root/src - podAnnotations: - checksum/dynamic-plugins: >- - {{- include "common.tplvalues.render" ( dict "value" .Values.global.dynamic "context" $) | sha256sum }} ingress: host: "{{ .Values.global.host }}" metrics: diff --git a/charts/backstage/vendor/backstage/charts/backstage/templates/backstage-deployment.yaml b/charts/backstage/vendor/backstage/charts/backstage/templates/backstage-deployment.yaml index bcdcd73a..540832bb 100644 --- a/charts/backstage/vendor/backstage/charts/backstage/templates/backstage-deployment.yaml +++ b/charts/backstage/vendor/backstage/charts/backstage/templates/backstage-deployment.yaml @@ -1,6 +1,11 @@ {{- $imageRepository := .Values.backstage.image.repository | required "The repository name of the image is required (e.g. my-backstage:tag | docker.io/my-backstage:tag) !" -}} {{- $imageTag := .Values.backstage.image.tag | required "The image tag is required (e.g my-backstage:tag | docker.io/my-backstage:tag) !" -}} {{- $installDir := .Values.backstage.installDir -}} +{{- $lightspeed := include "rhdh.lightspeed" . | fromYaml -}} +{{- $lightspeedRuntimeVolumeType := "" -}} +{{- if $lightspeed.enabled -}} +{{- $lightspeedRuntimeVolumeType = include "rhdh.lightspeed.runtimeVolumeType" (dict "volume" $lightspeed.runtimeVolume "path" "global.lightspeed.runtimeVolume") -}} +{{- end -}} --- apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }} kind: Deployment @@ -36,6 +41,11 @@ spec: {{- end }} annotations: checksum/app-config: {{ include "common.tplvalues.render" ( dict "value" .Values.backstage.appConfig "context" $) | sha256sum }} + checksum/dynamic-plugins: {{ include "common.tplvalues.render" ( dict "value" (dict "dynamic" .Values.global.dynamic "lightspeed" (dict "enabled" $lightspeed.enabled "plugins" $lightspeed.plugins)) "context" $) | sha256sum }} + {{- if $lightspeed.enabled }} + checksum/lightspeed-configmaps: {{ include "rhdh.lightspeed.configMapsChecksum" (dict "context" $ "lightspeed" $lightspeed) | sha256sum }} + checksum/lightspeed-secret: {{ include "rhdh.lightspeed.secretChecksum" (dict "context" $ "lightspeed" $lightspeed) | sha256sum }} + {{- end }} {{- if .Values.backstage.podAnnotations }} {{- include "common.tplvalues.render" ( dict "value" .Values.backstage.podAnnotations "context" $ ) | nindent 8 }} {{- end }} @@ -81,10 +91,57 @@ spec: configMap: name: {{ include "common.names.fullname" . }}-app-config {{- end }} + {{- if $lightspeed.enabled }} + - name: {{ $lightspeed.runtimeVolume.name }} + {{- if eq $lightspeedRuntimeVolumeType "persistentVolumeClaim" }} + persistentVolumeClaim: + {{- include "common.tplvalues.render" ( dict "value" $lightspeed.runtimeVolume.persistentVolumeClaim "context" $ ) | nindent 12 }} + {{- else }} + emptyDir: + {{- include "common.tplvalues.render" ( dict "value" $lightspeed.runtimeVolume.emptyDir "context" $ ) | nindent 12 }} + {{- end }} + - name: {{ $lightspeed.ragVolume.name }} + emptyDir: + {{- include "common.tplvalues.render" ( dict "value" $lightspeed.ragVolume.emptyDir "context" $ ) | nindent 12 }} + {{- range $lightspeed.configMaps }} + - name: {{ include "rhdh.lightspeed.configMapVolumeName" . }} + configMap: + name: {{ include "rhdh.lightspeed.configMapName" (dict "root" $ "configMap" .) }} + optional: {{ default false .optional }} + {{- end }} + {{- end }} {{- include "backstage.renderImagePullSecrets" . | nindent 6 }} - {{- if .Values.backstage.initContainers }} + {{- if or .Values.backstage.initContainers $lightspeed.enabled }} initContainers: + {{- if .Values.backstage.initContainers }} {{- include "common.tplvalues.render" ( dict "value" .Values.backstage.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if $lightspeed.enabled }} + - name: {{ $lightspeed.initContainer.name }} + image: {{ include "common.tplvalues.render" (dict "value" $lightspeed.initContainer.image "context" $) }} + imagePullPolicy: {{ $lightspeed.initContainer.imagePullPolicy | quote }} + {{- if $lightspeed.initContainer.securityContext }} + securityContext: + {{- include "common.tplvalues.render" (dict "value" $lightspeed.initContainer.securityContext "context" $) | nindent 12 }} + {{- end }} + {{- if $lightspeed.initContainer.command }} + command: {{- include "common.tplvalues.render" (dict "value" $lightspeed.initContainer.command "context" $) | nindent 12 }} + {{- end }} + {{- if $lightspeed.initContainer.args }} + args: {{- include "common.tplvalues.render" (dict "value" $lightspeed.initContainer.args "context" $) | nindent 12 }} + {{- end }} + {{- if $lightspeed.initContainer.env }} + env: {{- include "common.tplvalues.render" (dict "value" $lightspeed.initContainer.env "context" $) | nindent 12 }} + {{- end }} + {{- if $lightspeed.initContainer.resources }} + resources: {{- include "common.tplvalues.render" (dict "value" $lightspeed.initContainer.resources "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ $lightspeed.runtimeVolume.name }} + mountPath: {{ $lightspeed.runtimeVolume.mountPath | quote }} + - name: {{ $lightspeed.ragVolume.name }} + mountPath: {{ $lightspeed.ragVolume.initMountPath | quote }} + {{- end }} {{- end }} containers: - name: backstage-backend @@ -183,6 +240,48 @@ spec: {{- include "common.tplvalues.render" ( dict "value" .Values.backstage.extraVolumeMounts "context" $ ) | nindent 12 }} {{- end }} {{- end }} + {{- if $lightspeed.enabled }} + - name: {{ $lightspeed.sidecar.name }} + image: {{ include "common.tplvalues.render" (dict "value" $lightspeed.sidecar.image "context" $) }} + imagePullPolicy: {{ $lightspeed.sidecar.imagePullPolicy | quote }} + {{- if $lightspeed.sidecar.securityContext }} + securityContext: + {{- include "common.tplvalues.render" (dict "value" $lightspeed.sidecar.securityContext "context" $) | nindent 12 }} + {{- end }} + {{- if $lightspeed.sidecar.command }} + command: {{- include "common.tplvalues.render" (dict "value" $lightspeed.sidecar.command "context" $) | nindent 12 }} + {{- end }} + {{- if $lightspeed.sidecar.args }} + args: {{- include "common.tplvalues.render" (dict "value" $lightspeed.sidecar.args "context" $) | nindent 12 }} + {{- end }} + ports: + - name: {{ $lightspeed.sidecar.portName }} + containerPort: {{ $lightspeed.sidecar.containerPort }} + protocol: TCP + envFrom: + - secretRef: + name: {{ include "rhdh.lightspeed.secretName" (dict "context" $ "lightspeed" $lightspeed) }} + optional: {{ default false $lightspeed.secret.optional }} + {{- if $lightspeed.sidecar.env }} + env: {{- include "common.tplvalues.render" (dict "value" $lightspeed.sidecar.env "context" $) | nindent 12 }} + {{- end }} + {{- if $lightspeed.sidecar.resources }} + resources: {{- include "common.tplvalues.render" (dict "value" $lightspeed.sidecar.resources "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ $lightspeed.runtimeVolume.name }} + mountPath: {{ $lightspeed.runtimeVolume.mountPath | quote }} + - name: {{ $lightspeed.ragVolume.name }} + mountPath: {{ $lightspeed.ragVolume.mountPath | quote }} + {{- range $lightspeed.configMaps }} + - name: {{ include "rhdh.lightspeed.configMapVolumeName" . }} + mountPath: {{ .mountPath | quote }} + {{- if .subPath }} + subPath: {{ .subPath | quote }} + {{- end }} + readOnly: true + {{- end }} + {{- end }} {{- if .Values.backstage.extraContainers }} {{- include "common.tplvalues.render" ( dict "value" .Values.backstage.extraContainers "context" $) | nindent 8 }} {{- end }} diff --git a/hack/sync-lightspeed-configs.sh b/hack/sync-lightspeed-configs.sh new file mode 100755 index 00000000..7eebed50 --- /dev/null +++ b/hack/sync-lightspeed-configs.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DEFAULT_REPO="redhat-ai-dev/lightspeed-configs" +DEFAULT_REF="main" + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +LIGHTSPEED_DIR="${REPO_ROOT}/charts/backstage/files/lightspeed" + +# Format: upstream_path|destination_path|transform_function +TARGETS=( + "lightspeed-core-configs/lightspeed-stack.yaml|${LIGHTSPEED_DIR}/lightspeed-stack.yaml|copy_fetched_file" + "llama-stack-configs/config.yaml|${LIGHTSPEED_DIR}/config.yaml|copy_fetched_file" + "lightspeed-core-configs/rhdh-profile.py|${LIGHTSPEED_DIR}/rhdh-profile.py|copy_fetched_file" + "env/default-values.env|${LIGHTSPEED_DIR}/secret.yaml|render_secret_yaml_from_env" +) + +copy_fetched_file() { + local source_file=$1 + local destination_file=$2 + + cp "${source_file}" "${destination_file}" +} +render_secret_yaml_from_env() { + local source_file=$1 + local destination_file=$2 + + awk ' + /^[[:space:]]*$/ { next } + # Skip comments from the upstream .env file. + /^[[:space:]]*#/ { next } + { + separator = index($0, "=") + if (separator == 0) { + printf "error: unsupported env line: %s\n", $0 > "/dev/stderr" + exit 1 + } + + key = substr($0, 1, separator - 1) + # These image settings are intentionally not part of the chart-managed secret payload. + if (key == "LIGHTSPEED_CORE_IMAGE" || key == "RAG_CONTENT_IMAGE") { + next + } + + value = substr($0, separator + 1) + gsub(/\\/, "\\\\", value) + gsub(/"/, "\\\"", value) + printf "%s: \"%s\"\n", key, value + } + ' "${source_file}" > "${destination_file}" +} + +usage() { + cat <&2 + exit 1 + fi +} + +print_diff() { + local existing_file=$1 + local fetched_file=$2 + local relative_path=$3 + + if [[ -f "${existing_file}" ]]; then + diff -u \ + --label "${relative_path}" \ + --label "${relative_path}" \ + "${existing_file}" "${fetched_file}" || true + else + diff -u \ + --label "${relative_path}" \ + --label "${relative_path}" \ + /dev/null "${fetched_file}" || true + fi +} + +repo="${DEFAULT_REPO}" +ref="${DEFAULT_REF}" +check_only=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) + if [[ $# -lt 2 ]]; then + echo "error: --repo requires a value" >&2 + usage + exit 1 + fi + repo=$2 + shift 2 + ;; + --ref) + if [[ $# -lt 2 ]]; then + echo "error: --ref requires a value" >&2 + usage + exit 1 + fi + ref=$2 + shift 2 + ;; + --check) + check_only=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "error: unknown option: $1" >&2 + usage + exit 1 + ;; + esac +done + +tmpdir="$(mktemp -d)" +cleanup() { + rm -rf "${tmpdir}" +} +trap cleanup EXIT + +changed_count=0 + +for target in "${TARGETS[@]}"; do + IFS='|' read -r source_path destination_path transform_function <<< "${target}" + relative_destination=${destination_path#"${REPO_ROOT}/"} + upstream_url="https://raw.githubusercontent.com/${repo}/${ref}/${source_path}" + fetched_file="${tmpdir}/$(basename "${destination_path}").upstream" + rendered_file="${tmpdir}/$(basename "${destination_path}")" + + fetch_file "${upstream_url}" "${fetched_file}" + "${transform_function}" "${fetched_file}" "${rendered_file}" + + if [[ -f "${destination_path}" ]] && cmp -s "${destination_path}" "${rendered_file}"; then + echo "up to date: ${relative_destination}" + continue + fi + + if [[ "${check_only}" == true ]]; then + print_diff "${destination_path}" "${rendered_file}" "${relative_destination}" + else + mv "${rendered_file}" "${destination_path}" + echo "updated: ${relative_destination} <- ${upstream_url}" + fi + + changed_count=$((changed_count + 1)) +done + +if [[ "${check_only}" == true ]]; then + if [[ "${changed_count}" -gt 0 ]]; then + echo "lightspeed config sync is required for ${changed_count} file(s)" >&2 + exit 1 + fi + echo "lightspeed config files are already synced" +else + echo "sync complete from ${repo}@${ref}" +fi