guru/eclass/databases.eclass
Anna (cybertailor) Vyalkova cfdd31feb5
databases.eclass: fix "--add-deps" helper
It's called in ebuild scope so "+=" should be used.

Signed-off-by: Anna (cybertailor) Vyalkova <cyber+gentoo@sysrq.in>
2024-02-05 00:23:20 +05:00

532 lines
12 KiB
Bash

# Copyright 2022-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: databases.eclass
# @MAINTAINER:
# Anna <cyber+gentoo@sysrq.in>
# @AUTHOR:
# Anna <cyber+gentoo@sysrq.in>
# @SUPPORTED_EAPIS: 8
# @BLURB: eclass to test packages against databases
# @DESCRIPTION:
# A utility eclass providing functions for running databases.
#
# This eclass does not set any metadata variables nor export any phase, so it
# can be inherited safely.
#
# @SUBSECTION Supported databases
#
# - memcached (via "ememcached" helper)
#
# - MongoDB (via "emongod" helper)
#
# - MySQL / MariaDB (via "emysql" helper)
#
# - PostgreSQL (via "epostgres" helper)
#
# - Redis (via "eredis" helper)
#
# @SUBSECTION Helper usage
#
# --add-deps <use>
#
# Adds the server package to build-time dependencies under the given USE flag
# (IUSE will be set automatically).
#
# --die [msg]
#
# Prints the path to the server's log file to the console and aborts the
# current merge process with the given message.
#
# --get-dbpath
#
# Returns the directory where the server stores database files.
#
# --get-logfile
#
# Returns the path to the server's log file.
#
# --get-pidfile
#
# Returns the path to the server's PID file.
#
# --get-sockdir
#
# Returns the directory where the server's sockets are located.
#
# --get-sockfile
#
# Returns the path to the server's socket file.
#
# --start
#
# Starts the server on the default port.
#
# --start <port>
#
# Starts the server on the given port.
#
# --stop
#
# Stops the server.
#
# @EXAMPLE:
#
# @CODE
# EAPI=8
#
# ...
#
# DATABASES_REQ_USE=(
# [postgres]="xml"
# )
# inherit databases distutils-r1
#
# ...
#
# distutils_enable_tests pytest
#
# epostgres --add-deps test
#
# src_test() {
# epostgres --start 65432
# distutils-r1_src_test
# epostgres --stop
# }
# @CODE
case ${EAPI} in
8) ;;
*) die "${ECLASS}: EAPI ${EAPI} unsupported."
esac
if [[ ! ${_DATABASES_ECLASS} ]]; then
_DATABASES_ECLASS=1
# @ECLASS_VARIABLE: DATABASES_REQ_USE
# @PRE_INHERIT
# @DEFAULT_UNSET
# @DESCRIPTION:
# Bash associative array of USE flags required to be enabled on database
# servers, formed as a USE-dependency string.
#
# Keys are helper function names without the "e" prefix.
# @ECLASS_VARIABLE: DATABASES_DEPEND
# @OUTPUT_VARIABLE
# @DESCRIPTION:
# This is an eclass-generated bash associative array of dependency strings for
# database servers.
#
# Keys are helper function names without the "e" prefix.
declare -Ag DATABASES_DEPEND=()
# @FUNCTION: _databases_set_globals
# @INTERNAL
# @DESCRIPTION:
# Set the DATABASES_DEPEND variable.
_databases_set_globals() {
local -A db_pkgs=(
[memcached]="net-misc/memcached"
[mongod]="dev-db/mongodb"
[mysql]="virtual/mysql"
[postgres]="dev-db/postgresql"
[redis]="dev-db/redis"
)
local -A db_useflags=(
[mysql]="server"
[postgres]="server"
)
if declare -p DATABASES_REQ_USE &>/dev/null; then
[[ $(declare -p DATABASES_REQ_USE) == "declare -A"* ]] || \
die "DATABASES_REQ_USE must be declared as an associative array"
fi
local name dep usestr
for name in "${!db_pkgs[@]}"; do
dep=${db_pkgs[${name}]?}
usestr=${db_useflags[${name}]}
usestr+=",${DATABASES_REQ_USE[${name}]}"
# strip leading/trailing commas
usestr=${usestr#,}
usestr=${usestr%,}
[[ ${usestr} ]] && usestr="[${usestr}]"
DATABASES_DEPEND[${name?}]="${dep?}${usestr}"
done
readonly DATABASES_DEPEND
}
_databases_set_globals
unset -f _databases_set_globals
# ==============================================================================
# GENERIC FUNCTIONS
# ==============================================================================
# @FUNCTION: _databases_die
# @USAGE: <funcname> [msg]
# @INTERNAL
# @DESCRIPTION:
# Print the given message and the path to the server's log file to the console
# and die.
#
# This function supports being called via "nonfatal".
_databases_die() {
local funcname=${1?}
shift
eerror "See the server log for details:"
eerror " $(${funcname} --get-logfile)"
die -n "${@}"
}
# @FUNCTION: _databases_add_deps
# @USAGE: <funcname> <use>
# @INTERNAL
# @DESCRIPTION:
# Set the BDEPEND, IUSE and RESTRICT variables.
_databases_add_deps() {
local funcname=${1?}
local useflag=${2?}
IUSE+=" ${useflag}"
BDEPEND+=" ${useflag}? ( ${DATABASES_DEPEND[${funcname:1}]} )"
if [[ ${useflag} == "test" ]]; then
RESTRICT+= "!test? ( test )"
fi
}
# @FUNCTION: _databases_stop_service
# @USAGE: <funcname>
# @INTERNAL
# @DESCRIPTION:
# Default function to stop servers. Reads PID from a file and sends the TERM
# signal.
_databases_stop_service() {
debug-print-function "${FUNCNAME}" "${@}"
local funcname=${1?}
local srvname=${funcname:1}
local pidfile="$(${funcname} --get-pidfile)"
ebegin "Stopping ${srvname}"
kill "$(<"${pidfile}")"
eend $? || ${funcname} --die "Stopping ${srvname} failed"
}
# @FUNCTION: _databases_dispatch
# @USAGE: <funcname> <cmd> [args...]
# @INTERNAL
# @DESCRIPTION:
# Process the given command with its options.
#
# If "--start" command is used, "_${funcname}_start" function must be defined.
# Note that directories will be created automatically.
#
# If "_${funcname}_stop" function is not declared, the internal
# `_databases_stop_service` function will be used instead.
#
# "--get-*" and "-add-deps" helpers cannot be overloaded.
_databases_dispatch() {
local funcname=${1?}
local cmd=${2?}
shift; shift
case ${cmd} in
--add-deps)
_databases_add_deps ${funcname} "${@}"
;;
--die)
_databases_die ${funcname} "${@}"
;;
--get-dbpath)
echo "${T}"/${funcname}/db/
;;
--get-logfile)
echo "${T}"/${funcname}/${funcname}.log
;;
--get-pidfile)
echo "${T}"/${funcname}/${funcname}.pid
;;
--get-sockdir)
echo "${T}"/${funcname}/
;;
--get-sockfile)
echo "${T}"/${funcname}/${funcname}.sock
;;
--start)
local port=${1}
local start_fn=( _${funcname}_start ${port} )
if ! declare -f "${start_fn[0]}" >/dev/null; then
die "${ECLASS}: function not declared: ${start_fn[0]}"
fi
mkdir -p "${T}"/${funcname}/db/ || die "Creating database directory failed"
"${start_fn[@]}"
;;
--stop)
local stop_fn=( _${funcname}_stop )
if ! declare -f "${stop_fn[0]}" >/dev/null; then
# fall back to the default implementation
stop_fn=( _databases_stop_service ${funcname} )
fi
"${stop_fn[@]}"
;;
*) die "${funcname}: invalid command: ${cmd}" ;;
esac
}
# ==============================================================================
# MEMCACHED
# ==============================================================================
# @FUNCTION: _ememcached_start
# @USAGE: [port]
# @INTERNAL
# @DESCRIPTION:
# Start memcached server.
_ememcached_start() {
debug-print-function "${FUNCNAME}" "${@}"
local port=${1:-11211}
local myargs=(
--daemon
--port=${port}
--user=nobody
--listen=127.0.0.1
--pidfile=$(ememcached --get-pidfile)
)
ebegin "Spawning memcached"
memcached "${myargs[@]}" &>> $(ememcached --get-logfile)
eend $? || ememcached --die "Spawning memcached failed"
}
# @FUNCTION: ememcached
# @USAGE: <cmd> [args...]
# @DESCRIPTION:
# Manage memcached server on the given port (default: 11211).
ememcached() {
_databases_dispatch "${FUNCNAME}" "${@}"
}
# ==============================================================================
# MONGODB
# ==============================================================================
# @FUNCTION: _emongod_start
# @USAGE: [port]
# @INTERNAL
# @DESCRIPTION:
# Start MongoDB server.
_emongod_start() {
debug-print-function "${FUNCNAME}" "${@}"
local port=${1:-27017}
local logfile=$(emongod --get-logfile)
local myargs=(
--dbpath="$(emongod --get-dbpath)"
--nojournal
--bind-ip=127.0.0.1
--port=${port}
--unixSocketPrefix="$(emongod --get-sockdir)"
--logpath="${logfile}"
--fork
)
ebegin "Spawning mongodb"
LC_ALL=C mongod "${myargs[@]}" &>> "${logfile}"
eend $? || emongod --die "Spawning mongod failed"
}
# @FUNCTION: _emongod_stop
# @INTERNAL
# @DESCRIPTION:
# Stop MongoDB server.
_emongod_stop() {
debug-print-function "${FUNCNAME}" "${@}"
local myargs=(
--dbpath="$(emongod --get-dbpath)"
--shutdown
)
ebegin "Stopping mongodb"
mongod "${myargs[@]}" &>> "${logfile}"
eend $? || emongod --die "Stopping mongod failed"
}
# @FUNCTION: emongod
# @USAGE: <cmd> [args...]
# @DESCRIPTION:
# Manage MongoDB server on the given port (default: 27017).
emongod() {
_databases_dispatch "${FUNCNAME}" "${@}"
}
# ==============================================================================
# MYSQL
# ==============================================================================
# @FUNCTION: _emysql_start
# @USAGE: [port]
# @INTERNAL
# @DESCRIPTION:
# Create a new MySQL database and start MySQL server.
_emysql_start() {
debug-print-function "${FUNCNAME}" "${@}"
local port=${1:-3306}
local dbpath=$(emysql --get-dbpath)
local logfile=$(emysql --get-logfile)
local sockfile=$(emysql --get-sockfile)
local myinstallargs=(
--no-defaults
--auth-root-authentication-method=normal
--basedir="${BROOT}/usr"
--datadir="${dbpath}"
)
ebegin "Initializing mysql database"
mysql_install_db "${myinstallargs[@]}" &>> "${logfile}"
eend $? || emysql --die "Initializing mysql database failed"
local myargs=(
--no-defaults
--character-set-server=utf8
--pid-file="$(emysql --get-pidfile)"
--socket="${sockfile}"
--bind-address=127.0.0.1
--port=${port}
--datadir="${dbpath}"
--general-log-file="${logfile}"
--log-error="${logfile}"
)
einfo "Spawning mysql"
mysqld "${myargs[@]}" &>> "${logfile}" &
einfo "Waiting for mysqld to accept connections"
local timeout=30
while ! mysqladmin ping --socket="${sockfile}" --silent; do
sleep 1
let timeout-=1
[[ ${timeout} -eq 0 ]] && emysql --die "Timed out"
done
}
# @FUNCTION: emysql
# @USAGE: <cmd> [args...]
# @DESCRIPTION:
# Manage MySQL server on the given port (default: 3306).
emysql() {
_databases_dispatch "${FUNCNAME}" "${@}"
}
# ==============================================================================
# POSTGRESQL
# ==============================================================================
# @FUNCTION: _epostgres_start
# @USAGE: [port]
# @INTERNAL
# @DESCRIPTION:
# Create a new PostgreSQL database and start PostgreSQL server.
_epostgres_start() {
debug-print-function "${FUNCNAME}" "${@}"
local port=${1:-5432}
local dbpath=$(epostgres --get-dbpath)
local logfile=$(epostgres --get-logfile)
local myinstallargs=(
--pgdata="${dbpath}"
--user=postgres
)
ebegin "Initializing postgresql database"
initdb "${myinstallargs[@]}" &>> "${logfile}"
eend $? || epostgres --die "Initializing postgresql database failed"
local myargs=(
--pgdata="${dbpath}"
--log="${logfile}"
--options="-h '127.0.0.1' -p ${port} -k '$(epostgres --get-sockdir)'"
--wait
)
ebegin "Spawning postgresql"
pg_ctl "${myargs[@]}" start &>> "${logfile}"
eend $? || epostgres --die "Spawning postgresql failed"
}
# @FUNCTION: _epostgres_stop
# @INTERNAL
# @DESCRIPTION:
# Stop PosgreSQL server.
_epostgres_stop() {
debug-print-function "${FUNCNAME}" "${@}"
local myargs=(
--pgdata="$(epostgres --get-dbpath)"
--wait
)
ebegin "Stopping postgresql"
pg_ctl "${myargs[@]}" stop &>> "$(epostgres --get-logfile)"
eend $? || epostgres --die "Stopping postgresql failed"
}
# @FUNCTION: epostgres
# @USAGE: <cmd> [args...]
# @DESCRIPTION:
# Manage PostgreSQL server on the given port (default: 5432).
epostgres() {
_databases_dispatch "${FUNCNAME}" "${@}"
}
# ==============================================================================
# REDIS
# ==============================================================================
# @FUNCTION: _eredis_start
# @USAGE: [args...]
# @INTERNAL
# @DESCRIPTION:
# Start Redis server.
_eredis_start() {
debug-print-function "${FUNCNAME}" "${@}"
local port=${1:-6379}
local logfile="$(eredis --get-logfile)"
ebegin "Spawning redis"
redis-server - <<- EOF &>> "${logfile}"
daemonize yes
pidfile "$(eredis --get-pidfile)"
port ${port}
bind 127.0.0.1
unixsocket "$(eredis --get-sockfile)"
dir "$(eredis --get-dbpath)"
logfile "${logfile}"
EOF
eend $? || eredis --die "Spawning redis failed"
}
# @FUNCTION: eredis
# @USAGE: <cmd> [args...]
# @DESCRIPTION:
# Manage Redis server on the given port (default: 6379).
eredis() {
_databases_dispatch "${FUNCNAME}" "${@}"
}
fi