405 lines
13 KiB
Bash
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}"
|
|
}
|