#!/bin/bash # # Bitnami InfluxDB library # shellcheck disable=SC1090 # shellcheck disable=SC1091 # Load Generic Libraries . /liblog.sh . /libos.sh . /libvalidations.sh # Functions ######################## # Load global variables used on InfluxDB configuration # Globals: # INFLUXDB_* # Arguments: # None # Returns: # Series of exports to be used as 'eval' arguments ######################### influxdb_env() { cat <<"EOF" # Format log messages export MODULE="influxdb" export BITNAMI_DEBUG="${BITNAMI_DEBUG:-false}" # Paths export INFLUXDB_BASE_DIR="/opt/bitnami/influxdb" export INFLUXDB_VOLUME_DIR="/bitnami/influxdb" export INFLUXDB_BIN_DIR="${INFLUXDB_BASE_DIR}/bin" export INFLUXDB_DATA_DIR="${INFLUXDB_DATA_DIR:-${INFLUXDB_VOLUME_DIR}/data}" export INFLUXDB_DATA_WAL_DIR="${INFLUXDB_DATA_WAL_DIR:-${INFLUXDB_VOLUME_DIR}/wal}" export INFLUXDB_META_DIR="${INFLUXDB_META_DIR:-${INFLUXDB_VOLUME_DIR}/meta}" export INFLUXDB_CONF_DIR="${INFLUXDB_BASE_DIR}/etc" export INFLUXDB_CONF_FILE="${INFLUXDB_CONF_DIR}/influxdb.conf" export INFLUXDB_INITSCRIPTS_DIR="/docker-entrypoint-initdb.d" # Users export INFLUXDB_DAEMON_USER="influxdb" export INFLUXDB_DAEMON_GROUP="influxdb" # InfluxDB settings export INFLUXDB_REPORTING_DISABLED="${INFLUXDB_REPORTING_DISABLED:-true}" export INFLUXDB_HTTP_PORT_NUMBER="${INFLUXDB_HTTP_PORT_NUMBER:-8086}" export INFLUXDB_HTTP_BIND_ADDRESS="${INFLUXDB_HTTP_BIND_ADDRESS:-0.0.0.0:${INFLUXDB_HTTP_PORT_NUMBER}}" export INFLUXDB_PORT_NUMBER="${INFLUXDB_PORT_NUMBER:-8088}" export INFLUXDB_BIND_ADDRESS="${INFLUXDB_BIND_ADDRESS:-0.0.0.0:${INFLUXDB_PORT_NUMBER}}" # Authentication export INFLUXDB_ADMIN_USER="${INFLUXDB_ADMIN_USER:-admin}" export INFLUXDB_USER="${INFLUXDB_USER:-}" export INFLUXDB_READ_USER="${INFLUXDB_READ_USER:-}" export INFLUXDB_WRITE_USER="${INFLUXDB_WRITE_USER:-}" export INFLUXDB_DB="${INFLUXDB_DB:-}" EOF # The configuration can be provided in a configuration file or environment variables # This setting is necessary to determine certain validations/actions during the # initialization, so we need to check the config file when existing. if [[ -f "/opt/bitnami/influxdb/etc/influxdb.conf" ]]; then cat <<"EOF" INFLUXDB_HTTP_AUTH_ENABLED="${INFLUXDB_HTTP_AUTH_ENABLED:-$(influxdb_conf_get "auth-enabled")}" export INFLUXDB_HTTP_AUTH_ENABLED="${INFLUXDB_HTTP_AUTH_ENABLED:-true}" EOF else cat <<"EOF" export INFLUXDB_HTTP_AUTH_ENABLED="${INFLUXDB_HTTP_AUTH_ENABLED:-true}" EOF fi # Credentials should be allowed to be mounted as files to avoid sensitive data # in the environment variables if [[ -f "${INFLUXDB_ADMIN_USER_PASSWORD_FILE:-}" ]]; then cat <<"EOF" export INFLUXDB_ADMIN_USER_PASSWORD="$(< "${INFLUXDB_ADMIN_USER_PASSWORD_FILE}")" EOF else cat <<"EOF" export INFLUXDB_ADMIN_USER_PASSWORD="${INFLUXDB_ADMIN_USER_PASSWORD:-}" EOF fi if [[ -f "${INFLUXDB_USER_PASSWORD_FILE:-}" ]]; then cat <<"EOF" export INFLUXDB_USER_PASSWORD="$(< "${INFLUXDB_USER_PASSWORD_FILE}")" EOF else cat <<"EOF" export INFLUXDB_USER_PASSWORD="${INFLUXDB_USER_PASSWORD:-}" EOF fi if [[ -f "${INFLUXDB_READ_USER_PASSWORD_FILE:-}" ]]; then cat <<"EOF" export INFLUXDB_READ_USER_PASSWORD="$(< "${INFLUXDB_READ_USER_PASSWORD_FILE}")" EOF else cat <<"EOF" export INFLUXDB_READ_USER_PASSWORD="${INFLUXDB_READ_USER_PASSWORD:-}" EOF fi if [[ -f "${INFLUXDB_WRITE_USER_PASSWORD_FILE:-}" ]]; then cat <<"EOF" export INFLUXDB_WRITE_USER_PASSWORD="$(< "${INFLUXDB_WRITE_USER_PASSWORD_FILE}")" EOF else cat <<"EOF" export INFLUXDB_WRITE_USER_PASSWORD="${INFLUXDB_WRITE_USER_PASSWORD:-}" EOF fi } ######################## # Validate settings in INFLUXDB_* env vars # Globals: # INFLUXDB_* # Arguments: # None # Returns: # None ######################### influxdb_validate() { local error_code=0 debug "Validating settings in INFLUXDB_* env vars..." # Auxiliary functions print_validation_error() { error "$1" error_code=1 } check_password_file() { if ! is_empty_value "${!1:-}" && ! [[ -f "${!1:-}" ]]; then print_validation_error "The variable $1 is defined but the file ${!1} is not accessible or does not exist." fi } check_true_false_value() { if ! is_true_false_value "${!1}"; then print_validation_error "The allowed values for $1 are [true, false]" fi } check_conflicting_ports() { local -r total="$#" for i in $(seq 1 "$((total - 1))"); do for j in $(seq "$((i + 1))" "$total"); do if [[ "${!i}" -eq "${!j}" ]]; then print_validation_error "${!i} and ${!j} are bound to the same port" fi done done } # InfluxDB secret files validations local -a user_envs=("INFLUXDB_ADMIN_USER" "INFLUXDB_USER" "INFLUXDB_READ_USER" "INFLUXDB_WRITE_USER") local -a pwd_file_envs=( "${user_envs[@]/%/_PASSWORD_FILE}" ) for pwd_file in "${pwd_file_envs[@]}"; do check_password_file "$pwd_file" done # InfluxDB booleans validations read -r -a boolean_envs <<< "$(compgen -A variable | grep -E "INFLUXDB_.*_(ENABLED|DISABLED)" | tr '\r\n' ' ')" for boolean_env in "${boolean_envs[@]}"; do check_true_false_value "$boolean_env" done # InfluxDB authentication validations if ! is_boolean_yes "$INFLUXDB_HTTP_AUTH_ENABLED"; then warn "Authentication is disabled over HTTP and HTTPS. For safety reasons, enable it in a production environment." for user in "${user_envs[@]}"; do if [[ -n "${!user:-}" ]]; then warn "The ${user} environment variable will be ignored since authentication is disabled." fi done else for user in "${user_envs[@]}"; do pwd="${user/%/_PASSWORD}" if [[ -n "${!user:-}" ]] && [[ -z "${!pwd:-}" ]]; then print_validation_error "Authentication is enabled over HTTP and HTTPS and you did not provide a password for the ${!user} user. Please, specify a password for the ${!user} user by setting the '${user/%/_PASSWORD}' or '${user/%/_PASSWORD_FILE}' environment variables." fi done fi # InfluxDB port validations local -a ports_envs=("INFLUXDB_PORT_NUMBER" "INFLUXDB_HTTP_PORT_NUMBER") for p in "${ports_envs[@]}"; do if ! is_empty_value "${!p}" && ! err=$(validate_port -unprivileged "${!p}"); then print_validation_error "An invalid port was specified in the environment variable ${p}: ${err}" fi done check_conflicting_ports "${ports_envs[@]}" [[ "$error_code" -eq 0 ]] || exit "$error_code" } ######################## # Get a property's value from the the influxdb.conf file # Globals: # INFLUXDB_* # Arguments: # $1 - key # $2 - section # Returns: # None ######################### # TODO: use a golan binary (toml-parser) influxdb_conf_get() { local -r key="${1:?missing key}" # local -r section="${2:?missing section}" sed -n -e "s/^ *$key *= *//p" "$INFLUXDB_CONF_FILE" # toml-parser -r "$section" "$key" "$INFLUXDB_CONF_FILE" } ######################## # Modify the influxdb.conf file by setting a property # Globals: # INFLUXDB_* # Arguments: # $1 - section # $2 - key # $3 - value # Returns: # None ######################### # TODO: use a golan binary (toml-parser) to perform these substitutions # influxdb_conf_set() { # local -r section="${1:?missing section}" # local -r key="${2:?missing key}" # local -r value="${2:-}" # # toml-parser -w "$section" "$key" "$value" "$INFLUXDB_CONF_FILE" # } ######################## # Create basic influxdb.conf file using the example provided in the etc/ folder # Globals: # INFLUXDB_* # Arguments: # None # Returns: # None ######################### influxdb_create_config() { cp "${INFLUXDB_CONF_DIR}/influxdb.conf.default" "$INFLUXDB_CONF_FILE" # TODO: use a golan binary (toml-parser) to perform these substitutions # These settings: # - [meta] dir # - [data] dir # - [data] wal-dir # will be ignored and the values at the environment variables below will be used instead: # - INFLUXDB_META_DIR # - INFLUXDB_DATA_DIR # - INFLUXDB_DATA_WAL_DIR # However, to avoid confussion for users checking the configuration file, # we'll update them to reflec the same values. # influxdb_set_property "meta" "dir" "$INFLUXDB_META_DIR" # influxdb_set_property "data" "dir" "$INFLUXDB_DATA_DIR" # influxdb_set_property "data" "wal-dir" "$INFLUXDB_DATA_WAL_DIR" } ######################## # Start InfluxDB in background disabling authentication and waits until it's ready # Globals: # INFLUXDB_* # Arguments: # None # Returns: # None ######################### influxdb_start_bg_noauth() { info "Starting InfluxDB in background..." local start_command=("${INFLUXDB_BIN_DIR}/influxd" "-config" "$INFLUXDB_CONF_FILE") am_i_root && start_command=("gosu" "$INFLUXDB_DAEMON_USER" "${start_command[@]}") INFLUXDB_HTTP_HTTPS_ENABLED=false debug_execute "${start_command[@]}" & wait-for-port "$INFLUXDB_PORT_NUMBER" } ######################## # Check if InfluxDB is running # Globals: # INFLUXDB_* # Arguments: # None # Returns: # Boolean ######################### is_influxdb_running() { if pgrep "influxd" >/dev/null 2>&1; then true else false fi } ######################## # Stop InfluxDB # Globals: # INFLUXDB_* # Arguments: # None # Returns: # None ######################### influxdb_stop() { info "Stopping InfluxDB..." ! is_influxdb_running && return pkill --full --signal TERM "influxd" wait-for-port --state free "$INFLUXDB_PORT_NUMBER" } ######################## # Execute an arbitrary query using InfluxDB CLI # Globals: # INFLUXDB_* # Arguments: # $1 - Query to execute # $2 - Whether to use admin credentials to run the command or not # Returns: # None ######################### influxdb_execute_query() { local -r query="${1:-query is required}" local authenticate="${2:-false}" local flags=("-host" "127.0.0.1" "-port" "$INFLUXDB_HTTP_PORT_NUMBER") is_boolean_yes "$authenticate" && flags+=("-username" "${INFLUXDB_ADMIN_USER}" "-password" "${INFLUXDB_ADMIN_USER_PASSWORD}") debug_execute "${INFLUXDB_BIN_DIR}/influx" "${flags[@]}" "-execute" "$query" } ######################## # Creates the admin user # Globals: # INFLUXDB_* # Arguments: # None # Returns: # None ######################### influxdb_create_admin_user() { debug "Creating admin user..." influxdb_execute_query "CREATE USER \"${INFLUXDB_ADMIN_USER}\" WITH PASSWORD '${INFLUXDB_ADMIN_USER_PASSWORD}' WITH ALL PRIVILEGES" } ######################## # Creates a database # Globals: # INFLUXDB_* # Arguments: # $1 - Database name # Returns: # None ######################### influxdb_create_db() { local -r db="${1:?db is required}" debug "Creating database \"${db}\"..." influxdb_execute_query "CREATE DATABASE ${db}" "true" } ######################## # Creates an user # Globals: # INFLUXDB_* # Arguments: # $1 - User name # $2 - User password # Returns: # None ######################### influxdb_create_user() { local -r user="${1:?user is required}" local -r pwd="${2:?pwd is required}" debug "Creating user \"${user}\"..." influxdb_execute_query "CREATE USER \"${user}\" WITH PASSWORD '${pwd}'" "true" influxdb_execute_query "REVOKE ALL PRIVILEGES FROM \"${user}\"" "true" } ######################## # Creates a database # Globals: # INFLUXDB_* # Arguments: # $1 - User name # $2 - Database name # $3 - Role # Returns: # None ######################### influxdb_grant() { local -r user="${1:?user is required}" local -r db="${2:?db is required}" local -r role="${3:?role is required}" debug "Granting \"${role}\" permissions to user ${user} on database \"${db}\"..." influxdb_execute_query "GRANT ${role} ON \"${db}\" TO \"${user}\"" "true" } ######################## # Gets the role for an user # Arguments: # $1 - user # Returns: # String ######################### influxdb_user_role() { local role local -r user="${1:?user is required}" role="${user//_}" role="${role%USER}" role="${role#INFLUXDB}" echo "${role:-ALL}" } ######################## # Ensure InfluxDB is initialized # Globals: # INFLUXDB_* # Arguments: # None # Returns: # None ######################### influxdb_initialize() { info "Initializing InfluxDB..." # Detect custom configuration files if [[ -f "$INFLUXDB_CONF_FILE" ]]; then info "Custom configuration ${INFLUXDB_CONF_FILE} detected!" warn "The 'INFLUXDB_' environment variables override the equivalent options in the configuration file." warn "If a configuration option is not specified in either the configuration file or in an environment variable, InfluxDB uses its internal default configuration" else info "No injected configuration files found. Creating default config files..." influxdb_create_config fi local -r original_http_bind_address="$INFLUXDB_HTTP_BIND_ADDRESS" export INFLUXDB_HTTP_BIND_ADDRESS="127.0.0.1:${INFLUXDB_HTTP_PORT_NUMBER}" if is_dir_empty "$INFLUXDB_DATA_DIR"; then info "Deploying InfluxDB from scratch" if is_boolean_yes "$INFLUXDB_HTTP_AUTH_ENABLED"; then influxdb_start_bg_noauth info "Creating users and databases..." influxdb_create_admin_user [[ -n "$INFLUXDB_DB" ]] && influxdb_create_db "$INFLUXDB_DB" local -a user_envs=("INFLUXDB_USER" "INFLUXDB_READ_USER" "INFLUXDB_WRITE_USER") for user in "${user_envs[@]}"; do pwd="${user/%/_PASSWORD}" if [[ -n "${!user}" ]]; then influxdb_create_user "${!user}" "${!pwd}" [[ -n "$INFLUXDB_DB" ]] && influxdb_grant "${!user}" "$INFLUXDB_DB" "$(influxdb_user_role "$user")" fi done fi else info "Deploying InfluxDB with persisted data" fi export INFLUXDB_HTTP_BIND_ADDRESS="$original_http_bind_address" } ######################## # Run custom initialization scripts # Globals: # INFLUXDB_* # Arguments: # None # Returns: # None ######################### influxdb_custom_init_scripts() { if [[ -n $(find "${INFLUXDB_INITSCRIPTS_DIR}/" -type f -regex ".*\.\(sh\|txt\)") ]] && [[ ! -f "${INFLUXDB_INITSCRIPTS_DIR}/.user_scripts_initialized" ]] ; then info "Loading user's custom files from ${INFLUXDB_INITSCRIPTS_DIR} ..." local -r tmp_file="/tmp/filelist" if ! is_influxdb_running; then influxdb_start_bg_noauth fi find "${INFLUXDB_INITSCRIPTS_DIR}/" -type f -regex ".*\.\(sh\|txt\)" | sort > "$tmp_file" while read -r f; do case "$f" in *.sh) if [[ -x "$f" ]]; then debug "Executing $f"; "$f" else debug "Sourcing $f"; . "$f" fi ;; *.txt) debug "Executing $f"; influxdb_execute_query "$(<"$f")";; *) debug "Ignoring $f" ;; esac done < $tmp_file rm -f "$tmp_file" touch "$INFLUXDB_VOLUME_DIR"/.user_scripts_initialized fi }