From 58636aa06e9ee619d76e426d3cf7a9654a84d7d0 Mon Sep 17 00:00:00 2001 From: lingkai5wu Date: Thu, 15 Oct 2026 14:42:35 +0800 Subject: [PATCH] feat: accept multiple stack arguments --- manage.sh | 171 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 111 insertions(+), 60 deletions(-) diff --git a/manage.sh b/manage.sh index 01efd70..3df017b 100755 --- a/manage.sh +++ b/manage.sh @@ -28,6 +28,7 @@ RUN_SUCCESS=0 RUN_FAILED=0 RUN_FAILED_STACKS=() RUN_SKIPPED_STAGES=() +RUN_SKIPPED_STACKS=() die() { echo "ERROR: $*" >&2 @@ -69,28 +70,34 @@ handle_signal() { usage() { cat </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 ;; *)