#!/bin/bash
# Copyright VMware, Inc.
# SPDX-License-Identifier: APACHE-2.0
#
# Bitnami MongoDB library

# shellcheck disable=SC1090,SC1091

# Load Generic Libraries
. /opt/bitnami/scripts/libfile.sh
. /opt/bitnami/scripts/liblog.sh
. /opt/bitnami/scripts/libservice.sh
. /opt/bitnami/scripts/libvalidations.sh
. /opt/bitnami/scripts/libos.sh
. /opt/bitnami/scripts/libfs.sh
. /opt/bitnami/scripts/libnet.sh

########################
# Return field separator to use in lists. One of comma or semi-colon, comma
# being preferred.
# Globals:
#   None
# Arguments:
#   A (list) of fields
# Returns:
#   The separator used within that list
#########################
mongodb_field_separator() {
    if printf %s\\n "$1" | grep -q ','; then
        echo ','
    elif printf %s\\n "$1" | grep -q ';'; then
        echo ';'
    fi
}

########################
# Initialise the arrays databases, usernames and passwords to contain the
# fields from their respective environment variables.
# Globals:
#   MONGODB_EXTRA_DATABASES, MONGODB_EXTRA_USERNAMES, MONGODB_EXTRA_PASSWORDS
#   MONGODB_DATABASE, MONGODB_USERNAME, MONGODB_PASSWORD
# Arguments:
#   $1 - single: initialise based on MONGODB_DATABASE, MONGODB_USERNAME, MONGODB_PASSWORD
#   $1 - extra: initialise based on MONGODB_EXTRA_DATABASES, MONGODB_EXTRA_USERNAMES, MONGODB_EXTRA_PASSWORDS
#   $1 - all (or empty): initalise as both of the above
# Returns:
#   None
#########################
mongodb_auth() {
    case "${1:-all}" in
    extra)
        local -a databases_extra
        local -a usernames_extra
        local -a passwords_extra
        # Start by filling in locally scoped databases, usernames and
        # passwords arrays with the content of the _EXTRA_ environment
        # variables.
        IFS="$(mongodb_field_separator "$MONGODB_EXTRA_DATABASES")" read -r -a databases_extra <<<"$MONGODB_EXTRA_DATABASES"
        IFS="$(mongodb_field_separator "$MONGODB_EXTRA_USERNAMES")" read -r -a usernames_extra <<<"$MONGODB_EXTRA_USERNAMES"
        IFS="$(mongodb_field_separator "$MONGODB_EXTRA_PASSWORDS")" read -r -a passwords_extra <<<"$MONGODB_EXTRA_PASSWORDS"
        # Force missing empty passwords/database names (occurs when
        # MONGODB_EXTRA_PASSWORDS/DATABASES ends with a separator, e.g. a
        # comma or semi-colon), then copy into the databases, usernames and
        # passwords arrays (global).
        for ((i = 0; i < ${#usernames_extra[@]}; i++)); do
            if [[ -z "${passwords_extra[i]:-}" ]]; then
                passwords_extra[i]=""
            fi
            if [[ -z "${databases_extra[i]:-}" ]]; then
                databases_extra[i]=""
            fi
            databases+=("${databases_extra[i]}")
            usernames+=("${usernames_extra[i]}")
            passwords+=("${passwords_extra[i]}")
        done
        ;;
    single)
        # Add the content of the "regular" environment variables to the arrays
        databases+=("$MONGODB_DATABASE")
        usernames+=("$MONGODB_USERNAME")
        passwords+=("$MONGODB_PASSWORD")
        ;;
    all)
        # Perform the following in this order to respect the priority of the
        # environment variables.
        mongodb_auth single
        mongodb_auth extra
        ;;
    esac
}

########################
# Validate settings in MONGODB_* env. variables
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_validate() {
    info "Validating settings in MONGODB_* env vars..."

    local error_message=""
    local -r replicaset_error_message="In order to configure MongoDB replica set authentication you \
need to provide the MONGODB_REPLICA_SET_KEY on every node, specify MONGODB_ROOT_PASSWORD \
in the primary node and MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD in the rest of nodes"
    local error_code=0
    local usernames databases passwords

    # Auxiliary functions
    print_validation_error() {
        error "$1"
        error_code=1
    }

    check_yes_no_value() {
        if ! is_yes_no_value "${!1}" && ! is_true_false_value "${!1}"; then
            print_validation_error "The allowed values for ${1} are: yes no"
        fi
    }

    if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then
        if [[ "$MONGODB_REPLICA_SET_MODE" =~ ^(secondary|arbiter|hidden) ]]; then
            if [[ -z "$MONGODB_INITIAL_PRIMARY_HOST" ]]; then
                error_message="In order to configure MongoDB as a secondary or arbiter node \
you need to provide the MONGODB_INITIAL_PRIMARY_HOST env var"
                print_validation_error "$error_message"
            fi
            if { [[ -n "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" ]] && [[ -z "$MONGODB_REPLICA_SET_KEY" ]]; } ||
                { [[ -z "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" ]] && [[ -n "$MONGODB_REPLICA_SET_KEY" ]]; }; then
                print_validation_error "$replicaset_error_message"
            fi
            if [[ -n "$MONGODB_ROOT_PASSWORD" ]]; then
                error_message="MONGODB_ROOT_PASSWORD shouldn't be set on a 'non-primary' node"
                print_validation_error "$error_message"
            fi
        elif [[ "$MONGODB_REPLICA_SET_MODE" = "primary" ]]; then
            if { [[ -n "$MONGODB_ROOT_PASSWORD" ]] && [[ -z "$MONGODB_REPLICA_SET_KEY" ]]; } ||
                { [[ -z "$MONGODB_ROOT_PASSWORD" ]] && [[ -n "$MONGODB_REPLICA_SET_KEY" ]]; }; then
                print_validation_error "$replicaset_error_message"
            fi
            if [[ -n "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" ]]; then
                error_message="MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD shouldn't be set on a 'primary' node"
                print_validation_error "$error_message"
            fi
            if [[ -z "$MONGODB_ROOT_PASSWORD" ]] && ! is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then
                error_message="The MONGODB_ROOT_PASSWORD environment variable is empty or not set. \
Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. \
This is only recommended for development."
                print_validation_error "$error_message"
            fi
        else
            error_message="You set the environment variable MONGODB_REPLICA_SET_MODE with an invalid value. \
Available options are 'primary/secondary/arbiter/hidden'"
            print_validation_error "$error_message"
        fi
    fi

    check_yes_no_value "MONGODB_ENABLE_MAJORITY_READ"
    [[ "$(mongodb_get_version)" =~ ^5\..\. ]] && ! is_boolean_yes "$MONGODB_ENABLE_MAJORITY_READ" && warn "MONGODB_ENABLE_MAJORITY_READ=${MONGODB_ENABLE_MAJORITY_READ} Will be ignored in MongoDB 5.0"

    if [[ -n "$MONGODB_REPLICA_SET_KEY" ]] && ((${#MONGODB_REPLICA_SET_KEY} < 5)); then
        error_message="MONGODB_REPLICA_SET_KEY must be, at least, 5 characters long!"
        print_validation_error "$error_message"
    fi

    if [[ -n "$MONGODB_EXTRA_USERNAMES" ]]; then
        # Capture list of extra (only!) users, passwords and databases in the
        # usernames, passwords and databases arrays.
        mongodb_auth extra

        # Verify there as many usernames as passwords
        if [[ "${#usernames[@]}" -ne "${#passwords[@]}" ]]; then
            print_validation_error "Specify the same number of passwords on MONGODB_EXTRA_PASSWORDS as the number of users in MONGODB_EXTRA_USERNAMES"
        fi
        # When we have a list of databases, there should be as many databases as
        # users (thus as passwords).
        if [[ -n "$MONGODB_EXTRA_DATABASES" ]] && [[ "${#usernames[@]}" -ne "${#databases[@]}" ]]; then
            print_validation_error "Specify the same number of users on MONGODB_EXTRA_USERNAMES as the number of databases in MONGODB_EXTRA_DATABASES"
        fi
        # When the list of database is empty, then all users will be added to
        # default database.
        if [[ -z "$MONGODB_EXTRA_DATABASES" ]]; then
            warn "All users specified in MONGODB_EXTRA_USERNAMES will be added to the default database called 'test'"
        fi
    fi

    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 { [[ -n "$MONGODB_EXTRA_USERNAMES" ]] || [[ -n "$MONGODB_USERNAME" ]]; } && [[ -z "$MONGODB_ROOT_PASSWORD" ]]; then
        # Authorization is turned on as soon as a set of users or a root
        # password are given. If we have a set of users, but an empty root
        # password, validation should fail unless ALLOW_EMPTY_PASSWORD is turned
        # on.
        error_message="The MONGODB_ROOT_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with a blank root password. This is only recommended for development."
        print_validation_error "$error_message"
    fi

    # Warn for users with empty passwords, as these won't be created. Maybe
    # should we just end with an error here instead?
    if [[ -n "$MONGODB_EXTRA_USERNAMES" ]]; then
        # Here we can access the arrays usernames and passwordsa, as these have
        # been initialised earlier on.
        for ((i = 0; i < ${#passwords[@]}; i++)); do
            if [[ -z "${passwords[i]}" ]]; then
                warn "User ${usernames[i]} will not be created as its password is empty or not set. MongoDB cannot create users with blank passwords."
            fi
        done
    fi
    if [[ -n "$MONGODB_USERNAME" ]] && [[ -z "$MONGODB_PASSWORD" ]]; then
        warn "User $MONGODB_USERNAME will not be created as its password is empty or not set. MongoDB cannot create users with blank passwords."
    fi
    if ! is_boolean_yes "$ALLOW_EMPTY_PASSWORD" && [[ -n "$MONGODB_METRICS_USERNAME" ]] && [[ -z "$MONGODB_METRICS_PASSWORD" ]]; then
        error_message="The MONGODB_METRICS_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is only recommended for development."
        print_validation_error "$error_message"
    fi

    [[ "$error_code" -eq 0 ]] || exit "$error_code"
}

########################
# Copy mounted configuration files
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_copy_mounted_config() {
    if ! is_dir_empty "$MONGODB_MOUNTED_CONF_DIR"; then
        if ! cp -Lr "$MONGODB_MOUNTED_CONF_DIR"/* "$MONGODB_CONF_DIR"; then
            error "Issue copying mounted configuration files from $MONGODB_MOUNTED_CONF_DIR to $MONGODB_CONF_DIR. Make sure you are not mounting configuration files in $MONGODB_CONF_DIR and $MONGODB_MOUNTED_CONF_DIR at the same time"
            exit 1
        fi
    fi
}

########################
# Determine the hostname by which to contact the locally running mongo daemon
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   The value of get_machine_ip, $MONGODB_ADVERTISED_HOSTNAME or the current host address
########################
get_mongo_hostname() {
    if is_boolean_yes "$MONGODB_ADVERTISE_IP"; then
        get_machine_ip
    elif [[ -n "$MONGODB_ADVERTISED_HOSTNAME" ]]; then
        echo "$MONGODB_ADVERTISED_HOSTNAME"
    else
        hostname
    fi
}

########################
# Determine the port on which to contact the locally running mongo daemon
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   The value of $MONGODB_ADVERTISED_PORT_NUMBER or $MONGODB_PORT_NUMBER
########################
get_mongo_port() {
    if [[ -n "$MONGODB_ADVERTISED_PORT_NUMBER" ]]; then
        echo "$MONGODB_ADVERTISED_PORT_NUMBER"
    else
        echo "$MONGODB_PORT_NUMBER"
    fi
}

########################
# Drop local Database
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_drop_local_database() {
    info "Dropping local database to reset replica set setup..."
    local command=("mongodb_execute")

    if [[ -n "$MONGODB_USERNAME" ]] || [[ -n "$MONGODB_EXTRA_USERNAMES" ]]; then
        local usernames passwords databases
        mongodb_auth
        command=("${command[@]}" "${usernames[0]}" "${passwords[0]}")
    fi
    "${command[@]}" <<EOF
db.getSiblingDB('local').dropDatabase()
EOF
}

########################
# Check if MongoDB is running
# Globals:
#   MONGODB_PID_FILE
# Arguments:
#   None
# Returns:
#   Boolean
#########################
is_mongodb_running() {
    local pid
    pid="$(get_pid_from_file "$MONGODB_PID_FILE")"

    if [[ -z "$pid" ]]; then
        false
    else
        is_service_running "$pid"
    fi
}

########################
# Check if MongoDB is not running
# Globals:
#   MONGODB_PID_FILE
# Arguments:
#   None
# Returns:
#   Boolean
#########################
is_mongodb_not_running() {
    ! is_mongodb_running
    return "$?"
}

########################
# Retart MongoDB service
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_restart() {
    mongodb_stop
    mongodb_start_bg "$MONGODB_CONF_FILE"
}

########################
# Start MongoDB server in the background and waits until it's ready
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - Path to MongoDB configuration file
# Returns:
#   None
#########################
mongodb_start_bg() {
    # Use '--fork' option to enable daemon mode
    # ref: https://docs.mongodb.com/manual/reference/program/mongod/#cmdoption-mongod-fork
    local -r conf_file="${1:-$MONGODB_CONF_FILE}"
    local flags=("--fork" "--config=$conf_file")
    if [[ -n "${MONGODB_EXTRA_FLAGS:-}" ]]; then
        local extra_flags_array=()
        read -r -a extra_flags_array <<<"$MONGODB_EXTRA_FLAGS"
        [[ "${#extra_flags_array[@]}" -gt 0 ]] && flags+=("${extra_flags_array[@]}")
    fi

    debug "Starting MongoDB in background..."

    is_mongodb_running && return

    if am_i_root; then
        if is_boolean_yes "$MONGODB_ENABLE_NUMACTL"; then
            debug_execute run_as_user "$MONGODB_DAEMON_USER" numactl --interleave=all "$MONGODB_BIN_DIR/mongod" "${flags[@]}"
        else
            debug_execute run_as_user "$MONGODB_DAEMON_USER" "$MONGODB_BIN_DIR/mongod" "${flags[@]}"
        fi
    else
        if is_boolean_yes "$MONGODB_ENABLE_NUMACTL"; then
            debug_execute numactl --interleave=all "$MONGODB_BIN_DIR/mongod" "${flags[@]}"
        else
            debug_execute "$MONGODB_BIN_DIR/mongod" "${flags[@]}"
        fi
    fi

    # wait until the server is up and answering queries
    if ! retry_while "mongodb_is_mongodb_started" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
        error "MongoDB did not start"
        exit 1
    fi
}

########################
# Check if mongo is accepting requests
# Globals:
#   MONGODB_DATABASE and MONGODB_EXTRA_DATABASES
# Arguments:
#   None
# Returns:
#   Boolean
#########################
mongodb_is_mongodb_started() {
    local result

    result=$(
        mongodb_execute_print_output <<EOF
db
EOF
    )
    [[ -n "$result" ]]
}

########################
# Stop MongoDB
# Globals:
#   MONGODB_PID_FILE
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_stop() {
    ! is_mongodb_running && return
    info "Stopping MongoDB..."

    stop_service_using_pid "$MONGODB_PID_FILE"
    if ! retry_while "is_mongodb_not_running" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
        error "MongoDB failed to stop"
        exit 1
    fi
}

########################
# Apply regex in MongoDB configuration file
# Globals:
#   MONGODB_CONF_FILE
# Arguments:
#   $1 - match regex
#   $2 - substitute regex
# Returns:
#   None
#########################
mongodb_config_apply_regex() {
    local -r match_regex="${1:?match_regex is required}"
    local -r substitute_regex="${2:?substitute_regex is required}"
    local -r conf_file_path="${3:-$MONGODB_CONF_FILE}"
    local mongodb_conf

    mongodb_conf="$(sed -E "s@$match_regex@$substitute_regex@" "$conf_file_path")"
    echo "$mongodb_conf" >"$conf_file_path"
}

########################
# Change common logging settings
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_set_log_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"
    if ! mongodb_is_file_external "$conf_file_name"; then
        if [[ -n "$MONGODB_DISABLE_SYSTEM_LOG" ]]; then
            mongodb_config_apply_regex "quiet:.*" "quiet: $({ is_boolean_yes "$MONGODB_DISABLE_SYSTEM_LOG" && echo 'true'; } || echo 'false')" "$conf_file_path"
        fi
        if [[ -n "$MONGODB_SYSTEM_LOG_VERBOSITY" ]]; then
            mongodb_config_apply_regex "verbosity:.*" "verbosity: $MONGODB_SYSTEM_LOG_VERBOSITY" "$conf_file_path"
        fi
    else
        debug "$conf_file_name mounted. Skipping setting log settings"
    fi
}

########################
# Change journaling setting
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_set_journal_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"
    local mongodb_conf

    if ! mongodb_is_file_external "$conf_file_name"; then
        # Disable journal.enabled since it is not supported from 7.0 on
        if [[ "$(mongodb_get_version)" =~ ^7\..\. ]]; then
            mongodb_conf="$(sed '/journal:/,/enabled: .*/d' "$conf_file_path")"
            echo "$mongodb_conf" >"$conf_file_path"
        else
            if [[ -n "$MONGODB_ENABLE_JOURNAL" ]]; then
                mongodb_conf="$(sed -E "/^ *journal:/,/^ *[^:]*:/s/enabled:.*/enabled: $({ is_boolean_yes "$MONGODB_ENABLE_JOURNAL" && echo 'true'; } || echo 'false')/" "$conf_file_path")"
                echo "$mongodb_conf" >"$conf_file_path"
            fi
        fi
    else
        debug "$conf_file_name mounted. Skipping setting log settings"
    fi
}

########################
# Change common storage settings
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_set_storage_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"

    if ! mongodb_is_file_external "$conf_file_name"; then
        if [[ -n "$MONGODB_ENABLE_DIRECTORY_PER_DB" ]]; then
            mongodb_config_apply_regex "directoryPerDB:.*" "directoryPerDB: $({ is_boolean_yes "$MONGODB_ENABLE_DIRECTORY_PER_DB" && echo 'true'; } || echo 'false')" "$conf_file_path"
        fi
    else
        debug "$conf_file_name mounted. Skipping setting storage settings"
    fi
}

########################
# Change common network settings
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_set_net_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"

    if ! mongodb_is_file_external "$conf_file_name"; then
        if [[ -n "$MONGODB_PORT_NUMBER" ]]; then
            mongodb_config_apply_regex "port:.*" "port: $MONGODB_PORT_NUMBER" "$conf_file_path"
        fi
        if [[ -n "$MONGODB_ENABLE_IPV6" ]]; then
            mongodb_config_apply_regex "ipv6:.*" "ipv6: $({ is_boolean_yes "$MONGODB_ENABLE_IPV6" && echo 'true'; } || echo 'false')" "$conf_file_path"
        fi
    else
        debug "$conf_file_name mounted. Skipping setting port and IPv6 settings"
    fi
}
########################
# Change bind ip address to 0.0.0.0
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_set_listen_all_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"

    if ! mongodb_is_file_external "$conf_file_name"; then
        mongodb_config_apply_regex "#?bindIp:.*" "#bindIp:" "$conf_file_path"
        mongodb_config_apply_regex "#?bindIpAll:.*" "bindIpAll: true" "$conf_file_path"
    else
        debug "$conf_file_name mounted. Skipping IP binding to all addresses"
    fi
}

########################
# Disable javascript
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_disable_javascript_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"

    if ! mongodb_is_file_external "$conf_file_name"; then
        mongodb_config_apply_regex "#?security:" "security:\n  javascriptEnabled: false" "$conf_file_path"
    else
        debug "$conf_file_name mounted. Skipping disabling javascript"
    fi
}

########################
# Enable Auth
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Return
#   None
#########################
mongodb_set_auth_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"

    local authorization

    if ! mongodb_is_file_external "$conf_file_name"; then
        if [[ -n "$MONGODB_ROOT_PASSWORD" ]] || [[ -n "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" ]] || [[ -n "$MONGODB_PASSWORD" ]]; then
            authorization="$(yq eval .security.authorization "$MONGODB_CONF_FILE")"
            if [[ "$authorization" = "disabled" ]]; then

                info "Enabling authentication..."
                # TODO: replace 'sed' calls with 'yq' once 'yq write' does not remove comments
                mongodb_config_apply_regex "#?authorization:.*" "authorization: enabled" "$conf_file_path"
                mongodb_config_apply_regex "#?enableLocalhostAuthBypass:.*" "enableLocalhostAuthBypass: false" "$conf_file_path"
            fi
        fi
    else
        debug "$conf_file_name mounted. Skipping authorization enabling"
    fi
}

########################
# Enable ReplicaSetMode
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_set_replicasetmode_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"

    if ! mongodb_is_file_external "$conf_file_name"; then
        mongodb_config_apply_regex "#?replication:.*" "replication:" "$conf_file_path"
        mongodb_config_apply_regex "#?replSetName:" "replSetName:" "$conf_file_path"
        mongodb_config_apply_regex "#?enableMajorityReadConcern:.*" "enableMajorityReadConcern:" "$conf_file_path"
        if [[ -n "$MONGODB_REPLICA_SET_NAME" ]]; then
            mongodb_config_apply_regex "replSetName:.*" "replSetName: $MONGODB_REPLICA_SET_NAME" "$conf_file_path"
        fi
        if [[ -n "$MONGODB_ENABLE_MAJORITY_READ" ]]; then
            mongodb_config_apply_regex "enableMajorityReadConcern:.*" "enableMajorityReadConcern: $({ (is_boolean_yes "$MONGODB_ENABLE_MAJORITY_READ" || [[ "$(mongodb_get_version)" =~ ^5\..\. ]]) && echo 'true'; } || echo 'false')" "$conf_file_path"
        fi
    else
        debug "$conf_file_name mounted. Skipping replicaset mode enabling"
    fi
}

########################
# Create a MongoDB user and provide read/write permissions on a database
# Globals:
#   MONGODB_ROOT_PASSWORD
# Arguments:
#   $1 - Name of user
#   $2 - Password for user
#   $3 - Name of database (empty for default database)
# Returns:
#   None
#########################
mongodb_create_user() {
    local -r user="${1:?user is required}"
    local -r password="${2:-}"
    local -r database="${3:-}"
    local query

    if [[ -z "$password" ]]; then
        warn "Cannot create user '$user', no password provided"
        return 0
    fi
    # Build proper query (default database or specific one)
    query="db.getSiblingDB('$database').createUser({ user: '$user', pwd: '$password', roles: [{role: 'readWrite', db: '$database'}] })"
    [[ -z "$database" ]] && query="db.getSiblingDB(db.stats().db).createUser({ user: '$user', pwd: '$password', roles: [{role: 'readWrite', db: db.getSiblingDB(db.stats().db).stats().db }] })"
    # Create user, discarding mongo CLI output for clean logs
    info "Creating user '$user'..."
    mongodb_execute "$MONGODB_ROOT_USER" "$MONGODB_ROOT_PASSWORD" "" "127.0.0.1" <<<"$query"
}

########################
# Create the appropriate users
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_create_users() {
    info "Creating users..."

    if [[ -n "$MONGODB_ROOT_PASSWORD" ]] && ! [[ "$MONGODB_REPLICA_SET_MODE" =~ ^(secondary|arbiter|hidden) ]]; then
        info "Creating $MONGODB_ROOT_USER user..."
        mongodb_execute "" "" "" "127.0.0.1" <<EOF
db.getSiblingDB('admin').createUser({ user: '$MONGODB_ROOT_USER', pwd: '$MONGODB_ROOT_PASSWORD', roles: [{role: 'root', db: 'admin'}] })
EOF
    fi

    if [[ -n "$MONGODB_USERNAME" ]]; then
        mongodb_create_user "$MONGODB_USERNAME" "$MONGODB_PASSWORD" "$MONGODB_DATABASE"
    fi
    if [[ -n "$MONGODB_EXTRA_USERNAMES" ]]; then
        local databases usernames passwords

        # Fill in arrays called databases, usernames and passwords with
        # information from matching environment variables.
        mongodb_auth extra
        if [[ -n "$MONGODB_EXTRA_DATABASES" ]]; then
            # Loop over the databases, usernames and passwords arrays, creating
            # each user in the database at the same index.
            for ((i = 0; i < ${#databases[@]}; i++)); do
                mongodb_create_user "${usernames[i]}" "${passwords[i]}" "${databases[i]}"
            done
        else
            # Loop over all users and create them within the default database.
            for ((i = 0; i < ${#usernames[@]}; i++)); do
                mongodb_create_user "${usernames[i]}" "${passwords[i]}"
            done
        fi
    fi

    if [[ -n "$MONGODB_METRICS_USERNAME" ]] && [[ -n "$MONGODB_METRICS_PASSWORD" ]]; then
        info "Creating '$MONGODB_METRICS_USERNAME' user..."
        mongodb_execute "$MONGODB_ROOT_USER" "$MONGODB_ROOT_PASSWORD" "" "127.0.0.1" <<EOF
db.getSiblingDB('admin').createUser({ user: '$MONGODB_METRICS_USERNAME', pwd: '$MONGODB_METRICS_PASSWORD', roles: [{role: 'clusterMonitor', db: 'admin'},{ role: 'read', db: 'local' }] })
EOF
    fi
    info "Users created"
}

########################
# Set the path to the keyfile in mongodb.conf
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_set_keyfile_conf() {
    local -r conf_file_path="${1:-$MONGODB_CONF_FILE}"
    local -r conf_file_name="${conf_file_path#"$MONGODB_CONF_DIR"}"

    if ! mongodb_is_file_external "$conf_file_name"; then
        mongodb_config_apply_regex "#?keyFile:.*" "keyFile: $MONGODB_KEY_FILE" "$conf_file_path"
    else
        debug "$conf_file_name mounted. Skipping keyfile location configuration"
    fi
}

########################
# Create the replica set key file
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - key
# Returns:
#   None
#########################
mongodb_create_keyfile() {
    local -r key="${1:?key is required}"

    if ! mongodb_is_file_external "keyfile"; then
        info "Writing keyfile for replica set authentication..."
        echo "$key" >"$MONGODB_KEY_FILE"

        chmod 600 "$MONGODB_KEY_FILE"

        if am_i_root; then
            configure_permissions "$MONGODB_KEY_FILE" "$MONGODB_DAEMON_USER" "$MONGODB_DAEMON_GROUP" "" "600"
        else
            chmod 600 "$MONGODB_KEY_FILE"
        fi
    else
        debug "keyfile mounted. Skipping keyfile generation"
    fi
}

########################
# Get if primary node is initialized
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   None
#########################
mongodb_is_primary_node_initiated() {
    local node="${1:?node is required}"
    local port="${2:?port is required}"
    local result
    result=$(
        mongodb_execute_print_output "$MONGODB_ROOT_USER" "$MONGODB_ROOT_PASSWORD" "admin" "127.0.0.1" "$MONGODB_PORT_NUMBER" <<EOF
rs.initiate({"_id":"$MONGODB_REPLICA_SET_NAME", "members":[{"_id":0,"host":"$node:$port","priority":5}]})
EOF
    )

    # Code 23 is considered OK
    # It indicates that the node is already initialized
    if grep -q "already initialized" <<<"$result"; then
        warn "Node already initialized."
        return 0
    fi

    if ! grep -q "ok: 1" <<<"$result"; then
        warn "Problem initiating replica set
            request: rs.initiate({\"_id\":\"$MONGODB_REPLICA_SET_NAME\", \"members\":[{\"_id\":0,\"host\":\"$node:$port\",\"priority\":5}]})
            response: $result"
        return 1
    fi
}

########################
# Set "Default Write Concern"
# https://docs.mongodb.com/manual/reference/command/setDefaultRWConcern/
# Globals:
#   MONGODB_*
# Returns:
#   Boolean
#########################
mongodb_set_dwc() {
    local result

    result=$(
        mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
db.adminCommand({"setDefaultRWConcern" : 1, "defaultWriteConcern" : {"w" : "majority"}})
EOF
    )
    if grep -q "ok: 1" <<<"$result"; then
        debug 'Setting Default Write Concern to {"setDefaultRWConcern" : 1, "defaultWriteConcern" : {"w" : "majority"}}'
        return 0
    else
        return 1
    fi
}

########################
# Get if secondary node is pending
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   Boolean
#########################
mongodb_is_secondary_node_pending() {
    local node="${1:?node is required}"
    local port="${2:?port is required}"
    local result

    mongodb_set_dwc

    debug "Adding secondary node ${node}:${port}"
    result=$(
        mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
rs.add({host: '$node:$port', priority: 0, votes: 0})
EOF
    )
    debug "$result"

    # Error code 103 is considered OK
    # It indicates a possibly desynced configuration, which will become resynced when the secondary joins the replicaset
    # Note: Error NewReplicaSetConfigurationIncompatible rejects the node addition so we need to filter it out
    if { grep -q "code: 103" <<<"$result"; } && ! { grep -q "NewReplicaSetConfigurationIncompatible" <<<"$result"; }; then
        warn "The ReplicaSet configuration is not aligned with primary node's configuration. Starting secondary node so it syncs with ReplicaSet..."
        return 0
    fi
    grep -q "ok: 1" <<<"$result"
}

########################
# Get if secondary node is ready to be granted voting rights
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   Boolean
#########################
mongodb_is_secondary_node_ready() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"

    debug "Waiting for the node to be marked as secondary"
    result=$(
        mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
rs.status().members.filter(m => m.name === '$node:$port' && m.stateStr === 'SECONDARY').length === 1
EOF
    )
    debug "$result"

    grep -q "true" <<<"$result"
}

########################
# Grant voting rights to secondary node
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   Boolean
#########################
mongodb_configure_secondary_node_voting() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"

    debug "Granting voting rights to the node"
    local reconfig_cmd="rs.reconfigForPSASet(member, cfg)"
    [[ "$(mongodb_get_version)" =~ ^4\.(0|2)\. ]] && reconfig_cmd="rs.reconfig(cfg)"
    result=$(
        mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
cfg = rs.conf()
member = cfg.members.findIndex(m => m.host === '$node:$port')
cfg.members[member].priority = 1
cfg.members[member].votes = 1
$reconfig_cmd
EOF
    )
    debug "$result"

    grep -q "ok: 1" <<<"$result"
}

########################
# Get if hidden node is pending
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   Boolean
#########################
mongodb_is_hidden_node_pending() {
    local node="${1:?node is required}"
    local port="${2:?port is required}"
    local result

    mongodb_set_dwc

    debug "Adding hidden node ${node}:${port}"
    result=$(
        mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
rs.add({host: '$node:$port', hidden: true, priority: 0, votes: 0})
EOF
    )
    # Error code 103 is considered OK.
    # It indicates a possiblely desynced configuration,
    # which will become resynced when the hidden joins the replicaset.
    if grep -q "code: 103" <<<"$result"; then
        warn "The ReplicaSet configuration is not aligned with primary node's configuration. Starting hidden node so it syncs with ReplicaSet..."
        return 0
    fi
    grep -q "ok: 1" <<<"$result"
}

########################
# Get if arbiter node is pending
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   Boolean
#########################
mongodb_is_arbiter_node_pending() {
    local node="${1:?node is required}"
    local port="${2:?port is required}"
    local result

    mongodb_set_dwc

    debug "Adding arbiter node ${node}:${port}"
    result=$(
        mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
rs.addArb('$node:$port')
EOF
    )
    grep -q "ok: 1" <<<"$result"
}

########################
# Configure primary node
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   None
#########################
mongodb_configure_primary() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"

    info "Configuring MongoDB primary node"
    wait-for-port --timeout 360 "$MONGODB_PORT_NUMBER"

    if ! retry_while "mongodb_is_primary_node_initiated $node $port" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
        error "MongoDB primary node failed to get configured"
        exit 1
    fi
}

########################
# Wait for Confirmation
# Globals:
#   None
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   Boolean
#########################
mongodb_wait_confirmation() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"

    debug "Waiting until ${node}:${port} is added to the replica set..."
    if ! retry_while "mongodb_node_currently_in_cluster ${node} ${port}" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
        error "Unable to confirm that ${node}:${port} has been added to the replica set!"
        exit 1
    else
        info "Node ${node}:${port} is confirmed!"
    fi
}

########################
# Check if primary node is ready
# Globals:
#   None
# Returns:
#   None
#########################
mongodb_is_primary_node_up() {
    local -r host="${1:?node is required}"
    local -r port="${2:?port is required}"
    local -r user="${3:?user is required}"
    local -r password="${4:-}"

    debug "Validating $host as primary node..."

    result=$(
        mongodb_execute_print_output "$user" "$password" "admin" "$host" "$port" <<EOF
db.isMaster().ismaster
EOF
    )
    grep -q "true" <<<"$result"
}

########################
# Check if a MongoDB node is running
# Globals:
#   None
# Arguments:
#   None
# Returns:
#   Boolean
#########################
mongodb_is_node_available() {
    local -r host="${1:?node is required}"
    local -r port="${2:?port is required}"
    local -r user="${3:?user is required}"
    local -r password="${4:-}"

    local result
    result=$(
        mongodb_execute_print_output "$user" "$password" "admin" "$host" "$port" <<EOF
db.getUsers()
EOF
    )
    if ! grep -q "user:" <<<"$result"; then
        # If no password was provided on first run
        # it may be the case that DB is up but has no users
        [[ -z $password ]] && grep -q "\[\]" <<<"$result"
    fi
}

########################
# Wait for node
# Globals:
#   MONGODB_*
# Returns:
#   Boolean
#########################
mongodb_wait_for_node() {
    local -r host="${1:?node is required}"
    local -r port="${2:?port is required}"
    local -r user="${3:?user is required}"
    local -r password="${4:-}"
    debug "Waiting for primary node..."

    info "Trying to connect to MongoDB server $host..."
    if ! retry_while "wait-for-port --host $host --timeout 10 $port" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
        error "Unable to connect to host $host"
        exit 1
    else
        info "Found MongoDB server listening at $host:$port !"
    fi

    if ! retry_while "mongodb_is_node_available $host $port $user $password" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
        error "Node $host did not become available"
        exit 1
    else
        info "MongoDB server listening and working at $host:$port !"
    fi
}

########################
# Wait for primary node
# Globals:
#   MONGODB_*
# Returns:
#   Boolean
#########################
mongodb_wait_for_primary_node() {
    local -r host="${1:?node is required}"
    local -r port="${2:?port is required}"
    local -r user="${3:?user is required}"
    local -r password="${4:-}"
    debug "Waiting for primary node..."

    mongodb_wait_for_node "$host" "$port" "$user" "$password"

    debug "Waiting for primary host $host to be ready..."
    if ! retry_while "mongodb_is_primary_node_up $host $port $user $password" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
        error "Unable to validate $host as primary node in the replica set scenario!"
        exit 1
    else
        info "Primary node ready."
    fi
}

########################
# Configure secondary node
# Globals:
#   None
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   None
#########################
mongodb_configure_secondary() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"

    mongodb_wait_for_primary_node "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD"

    if mongodb_node_currently_in_cluster "$node" "$port"; then
        info "Node currently in the cluster"
    else
        info "Adding node to the cluster"
        if ! retry_while "mongodb_is_secondary_node_pending $node $port" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
            error "Secondary node did not get ready"
            exit 1
        fi
        mongodb_wait_confirmation "$node" "$port"

        # Ensure that secondary nodes do not count as voting members until they are fully initialized
        # https://docs.mongodb.com/manual/reference/method/rs.add/#behavior
        if ! retry_while "mongodb_is_secondary_node_ready $node $port" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
            error "Secondary node did not get marked as secondary"
            exit 1
        fi

        # Grant voting rights to node
        # https://docs.mongodb.com/manual/tutorial/modify-psa-replica-set-safely/
        if ! retry_while "mongodb_configure_secondary_node_voting $node $port" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
            error "Secondary node did not get marked as secondary"
            exit 1
        fi

        # Mark node as readable. This is necessary in cases where the PVC is lost
        if is_boolean_yes "$MONGODB_SET_SECONDARY_OK"; then
            mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" <<EOF
rs.secondaryOk()
EOF
        fi

    fi
}

########################
# Configure hidden node
# Globals:
#   None
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   None
#########################
mongodb_configure_hidden() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"

    mongodb_wait_for_primary_node "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD"

    if mongodb_node_currently_in_cluster "$node" "$port"; then
        info "Node currently in the cluster"
    else
        info "Adding hidden node to the cluster"
        if ! retry_while "mongodb_is_hidden_node_pending $node $port" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
            error "Hidden node did not get ready"
            exit 1
        fi
        mongodb_wait_confirmation "$node" "$port"
    fi
}

########################
# Configure arbiter node
# Globals:
#   None
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   None
#########################
mongodb_configure_arbiter() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"

    mongodb_wait_for_primary_node "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD"

    if mongodb_node_currently_in_cluster "$node" "$port"; then
        info "Node currently in the cluster"
    else
        info "Configuring MongoDB arbiter node"
        if ! retry_while "mongodb_is_arbiter_node_pending $node $port" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY"; then
            error "Arbiter node did not get ready"
            exit 1
        fi
        mongodb_wait_confirmation "$node" "$port"
    fi
}

########################
# Get if the replica set in synced
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_is_not_in_sync() {
    local result

    result=$(
        mongodb_execute_print_output "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
db.printSecondaryReplicationInfo()
EOF
    )

    grep -q -E "'0 secs" <<<"$result"
}

########################
# Wait until initial data sync complete
# Globals:
#   MONGODB_LOG_FILE
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_wait_until_sync_complete() {
    info "Waiting until initial data sync is complete..."

    if ! retry_while "mongodb_is_not_in_sync" "$MONGODB_INIT_RETRY_ATTEMPTS" "$MONGODB_INIT_RETRY_DELAY" 1; then
        error "Initial data sync did not finish after $((MONGODB_INIT_RETRY_ATTEMPTS * MONGODB_INIT_RETRY_DELAY)) seconds"
        exit 1
    else
        info "initial data sync completed"
    fi
}

########################
# Get current status of the replicaset
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - node
#   $2 - port
# Returns:
#   None
#########################
mongodb_node_currently_in_cluster() {
    local -r node="${1:?node is required}"
    local -r port="${2:?port is required}"
    local result

    result=$(
        mongodb_execute "$MONGODB_INITIAL_PRIMARY_ROOT_USER" "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" "admin" "$MONGODB_INITIAL_PRIMARY_HOST" "$MONGODB_INITIAL_PRIMARY_PORT_NUMBER" <<EOF
rs.status().members
EOF
    )
    grep -q -E "'${node}:${port}'" <<<"$result"
}

########################
# Configure Replica Set
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_configure_replica_set() {
    local node
    local port

    info "Configuring MongoDB replica set..."

    node=$(get_mongo_hostname)
    port=$(get_mongo_port)
    mongodb_restart

    case "$MONGODB_REPLICA_SET_MODE" in
    "primary")
        mongodb_configure_primary "$node" "$port"
        ;;
    "secondary")
        mongodb_configure_secondary "$node" "$port"
        ;;
    "arbiter")
        mongodb_configure_arbiter "$node" "$port"
        ;;
    "hidden")
        mongodb_configure_hidden "$node" "$port"
        ;;
    "dynamic")
        # Do nothing
        ;;
    esac

    if [[ "$MONGODB_REPLICA_SET_MODE" = "secondary" ]]; then
        mongodb_wait_until_sync_complete
    fi
}

########################
# Configure permisions
# Globals:
#   None
# Arguments:
#   $1 - path array
#   $2 - user
#   $3 - group
#   $4 - mode for directories
#   $5 - mode for files
# Returns:
#   None
#########################
configure_permissions() {
    local -r path=${1:?path is required}
    local -r user=${2:?user is required}
    local -r group=${3:?group is required}
    local -r dir_mode=${4:-false}
    local -r file_mode=${5:-false}

    if [[ -e "$path" ]]; then
        if [[ -n $dir_mode ]] && [[ -n $file_mode ]]; then
            find -L "$path" -type d -exec chmod "$dir_mode" {} \;
        fi
        if [[ -n $file_mode ]]; then
            find -L "$path" -type f -exec chmod "$file_mode" {} \;
        fi
        chown -LR "$user":"$group" "$path"
    else
        warn "$path do not exist."
    fi
}

###############
# Initialize MongoDB service
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_initialize() {
    info "Initializing MongoDB..."

    rm -f "$MONGODB_PID_FILE"
    mongodb_copy_mounted_config
    mongodb_set_net_conf "$MONGODB_CONF_FILE"
    mongodb_set_log_conf "$MONGODB_CONF_FILE"
    mongodb_set_journal_conf "$MONGODB_CONF_FILE"
    mongodb_set_storage_conf "$MONGODB_CONF_FILE"
    is_boolean_yes "$MONGODB_DISABLE_JAVASCRIPT" && mongodb_disable_javascript_conf "$MONGODB_CONF_FILE"

    # Create the ReplicaSet keyFile before Mongo starts in case it is referenced in the $MONGODB_CONF_FILE
    if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then
        if [[ -n "$MONGODB_REPLICA_SET_KEY" ]]; then
            mongodb_create_keyfile "$MONGODB_REPLICA_SET_KEY"
            mongodb_set_keyfile_conf "$MONGODB_CONF_FILE"
        fi
    fi

    if is_dir_empty "$MONGODB_DATA_DIR/db"; then
        info "Deploying MongoDB from scratch..."
        ensure_dir_exists "$MONGODB_DATA_DIR/db"
        am_i_root && chown -R "$MONGODB_DAEMON_USER" "$MONGODB_DATA_DIR/db"

        mongodb_start_bg "$MONGODB_CONF_FILE"
        mongodb_create_users
        if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then
            mongodb_set_replicasetmode_conf "$MONGODB_CONF_FILE"
            mongodb_set_listen_all_conf "$MONGODB_CONF_FILE"
            mongodb_configure_replica_set
        fi

        mongodb_stop
    else
        mongodb_set_auth_conf "$MONGODB_CONF_FILE"
        info "Deploying MongoDB with persisted data..."
        if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then
            if [[ "$MONGODB_REPLICA_SET_MODE" = "dynamic" ]]; then
                mongodb_ensure_dynamic_mode_consistency
            fi
            mongodb_set_replicasetmode_conf "$MONGODB_CONF_FILE"
        fi
    fi

    mongodb_set_auth_conf "$MONGODB_CONF_FILE"
}

########################
# Check that the dynamic instance configuration is consistent
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_ensure_dynamic_mode_consistency() {
    if grep -q -E "^[[:space:]]*replSetName: $MONGODB_REPLICA_SET_NAME" "$MONGODB_CONF_FILE"; then
        info "ReplicaSetMode set to \"dynamic\" and replSetName different from config file."
        info "Dropping local database ..."
        mongodb_start_bg "$MONGODB_CONF_FILE"
        mongodb_drop_local_database
        mongodb_stop
    fi
}

########################
# Check if a given file was mounted externally
# Globals:
#   MONGODB_*
# Arguments:
#   $1 - Filename
# Returns:
#   true if the file was mounted externally, false otherwise
#########################
mongodb_is_file_external() {
    local -r filename="${1:?file_is_missing}"
    if [[ -f "${MONGODB_MOUNTED_CONF_DIR}/${filename}" ]] || { [[ -f "${MONGODB_CONF_DIR}/${filename}" ]] && ! test -w "${MONGODB_CONF_DIR}/${filename}"; }; then
        true
    else
        false
    fi
}

########################
# Get MongoDB version
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   version
#########################
mongodb_get_version() {
    mongod --version 2>/dev/null | awk -F\" '/"version"/ {print $4}'
}

########################
# Run custom initialization scripts
# Globals:
#   MONGODB_*
# Arguments:
#   None
# Returns:
#   None
#########################
mongodb_custom_init_scripts() {
    local run_custom_init_scripts="no"
    if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then
        if [[ "$MONGODB_REPLICA_SET_MODE" != "primary" ]]; then
            debug "Skipping loading custom scripts on non-primary nodes..."
        elif [[ -n $(find "$MONGODB_INITSCRIPTS_DIR/" -type f -regex ".*\.\(sh\|js\|js.gz\)") ]]; then
            if [[ -f "$MONGODB_VOLUME_DIR/.user_scripts_initialized" ]]; then
                debug "Skipping loading custom scripts on container restarts..."
            else
                run_custom_init_scripts="yes"
            fi
        fi
    elif [[ -n $(find "$MONGODB_INITSCRIPTS_DIR/" -type f -regex ".*\.\(sh\|js\|js.gz\)") ]]; then
        if [[ -f "$MONGODB_VOLUME_DIR/.user_scripts_initialized" ]]; then
            debug "Skipping loading custom scripts on container restarts..."
        else
            run_custom_init_scripts="yes"
        fi
    fi
    if is_boolean_yes "$run_custom_init_scripts"; then
        info "Loading user's custom files from $MONGODB_INITSCRIPTS_DIR ..."
        mongodb_start_bg "$MONGODB_CONF_FILE"
        local -r tmp_file=/tmp/filelist
        local mongo_user
        local mongo_pass
        if [[ -n "$MONGODB_ROOT_PASSWORD" ]]; then
            mongo_user="$MONGODB_ROOT_USER"
            mongo_pass="$MONGODB_ROOT_PASSWORD"
        elif [[ -n "$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD" ]]; then
            mongo_user="$MONGODB_ROOT_USER"
            mongo_pass="$MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD"
        else
            local databases usernames passwords

            mongodb_auth
            mongo_user="${usernames[0]}"
            mongo_pass="${passwords[0]}"
        fi
        find "$MONGODB_INITSCRIPTS_DIR" -type f -regex ".*\.\(sh\|js\|js.gz\)" | 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
                ;;
            *.js)
                debug "Executing $f"
                mongodb_execute_print_output "$mongo_user" "$mongo_pass" <"$f"
                ;;
            *.js.gz)
                debug "Executing $f"
                gunzip -c "$f" | mongodb_execute_print_output "$mongo_user" "$mongo_pass"
                ;;
            *) debug "Ignoring $f" ;;
            esac
        done <$tmp_file
        touch "$MONGODB_VOLUME_DIR"/.user_scripts_initialized
    fi
}

########################
# Execute an arbitrary query/queries against the running MongoDB service
# Stdin:
#   Query/queries to execute
# Arguments:
#   $1 - User to run queries
#   $2 - Password
#   $3 - Database where to run the queries
#   $4 - Host (default to result of get_mongo_hostname function)
#   $5 - Port (default $MONGODB_PORT_NUMBER)
#   $6 - Extra arguments (default $MONGODB_SHELL_EXTRA_FLAGS)
# Returns:
#   output of mongo query
########################
mongodb_execute_print_output() {
    local -r user="${1:-}"
    local -r password="${2:-}"
    local -r database="${3:-}"
    local -r host="${4:-$(get_mongo_hostname)}"
    local -r port="${5:-$MONGODB_PORT_NUMBER}"
    local -r extra_args="${6:-$MONGODB_SHELL_EXTRA_FLAGS}"
    local final_user="$user"
    # If password is empty it means no auth, do not specify user
    [[ -z "$password" ]] && final_user=""

    local -a args=("--host" "$host" "--port" "$port")
    [[ -n "$final_user" ]] && args+=("-u" "$final_user")
    [[ -n "$password" ]] && args+=("-p" "$password")
    if [[ -n "$extra_args" ]]; then
        local extra_args_array=()
        read -r -a extra_args_array <<<"$extra_args"
        [[ "${#extra_args_array[@]}" -gt 0 ]] && args+=("${extra_args_array[@]}")
    fi
    [[ -n "$database" ]] && args+=("$database")

    "$MONGODB_BIN_DIR/mongosh" "${args[@]}"
}

########################
# Execute an arbitrary query/queries against the running MongoDB service,
# discard its output unless BITNAMI_DEBUG is true
# Stdin:
#   Query/queries to execute
# Globals:
#   BITNAMI_DEBUG
# Arguments:
#   $1 - User to run queries
#   $2 - Password
#   $3 - Database where to run the queries
#   $4 - Host (default to result of get_mongo_hostname function)
#   $5 - Port (default $MONGODB_PORT_NUMBER)
#   $6 - Extra arguments (default $MONGODB_SHELL_EXTRA_FLAGS)
# Returns:
#   None
########################
mongodb_execute() {
    debug_execute mongodb_execute_print_output "$@"
}

# Copyright VMware, Inc.
# SPDX-License-Identifier: APACHE-2.0

# shellcheck disable=SC2148

########################
# Execute an arbitrary query/queries against the running MongoDB service
# Stdin:
#   Query/queries to execute
# Arguments:
#   $1 - User to run queries
#   $2 - Password
#   $3 - Database where to run the queries
#   $4 - Host (default to result of get_mongo_hostname function)
#   $5 - Port (default $MONGODB_PORT_NUMBER)
#   $6 - Extra arguments (default $MONGODB_SHELL_EXTRA_FLAGS)
# Returns:
#   None
########################
mongodb_execute() {
    local -r user="${1:-}"
    local -r password="${2:-}"
    local -r database="${3:-}"
    local -r host="${4:-$(get_mongo_hostname)}"
    local -r port="${5:-$MONGODB_PORT_NUMBER}"
    local -r extra_args="${6:-$MONGODB_SHELL_EXTRA_FLAGS}"
    local final_user="$user"
    # If password is empty it means no auth, do not specify user
    [[ -z "$password" ]] && final_user=""

    local -a args=("--host" "$host" "--port" "$port")
    [[ -n "$final_user" ]] && args+=("-u" "$final_user")
    [[ -n "$password" ]] && args+=("-p" "$password")
    if [[ -n "$extra_args" ]]; then
        local extra_args_array=()
        read -r -a extra_args_array <<<"$extra_args"
        [[ "${#extra_args_array[@]}" -gt 0 ]] && args+=("${extra_args_array[@]}")
    fi
    [[ -n "$database" ]] && args+=("$database")

    "$MONGODB_BIN_DIR/mongosh" "${args[@]}"
}
