feat: improve staged execution safety
This commit is contained in:
1 parent
ebab7d3213
commit
1143ae9f62
1 file changed
+84
-64
@@ -10,29 +10,49 @@ LOCK_FILE="${STACKS_LOCK_FILE:-/run/lock/stacks-manage.lock}"
|
|||||||
|
|
||||||
DEFAULT_MAX_PARALLEL=2
|
DEFAULT_MAX_PARALLEL=2
|
||||||
|
|
||||||
BEFORE_GROUPS=()
|
BEFORE_STAGES=()
|
||||||
BEFORE_LIMITS=()
|
BEFORE_LIMITS=()
|
||||||
AFTER_GROUPS=()
|
AFTER_STAGES=()
|
||||||
AFTER_LIMITS=()
|
AFTER_LIMITS=()
|
||||||
CONFIGURED_STACKS=()
|
CONFIGURED_STACKS=()
|
||||||
EXCLUDED_STACKS=()
|
EXCLUDED_STACKS=()
|
||||||
|
|
||||||
DISCOVERED_STACKS=()
|
DISCOVERED_STACKS=()
|
||||||
MANAGED_STACKS=()
|
MANAGED_STACKS=()
|
||||||
GROUP_NAMES=()
|
STAGE_NAMES=()
|
||||||
GROUP_LIMITS=()
|
STAGE_LIMITS=()
|
||||||
GROUP_STACKS=()
|
STAGE_STACKS=()
|
||||||
|
|
||||||
RUN_SUCCESS=0
|
RUN_SUCCESS=0
|
||||||
RUN_FAILED=0
|
RUN_FAILED=0
|
||||||
RUN_FAILED_STACKS=()
|
RUN_FAILED_STACKS=()
|
||||||
RUN_SKIPPED_GROUPS=()
|
RUN_SKIPPED_STAGES=()
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
echo "ERROR: $*" >&2
|
echo "ERROR: $*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((BASH_VERSINFO[0] < 5 ||
|
||||||
|
(BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] < 1))); then
|
||||||
|
die "Bash 5.1 or newer is required"
|
||||||
|
fi
|
||||||
|
|
||||||
|
handle_signal() {
|
||||||
|
local status="$1"
|
||||||
|
local -a pids=()
|
||||||
|
|
||||||
|
trap - INT TERM
|
||||||
|
mapfile -t pids < <(jobs -pr)
|
||||||
|
((${#pids[@]} == 0)) || kill -TERM "${pids[@]}" 2>/dev/null
|
||||||
|
wait 2>/dev/null
|
||||||
|
printf '\n'
|
||||||
|
exit "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'handle_signal 130' INT
|
||||||
|
trap 'handle_signal 143' TERM
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") COMMAND [STACK]
|
Usage: $(basename "$0") COMMAND [STACK]
|
||||||
@@ -49,13 +69,13 @@ Commands:
|
|||||||
logs Follow logs for a stack
|
logs Follow logs for a stack
|
||||||
config Render Compose configuration for a stack
|
config Render Compose configuration for a stack
|
||||||
images Show configured images and versions
|
images Show configured images and versions
|
||||||
list List discovered stacks and their group/status
|
list List discovered stacks and their stage/status
|
||||||
|
|
||||||
Bulk execution:
|
Bulk execution:
|
||||||
up/start/restart/pull/update run before, default, then after.
|
up/start/restart/pull/update run before, default, then after.
|
||||||
stop/down run the same groups in reverse order.
|
stop/down run the same stages in reverse order.
|
||||||
Each before/after declaration creates an ordered group.
|
Each before/after declaration creates an ordered stage.
|
||||||
Stacks within a group run in parallel up to its configured limit.
|
Stacks within a stage run in parallel up to its configured limit.
|
||||||
ps/images/list keep ordered output.
|
ps/images/list keep ordered output.
|
||||||
|
|
||||||
Configuration: $CONFIG_FILE
|
Configuration: $CONFIG_FILE
|
||||||
@@ -96,13 +116,13 @@ max_parallel() {
|
|||||||
DEFAULT_MAX_PARALLEL="$1"
|
DEFAULT_MAX_PARALLEL="$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
add_stage_group() {
|
add_stage() {
|
||||||
local groups_name="$1"
|
local stages_name="$1"
|
||||||
local limits_name="$2"
|
local limits_name="$2"
|
||||||
local stage="$3"
|
local stage="$3"
|
||||||
local limit=""
|
local limit=""
|
||||||
local stack
|
local stack
|
||||||
local -n groups="$groups_name"
|
local -n stages="$stages_name"
|
||||||
local -n limits="$limits_name"
|
local -n limits="$limits_name"
|
||||||
shift 3
|
shift 3
|
||||||
|
|
||||||
@@ -124,12 +144,12 @@ add_stage_group() {
|
|||||||
CONFIGURED_STACKS+=("$stack")
|
CONFIGURED_STACKS+=("$stack")
|
||||||
done
|
done
|
||||||
|
|
||||||
groups+=("$*")
|
stages+=("$*")
|
||||||
limits+=("$limit")
|
limits+=("$limit")
|
||||||
}
|
}
|
||||||
|
|
||||||
before() { add_stage_group BEFORE_GROUPS BEFORE_LIMITS before "$@"; }
|
before() { add_stage BEFORE_STAGES BEFORE_LIMITS before "$@"; }
|
||||||
after() { add_stage_group AFTER_GROUPS AFTER_LIMITS after "$@"; }
|
after() { add_stage AFTER_STAGES AFTER_LIMITS after "$@"; }
|
||||||
|
|
||||||
# Exclude stacks from all bulk operations.
|
# Exclude stacks from all bulk operations.
|
||||||
exclude() {
|
exclude() {
|
||||||
@@ -184,36 +204,36 @@ validate_config_stacks() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
append_stage_groups() {
|
append_stages() {
|
||||||
local stage="$1"
|
local stage="$1"
|
||||||
local groups_name="$2"
|
local stages_name="$2"
|
||||||
local limits_name="$3"
|
local limits_name="$3"
|
||||||
local i name
|
local i name
|
||||||
local -a stacks=()
|
local -a stacks=()
|
||||||
local -n groups="$groups_name"
|
local -n stages="$stages_name"
|
||||||
local -n limits="$limits_name"
|
local -n limits="$limits_name"
|
||||||
|
|
||||||
for ((i = 0; i < ${#groups[@]}; i++)); do
|
for ((i = 0; i < ${#stages[@]}; i++)); do
|
||||||
name="$stage"
|
name="$stage"
|
||||||
((${#groups[@]} > 1)) && name+="-$((i + 1))"
|
((${#stages[@]} > 1)) && name+="-$((i + 1))"
|
||||||
GROUP_NAMES+=("$name")
|
STAGE_NAMES+=("$name")
|
||||||
GROUP_LIMITS+=("${limits[i]:-$DEFAULT_MAX_PARALLEL}")
|
STAGE_LIMITS+=("${limits[i]:-$DEFAULT_MAX_PARALLEL}")
|
||||||
GROUP_STACKS+=("${groups[i]}")
|
STAGE_STACKS+=("${stages[i]}")
|
||||||
read -r -a stacks <<<"${groups[i]}"
|
read -r -a stacks <<<"${stages[i]}"
|
||||||
MANAGED_STACKS+=("${stacks[@]}")
|
MANAGED_STACKS+=("${stacks[@]}")
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
build_execution_groups() {
|
build_execution_stages() {
|
||||||
local stack
|
local stack
|
||||||
local -a default_stacks=()
|
local -a default_stacks=()
|
||||||
|
|
||||||
GROUP_NAMES=()
|
STAGE_NAMES=()
|
||||||
GROUP_LIMITS=()
|
STAGE_LIMITS=()
|
||||||
GROUP_STACKS=()
|
STAGE_STACKS=()
|
||||||
MANAGED_STACKS=()
|
MANAGED_STACKS=()
|
||||||
|
|
||||||
append_stage_groups before BEFORE_GROUPS BEFORE_LIMITS
|
append_stages before BEFORE_STAGES BEFORE_LIMITS
|
||||||
|
|
||||||
for stack in "${DISCOVERED_STACKS[@]}"; do
|
for stack in "${DISCOVERED_STACKS[@]}"; do
|
||||||
if ! contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
if ! contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
||||||
@@ -223,24 +243,24 @@ build_execution_groups() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
if ((${#default_stacks[@]} > 0)); then
|
if ((${#default_stacks[@]} > 0)); then
|
||||||
GROUP_NAMES+=("default")
|
STAGE_NAMES+=("default")
|
||||||
GROUP_LIMITS+=("$DEFAULT_MAX_PARALLEL")
|
STAGE_LIMITS+=("$DEFAULT_MAX_PARALLEL")
|
||||||
GROUP_STACKS+=("${default_stacks[*]}")
|
STAGE_STACKS+=("${default_stacks[*]}")
|
||||||
MANAGED_STACKS+=("${default_stacks[@]}")
|
MANAGED_STACKS+=("${default_stacks[@]}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
append_stage_groups after AFTER_GROUPS AFTER_LIMITS
|
append_stages after AFTER_STAGES AFTER_LIMITS
|
||||||
}
|
}
|
||||||
|
|
||||||
stack_group() {
|
stack_stage() {
|
||||||
local target="$1"
|
local target="$1"
|
||||||
local i
|
local i
|
||||||
local -a stacks=()
|
local -a stacks=()
|
||||||
|
|
||||||
for ((i = 0; i < ${#GROUP_NAMES[@]}; i++)); do
|
for ((i = 0; i < ${#STAGE_NAMES[@]}; i++)); do
|
||||||
read -r -a stacks <<<"${GROUP_STACKS[i]}"
|
read -r -a stacks <<<"${STAGE_STACKS[i]}"
|
||||||
if contains "$target" "${stacks[@]}"; then
|
if contains "$target" "${stacks[@]}"; then
|
||||||
echo "${GROUP_NAMES[i]}"
|
echo "${STAGE_NAMES[i]}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -289,11 +309,11 @@ run_stack() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
run_group() {
|
run_stage() {
|
||||||
local group_index="$1"
|
local stage_index="$1"
|
||||||
local action="$2"
|
local action="$2"
|
||||||
local group_name="${GROUP_NAMES[group_index]}"
|
local stage_name="${STAGE_NAMES[stage_index]}"
|
||||||
local limit="${GROUP_LIMITS[group_index]}"
|
local limit="${STAGE_LIMITS[stage_index]}"
|
||||||
local stack pid finished_pid status success_result working_result details
|
local stack pid finished_pid status success_result working_result details
|
||||||
local index finished_index total
|
local index finished_index total
|
||||||
local next=0 running=0 completed=0 failed=0 stack_width=0 interactive=0 rendered=0
|
local next=0 running=0 completed=0 failed=0 stack_width=0 interactive=0 rendered=0
|
||||||
@@ -310,7 +330,7 @@ run_group() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
read -r -a stacks <<<"${GROUP_STACKS[group_index]}"
|
read -r -a stacks <<<"${STAGE_STACKS[stage_index]}"
|
||||||
total="${#stacks[@]}"
|
total="${#stacks[@]}"
|
||||||
((total > 0)) || return 0
|
((total > 0)) || return 0
|
||||||
for ((index = 0; index < total; index++)); do
|
for ((index = 0; index < total; index++)); do
|
||||||
@@ -335,7 +355,7 @@ run_group() {
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
if ((!interactive)); then
|
if ((!interactive)); then
|
||||||
echo "==> Group $group_name: $action ($details)"
|
echo "==> Stage $stage_name: $action ($details)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$action" in
|
case "$action" in
|
||||||
@@ -378,7 +398,7 @@ run_group() {
|
|||||||
if ((interactive)); then
|
if ((interactive)); then
|
||||||
((rendered)) && printf '\033[%dA' "$((total + 1))"
|
((rendered)) && printf '\033[%dA' "$((total + 1))"
|
||||||
printf '\r\033[2K[+] %s: %s %d/%d\n' \
|
printf '\r\033[2K[+] %s: %s %d/%d\n' \
|
||||||
"$group_name" "$action" "$completed" "$total"
|
"$stage_name" "$action" "$completed" "$total"
|
||||||
for ((index = 0; index < total; index++)); do
|
for ((index = 0; index < total; index++)); do
|
||||||
printf '\r\033[2K %s%s%s Stack %-*s %s%s%s\n' \
|
printf '\r\033[2K %s%s%s Stack %-*s %s%s%s\n' \
|
||||||
"${state_colors[index]}" "${markers[index]}" "$color_reset" \
|
"${state_colors[index]}" "${markers[index]}" "$color_reset" \
|
||||||
@@ -434,39 +454,39 @@ print_run_summary() {
|
|||||||
echo "Failed stacks: ${RUN_FAILED_STACKS[*]}"
|
echo "Failed stacks: ${RUN_FAILED_STACKS[*]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ((${#RUN_SKIPPED_GROUPS[@]} > 0)); then
|
if ((${#RUN_SKIPPED_STAGES[@]} > 0)); then
|
||||||
echo "Skipped groups: ${RUN_SKIPPED_GROUPS[*]}"
|
echo "Skipped stages: ${RUN_SKIPPED_STAGES[*]}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_skipped_groups() {
|
collect_skipped_stages() {
|
||||||
local failed_index="$1"
|
local failed_index="$1"
|
||||||
local direction="$2"
|
local direction="$2"
|
||||||
local i
|
local i
|
||||||
|
|
||||||
RUN_SKIPPED_GROUPS=()
|
RUN_SKIPPED_STAGES=()
|
||||||
|
|
||||||
if [[ "$direction" == "reverse" ]]; then
|
if [[ "$direction" == "reverse" ]]; then
|
||||||
for ((i = failed_index - 1; i >= 0; i--)); do
|
for ((i = failed_index - 1; i >= 0; i--)); do
|
||||||
RUN_SKIPPED_GROUPS+=("${GROUP_NAMES[i]}")
|
RUN_SKIPPED_STAGES+=("${STAGE_NAMES[i]}")
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
for ((i = failed_index + 1; i < ${#GROUP_NAMES[@]}; i++)); do
|
for ((i = failed_index + 1; i < ${#STAGE_NAMES[@]}; i++)); do
|
||||||
RUN_SKIPPED_GROUPS+=("${GROUP_NAMES[i]}")
|
RUN_SKIPPED_STAGES+=("${STAGE_NAMES[i]}")
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_all_grouped() {
|
run_all_staged() {
|
||||||
local action="$1"
|
local action="$1"
|
||||||
local direction="forward"
|
local direction="forward"
|
||||||
local start=0 end="${#GROUP_NAMES[@]}" step=1
|
local start=0 end="${#STAGE_NAMES[@]}" step=1
|
||||||
local i
|
local i
|
||||||
|
|
||||||
RUN_SUCCESS=0
|
RUN_SUCCESS=0
|
||||||
RUN_FAILED=0
|
RUN_FAILED=0
|
||||||
RUN_FAILED_STACKS=()
|
RUN_FAILED_STACKS=()
|
||||||
RUN_SKIPPED_GROUPS=()
|
RUN_SKIPPED_STAGES=()
|
||||||
if [[ "$action" == "stop" || "$action" == "down" ]]; then
|
if [[ "$action" == "stop" || "$action" == "down" ]]; then
|
||||||
direction="reverse"
|
direction="reverse"
|
||||||
start=$((end - 1))
|
start=$((end - 1))
|
||||||
@@ -475,8 +495,8 @@ run_all_grouped() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for ((i = start; i != end; i += step)); do
|
for ((i = start; i != end; i += step)); do
|
||||||
if ! run_group "$i" "$action"; then
|
if ! run_stage "$i" "$action"; then
|
||||||
collect_skipped_groups "$i" "$direction"
|
collect_skipped_stages "$i" "$direction"
|
||||||
print_run_summary
|
print_run_summary
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -501,21 +521,21 @@ run_all_sequential() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
list_stacks() {
|
list_stacks() {
|
||||||
local stack group_name status
|
local stack stage_name status
|
||||||
|
|
||||||
printf '%-24s %-16s %s\n' "STACK" "GROUP" "STATUS"
|
printf '%-24s %-16s %s\n' "STACK" "STAGE" "STATUS"
|
||||||
printf '%-24s %-16s %s\n' "------------------------" "----------------" "---------------"
|
printf '%-24s %-16s %s\n' "------------------------" "----------------" "---------------"
|
||||||
|
|
||||||
for stack in "${DISCOVERED_STACKS[@]}"; do
|
for stack in "${DISCOVERED_STACKS[@]}"; do
|
||||||
if contains "$stack" "${EXCLUDED_STACKS[@]}"; then
|
if contains "$stack" "${EXCLUDED_STACKS[@]}"; then
|
||||||
group_name="-"
|
stage_name="-"
|
||||||
status="excluded"
|
status="excluded"
|
||||||
else
|
else
|
||||||
group_name="$(stack_group "$stack")"
|
stage_name="$(stack_stage "$stack")"
|
||||||
status="managed"
|
status="managed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '%-24s %-16s %s\n' "$stack" "$group_name" "$status"
|
printf '%-24s %-16s %s\n' "$stack" "$stage_name" "$status"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,7 +593,7 @@ acquire_lock() {
|
|||||||
load_config
|
load_config
|
||||||
discover_stacks
|
discover_stacks
|
||||||
validate_config_stacks
|
validate_config_stacks
|
||||||
build_execution_groups
|
build_execution_stages
|
||||||
|
|
||||||
ACTION="${1:-}"
|
ACTION="${1:-}"
|
||||||
TARGET="${2:-}"
|
TARGET="${2:-}"
|
||||||
@@ -588,7 +608,7 @@ up | start | stop | restart | down | pull | update)
|
|||||||
require_target "$ACTION" "$TARGET"
|
require_target "$ACTION" "$TARGET"
|
||||||
run_stack "$TARGET" "$ACTION"
|
run_stack "$TARGET" "$ACTION"
|
||||||
else
|
else
|
||||||
run_all_grouped "$ACTION"
|
run_all_staged "$ACTION"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
ps)
|
ps)
|
||||||
|
|||||||
Reference in new issue
Block a user