From b5eeeab306ae0d9d9bbe12bafd4301f4a8a91cbc Mon Sep 17 00:00:00 2001 From: DoDiODev Date: Tue, 3 Nov 2026 10:06:23 +0100 Subject: [PATCH] fix(grafana): run entrypoint with POSIX sh grafana/grafana:13.3.0 no longer ships bash, so the image failed to start with "exec /entrypoint.sh: no such file or directory". Switch the shebang to /bin/sh and replace the only bashism ($'\n'). Add grafana/scripts/smoke-test.sh plus a pull_request workflow that builds the image and checks that it starts and creates a healthy datasource against MySQL and PostgreSQL, so base image bumps that break startup are caught before merge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/grafana-image-smoke-test.yml | 41 +++++++ grafana/scripts/entrypoint.sh | 8 +- grafana/scripts/smoke-test.sh | 101 ++++++++++++++++++ 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/grafana-image-smoke-test.yml create mode 100755 grafana/scripts/smoke-test.sh diff --git a/.github/workflows/grafana-image-smoke-test.yml b/.github/workflows/grafana-image-smoke-test.yml new file mode 100644 index 00000000000..6b290134132 --- /dev/null +++ b/.github/workflows/grafana-image-smoke-test.yml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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: grafana-image-smoke-test + +on: + pull_request: + branches: + - main + - release-* + paths: + - 'grafana/**' + - '.github/workflows/grafana-image-smoke-test.yml' + +jobs: + grafana-image-smoke-test: + name: grafana image smoke test (${{ matrix.db }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + db: [mysql, postgresql] + steps: + - uses: actions/checkout@v7 + - name: Build grafana image + run: docker build -t devlake-dashboard:smoke grafana + - name: Start image and check datasource + run: grafana/scripts/smoke-test.sh devlake-dashboard:smoke ${{ matrix.db }} diff --git a/grafana/scripts/entrypoint.sh b/grafana/scripts/entrypoint.sh index de20a196c9e..99ffa44f3b0 100644 --- a/grafana/scripts/entrypoint.sh +++ b/grafana/scripts/entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -271,6 +271,9 @@ fi echo "Creating datasource via API..." DATASOURCE_OK=false +# The grafana/grafana image (Alpine/busybox) ships no bash, so stay POSIX sh. +NL=' +' for i in $(seq 1 10); do # "|| true": a failing curl must not end this script (set -e) and with it the # container; the status check below reports the problem instead. @@ -278,7 +281,8 @@ for i in $(seq 1 10); do -H "Content-Type: application/json" \ -d @"$PAYLOAD_FILE" 2>&1) || true STATUS="${RESPONSE##*HTTP_STATUS=}" - RESPONSE="${RESPONSE%$'\n'HTTP_STATUS=*}" + RESPONSE="${RESPONSE%HTTP_STATUS=*}" + RESPONSE="${RESPONSE%"$NL"}" if [ "$STATUS" = "401" ] || [ "$STATUS" = "403" ]; then # Retrying cannot help: the credentials do not match the Grafana admin. diff --git a/grafana/scripts/smoke-test.sh b/grafana/scripts/smoke-test.sh new file mode 100755 index 00000000000..fe7f4a1acc7 --- /dev/null +++ b/grafana/scripts/smoke-test.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +# Smoke test for the devlake-dashboard (Grafana) image: starts it next to a +# throwaway database and checks that the entrypoint runs, Grafana becomes +# healthy and the DevLake datasource is created and can reach the database. +# +# Usage: grafana/scripts/smoke-test.sh [mysql|postgresql] +# Uses uniquely named containers/network only; nothing else is touched. + +set -euo pipefail + +IMAGE="${1:?usage: $0 [mysql|postgresql]}" +DB_TYPE="${2:-mysql}" +TIMEOUT="${SMOKE_TIMEOUT:-180}" +MYSQL_IMAGE="${MYSQL_IMAGE:-mysql:8.4}" +POSTGRES_IMAGE="${POSTGRES_IMAGE:-postgres:18}" + +PREFIX="grafana-smoke-$$" +NET="$PREFIX-net" +DB="$PREFIX-db" +GRAFANA="$PREFIX-grafana" + +cleanup() { + docker rm -fv "$GRAFANA" "$DB" >/dev/null 2>&1 || true + docker network rm "$NET" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +fail() { + echo "FAIL: $*" + echo "----- grafana container logs -----" + docker logs "$GRAFANA" 2>&1 | grep -v 'logger=' | tail -50 || true + exit 1 +} + +docker network create "$NET" >/dev/null + +case "$DB_TYPE" in + mysql) + docker run -d -q --name "$DB" --network "$NET" \ + -e MYSQL_ROOT_PASSWORD=admin -e MYSQL_DATABASE=lake \ + -e MYSQL_USER=merico -e MYSQL_PASSWORD=merico \ + "$MYSQL_IMAGE" >/dev/null + DS_UID="devlake-mysql-api" + ;; + postgresql) + docker run -d -q --name "$DB" --network "$NET" \ + -e POSTGRES_DB=lake -e POSTGRES_USER=merico -e POSTGRES_PASSWORD=merico \ + "$POSTGRES_IMAGE" >/dev/null + DS_UID="devlake-postgres-api" + ;; + *) + echo "unknown database type '$DB_TYPE' (expected mysql or postgresql)" + exit 2 + ;; +esac + +docker run -d --name "$GRAFANA" --network "$NET" \ + -e DATABASE_TYPE="$DB_TYPE" -e DATABASE_HOST="$DB" \ + "$IMAGE" >/dev/null + +echo "Waiting for $IMAGE ($DB_TYPE) to create its datasource..." +deadline=$((SECONDS + TIMEOUT)) +until docker logs "$GRAFANA" 2>&1 | grep -q "Datasource created successfully"; do + if [ "$(docker inspect -f '{{.State.Running}}' "$GRAFANA")" != "true" ]; then + fail "grafana container exited (exit code $(docker inspect -f '{{.State.ExitCode}}' "$GRAFANA"))" + fi + if docker logs "$GRAFANA" 2>&1 | grep -q "ERROR: datasource"; then + fail "entrypoint could not create the datasource" + fi + [ "$SECONDS" -lt "$deadline" ] || fail "datasource not created within ${TIMEOUT}s" + sleep 3 +done + +api() { + docker exec "$GRAFANA" curl -fsS --user admin:admin "http://localhost:3000/api/$1" +} + +api health >/dev/null || fail "/api/health not OK" + +# The database may still be initialising; retry the datasource health check. +until api "datasources/uid/$DS_UID/health" >/dev/null 2>&1; do + [ "$SECONDS" -lt "$deadline" ] || fail "datasource $DS_UID cannot reach the $DB_TYPE database" + sleep 3 +done + +echo "OK: $IMAGE starts and datasource $DS_UID is healthy ($DB_TYPE)"