feat: accept multiple stack arguments
This commit is contained in:
1 parent
c002ec3244
commit
58636aa06e
1 file changed
+107
-56
@@ -28,6 +28,7 @@ RUN_SUCCESS=0
|
||||
RUN_FAILED=0
|
||||
RUN_FAILED_STACKS=()
|
||||
RUN_SKIPPED_STAGES=()
|
||||
RUN_SKIPPED_STACKS=()
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
@@ -69,26 +70,32 @@ handle_signal() {
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") COMMAND [STACK]
|
||||
Usage: $(basename "$0") COMMAND [STACK...]
|
||||
|
||||
STACK may be a plain stack name or a stack folder such as qbittorrent/
|
||||
or ./qbittorrent/. Folder references resolve against the stacks root;
|
||||
paths outside it are rejected.
|
||||
|
||||
With several STACK arguments, lifecycle, ps, and images run the stacks
|
||||
one at a time in the order given. A lifecycle failure stops the run:
|
||||
the remaining stacks are skipped and, with stop or down, left untouched.
|
||||
ps continues inspecting the remaining stacks and reports failed stacks
|
||||
at the end.
|
||||
|
||||
Lifecycle:
|
||||
up [STACK] Create and start containers
|
||||
start [STACK] Start existing containers
|
||||
stop [STACK] Stop containers
|
||||
restart [STACK] Restart containers
|
||||
down [STACK] Stop and remove containers and networks
|
||||
pull [STACK] Pull images
|
||||
update [STACK] Pull images and apply Compose changes
|
||||
up [STACK...] Create and start containers
|
||||
start [STACK...] Start existing containers
|
||||
stop [STACK...] Stop containers
|
||||
restart [STACK...] Restart containers
|
||||
down [STACK...] Stop and remove containers and networks
|
||||
pull [STACK...] Pull images
|
||||
update [STACK...] Pull images and apply Compose changes
|
||||
|
||||
Inspect:
|
||||
ps [STACK] Show container status
|
||||
ps [STACK...] Show container status
|
||||
logs STACK Follow logs
|
||||
config STACK Render Compose configuration
|
||||
images [STACK] Show services, configured images, and versions
|
||||
images [STACK...] Show services, configured images, and versions
|
||||
list List stacks, stages, and management status
|
||||
help, -h, --help Show this help
|
||||
|
||||
@@ -165,25 +172,25 @@ validate_name() {
|
||||
# folder inside STACKS_DIR; paths to folders elsewhere are rejected.
|
||||
normalize_target() {
|
||||
local target_name="$1"
|
||||
local -n target="$target_name"
|
||||
local original="$target"
|
||||
local -n value="$target_name"
|
||||
local original="$value"
|
||||
local name path resolved canonical root
|
||||
|
||||
while [[ "$target" == */ ]]; do
|
||||
target="${target%/}"
|
||||
while [[ "$value" == */ ]]; do
|
||||
value="${value%/}"
|
||||
done
|
||||
|
||||
# Names without a path component are validated against the stacks.
|
||||
if [[ "$target" != */* ]]; then
|
||||
[[ -n "$target" ]] || die "invalid stack path: $original"
|
||||
if [[ "$value" != */* ]]; then
|
||||
[[ -n "$value" ]] || die "invalid stack path: $original"
|
||||
return 0
|
||||
fi
|
||||
|
||||
name="${target##*/}"
|
||||
name="${value##*/}"
|
||||
[[ -n "$name" ]] || die "invalid stack path: $original"
|
||||
root="$(readlink -f -- "$STACKS_DIR" 2>/dev/null)" ||
|
||||
die "invalid STACKS_DIR: $STACKS_DIR"
|
||||
path="$target"
|
||||
path="$value"
|
||||
[[ "$path" == /* ]] || path="$STACKS_DIR/$path"
|
||||
resolved="$(readlink -f -- "$path" 2>/dev/null)" ||
|
||||
die "stack folder not found: $original"
|
||||
@@ -192,7 +199,7 @@ normalize_target() {
|
||||
[[ "$resolved" == "$canonical" ]] ||
|
||||
die "not a stack folder in $root: $original"
|
||||
|
||||
target="$name"
|
||||
value="$name"
|
||||
}
|
||||
|
||||
# Set the maximum concurrency within each stage.
|
||||
@@ -567,6 +574,10 @@ print_run_summary() {
|
||||
echo "Failed stacks: ${RUN_FAILED_STACKS[*]}"
|
||||
fi
|
||||
|
||||
if ((${#RUN_SKIPPED_STACKS[@]} > 0)); then
|
||||
echo "Skipped stacks: ${RUN_SKIPPED_STACKS[*]}"
|
||||
fi
|
||||
|
||||
if ((${#RUN_SKIPPED_STAGES[@]} > 0)); then
|
||||
echo "Skipped stages: ${RUN_SKIPPED_STAGES[*]}"
|
||||
fi
|
||||
@@ -600,6 +611,7 @@ run_all_staged() {
|
||||
RUN_FAILED=0
|
||||
RUN_FAILED_STACKS=()
|
||||
RUN_SKIPPED_STAGES=()
|
||||
RUN_SKIPPED_STACKS=()
|
||||
if [[ "$action" == "stop" || "$action" == "down" ]]; then
|
||||
direction="reverse"
|
||||
start=$((end - 1))
|
||||
@@ -618,11 +630,41 @@ run_all_staged() {
|
||||
print_run_summary
|
||||
}
|
||||
|
||||
# Run the given stacks one at a time, in the given order. A failure stops
|
||||
# the run; the remaining stacks are reported as skipped.
|
||||
run_stacks_sequential() {
|
||||
local action="$1"
|
||||
local index stack
|
||||
local -a stacks=("${@:2}")
|
||||
|
||||
RUN_SUCCESS=0
|
||||
RUN_FAILED=0
|
||||
RUN_FAILED_STACKS=()
|
||||
RUN_SKIPPED_STAGES=()
|
||||
RUN_SKIPPED_STACKS=()
|
||||
|
||||
for ((index = 0; index < ${#stacks[@]}; index++)); do
|
||||
stack="${stacks[index]}"
|
||||
if run_stack "$stack" "$action"; then
|
||||
((RUN_SUCCESS += 1))
|
||||
else
|
||||
((RUN_FAILED += 1))
|
||||
RUN_FAILED_STACKS+=("$stack")
|
||||
RUN_SKIPPED_STACKS=("${stacks[@]:$((index + 1))}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
print_run_summary
|
||||
((RUN_FAILED == 0))
|
||||
}
|
||||
|
||||
run_all_sequential() {
|
||||
local action="$1"
|
||||
shift
|
||||
local stack
|
||||
local -a failed_stacks=()
|
||||
for stack in "${MANAGED_STACKS[@]}"; do
|
||||
for stack in "$@"; do
|
||||
run_stack "$stack" "$action" || failed_stacks+=("$stack")
|
||||
done
|
||||
|
||||
@@ -678,17 +720,11 @@ list_stacks() {
|
||||
}
|
||||
|
||||
list_images() {
|
||||
local target="${1:-}"
|
||||
local stack output service image
|
||||
local failed=0
|
||||
local -a stacks=("${MANAGED_STACKS[@]}") cells=(STACK SERVICE IMAGE)
|
||||
local -a cells=(STACK SERVICE IMAGE)
|
||||
|
||||
if [[ -n "$target" ]]; then
|
||||
require_target "images" "$target"
|
||||
stacks=("$target")
|
||||
fi
|
||||
|
||||
for stack in "${stacks[@]}"; do
|
||||
for stack in "$@"; do
|
||||
# Read service/image pairs from Compose's canonical YAML (two-space indent).
|
||||
if ! output="$(compose "$stack" config --format yaml | awk -v project="$stack" '
|
||||
{ gsub(/["\047]/, "") }
|
||||
@@ -728,6 +764,17 @@ require_target() {
|
||||
is_managed "$target" || die "stack is excluded: $target"
|
||||
}
|
||||
|
||||
# Validate every requested stack before acting on any of them.
|
||||
require_targets() {
|
||||
local command="$1"
|
||||
local target
|
||||
shift
|
||||
|
||||
for target in "$@"; do
|
||||
require_target "$command" "$target"
|
||||
done
|
||||
}
|
||||
|
||||
acquire_lock() {
|
||||
mkdir -p "$(dirname "$LOCK_FILE")" ||
|
||||
die "cannot create lock directory"
|
||||
@@ -739,10 +786,10 @@ acquire_lock() {
|
||||
|
||||
main() {
|
||||
local ACTION="${1:-}"
|
||||
local TARGET="${2:-}"
|
||||
local target
|
||||
local -a requested=("${@:2}")
|
||||
local -a targets=()
|
||||
|
||||
[[ $# -le 2 ]] || die "too many arguments"
|
||||
local -a stacks=()
|
||||
|
||||
case "$ACTION" in
|
||||
help | -h | --help)
|
||||
@@ -751,56 +798,60 @@ main() {
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$TARGET" ]]; then
|
||||
normalize_target TARGET
|
||||
fi
|
||||
for target in "${requested[@]}"; do
|
||||
normalize_target target
|
||||
contains "$target" "${targets[@]}" &&
|
||||
die "stack is specified more than once: $target"
|
||||
targets+=("$target")
|
||||
done
|
||||
|
||||
load_config
|
||||
discover_stacks
|
||||
validate_config_stacks
|
||||
build_execution_stages
|
||||
|
||||
# Commands act on the requested stacks, or on every managed stack when
|
||||
# none were requested.
|
||||
stacks=("${targets[@]}")
|
||||
((${#stacks[@]} > 0)) || stacks=("${MANAGED_STACKS[@]}")
|
||||
|
||||
case "$ACTION" in
|
||||
up | start | stop | restart | down | pull | update)
|
||||
acquire_lock
|
||||
require_targets "$ACTION" "${targets[@]}"
|
||||
|
||||
if [[ -n "$TARGET" ]]; then
|
||||
require_target "$ACTION" "$TARGET"
|
||||
targets=("$TARGET")
|
||||
else
|
||||
targets=("${MANAGED_STACKS[@]}")
|
||||
fi
|
||||
if [[ "$ACTION" == "up" || "$ACTION" == "update" ]]; then
|
||||
preflight_configs "${targets[@]}" || return 1
|
||||
preflight_configs "${stacks[@]}" || return 1
|
||||
fi
|
||||
|
||||
if [[ -n "$TARGET" ]]; then
|
||||
run_stack "$TARGET" "$ACTION"
|
||||
else
|
||||
if ((${#targets[@]} == 0)); then
|
||||
run_all_staged "$ACTION"
|
||||
elif ((${#targets[@]} == 1)); then
|
||||
run_stack "${stacks[0]}" "$ACTION"
|
||||
else
|
||||
run_stacks_sequential "$ACTION" "${stacks[@]}"
|
||||
fi
|
||||
;;
|
||||
ps)
|
||||
if [[ -n "$TARGET" ]]; then
|
||||
require_target "$ACTION" "$TARGET"
|
||||
run_stack "$TARGET" "$ACTION"
|
||||
else
|
||||
run_all_sequential "$ACTION"
|
||||
fi
|
||||
require_targets "$ACTION" "${targets[@]}"
|
||||
run_all_sequential "$ACTION" "${stacks[@]}"
|
||||
;;
|
||||
logs)
|
||||
require_target "$ACTION" "$TARGET"
|
||||
compose "$TARGET" logs -f --tail=100
|
||||
((${#targets[@]} <= 1)) || die "$ACTION expects a single stack"
|
||||
require_target "$ACTION" "${targets[0]:-}"
|
||||
compose "${targets[0]}" logs -f --tail=100
|
||||
;;
|
||||
config)
|
||||
require_target "$ACTION" "$TARGET"
|
||||
compose "$TARGET" config
|
||||
((${#targets[@]} <= 1)) || die "$ACTION expects a single stack"
|
||||
require_target "$ACTION" "${targets[0]:-}"
|
||||
compose "${targets[0]}" config
|
||||
;;
|
||||
images)
|
||||
list_images "$TARGET"
|
||||
require_targets "$ACTION" "${targets[@]}"
|
||||
list_images "${stacks[@]}"
|
||||
;;
|
||||
list)
|
||||
[[ -z "$TARGET" ]] || die "list does not accept a stack name"
|
||||
((${#targets[@]} == 0)) || die "list does not accept a stack name"
|
||||
list_stacks
|
||||
;;
|
||||
*)
|
||||
|
||||
Reference in new issue
Block a user