From 01aee4fc61bce14048e00df506f1b376b532bffd Mon Sep 17 00:00:00 2001 From: 0x5t4l1n Date: Tue, 27 Oct 2026 10:25:15 +0530 Subject: [PATCH] Preserve database credentials as strings in Helm ConfigMaps --- .github/workflows/helm-tests.yml | 27 +++++++ deploy/helm/Chart.yaml | 2 +- deploy/helm/templates/chatbot/config.yaml | 12 +-- deploy/helm/templates/community/config.yaml | 12 +-- deploy/helm/templates/identity/config.yaml | 6 +- deploy/helm/templates/mongodb/config.yaml | 4 +- deploy/helm/templates/postgres/config.yaml | 6 +- deploy/helm/templates/workshop/config.yaml | 12 +-- deploy/tests/README.md | 14 ++++ deploy/tests/requirements.txt | 2 + deploy/tests/test_helm_configmaps.py | 83 +++++++++++++++++++++ 11 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/helm-tests.yml create mode 100644 deploy/tests/README.md create mode 100644 deploy/tests/requirements.txt create mode 100644 deploy/tests/test_helm_configmaps.py diff --git a/.github/workflows/helm-tests.yml b/.github/workflows/helm-tests.yml new file mode 100644 index 000000000..d1b98dff2 --- /dev/null +++ b/.github/workflows/helm-tests.yml @@ -0,0 +1,27 @@ +name: Helm rendering tests + +on: + pull_request: + paths: + - 'deploy/helm/**' + - 'deploy/tests/**' + - '.github/workflows/helm-tests.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + configmaps: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - uses: azure/setup-helm@v4 + with: + version: 'v3.16.4' + - run: python -m pip install -r deploy/tests/requirements.txt + - run: python -m pytest deploy/tests -q + - run: helm lint deploy/helm diff --git a/deploy/helm/Chart.yaml b/deploy/helm/Chart.yaml index 993f5abec..3910d7620 100644 --- a/deploy/helm/Chart.yaml +++ b/deploy/helm/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.5 +version: 0.1.6 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/deploy/helm/templates/chatbot/config.yaml b/deploy/helm/templates/chatbot/config.yaml index 32575266f..2797a71a8 100644 --- a/deploy/helm/templates/chatbot/config.yaml +++ b/deploy/helm/templates/chatbot/config.yaml @@ -11,15 +11,15 @@ data: WEB_SERVICE: {{ .Values.web.service.name }} TLS_ENABLED: {{ .Values.tlsEnabled | quote }} DB_HOST: {{ .Values.postgresdb.service.name }} - DB_USER: {{ .Values.postgresdb.config.postgresUser }} - DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword }} - DB_NAME: {{ .Values.postgresdb.config.postgresDbName }} + DB_USER: {{ .Values.postgresdb.config.postgresUser | quote }} + DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword | quote }} + DB_NAME: {{ .Values.postgresdb.config.postgresDbName | quote }} DB_PORT: {{ .Values.postgresdb.port | quote }} MONGO_DB_HOST: {{ .Values.mongodb.service.name }} MONGO_DB_PORT: {{ .Values.mongodb.port | quote }} - MONGO_DB_USER: {{ .Values.mongodb.config.mongoUser }} - MONGO_DB_PASSWORD: {{ .Values.mongodb.config.mongoPassword }} - MONGO_DB_NAME: {{ .Values.mongodb.config.mongoDbName }} + MONGO_DB_USER: {{ .Values.mongodb.config.mongoUser | quote }} + MONGO_DB_PASSWORD: {{ .Values.mongodb.config.mongoPassword | quote }} + MONGO_DB_NAME: {{ .Values.mongodb.config.mongoDbName | quote }} CHATBOT_LIFE: {{ .Values.chatbotLife | quote }} CHATBOT_OPENAI_API_KEY: {{ .Values.chatbotOpenaiApiKey | default .Values.openAIApiKey | quote }} CHATBOT_OPENAI_BASE_URL: {{ .Values.chatbotOpenaiBaseUrl | quote }} diff --git a/deploy/helm/templates/community/config.yaml b/deploy/helm/templates/community/config.yaml index f858c0063..0abb2a789 100644 --- a/deploy/helm/templates/community/config.yaml +++ b/deploy/helm/templates/community/config.yaml @@ -11,14 +11,14 @@ data: LOG_LEVEL: {{ .Values.logLevel | upper }} IDENTITY_SERVICE: {{ .Values.identity.service.name }}:{{ .Values.identity.port }} DB_HOST: {{ .Values.postgresdb.service.name }} - DB_USER: {{ .Values.postgresdb.config.postgresUser }} - DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword }} - DB_NAME: {{ .Values.postgresdb.config.postgresDbName }} + DB_USER: {{ .Values.postgresdb.config.postgresUser | quote }} + DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword | quote }} + DB_NAME: {{ .Values.postgresdb.config.postgresDbName | quote }} DB_PORT: {{ .Values.postgresdb.port | quote }} MONGO_DB_HOST: {{ .Values.mongodb.service.name }} MONGO_DB_PORT: {{ .Values.mongodb.port | quote }} - MONGO_DB_USER: {{ .Values.mongodb.config.mongoUser }} - MONGO_DB_PASSWORD: {{ .Values.mongodb.config.mongoPassword }} - MONGO_DB_NAME: {{ .Values.mongodb.config.mongoDbName }} + MONGO_DB_USER: {{ .Values.mongodb.config.mongoUser | quote }} + MONGO_DB_PASSWORD: {{ .Values.mongodb.config.mongoPassword | quote }} + MONGO_DB_NAME: {{ .Values.mongodb.config.mongoDbName | quote }} SERVER_PORT: {{ .Values.community.port | quote }} TLS_ENABLED: {{ .Values.tlsEnabled | quote }} diff --git a/deploy/helm/templates/identity/config.yaml b/deploy/helm/templates/identity/config.yaml index 98046df2f..6a78e3721 100644 --- a/deploy/helm/templates/identity/config.yaml +++ b/deploy/helm/templates/identity/config.yaml @@ -14,9 +14,9 @@ data: ENABLE_SHELL_INJECTION: {{ .Values.enableShellInjection | quote }} ENABLE_LOG4J: {{ .Values.enableLog4j | quote }} DB_HOST: {{ .Values.postgresdb.service.name }} - DB_USER: {{ .Values.postgresdb.config.postgresUser }} - DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword }} - DB_NAME: {{ .Values.postgresdb.config.postgresDbName }} + DB_USER: {{ .Values.postgresdb.config.postgresUser | quote }} + DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword | quote }} + DB_NAME: {{ .Values.postgresdb.config.postgresDbName | quote }} DB_PORT: {{ .Values.postgresdb.port | quote }} MAILHOG_HOST: {{ .Values.mailhog.smtpService.name }} MAILHOG_PORT: {{ .Values.mailhog.smtpPort | quote }} diff --git a/deploy/helm/templates/mongodb/config.yaml b/deploy/helm/templates/mongodb/config.yaml index bb1280812..5d0b0fa56 100644 --- a/deploy/helm/templates/mongodb/config.yaml +++ b/deploy/helm/templates/mongodb/config.yaml @@ -8,5 +8,5 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} data: - MONGO_INITDB_ROOT_USERNAME: {{ .Values.mongodb.config.mongoUser }} - MONGO_INITDB_ROOT_PASSWORD: {{ .Values.mongodb.config.mongoPassword }} + MONGO_INITDB_ROOT_USERNAME: {{ .Values.mongodb.config.mongoUser | quote }} + MONGO_INITDB_ROOT_PASSWORD: {{ .Values.mongodb.config.mongoPassword | quote }} diff --git a/deploy/helm/templates/postgres/config.yaml b/deploy/helm/templates/postgres/config.yaml index c8bc04d5d..837656783 100644 --- a/deploy/helm/templates/postgres/config.yaml +++ b/deploy/helm/templates/postgres/config.yaml @@ -8,6 +8,6 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} data: - POSTGRES_USER: {{ .Values.postgresdb.config.postgresUser }} - POSTGRES_PASSWORD: {{ .Values.postgresdb.config.postgresPassword }} - POSTGRES_DB: {{ .Values.postgresdb.config.postgresDbName }} + POSTGRES_USER: {{ .Values.postgresdb.config.postgresUser | quote }} + POSTGRES_PASSWORD: {{ .Values.postgresdb.config.postgresPassword | quote }} + POSTGRES_DB: {{ .Values.postgresdb.config.postgresDbName | quote }} diff --git a/deploy/helm/templates/workshop/config.yaml b/deploy/helm/templates/workshop/config.yaml index 6c7227354..1e2e24e58 100644 --- a/deploy/helm/templates/workshop/config.yaml +++ b/deploy/helm/templates/workshop/config.yaml @@ -10,15 +10,15 @@ data: IDENTITY_SERVICE: {{ .Values.identity.service.name }}:{{ .Values.identity.port }} SECRET_KEY: {{ .Values.workshop.config.secretKey }} DB_HOST: {{ .Values.postgresdb.service.name }} - DB_USER: {{ .Values.postgresdb.config.postgresUser }} - DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword }} - DB_NAME: {{ .Values.postgresdb.config.postgresDbName }} + DB_USER: {{ .Values.postgresdb.config.postgresUser | quote }} + DB_PASSWORD: {{ .Values.postgresdb.config.postgresPassword | quote }} + DB_NAME: {{ .Values.postgresdb.config.postgresDbName | quote }} DB_PORT: {{ .Values.postgresdb.port | quote }} MONGO_DB_HOST: {{ .Values.mongodb.service.name }} MONGO_DB_PORT: {{ .Values.mongodb.port | quote }} - MONGO_DB_USER: {{ .Values.mongodb.config.mongoUser }} - MONGO_DB_PASSWORD: {{ .Values.mongodb.config.mongoPassword }} - MONGO_DB_NAME: {{ .Values.mongodb.config.mongoDbName }} + MONGO_DB_USER: {{ .Values.mongodb.config.mongoUser | quote }} + MONGO_DB_PASSWORD: {{ .Values.mongodb.config.mongoPassword | quote }} + MONGO_DB_NAME: {{ .Values.mongodb.config.mongoDbName | quote }} SERVER_PORT: {{ .Values.workshop.port | quote }} API_GATEWAY_URL: {{ if .Values.apiGatewayServiceInstall }}"https://{{ .Values.apiGatewayService.service.name }}"{{ else }}{{ .Values.apiGatewayServiceUrl }}{{ end }} TLS_ENABLED: {{ .Values.tlsEnabled | quote }} diff --git a/deploy/tests/README.md b/deploy/tests/README.md new file mode 100644 index 000000000..ec891a19e --- /dev/null +++ b/deploy/tests/README.md @@ -0,0 +1,14 @@ +# Helm rendering tests + +Install Helm 3 and the Python test dependencies, then run from the repository root: + +```sh +python -m pip install -r deploy/tests/requirements.txt +python -m pytest deploy/tests -q +helm lint deploy/helm +``` + +Helm must be on `PATH`, or set `HELM_BINARY` to its executable path. These tests +render the chart locally; they do not need a Kubernetes cluster. They verify that +ConfigMap data contains strings and database credentials survive YAML rendering, +including numeric-looking values, empty strings, whitespace and special characters. diff --git a/deploy/tests/requirements.txt b/deploy/tests/requirements.txt new file mode 100644 index 000000000..33438ef50 --- /dev/null +++ b/deploy/tests/requirements.txt @@ -0,0 +1,2 @@ +pytest +PyYAML diff --git a/deploy/tests/test_helm_configmaps.py b/deploy/tests/test_helm_configmaps.py new file mode 100644 index 000000000..a9a3cd7ea --- /dev/null +++ b/deploy/tests/test_helm_configmaps.py @@ -0,0 +1,83 @@ +"""Render the chart and validate ConfigMap data without a Kubernetes cluster.""" + +import os +import shutil +import subprocess +from pathlib import Path + +import pytest +import yaml + +CHART = Path(__file__).resolve().parents[1] / "helm" +HELM = os.environ.get("HELM_BINARY") or shutil.which("helm") + + +def render_configmaps(tmp_path, overrides): + if not HELM: + pytest.skip("Helm is required; install Helm or set HELM_BINARY") + values = tmp_path / "values.yaml" + values.write_text(yaml.safe_dump(overrides), encoding="utf-8") + result = subprocess.run( + [HELM, "template", "crapi", str(CHART), "-f", str(values)], + check=True, + capture_output=True, + text=True, + ) + return { + document["metadata"]["name"]: document["data"] + for document in yaml.safe_load_all(result.stdout) + if document and document["kind"] == "ConfigMap" + } + + +def assert_string_data(configmaps): + assert configmaps, "The chart must render ConfigMaps" + for name, data in configmaps.items(): + for key, value in data.items(): + assert isinstance(value, str), f"{name}.{key}: {value!r} is not a string" + + +def test_default_configmap_data_is_strings(tmp_path): + assert_string_data(render_configmaps(tmp_path, {})) + + +@pytest.mark.parametrize( + "credential", + ["123456", "true", "null", "", " leading space ", "value: # comment\nnext line"], +) +def test_database_credentials_survive_yaml_rendering(tmp_path, credential): + configmaps = render_configmaps( + tmp_path, + { + "postgresdb": { + "config": { + "postgresUser": credential, + "postgresPassword": credential, + "postgresDbName": credential, + } + }, + "mongodb": { + "config": { + "mongoUser": credential, + "mongoPassword": credential, + "mongoDbName": credential, + } + }, + }, + ) + assert_string_data(configmaps) + for name in ( + "crapi-chatbot-configmap", + "crapi-community-configmap", + "crapi-identity-configmap", + "crapi-workshop-configmap", + ): + for key in ("DB_USER", "DB_PASSWORD", "DB_NAME"): + assert configmaps[name][key] == credential + if name != "crapi-identity-configmap": + for key in ("MONGO_DB_USER", "MONGO_DB_PASSWORD", "MONGO_DB_NAME"): + assert configmaps[name][key] == credential + for key in ("POSTGRES_USER", "POSTGRES_PASSWORD", "POSTGRES_DB"): + assert configmaps["postgres-config"][key] == credential + for key in ("MONGO_INITDB_ROOT_USERNAME", "MONGO_INITDB_ROOT_PASSWORD"): + assert configmaps["mongodb-config"][key] == credential