chore: add Docker Compose stack management script
This commit is contained in:
1 parent
a03633a5bb
commit
21721df7f5
1 file changed
+254
@@ -0,0 +1,254 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")"
|
||||||
|
STACKS_DIR="${STACKS_DIR:-$SCRIPT_DIR}"
|
||||||
|
CONFIG_FILE="${STACKS_CONFIG:-$STACKS_DIR/manage.conf}"
|
||||||
|
LOCK_FILE="${STACKS_LOCK_FILE:-/run/lock/stacks-manage.lock}"
|
||||||
|
|
||||||
|
WHITELIST=()
|
||||||
|
BLACKLIST=()
|
||||||
|
PRIORITY=()
|
||||||
|
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
bash -n "$CONFIG_FILE" || die "Syntax error in config file"
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DISCOVERED_STACKS=()
|
||||||
|
MANAGED_STACKS=()
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") COMMAND [STACK]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
up Create and start containers
|
||||||
|
start Start existing containers
|
||||||
|
stop Stop containers
|
||||||
|
restart Restart containers
|
||||||
|
down Stop and remove containers and networks
|
||||||
|
pull Pull images
|
||||||
|
update Pull images and apply Compose changes
|
||||||
|
ps Show container status
|
||||||
|
logs Follow logs for a stack
|
||||||
|
config Render Compose configuration for a stack
|
||||||
|
list List discovered stacks and their status
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
contains() {
|
||||||
|
local target="$1"
|
||||||
|
shift
|
||||||
|
local item
|
||||||
|
|
||||||
|
for item in "$@"; do
|
||||||
|
[[ "$item" == "$target" ]] && return 0
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
discover_stacks() {
|
||||||
|
local compose_file stack
|
||||||
|
|
||||||
|
DISCOVERED_STACKS=()
|
||||||
|
|
||||||
|
for compose_file in "$STACKS_DIR"/*/compose.yaml; do
|
||||||
|
stack="${compose_file%/compose.yaml}"
|
||||||
|
DISCOVERED_STACKS+=("${stack##*/}")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
is_discovered() {
|
||||||
|
contains "$1" "${DISCOVERED_STACKS[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_managed() {
|
||||||
|
local stack="$1"
|
||||||
|
|
||||||
|
is_discovered "$stack" || return 1
|
||||||
|
[[ ! -e "$STACKS_DIR/$stack/.disabled" ]] || return 1
|
||||||
|
contains "$stack" "${BLACKLIST[@]}" && return 1
|
||||||
|
((${#WHITELIST[@]} == 0)) || contains "$stack" "${WHITELIST[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_managed_stacks() {
|
||||||
|
local stack
|
||||||
|
|
||||||
|
MANAGED_STACKS=()
|
||||||
|
|
||||||
|
for stack in "${PRIORITY[@]}"; do
|
||||||
|
if is_managed "$stack" && ! contains "$stack" "${MANAGED_STACKS[@]}"; then
|
||||||
|
MANAGED_STACKS+=("$stack")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for stack in "${DISCOVERED_STACKS[@]}"; do
|
||||||
|
if is_managed "$stack" && ! contains "$stack" "${MANAGED_STACKS[@]}"; then
|
||||||
|
MANAGED_STACKS+=("$stack")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
local stack="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
(
|
||||||
|
cd -- "$STACKS_DIR/$stack" || exit 1
|
||||||
|
docker compose "$@"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
run_stack() {
|
||||||
|
local stack="$1"
|
||||||
|
local action="$2"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> $stack: $action"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
up)
|
||||||
|
compose "$stack" config -q &&
|
||||||
|
compose "$stack" up -d
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
compose "$stack" config -q &&
|
||||||
|
compose "$stack" pull &&
|
||||||
|
compose "$stack" up -d
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
compose "$stack" "$action"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
run_all() {
|
||||||
|
local action="$1"
|
||||||
|
local success=0 failed=0 stack i
|
||||||
|
|
||||||
|
if [[ "$action" == "stop" || "$action" == "down" ]]; then
|
||||||
|
for ((i = ${#MANAGED_STACKS[@]} - 1; i >= 0; i--)); do
|
||||||
|
stack="${MANAGED_STACKS[i]}"
|
||||||
|
|
||||||
|
if run_stack "$stack" "$action"; then
|
||||||
|
((success += 1))
|
||||||
|
else
|
||||||
|
((failed += 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
for stack in "${MANAGED_STACKS[@]}"; do
|
||||||
|
if run_stack "$stack" "$action"; then
|
||||||
|
((success += 1))
|
||||||
|
else
|
||||||
|
((failed += 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Succeeded: $success"
|
||||||
|
echo "Failed: $failed"
|
||||||
|
|
||||||
|
((failed == 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
stack_status() {
|
||||||
|
local stack="$1"
|
||||||
|
|
||||||
|
if [[ -e "$STACKS_DIR/$stack/.disabled" ]]; then
|
||||||
|
echo "disabled"
|
||||||
|
elif contains "$stack" "${BLACKLIST[@]}"; then
|
||||||
|
echo "blacklisted"
|
||||||
|
elif ((${#WHITELIST[@]} > 0)) && ! contains "$stack" "${WHITELIST[@]}"; then
|
||||||
|
echo "not whitelisted"
|
||||||
|
else
|
||||||
|
echo "managed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
list_stacks() {
|
||||||
|
local stack
|
||||||
|
|
||||||
|
printf '%-24s %s\n' "STACK" "STATUS"
|
||||||
|
printf '%-24s %s\n' "------------------------" "---------------"
|
||||||
|
|
||||||
|
for stack in "${DISCOVERED_STACKS[@]}"; do
|
||||||
|
printf '%-24s %s\n' "$stack" "$(stack_status "$stack")"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
require_target() {
|
||||||
|
local command="$1"
|
||||||
|
local target="$2"
|
||||||
|
|
||||||
|
[[ -n "$target" ]] || die "$command requires a stack name"
|
||||||
|
is_discovered "$target" || die "Stack not found: $target"
|
||||||
|
is_managed "$target" || die "Stack is excluded: $target"
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire_lock() {
|
||||||
|
mkdir -p "$(dirname "$LOCK_FILE")" ||
|
||||||
|
die "Cannot create lock directory"
|
||||||
|
exec {LOCK_FD}>"$LOCK_FILE" ||
|
||||||
|
die "Cannot open lock file: $LOCK_FILE"
|
||||||
|
flock -n "$LOCK_FD" ||
|
||||||
|
die "Another manage.sh operation is already running"
|
||||||
|
}
|
||||||
|
|
||||||
|
discover_stacks
|
||||||
|
build_managed_stacks
|
||||||
|
|
||||||
|
ACTION="${1:-}"
|
||||||
|
TARGET="${2:-}"
|
||||||
|
|
||||||
|
[[ $# -le 2 ]] || die "Too many arguments"
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
up | start | stop | restart | down | pull | update)
|
||||||
|
acquire_lock
|
||||||
|
|
||||||
|
if [[ -n "$TARGET" ]]; then
|
||||||
|
require_target "$ACTION" "$TARGET"
|
||||||
|
run_stack "$TARGET" "$ACTION"
|
||||||
|
else
|
||||||
|
run_all "$ACTION"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
ps)
|
||||||
|
if [[ -n "$TARGET" ]]; then
|
||||||
|
require_target "$ACTION" "$TARGET"
|
||||||
|
run_stack "$TARGET" "$ACTION"
|
||||||
|
else
|
||||||
|
run_all "$ACTION"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
require_target "$ACTION" "$TARGET"
|
||||||
|
compose "$TARGET" logs -f --tail=100
|
||||||
|
;;
|
||||||
|
config)
|
||||||
|
require_target "$ACTION" "$TARGET"
|
||||||
|
compose "$TARGET" config
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
[[ -z "$TARGET" ]] || die "list does not accept a stack name"
|
||||||
|
list_stacks
|
||||||
|
;;
|
||||||
|
help | -h | --help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in new issue
Block a user