mirror of
https://github.com/bitnami/containers.git
synced 2026-03-15 06:48:04 +08:00
Signed-off-by: Bitnami Containers <bitnami-bot@vmware.com> Signed-off-by: Bitnami Containers <bitnami-bot@vmware.com> Signed-off-by: Javier Salmeron Garcia <jsalmeron@vmware.com> Co-authored-by: Javier Salmeron Garcia <jsalmeron@vmware.com>
276 lines
9.6 KiB
Bash
276 lines
9.6 KiB
Bash
#!/bin/bash
|
|
#
|
|
# Bitnami ClickHouse library
|
|
|
|
# shellcheck disable=SC1090
|
|
# shellcheck disable=SC1091
|
|
|
|
# Load generic libraries
|
|
. /opt/bitnami/scripts/libfs.sh
|
|
. /opt/bitnami/scripts/libos.sh
|
|
. /opt/bitnami/scripts/libnet.sh
|
|
. /opt/bitnami/scripts/libfile.sh
|
|
. /opt/bitnami/scripts/libvalidations.sh
|
|
. /opt/bitnami/scripts/libservice.sh
|
|
|
|
########################
|
|
# Validate settings in CLICKHOUSE_* env vars
|
|
# Globals:
|
|
# CLICKHOUSE_*
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# 0 if the validation succeeded, 1 otherwise
|
|
#########################
|
|
clickhouse_validate() {
|
|
debug "Validating settings in CLICKHOUSE_* environment variables..."
|
|
local error_code=0
|
|
|
|
# Auxiliary functions
|
|
print_validation_error() {
|
|
error "$1"
|
|
error_code=1
|
|
}
|
|
check_empty_value() {
|
|
if is_empty_value "${!1}"; then
|
|
print_validation_error "${1} must be set"
|
|
fi
|
|
}
|
|
check_valid_port() {
|
|
local port_var="${1:?missing port variable}"
|
|
local err
|
|
if ! err="$(validate_port "${!port_var}")"; then
|
|
print_validation_error "An invalid port was specified in the environment variable ${port_var}: ${err}."
|
|
fi
|
|
}
|
|
|
|
# Validate user inputs
|
|
! is_empty_value "$CLICKHOUSE_HTTP_PORT" && check_valid_port "CLICKHOUSE_HTTP_PORT"
|
|
! is_empty_value "$CLICKHOUSE_TCP_PORT" && check_valid_port "CLICKHOUSE_TCP_PORT"
|
|
! is_empty_value "$CLICKHOUSE_MYSQL_PORT" && check_valid_port "CLICKHOUSE_MYSQL_PORT"
|
|
! is_empty_value "$CLICKHOUSE_POSTGRESQL_PORT" && check_valid_port "CLICKHOUSE_POSTGRESQL_PORT"
|
|
! is_empty_value "$CLICKHOUSE_INTERSERVER_HTTP_PORT" && check_valid_port "CLICKHOUSE_INTERSERVER_HTTP_PORT"
|
|
|
|
# Validate credentials
|
|
if is_boolean_yes "${ALLOW_EMPTY_PASSWORD:-}"; then
|
|
warn "You set the environment variable ALLOW_EMPTY_PASSWORD=${ALLOW_EMPTY_PASSWORD:-}. For safety reasons, do not use this flag in a production environment."
|
|
elif is_empty_value "$CLICKHOUSE_ADMIN_PASSWORD"; then
|
|
print_validation_error "The CLICKHOUSE_ADMIN_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow a blank password. This is only recommended for development environments."
|
|
fi
|
|
|
|
return "$error_code"
|
|
}
|
|
|
|
########################
|
|
# Copy configuration from the mounted folder to the etc folder
|
|
# In charts mounting directly in the configuration folder would not
|
|
# allow the use of multiple ConfigMaps and Secrets
|
|
# Globals:
|
|
# CLICKHOUSE_*
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# None
|
|
#########################
|
|
clickhouse_copy_mounted_configuration() {
|
|
if [[ -w "$CLICKHOUSE_CONF_DIR" ]]; then
|
|
if ! is_mounted_dir_empty "$CLICKHOUSE_MOUNTED_CONF_DIR"; then
|
|
info "Copying mounted configuration from $CLICKHOUSE_MOUNTED_CONF_DIR"
|
|
# Copy first the files at the base of the mounted folder to go to ClickHouse
|
|
# base etc folder
|
|
find "$CLICKHOUSE_MOUNTED_CONF_DIR" -maxdepth 1 -type f -exec cp -L {} "$CLICKHOUSE_CONF_DIR" \;
|
|
|
|
# The ClickHouse override directories (etc/conf.d and etc/users.d) do not support subfolders. That means we cannot
|
|
# copy directly with cp -RL because we need all override xml files to have at the root of these subfolders. In the helm
|
|
# chart we want to allow overrides from different ConfigMaps and Secrets so we need to use the find command
|
|
find "${CLICKHOUSE_MOUNTED_CONF_DIR}/conf.d" -type f -exec cp -L {} "${CLICKHOUSE_CONF_DIR}/conf.d" \;
|
|
find "${CLICKHOUSE_MOUNTED_CONF_DIR}/users.d" -type f -exec cp -L {} "${CLICKHOUSE_CONF_DIR}/users.d" \;
|
|
fi
|
|
else
|
|
warn "The folder $CLICKHOUSE_CONF_DIR is not writable. This is likely because a read-only filesystem was mounted in that folder. Using $CLICKHOUSE_MOUNTED_DIR is recommended"
|
|
fi
|
|
}
|
|
|
|
########################
|
|
# Add or modify an entry in the ClickHouse configuration file
|
|
# Globals:
|
|
# CLICKHOUSE_*
|
|
# Arguments:
|
|
# $1 - XPath expression
|
|
# $2 - Value to assign to the variable
|
|
# $3 - Configuration file
|
|
# Returns:
|
|
# None
|
|
#########################
|
|
clickhouse_conf_set() {
|
|
local -r xpath="${1:?key missing}"
|
|
# We allow empty values
|
|
local -r value="${2:-}"
|
|
local -r config_file="${3:-$CLICKHOUSE_CONF_FILE}"
|
|
debug "Setting ${xpath} to '${value}' in ClickHouse configuration file $config_file"
|
|
# Check if the entry exists in the XML file
|
|
if xmlstarlet --quiet sel -t -v "$xpath" "$config_file"; then
|
|
# Base case
|
|
# It exists, so replace the entry
|
|
if ! is_empty_value "$value"; then
|
|
xmlstarlet ed -L -u "$xpath" -v "$value" "$config_file"
|
|
fi
|
|
else
|
|
# It does not exist, so add the subnode
|
|
local -r parentNode="$(dirname "$xpath")"
|
|
local -r newNode="$(basename "$xpath")"
|
|
# Recursive call to add parent nodes
|
|
clickhouse_conf_set "$parentNode"
|
|
if is_empty_value "$value"; then
|
|
xmlstarlet ed -L --subnode "${parentNode}" -t "elem" -n "${newNode}" "$config_file"
|
|
else
|
|
xmlstarlet ed -L --subnode "${parentNode}" -t "elem" -n "${newNode}" -v "$value" "$config_file"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
########################
|
|
# Check if ClickHouse daemon is running
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# Boolean
|
|
#########################
|
|
is_clickhouse_running() {
|
|
pid="$(get_pid_from_file "$CLICKHOUSE_PID_FILE")"
|
|
if [[ -n "$pid" ]]; then
|
|
is_service_running "$pid"
|
|
else
|
|
false
|
|
fi
|
|
}
|
|
|
|
########################
|
|
# Check if ClickHouse daemon is not running
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# Boolean
|
|
#########################
|
|
is_clickhouse_not_running() {
|
|
! is_clickhouse_running
|
|
}
|
|
|
|
########################
|
|
# Stop ClickHouse daemons
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# None
|
|
#########################
|
|
clickhouse_stop() {
|
|
! is_clickhouse_running && return
|
|
stop_service_using_pid "$CLICKHOUSE_PID_FILE"
|
|
}
|
|
|
|
########################
|
|
# Initialize ClickHouse
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# None
|
|
#########################
|
|
clickhouse_initialize() {
|
|
# Logic based on the upstream ClickHouse container
|
|
# For the container itself we keep the logic simple. In the helm chart we rely on the mounting of configuration files with overrides
|
|
# Source: https://github.com/ClickHouse/ClickHouse/blob/master/docker/server/entrypoint.sh
|
|
|
|
# This fixes an issue where the trap would kill the entrypoint.sh, if a PID was left over from a previous run
|
|
# Exec replaces the process without creating a new one, and when the container is restarted it may have the same PID
|
|
rm -f "$CLICKHOUSE_PID_FILE"
|
|
|
|
clickhouse_copy_mounted_configuration
|
|
if [[ "$CLICKHOUSE_ADMIN_USER" != "default" ]]; then
|
|
# If we need to set an admin user different from default, we create a configuration override
|
|
local -r admin_user_override="${CLICKHOUSE_CONF_DIR}/users.d/__bitnami_default_user.xml"
|
|
cat <<EOF >"${admin_user_override}"
|
|
<clickhouse>
|
|
<!-- Docs: <https://clickhouse.com/docs/en/operations/settings/settings_users/> -->
|
|
<users>
|
|
<!-- Remove default user -->
|
|
<default remove="remove">
|
|
</default>
|
|
|
|
<${CLICKHOUSE_ADMIN_USER}>
|
|
<profile>default</profile>
|
|
<password from_env="CLICKHOUSE_ADMIN_PASSWORD"></password>
|
|
<networks>
|
|
<ip>::/0</ip>
|
|
</networks>
|
|
<quota>default</quota>
|
|
<access_management>1</access_management>
|
|
</${CLICKHOUSE_ADMIN_USER}>
|
|
</users>
|
|
</clickhouse>
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
########################
|
|
# Start ClickHouse daemon
|
|
# Arguments:
|
|
# $1 - Log file to check the startup message
|
|
# Returns:
|
|
# None
|
|
#########################
|
|
clickhouse_start_bg() {
|
|
local -r log_file="${1:-$CLICKHOUSE_LOG_FILE}"
|
|
info "Starting ClickHouse in background"
|
|
is_clickhouse_running && return
|
|
# This function is meant to be called for internal operations like the init scripts
|
|
local -r cmd=("${CLICKHOUSE_BASE_DIR}/bin/clickhouse-server")
|
|
local -r args=("--config-file=${CLICKHOUSE_CONF_FILE}" "--pid-file=${CLICKHOUSE_PID_FILE}" "--" "--listen_host=127.0.0.1")
|
|
if am_i_root; then
|
|
gosu "$CLICKHOUSE_DAEMON_USER" "${cmd[@]}" "${args[@]}" >"$log_file" 2>&1 &
|
|
else
|
|
"${cmd[@]}" "${args[@]}" >"$log_file" 2>&1 &
|
|
fi
|
|
if ! retry_while is_clickhouse_running; then
|
|
error "ClickHouse failed to start"
|
|
exit 1
|
|
fi
|
|
wait_for_log_entry "Application: Ready for connections" "$log_file"
|
|
info "ClickHouse started successfully"
|
|
}
|
|
|
|
########################
|
|
# Run custom scripts
|
|
# Globals:
|
|
# CLICKHOUSE_*
|
|
# Arguments:
|
|
# $1 - 'init' or 'start' ('init' runs on first container start, 'start' runs everytime the container starts)
|
|
# Returns:
|
|
# None
|
|
#########################
|
|
clickhouse_custom_scripts() {
|
|
if [[ -n $(find /docker-entrypoint-"$1"db.d/ -type f -regex ".*\.sh") ]] && { [[ ! -f "$CLICKHOUSE_DATA_DIR/.user_scripts_initialized" ]] || [[ $1 == start ]]; }; then
|
|
clickhouse_start_bg "$CLICKHOUSE_LOG_DIR/clickhouse_init_scripts.log"
|
|
info "Loading user's custom files from /docker-entrypoint-$1db.d"
|
|
for f in /docker-entrypoint-"$1"db.d/*; do
|
|
debug "Executing $f"
|
|
case "$f" in
|
|
*.sh)
|
|
if [[ -x "$f" ]]; then
|
|
if ! "$f"; then
|
|
error "Failed executing $f"
|
|
return 1
|
|
fi
|
|
else
|
|
warn "Sourcing $f as it is not executable by the current user, any error may cause initialization to fail"
|
|
. "$f"
|
|
fi
|
|
;;
|
|
*)
|
|
warn "Skipping $f, supported formats are: .sh"
|
|
;;
|
|
esac
|
|
done
|
|
touch "${CLICKHOUSE_DATA_DIR}/.user_scripts_initialized"
|
|
fi
|
|
}
|