diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 9a8a9e7f8e..85a1c149f7 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -594,14 +594,14 @@ services: restart: "no" container_name: dbrepo-dashboard-ui hostname: dashboard-ui - image: docker.io/bitnamilegacy/grafana:11.4.0 + image: docker.io/grafana/grafana:11.6.16 volumes: - ./config/dashboards:/app/dashboards - - ./config/grafana.ini:/opt/bitnami/grafana/conf/grafana.ini - - ./config/provisioning/dashboards/provider.yaml:/opt/bitnami/grafana/conf/provisioning/dashboards/provider.yaml - - ./config/provisioning/datasources/infinity.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/infinity.yaml - - ./config/provisioning/datasources/prometheus.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/prometheus.yaml - - ./config/provisioning/datasources/opensearch.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/opensearch.yaml + - ./config/grafana.ini:/etc/grafana/grafana.ini + - ./config/provisioning/dashboards/provider.yaml:/etc/grafana/provisioning/dashboards/provider.yaml + - ./config/provisioning/datasources/infinity.yaml:/etc/grafana/provisioning/datasources/infinity.yaml + - ./config/provisioning/datasources/prometheus.yaml:/etc/grafana/provisioning/datasources/prometheus.yaml + - ./config/provisioning/datasources/opensearch.yaml:/etc/grafana/provisioning/datasources/opensearch.yaml environment: BASE_URL: "${BASE_URL:-http://localhost}" DASHBOARD_DB_NAME: "${DASHBOARD_DB_NAME:-grafana}" @@ -610,7 +610,8 @@ services: GF_SERVER_ROOT_URL: http://dashboard-ui:3000/dashboard/ GF_SECURITY_ADMIN_USER: "${DASHBOARD_UI_USERNAME:-admin}" GF_SECURITY_ADMIN_PASSWORD: "${DASHBOARD_UI_PASSWORD:-admin}" - GF_INSTALL_PLUGINS: "yesoreyeram-infinity-datasource,grafana-opensearch-datasource" + GF_INSTALL_PLUGINS: "yesoreyeram-infinity-datasource 4.0.0,grafana-opensearch-datasource 2.34.4" + GF_PLUGINS_PREINSTALL_DISABLED: "true" READONLY_USERNAME: "${READONLY_USERNAME:-readonly}" READONLY_PASSWORD: "${READONLY_PASSWORD:-readonly}" healthcheck: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d561adbb4a..dddac94345 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,8 @@ jobs: IGNORE_VOLUMES=1 IGNORE_IMAGE=1 bash .scripts/check-service.sh dbrepo-storage-service-init IGNORE_IMAGE=1 bash .scripts/check-service.sh dbrepo-dashboard-service-init IGNORE_IMAGE=1 bash .scripts/check-service.sh dbrepo-ui + - name: Check Grafana plugin pins + run: python3 .scripts/test-grafana-plugins.py java-services: runs-on: ubuntu-latest diff --git a/.live/config/includes.chroot_after_packages/etc/skel/docker-compose.yml b/.live/config/includes.chroot_after_packages/etc/skel/docker-compose.yml index fd8ca9f769..5dff65b45d 100644 --- a/.live/config/includes.chroot_after_packages/etc/skel/docker-compose.yml +++ b/.live/config/includes.chroot_after_packages/etc/skel/docker-compose.yml @@ -580,23 +580,24 @@ services: restart: "no" container_name: dbrepo-dashboard-ui hostname: dashboard-ui - image: docker.io/bitnamilegacy/grafana:${GRAFANA_VERSION} + image: docker.io/grafana/grafana:11.6.16 ports: - "3000:3000" volumes: - ./config/dashboards:/app/dashboards - - ./config/grafana.ini:/opt/bitnami/grafana/conf/grafana.ini - - ./config/provisioning/dashboards/provider.yaml:/opt/bitnami/grafana/conf/provisioning/dashboards/provider.yaml - - ./config/provisioning/datasources/infinity.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/infinity.yaml - - ./config/provisioning/datasources/prometheus.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/prometheus.yaml - - ./config/provisioning/datasources/opensearch.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/opensearch.yaml + - ./config/grafana.ini:/etc/grafana/grafana.ini + - ./config/provisioning/dashboards/provider.yaml:/etc/grafana/provisioning/dashboards/provider.yaml + - ./config/provisioning/datasources/infinity.yaml:/etc/grafana/provisioning/datasources/infinity.yaml + - ./config/provisioning/datasources/prometheus.yaml:/etc/grafana/provisioning/datasources/prometheus.yaml + - ./config/provisioning/datasources/opensearch.yaml:/etc/grafana/provisioning/datasources/opensearch.yaml environment: BASE_URL: "${BASE_URL:-http://localhost}" DASHBOARD_DB_NAME: "${DASHBOARD_DB_NAME:-grafana}" DASHBOARD_DB_USERNAME: "${DASHBOARD_DB_USERNAME:-grafana}" DASHBOARD_DB_PASSWORD: "${DASHBOARD_DB_PASSWORD:-dbrepo}" GF_SERVER_ROOT_URL: http://dashboard-ui:3000/dashboard/ - GF_INSTALL_PLUGINS: "yesoreyeram-infinity-datasource,grafana-opensearch-datasource" + GF_INSTALL_PLUGINS: "yesoreyeram-infinity-datasource 4.0.0,grafana-opensearch-datasource 2.34.4" + GF_PLUGINS_PREINSTALL_DISABLED: "true" healthcheck: test: curl -fsSL --head 127.0.0.1:3000 <<: *healthcheck-params diff --git a/.scripts/test-grafana-plugins.py b/.scripts/test-grafana-plugins.py new file mode 100644 index 0000000000..1df97077dc --- /dev/null +++ b/.scripts/test-grafana-plugins.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Run with python3 .scripts/test-grafana-plugins.py; requires Docker Compose, not a daemon.""" + +import json +import os +from pathlib import Path +import re +import subprocess + + +ROOT = Path(__file__).resolve().parents[1] +# Do not inherit deployment settings or Compose overrides from the calling shell. +ENV = {key: os.environ[key] for key in ("PATH", "HOME") if key in os.environ} +COMPOSE_FILES = ( + "docker-compose.yml", + ".docker/docker-compose.yml", + ".live/config/includes.chroot_after_packages/etc/skel/docker-compose.yml", +) +# Plugin versions must be compatible with the Grafana image; update the image and both plugins together. +IMAGE = "grafana/grafana:11.6.16" +PLUGINS = ["yesoreyeram-infinity-datasource 4.0.0", "grafana-opensearch-datasource 2.34.4"] + + +def check(): + for compose in COMPOSE_FILES: + result = subprocess.run( + ["docker", "compose", "-f", str(ROOT / compose), "config", "--format", "json"], + env=ENV, capture_output=True, text=True, check=True, + ) + service = json.loads(result.stdout)["services"]["dbrepo-dashboard-ui"] + environment = service["environment"] + # An unpinned plugin installs the newest release, which can require a newer Grafana. + assert environment["GF_INSTALL_PLUGINS"].split(",") == PLUGINS, (compose, environment["GF_INSTALL_PLUGINS"]) + # Grafana otherwise installs unpinned default apps in the background. + assert environment["GF_PLUGINS_PREINSTALL_DISABLED"] == "true", compose + assert service["image"] == f"docker.io/{IMAGE}", (compose, service["image"]) + for volume in service["volumes"]: + if volume["source"].endswith((".ini", ".yaml")): + assert volume["target"].startswith("/etc/grafana/"), (compose, volume["target"]) + # The chart's default values do not render on their own, so read the dashboardui block directly. + values = (ROOT / "helm/dbrepo/values.yaml").read_text() + dashboardui = re.search(r"^dashboardui:\n(.*?)^\S", values, re.M | re.S).group(1) + plugins = re.search(r"^ plugins:\n((?: - .*\n)+)", dashboardui, re.M).group(1) + assert [line.strip().removeprefix("- ") for line in plugins.splitlines()] == PLUGINS, ("helm", plugins) + assert re.search(r'^ GF_PLUGINS_PREINSTALL_DISABLED: "true"$', dashboardui, re.M), "helm" + image = [re.search(rf"^ {key}: (\S+)$", dashboardui, re.M)[1] for key in ("repository", "tag")] + assert ":".join(image) == IMAGE, ("helm", image) + print("PASS: all Compose files and the Helm chart pin Grafana plugins for the official image") + + +if __name__ == "__main__": + check() diff --git a/dbrepo-dashboard-service/tests/grafana/__init__.py b/dbrepo-dashboard-service/tests/grafana/__init__.py index a59a6bb6f4..395db2336c 100644 --- a/dbrepo-dashboard-service/tests/grafana/__init__.py +++ b/dbrepo-dashboard-service/tests/grafana/__init__.py @@ -6,7 +6,7 @@ class GrafanaContainer(DockerContainer): MGMT_PORT = 3000 - def __init__(self, image: str = "bitnamilegacy/grafana:11", **kwargs) -> None: + def __init__(self, image: str = "grafana/grafana:11.6.16", **kwargs) -> None: super().__init__(image=image, **kwargs) self.with_exposed_ports(self.MGMT_PORT) diff --git a/docker-compose.yml b/docker-compose.yml index 1d1d3c1f18..f3b490c975 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -577,14 +577,14 @@ services: restart: "no" container_name: dbrepo-dashboard-ui hostname: dashboard-ui - image: docker.io/bitnamilegacy/grafana:11.4.0 + image: docker.io/grafana/grafana:11.6.16 volumes: - ./dbrepo-dashboard-ui/dashboards:/app/dashboards - - ./dbrepo-dashboard-ui/grafana.ini:/opt/bitnami/grafana/conf/grafana.ini - - ./dbrepo-dashboard-ui/provisioning/dashboards/provider.yaml:/opt/bitnami/grafana/conf/provisioning/dashboards/provider.yaml - - ./dbrepo-dashboard-ui/provisioning/datasources/infinity.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/infinity.yaml - - ./dbrepo-dashboard-ui/provisioning/datasources/prometheus.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/prometheus.yaml - - ./dbrepo-dashboard-ui/provisioning/datasources/opensearch.yaml:/opt/bitnami/grafana/conf/provisioning/datasources/opensearch.yaml + - ./dbrepo-dashboard-ui/grafana.ini:/etc/grafana/grafana.ini + - ./dbrepo-dashboard-ui/provisioning/dashboards/provider.yaml:/etc/grafana/provisioning/dashboards/provider.yaml + - ./dbrepo-dashboard-ui/provisioning/datasources/infinity.yaml:/etc/grafana/provisioning/datasources/infinity.yaml + - ./dbrepo-dashboard-ui/provisioning/datasources/prometheus.yaml:/etc/grafana/provisioning/datasources/prometheus.yaml + - ./dbrepo-dashboard-ui/provisioning/datasources/opensearch.yaml:/etc/grafana/provisioning/datasources/opensearch.yaml environment: BASE_URL: "${BASE_URL:-http://localhost}" DASHBOARD_DB_NAME: "${DASHBOARD_DB_NAME:-grafana}" @@ -593,7 +593,8 @@ services: GF_SERVER_ROOT_URL: http://dashboard-ui:3000/dashboard/ GF_SECURITY_ADMIN_USER: "${DASHBOARD_UI_USERNAME:-admin}" GF_SECURITY_ADMIN_PASSWORD: "${DASHBOARD_UI_PASSWORD:-admin}" - GF_INSTALL_PLUGINS: "yesoreyeram-infinity-datasource,grafana-opensearch-datasource" + GF_INSTALL_PLUGINS: "yesoreyeram-infinity-datasource 4.0.0,grafana-opensearch-datasource 2.34.4" + GF_PLUGINS_PREINSTALL_DISABLED: "true" READONLY_USERNAME: "${READONLY_USERNAME:-readonly}" READONLY_PASSWORD: "${READONLY_PASSWORD:-readonly}" healthcheck: diff --git a/helm/dbrepo/Chart.lock b/helm/dbrepo/Chart.lock index 7f4f1d35ae..955ef2493c 100644 --- a/helm/dbrepo/Chart.lock +++ b/helm/dbrepo/Chart.lock @@ -21,8 +21,8 @@ dependencies: repository: file://../seaweedfs version: 4.8.7 - name: grafana - repository: oci://registry-1.docker.io/bitnamicharts - version: 11.4.2 + repository: https://grafana-community.github.io/helm-charts + version: 8.15.0 - name: prometheus repository: oci://registry-1.docker.io/bitnamicharts version: 1.3.22 @@ -32,5 +32,5 @@ dependencies: - name: valkey repository: oci://registry-1.docker.io/bitnamicharts version: 5.0.6 -digest: sha256:c242d9a70351f71a3adcbbbcdbadbed6bda3719a9442b255d703710411978f5b -generated: "2025-11-10T08:59:27.685993972+01:00" +digest: sha256:905ac142cf5633f36c623e16ece65858d0f5c59e769649c38a7247b1b7ead328 +generated: "2026-09-28T10:26:42.339268+07:00" diff --git a/helm/dbrepo/Chart.yaml b/helm/dbrepo/Chart.yaml index e2d84b242b..88425e526b 100644 --- a/helm/dbrepo/Chart.yaml +++ b/helm/dbrepo/Chart.yaml @@ -73,8 +73,8 @@ dependencies: condition: storageservice.enabled - name: grafana alias: dashboardui - version: 11.4.2 - repository: oci://registry-1.docker.io/bitnamicharts + version: 8.15.0 + repository: https://grafana-community.github.io/helm-charts condition: dashboardui.enabled - name: prometheus alias: metricdb diff --git a/helm/dbrepo/README.md b/helm/dbrepo/README.md index 828a33e57b..34b7780d5d 100644 --- a/helm/dbrepo/README.md +++ b/helm/dbrepo/README.md @@ -458,12 +458,11 @@ mqtt.prefetch = 10 ### Dashboard UI -| Name | Description | Value | -| ---------------------------------- | ----------------------------------- | -------------------------- | -| `dashboardui.enabled` | Enable the Dashboard UI. | `true` | -| `dashboardui.metrics.enabled` | Enable the metrics sidecar. | `true` | -| `dashboardui.endpoint` | The endpoint for the microservices. | `http://dashboard-ui:3000` | -| `dashboardui.grafana.replicaCount` | The number of replicas. | `2` | +| Name | Description | Value | +| ---------------------- | ----------------------------------- | -------------------------- | +| `dashboardui.enabled` | Enable the Dashboard UI. | `true` | +| `dashboardui.endpoint` | The endpoint for the microservices. | `http://dashboard-ui:3000` | +| `dashboardui.replicas` | The number of replicas. | `2` | ### Metric Service diff --git a/helm/dbrepo/charts/grafana-11.4.2.tgz b/helm/dbrepo/charts/grafana-11.4.2.tgz deleted file mode 100644 index efc2fb1e6b..0000000000 Binary files a/helm/dbrepo/charts/grafana-11.4.2.tgz and /dev/null differ diff --git a/helm/dbrepo/charts/grafana-8.15.0.tgz b/helm/dbrepo/charts/grafana-8.15.0.tgz new file mode 100644 index 0000000000..4774854b88 Binary files /dev/null and b/helm/dbrepo/charts/grafana-8.15.0.tgz differ diff --git a/helm/dbrepo/templates/dashboard-ui-legacy-pvc.yaml b/helm/dbrepo/templates/dashboard-ui-legacy-pvc.yaml new file mode 100644 index 0000000000..8cce9b0515 --- /dev/null +++ b/helm/dbrepo/templates/dashboard-ui-legacy-pvc.yaml @@ -0,0 +1,29 @@ +{{- if .Values.dashboardui.enabled }} +{{- /* +The Bitnami Grafana chart used before 1.14.2 kept a SQLite database on this claim. Grafana now uses the dashboard +database, so Helm would delete the claim on upgrade. Keep it instead, rendering only the fields the Bitnami chart +rendered, so that neither upgrades nor rollbacks touch its immutable spec. +lookup returns nothing in helm template, helm diff, plain --dry-run and Argo CD, so their output shows the claim as deleted. +*/}} +{{- with lookup "v1" "PersistentVolumeClaim" (include "common.names.namespace" $) "dashboard-ui" }} +{{- if eq (index (.metadata.annotations | default dict) "meta.helm.sh/release-name" | default "") $.Release.Name }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: dashboard-ui + namespace: {{ .metadata.namespace | quote }} + annotations: + helm.sh/resource-policy: keep +spec: + accessModes: {{- toYaml .spec.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .spec.resources.requests.storage | quote }} + {{- /* The same values the Bitnami chart read; copying the live class unconditionally breaks rollbacks. */}} + {{- if or $.Values.global.storageClass $.Values.dashboardui.persistence.storageClass $.Values.global.defaultStorageClass }} + storageClassName: {{ .spec.storageClassName | quote }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/helm/dbrepo/templates/dashboard-ui-secret.yaml b/helm/dbrepo/templates/dashboard-ui-secret.yaml index a254a50d36..3b4312c522 100644 --- a/helm/dbrepo/templates/dashboard-ui-secret.yaml +++ b/helm/dbrepo/templates/dashboard-ui-secret.yaml @@ -7,6 +7,8 @@ metadata: name: dashboard-ui-secret namespace: {{ include "common.names.namespace" . | quote }} stringData: + admin-user: "{{ .Values.dashboardui.admin.user }}" + admin-password: "{{ .Values.dashboardui.admin.password }}" BASE_URL: "https://{{ .Values.hostname }}" GF_AUTH_ANONYMOUS_ENABLED: "true" GF_AUTH_ANONYMOUS_ORG_ROLE: "Viewer" diff --git a/helm/dbrepo/values.schema.json b/helm/dbrepo/values.schema.json index 76151a8d4d..49525f4ef3 100644 --- a/helm/dbrepo/values.schema.json +++ b/helm/dbrepo/values.schema.json @@ -782,21 +782,8 @@ }, "user": { "type": "string" - } - } - }, - "dashboardsProvider": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "datasources": { - "type": "object", - "properties": { - "secretName": { + }, + "existingSecret": { "type": "string" } } @@ -810,38 +797,14 @@ "fullnameOverride": { "type": "string" }, - "grafana": { - "type": "object", - "properties": { - "extraEnvVarsSecret": { - "type": "string" - }, - "replicaCount": { - "type": "integer" - }, - "updateStrategy": { - "type": "object", - "properties": { - "type": { - "type": "string" - } - } - } - } - }, "image": { "type": "object", "properties": { "repository": { "type": "string" - } - } - }, - "metrics": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" + }, + "tag": { + "type": "string" } } }, @@ -852,6 +815,9 @@ "type": "boolean" } } + }, + "replicas": { + "type": "integer" } } }, diff --git a/helm/dbrepo/values.yaml b/helm/dbrepo/values.yaml index 0331436756..fdc6d354d0 100644 --- a/helm/dbrepo/values.yaml +++ b/helm/dbrepo/values.yaml @@ -1172,41 +1172,54 @@ dashboardui: fullnameOverride: dashboard-ui image: ## @skip dashboardui.image.repository - repository: bitnamilegacy/grafana - ## @skip dashboardui.admin + repository: grafana/grafana + ## @skip dashboardui.image.tag + tag: 11.6.16 admin: - ## @param dashboardui.admin.existingSecret The existing secret with the Grafana admin credentials. - existingSecret: dashboard-service-secret - ## @param dashboardui.admin.userKey The key in the existing secret that contains the Grafana admin username. - userKey: admin-user - ## @param dashboardui.admin.passwordKey The key in the existing secret that contains the Grafana admin password. - passwordKey: admin-password - ## @skip dashboardui.smtp - smtp: - ## @param dashboardui.smtp.existingSecret The existing secret containing SMTP credentials. - existingSecret: dashboard-service-secret - ## @skip dashboardui.plugins - plugins: [] - ## @skip dashboardui.dashboardProviders - dashboardProviders: {} - ## @skip dashboardui.dashboardsConfigMaps - dashboardsConfigMaps: - - configMapName: dashboard-service-config - fileName: dashboards.json - ## @skip dashboardui.notifiers - notifiers: {} - ## @skip dashboardui.datasources - datasources: - datasources.yaml: - apiVersion: 1 - datasources: - - name: Prometheus - type: prometheus - url: http://metric-db-kube-prometheus-prometheus:9090 - access: proxy - isDefault: true - ## @param dashboardui.replicaCount The number of replicas. - replicaCount: 2 + ## @skip dashboardui.admin.user + user: "admin" + ## @skip dashboardui.admin.password + password: "admin" + ## @skip dashboardui.admin.existingSecret + existingSecret: dashboard-ui-secret + ## @param dashboardui.endpoint The endpoint for the microservices. + endpoint: http://dashboard-ui:3000 + service: + ## @skip dashboardui.service.port + port: 3000 + ## @skip dashboardui.plugins Plugin versions must be compatible with the Grafana image. + plugins: + - yesoreyeram-infinity-datasource 4.0.0 + - grafana-opensearch-datasource 2.34.4 + env: + ## @skip dashboardui.env.GF_PLUGINS_PREINSTALL_DISABLED Do not install unpinned default plugins. + GF_PLUGINS_PREINSTALL_DISABLED: "true" + ## @skip dashboardui.envFromSecret + envFromSecret: dashboard-ui-secret + ## @skip dashboardui.extraSecretMounts + extraSecretMounts: + - name: datasources + mountPath: /etc/grafana/provisioning/datasources + secretName: dashboard-ui-datasources-secret + readOnly: true + ## @skip dashboardui.useStatefulSet The Deployment of the Bitnami chart used before 1.14.2 has a selector that cannot be changed in place, so Helm replaces it with a StatefulSet on upgrade. + useStatefulSet: true + ## @skip dashboardui.headlessService + headlessService: true + rbac: + ## @skip dashboardui.rbac.create No sidecars are used, so Grafana needs no cluster-wide permissions. + create: false + ## @param dashboardui.replicas The number of replicas. + replicas: 2 + podDisruptionBudget: + ## @skip dashboardui.podDisruptionBudget.maxUnavailable + maxUnavailable: 1 + persistence: + ## @skip dashboardui.persistence.enabled Grafana stores its data in the dashboard database. + enabled: false + testFramework: + ## @skip dashboardui.testFramework.enabled + enabled: false ## @section Metric Database