Files
grafana/rootfs/opt/bitnami/scripts/libgrafana.sh
Bernhard Radermacher 2a6f017ad6 initial
2026-02-09 06:28:41 +00:00

405 lines
13 KiB
Bash

#!/bin/bash
# Copyright Broadcom, Inc. All Rights Reserved.
# SPDX-License-Identifier: APACHE-2.0
#
# Bitnami Grafana library
# shellcheck disable=SC1091
# Load generic libraries
. /opt/bitnami/scripts/liblog.sh
. /opt/bitnami/scripts/libos.sh
. /opt/bitnami/scripts/libvalidations.sh
. /opt/bitnami/scripts/libversion.sh
# Load database library
if [[ -f /opt/bitnami/scripts/libmysqlclient.sh ]]; then
. /opt/bitnami/scripts/libmysqlclient.sh
elif [[ -f /opt/bitnami/scripts/libmysql.sh ]]; then
. /opt/bitnami/scripts/libmysql.sh
elif [[ -f /opt/bitnami/scripts/libmariadb.sh ]]; then
. /opt/bitnami/scripts/libmariadb.sh
fi
########################
# Print the value of a Grafana environment variable
# Globals:
# GF_*
# GRAFANA_CFG_*
# Arguments:
# None
# Returns:
# The value in the environment variable
#########################
grafana_env_var_value() {
local -r name="${1:?missing name}"
local gf_env_var="GF_${name}"
local grafana_cfg_env_var="GRAFANA_CFG_${name}"
if [[ -n "${!gf_env_var:-}" ]]; then
echo "${!gf_env_var:-}"
elif [[ -n "${!grafana_cfg_env_var}" ]]; then
echo "${!grafana_cfg_env_var:-}"
else
error "${gf_env_var} or ${grafana_cfg_env_var} must be set"
fi
}
########################
# Validate settings in GRAFANA_* env vars
# Globals:
# GRAFANA_*
# Arguments:
# None
# Returns:
# 0 if the validation succeeded, 1 otherwise
#########################
grafana_validate() {
debug "Validating settings in GRAFANA_* environment variables..."
local error_code=0
# Auxiliary functions
print_validation_error() {
error "$1"
error_code=1
}
check_path_exists() {
if [[ ! -e "$1" ]]; then
print_validation_error "The directory ${1} does not exist"
fi
}
# Validate user inputs
[[ -e "$GF_OP_PATHS_CONFIG" ]] || check_path_exists "$(grafana_env_var_value PATHS_CONFIG)"
[[ -e "$GF_OP_PATHS_DATA" ]] || check_path_exists "$(grafana_env_var_value PATHS_DATA)"
[[ -e "$GF_OP_PATHS_LOGS" ]] || check_path_exists "$(grafana_env_var_value PATHS_LOGS)"
[[ -e "$GF_OP_PATHS_PROVISIONING" ]] || check_path_exists "$(grafana_env_var_value PATHS_PROVISIONING)"
return "$error_code"
}
########################
# Ensure Grafana is initialized
# Globals:
# GRAFANA_*
# Arguments:
# None
# Returns:
# None
#########################
grafana_initialize() {
# Ensure compatibility with Grafana Operator
local grafana_var grafana_operator_var
for path_suffix in "config" "data" "logs" "provisioning"; do
grafana_var="GF_PATHS_${path_suffix^^}"
grafana_operator_var="GF_OP_PATHS_${path_suffix^^}"
if [[ -e "${!grafana_operator_var}" && "${!grafana_operator_var}" != "${!grafana_var}" ]]; then
info "Ensuring ${!grafana_operator_var} points to ${!grafana_var}"
rm -rf "${!grafana_var}"
ln -sfn "${!grafana_operator_var}" "${!grafana_var}"
fi
done
if am_i_root; then
for dir in "$GF_PATHS_DATA" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS"; do
is_mounted_dir_empty "$dir" && configure_permissions_ownership "$dir" -d "775" -f "664" -u "$GRAFANA_DAEMON_USER"
done
fi
# Install plugins in a Grafana operator-compatible environment, useful to for starting the image as an init container
# Based on https://github.com/grafana-operator/grafana-operator/blob/master/controllers/grafana/pluginsHelper.go
if [[ -d "$GF_OP_PLUGINS_INIT_DIR" ]]; then
info "Detected mounted plugins directory at '${GF_OP_PLUGINS_INIT_DIR}'. The container will exit after installing plugins as grafana-operator."
if [[ -n "$GF_INSTALL_PLUGINS" ]]; then
GF_PATHS_PLUGINS="$GF_OP_PLUGINS_INIT_DIR" grafana_install_plugins
else
warn "There are no plugins to install"
fi
return 255
fi
# Recover plugins installed when building the image
if [[ ! -e "$(grafana_env_var_value PATHS_PLUGINS)" ]] || [[ -z "$(ls -A "$(grafana_env_var_value PATHS_PLUGINS)")" ]]; then
mkdir -p "$(grafana_env_var_value PATHS_PLUGINS)"
if [[ -e "$GRAFANA_DEFAULT_PLUGINS_DIR" ]] && [[ -n "$(ls -A "$GRAFANA_DEFAULT_PLUGINS_DIR")" ]]; then
cp -r "$GRAFANA_DEFAULT_PLUGINS_DIR"/* "$(grafana_env_var_value PATHS_PLUGINS)"
fi
fi
# Configure configuration file based on environment variables
grafana_configure_from_environment_variables
# Install plugins
grafana_install_plugins
# Configure Grafana feature toggles
! is_empty_value "$GF_FEATURE_TOGGLES" && grafana_conf_set "feature_toggles" "enable" "$GF_FEATURE_TOGGLES"
# If using an external database, avoid nodes collition during migration
if is_boolean_yes "$GRAFANA_MIGRATION_LOCK"; then
grafana_migrate_db
fi
# Avoid exit code of previous commands to affect the result of this function
true
}
########################
# Runs Grafana migration using a database lock to avoid collision with other Grafana nodes
# If database is locked, wait until unlocked and continue. Otherwise, run Grafana to perform migration.
# Globals:
# GRAFANA_CFG_*
# Arguments:
# None
# Returns:
# None
#########################
grafana_migrate_db() {
local -r db_host="${GRAFANA_CFG_DATABASE_HOST:-mysql}"
local -r db_port="${GRAFANA_CFG_DATABASE_PORT:-3306}"
local -r db_name="${GRAFANA_CFG_DATABASE_NAME:-}"
local -r db_user="${GRAFANA_CFG_DATABASE_USER:-}"
local -r db_pass="${GRAFANA_CFG_DATABASE_PASSWORD:-}"
local -r grafana_host="${GRAFANA_CFG_SERVER_HTTP_ADDR:-localhost}"
local -r grafana_port="${GRAFANA_CFG_SERVER_HTTP_PORT:-3000}"
local -r grafana_protocol="${GRAFANA_CFG_SERVER_PROTOCOL:-http}"
local -r sleep_time="${GRAFANA_SLEEP_TIME:-5}"
local -r retries="${GRAFANA_RETRY_ATTEMPTS:-12}"
lock_db() {
debug_execute mysql_remote_execute_print_output "$db_host" "$db_port" "$db_name" "$db_user" "$db_pass" <<EOF
create table db_lock(
id INT PRIMARY KEY
);
EOF
}
release_db() {
debug_execute mysql_remote_execute_print_output "$db_host" "$db_port" "$db_name" "$db_user" "$db_pass" <<EOF
drop table if exists db_lock;
EOF
}
is_db_unlocked() {
local result
result=$(mysql_remote_execute_print_output "$db_host" "$db_port" "$db_name" "$db_user" "$db_pass" <<EOF
show tables like 'db_lock';
EOF
)
if grep -q "db_lock" <<<"$result"; then
return 1
else
return 0
fi
}
if lock_db; then
info "Starting Grafana database migration"
grafana_start_bg
# Grafana will start listening HTTP connections once the database initialization has succeeded
if ! retry_while "debug_execute curl --silent ${grafana_protocol}://${grafana_host}:${grafana_port}" "$retries" "$sleep_time"; then
error "Grafana failed to start in the background. Releasing database lock before exit."
# Release the lock
release_db
return 1
fi
grafana_stop
# Release the lock
release_db
info "Grafana database migration completed. Lock released."
else
info "Grafana database migration in progress detected. Waiting for lock to be released before initializing Grafana"
if ! retry_while "is_db_unlocked" "$retries" "$sleep_time"; then
error "Failed waiting for database lock to be released. If there is no migration in progress, manually drop table 'db_lock' from the grafana database"
return 1
fi
fi
}
########################
# Start Grafana in background
# Arguments:
# None
# Returns:
# None
#########################
grafana_start_bg() {
local cmd="grafana"
local -a args=(
# Based on https://github.com/grafana/grafana/blob/v8.2.5/packaging/docker/run.sh
"server"
"--homepath=${GF_PATHS_HOME}"
"--config=${GF_PATHS_CONFIG}"
"--packaging=docker"
"--pidfile=${GRAFANA_PID_FILE}"
"cfg:default.log.mode=console"
"cfg:default.paths.data=${GF_PATHS_DATA}"
"cfg:default.paths.logs=${GF_PATHS_LOGS}"
"cfg:default.paths.plugins=${GF_PATHS_PLUGINS}"
"cfg:default.paths.provisioning=${GF_PATHS_PROVISIONING}"
)
cd "$GRAFANA_BASE_DIR" || return
info "Starting Grafana in background"
if am_i_root; then
debug_execute run_as_user "$GRAFANA_DAEMON_USER" "$cmd" "${args[@]}" &
else
debug_execute "$cmd" "${args[@]}" &
fi
}
########################
# Update Grafana config file with settings provided via environment variables
# Globals:
# GRAFANA_CFG_*
# GF_PATHS_CONFIG
# Arguments:
# None
# Returns:
# None
#########################
grafana_configure_from_environment_variables() {
local section_key_pair section key value
# Map environment variables to config properties
for var in "${!GRAFANA_CFG_@}"; do
# shellcheck disable=SC2001
section_key_pair="$(sed 's/^GRAFANA_CFG_//g' <<< "$var" | tr '[:upper:]' '[:lower:]')"
section="${section_key_pair/_*}"
key="${section_key_pair#*_}"
value="${!var}"
grafana_conf_set "$section" "$key" "$value"
done
}
########################
# Update a single configuration in Grafana's config file
# Globals:
# GF_PATHS_CONFIG
# Arguments:
# $1 - section
# $2 - key
# $3 - value
# Returns:
# None
#########################
grafana_conf_set() {
local -r section="${1:?missing key}"
local -r key="${2:?missing key}"
local -r value="${3:-}"
debug "Setting configuration ${section}.${key} with value '${value}' to configuration file"
ini-file set --section "$section" --key "$key" --value "$value" "$(grafana_env_var_value PATHS_CONFIG)"
}
########################
# Install plugins
# Globals:
# GRAFANA_*
# Arguments:
# None
# Returns:
# None
#########################
grafana_install_plugins() {
[[ -z "$GF_INSTALL_PLUGINS" ]] && return
local -a plugin_list
IFS="," read -r -a plugin_list <<< "$(tr ';' ',' <<< "${GF_INSTALL_PLUGINS}")"
if [[ "${#plugin_list[@]}" -le 0 ]]; then
warn "There are no plugins to install"
return
fi
local plugin_id plugin_version
local -a grafana_plugin_install_args
local -a plugin_url_array
local -a plugin_id_version_array
for plugin in "${plugin_list[@]}"; do
plugin_id="$plugin"
plugin_version=""
grafana_plugin_install_args=("--pluginsDir" "$(grafana_env_var_value PATHS_PLUGINS)")
is_boolean_yes "$GF_INSTALL_PLUGINS_SKIP_TLS" && grafana_plugin_install_args+=("--insecure")
if grep -q '=' <<< "$plugin"; then
read -r -a plugin_url_array <<< "$(tr '=' ' ' <<< "${plugin}")"
info "Installing plugin ${plugin_url_array[0]} from URL ${plugin_url_array[1]}"
plugin_id="${plugin_url_array[0]}"
grafana_plugin_install_args+=("--pluginUrl" "${plugin_url_array[1]}")
elif grep ':' <<< "$plugin"; then
read -r -a plugin_id_version_array <<< "$(tr ':' ' ' <<< "${plugin}")"
plugin_id="${plugin_id_version_array[0]}"
plugin_version="${plugin_id_version_array[1]}"
info "Installing plugin ${plugin_id} @ ${plugin_version}"
elif grep ' ' <<< "$plugin"; then
read -r -a plugin_id_version_array <<< "$plugin"
plugin_id="${plugin_id_version_array[0]}"
plugin_version="${plugin_id_version_array[1]}"
info "Installing plugin ${plugin_id} @ ${plugin_version}"
else
info "Installing plugin ${plugin_id}"
fi
grafana_plugin_install_args+=("plugins" "install" "${plugin_id}")
if [[ -n "$plugin_version" ]]; then
grafana_plugin_install_args+=("$plugin_version")
fi
grafana cli "${grafana_plugin_install_args[@]}"
done
}
########################
# Check if Grafana is running
# Arguments:
# None
# Returns:
# Boolean
#########################
is_grafana_running() {
local pid
pid="$(get_pid_from_file "$GRAFANA_PID_FILE")"
if [[ -n "$pid" ]]; then
is_service_running "$pid"
else
false
fi
}
########################
# Check if Grafana is not running
# Arguments:
# None
# Returns:
# Boolean
#########################
is_grafana_not_running() {
! is_grafana_running
}
########################
# Stop Grafana
# Arguments:
# None
# Returns:
# None
#########################
grafana_stop() {
is_grafana_not_running && return
info "Stopping Grafana"
stop_service_using_pid "$GRAFANA_PID_FILE"
}
########################
# Returns grafana major version
# Globals:
# GRAFANA_BIN_DIR
# Arguments:
# None
# Returns:
# None
#########################
get_grafana_major_version() {
grafana_version="$("${GRAFANA_BIN_DIR}/grafana" -v)"
grafana_version="${grafana_version#"grafana version "}"
major_version="$(get_sematic_version "$grafana_version" 1)"
echo "${major_version:-0}"
}