refactor: simplify staged stack configuration
This commit is contained in:
1 parent
48ddc3e728
commit
ebab7d3213
2 files changed
+65
-79
No files matched your search
+9
-21
@@ -1,26 +1,14 @@
|
|||||||
# Global maximum number of stacks executed concurrently within a group.
|
# Maximum number of stacks executed concurrently within each stage.
|
||||||
# Use 0 for unlimited concurrency.
|
# Use 0 for unlimited concurrency.
|
||||||
max_parallel 2
|
max_parallel 3
|
||||||
|
|
||||||
# Groups run from top to bottom for up/start/restart/pull/update.
|
# Each declaration is an ordered stage; stacks on the same line run in parallel.
|
||||||
# They run from bottom to top for stop/down.
|
# Add --parallel N before the stacks to override max_parallel for that stage.
|
||||||
group gateway --parallel 1 \
|
# Example: before --parallel 2 stack-a stack-b
|
||||||
nginx
|
# Unlisted stacks run in the default stage.
|
||||||
|
before nginx
|
||||||
group network \
|
before adguardhome ddns-go
|
||||||
adguardhome \
|
after backrest
|
||||||
ddns-go
|
|
||||||
|
|
||||||
group services --parallel 3 \
|
|
||||||
samba \
|
|
||||||
immich \
|
|
||||||
gitea \
|
|
||||||
openlist \
|
|
||||||
homeassistant \
|
|
||||||
qbittorrent
|
|
||||||
|
|
||||||
group backup --parallel 1 \
|
|
||||||
backrest
|
|
||||||
|
|
||||||
# Excluded stacks are ignored by all bulk commands.
|
# Excluded stacks are ignored by all bulk commands.
|
||||||
# exclude experimental-stack
|
# exclude experimental-stack
|
||||||
@@ -10,9 +10,10 @@ LOCK_FILE="${STACKS_LOCK_FILE:-/run/lock/stacks-manage.lock}"
|
|||||||
|
|
||||||
DEFAULT_MAX_PARALLEL=2
|
DEFAULT_MAX_PARALLEL=2
|
||||||
|
|
||||||
CONFIG_GROUP_NAMES=()
|
BEFORE_GROUPS=()
|
||||||
CONFIG_GROUP_LIMITS=()
|
BEFORE_LIMITS=()
|
||||||
CONFIG_GROUP_STACKS=()
|
AFTER_GROUPS=()
|
||||||
|
AFTER_LIMITS=()
|
||||||
CONFIGURED_STACKS=()
|
CONFIGURED_STACKS=()
|
||||||
EXCLUDED_STACKS=()
|
EXCLUDED_STACKS=()
|
||||||
|
|
||||||
@@ -21,7 +22,6 @@ MANAGED_STACKS=()
|
|||||||
GROUP_NAMES=()
|
GROUP_NAMES=()
|
||||||
GROUP_LIMITS=()
|
GROUP_LIMITS=()
|
||||||
GROUP_STACKS=()
|
GROUP_STACKS=()
|
||||||
UNASSIGNED_STACKS=()
|
|
||||||
|
|
||||||
RUN_SUCCESS=0
|
RUN_SUCCESS=0
|
||||||
RUN_FAILED=0
|
RUN_FAILED=0
|
||||||
@@ -52,9 +52,10 @@ Commands:
|
|||||||
list List discovered stacks and their group/status
|
list List discovered stacks and their group/status
|
||||||
|
|
||||||
Bulk execution:
|
Bulk execution:
|
||||||
up/start/restart/pull/update run groups in declaration order.
|
up/start/restart/pull/update run before, default, then after.
|
||||||
stop/down run groups in reverse order.
|
stop/down run the same groups in reverse order.
|
||||||
Stacks within a group run in parallel up to that group's limit.
|
Each before/after declaration creates an ordered group.
|
||||||
|
Stacks within a group 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
|
||||||
@@ -88,52 +89,48 @@ validate_name() {
|
|||||||
die "invalid $kind name: $value"
|
die "invalid $kind name: $value"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set the default concurrency for groups without an override.
|
# Set the maximum concurrency within each stage.
|
||||||
max_parallel() {
|
max_parallel() {
|
||||||
[[ $# -eq 1 ]] || die "max_parallel expects exactly one value"
|
[[ $# -eq 1 ]] || die "max_parallel expects exactly one value"
|
||||||
validate_parallel_limit "$1"
|
validate_parallel_limit "$1"
|
||||||
DEFAULT_MAX_PARALLEL="$1"
|
DEFAULT_MAX_PARALLEL="$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Declare an ordered group and its stacks.
|
add_stage_group() {
|
||||||
# Usage: group NAME [--parallel N] STACK...
|
local groups_name="$1"
|
||||||
group() {
|
local limits_name="$2"
|
||||||
[[ $# -ge 2 ]] || die "group expects NAME [--parallel N] STACK..."
|
local stage="$3"
|
||||||
|
|
||||||
local name="$1"
|
|
||||||
local limit=""
|
local limit=""
|
||||||
local stack
|
local stack
|
||||||
shift
|
local -n groups="$groups_name"
|
||||||
|
local -n limits="$limits_name"
|
||||||
validate_name "group" "$name"
|
shift 3
|
||||||
[[ "$name" != "unassigned" ]] ||
|
|
||||||
die "group name is reserved: $name"
|
|
||||||
contains "$name" "${CONFIG_GROUP_NAMES[@]}" &&
|
|
||||||
die "group is declared more than once: $name"
|
|
||||||
|
|
||||||
if [[ "${1:-}" == "--parallel" ]]; then
|
if [[ "${1:-}" == "--parallel" ]]; then
|
||||||
[[ $# -ge 3 ]] || die "group $name: --parallel expects a value and at least one stack"
|
[[ $# -ge 3 ]] || die "$stage --parallel expects a value and at least one stack"
|
||||||
limit="$2"
|
limit="$2"
|
||||||
validate_parallel_limit "$limit"
|
validate_parallel_limit "$limit"
|
||||||
shift 2
|
shift 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ $# -ge 1 ]] || die "group has no stacks: $name"
|
[[ $# -ge 1 ]] || die "$stage expects at least one stack"
|
||||||
|
|
||||||
for stack in "$@"; do
|
for stack in "$@"; do
|
||||||
validate_name "stack" "$stack"
|
validate_name "stack" "$stack"
|
||||||
contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
||||||
die "stack is assigned to more than one group: $stack"
|
die "stack is assigned more than once: $stack"
|
||||||
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
||||||
die "stack is both grouped and excluded: $stack"
|
die "stack is both assigned and excluded: $stack"
|
||||||
CONFIGURED_STACKS+=("$stack")
|
CONFIGURED_STACKS+=("$stack")
|
||||||
done
|
done
|
||||||
|
|
||||||
CONFIG_GROUP_NAMES+=("$name")
|
groups+=("$*")
|
||||||
CONFIG_GROUP_LIMITS+=("$limit")
|
limits+=("$limit")
|
||||||
CONFIG_GROUP_STACKS+=("$*")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
before() { add_stage_group BEFORE_GROUPS BEFORE_LIMITS before "$@"; }
|
||||||
|
after() { add_stage_group AFTER_GROUPS AFTER_LIMITS after "$@"; }
|
||||||
|
|
||||||
# Exclude stacks from all bulk operations.
|
# Exclude stacks from all bulk operations.
|
||||||
exclude() {
|
exclude() {
|
||||||
[[ $# -ge 1 ]] || die "exclude expects at least one stack"
|
[[ $# -ge 1 ]] || die "exclude expects at least one stack"
|
||||||
@@ -144,7 +141,7 @@ exclude() {
|
|||||||
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
||||||
die "stack is excluded more than once: $stack"
|
die "stack is excluded more than once: $stack"
|
||||||
contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
||||||
die "stack is both grouped and excluded: $stack"
|
die "stack is both assigned and excluded: $stack"
|
||||||
EXCLUDED_STACKS+=("$stack")
|
EXCLUDED_STACKS+=("$stack")
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@@ -152,7 +149,7 @@ exclude() {
|
|||||||
load_config() {
|
load_config() {
|
||||||
[[ -f "$CONFIG_FILE" ]] || return 0
|
[[ -f "$CONFIG_FILE" ]] || return 0
|
||||||
|
|
||||||
# The trusted config calls max_parallel, group, and exclude above.
|
# The trusted config calls max_parallel, before, after, and exclude above.
|
||||||
source "$CONFIG_FILE" || die "cannot load config file: $CONFIG_FILE"
|
source "$CONFIG_FILE" || die "cannot load config file: $CONFIG_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,45 +184,52 @@ validate_config_stacks() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
build_execution_groups() {
|
append_stage_groups() {
|
||||||
local i stack limit
|
local stage="$1"
|
||||||
|
local groups_name="$2"
|
||||||
|
local limits_name="$3"
|
||||||
|
local i name
|
||||||
local -a stacks=()
|
local -a stacks=()
|
||||||
|
local -n groups="$groups_name"
|
||||||
|
local -n limits="$limits_name"
|
||||||
|
|
||||||
|
for ((i = 0; i < ${#groups[@]}; i++)); do
|
||||||
|
name="$stage"
|
||||||
|
((${#groups[@]} > 1)) && name+="-$((i + 1))"
|
||||||
|
GROUP_NAMES+=("$name")
|
||||||
|
GROUP_LIMITS+=("${limits[i]:-$DEFAULT_MAX_PARALLEL}")
|
||||||
|
GROUP_STACKS+=("${groups[i]}")
|
||||||
|
read -r -a stacks <<<"${groups[i]}"
|
||||||
|
MANAGED_STACKS+=("${stacks[@]}")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
build_execution_groups() {
|
||||||
|
local stack
|
||||||
|
local -a default_stacks=()
|
||||||
|
|
||||||
GROUP_NAMES=()
|
GROUP_NAMES=()
|
||||||
GROUP_LIMITS=()
|
GROUP_LIMITS=()
|
||||||
GROUP_STACKS=()
|
GROUP_STACKS=()
|
||||||
MANAGED_STACKS=()
|
MANAGED_STACKS=()
|
||||||
UNASSIGNED_STACKS=()
|
|
||||||
|
|
||||||
for ((i = 0; i < ${#CONFIG_GROUP_NAMES[@]}; i++)); do
|
append_stage_groups before BEFORE_GROUPS BEFORE_LIMITS
|
||||||
limit="${CONFIG_GROUP_LIMITS[i]:-$DEFAULT_MAX_PARALLEL}"
|
|
||||||
GROUP_NAMES+=("${CONFIG_GROUP_NAMES[i]}")
|
|
||||||
GROUP_LIMITS+=("$limit")
|
|
||||||
GROUP_STACKS+=("${CONFIG_GROUP_STACKS[i]}")
|
|
||||||
|
|
||||||
read -r -a stacks <<<"${CONFIG_GROUP_STACKS[i]}"
|
|
||||||
MANAGED_STACKS+=("${stacks[@]}")
|
|
||||||
done
|
|
||||||
|
|
||||||
for stack in "${DISCOVERED_STACKS[@]}"; do
|
for stack in "${DISCOVERED_STACKS[@]}"; do
|
||||||
if ! contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
if ! contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
||||||
! contains "$stack" "${EXCLUDED_STACKS[@]}"; then
|
! contains "$stack" "${EXCLUDED_STACKS[@]}"; then
|
||||||
UNASSIGNED_STACKS+=("$stack")
|
default_stacks+=("$stack")
|
||||||
MANAGED_STACKS+=("$stack")
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if ((${#UNASSIGNED_STACKS[@]} > 0)); then
|
if ((${#default_stacks[@]} > 0)); then
|
||||||
GROUP_NAMES+=("unassigned")
|
GROUP_NAMES+=("default")
|
||||||
GROUP_LIMITS+=("$DEFAULT_MAX_PARALLEL")
|
GROUP_LIMITS+=("$DEFAULT_MAX_PARALLEL")
|
||||||
GROUP_STACKS+=("${UNASSIGNED_STACKS[*]}")
|
GROUP_STACKS+=("${default_stacks[*]}")
|
||||||
|
MANAGED_STACKS+=("${default_stacks[@]}")
|
||||||
fi
|
fi
|
||||||
}
|
|
||||||
|
|
||||||
warn_unassigned() {
|
append_stage_groups after AFTER_GROUPS AFTER_LIMITS
|
||||||
((${#UNASSIGNED_STACKS[@]} == 0)) && return 0
|
|
||||||
|
|
||||||
echo "WARNING: unassigned stacks will run in the final implicit group: ${UNASSIGNED_STACKS[*]}" >&2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stack_group() {
|
stack_group() {
|
||||||
@@ -463,8 +467,6 @@ run_all_grouped() {
|
|||||||
RUN_FAILED=0
|
RUN_FAILED=0
|
||||||
RUN_FAILED_STACKS=()
|
RUN_FAILED_STACKS=()
|
||||||
RUN_SKIPPED_GROUPS=()
|
RUN_SKIPPED_GROUPS=()
|
||||||
warn_unassigned
|
|
||||||
|
|
||||||
if [[ "$action" == "stop" || "$action" == "down" ]]; then
|
if [[ "$action" == "stop" || "$action" == "down" ]]; then
|
||||||
direction="reverse"
|
direction="reverse"
|
||||||
start=$((end - 1))
|
start=$((end - 1))
|
||||||
@@ -487,8 +489,6 @@ run_all_sequential() {
|
|||||||
local action="$1"
|
local action="$1"
|
||||||
local stack
|
local stack
|
||||||
local -a failed_stacks=()
|
local -a failed_stacks=()
|
||||||
warn_unassigned
|
|
||||||
|
|
||||||
for stack in "${MANAGED_STACKS[@]}"; do
|
for stack in "${MANAGED_STACKS[@]}"; do
|
||||||
run_stack "$stack" "$action" || failed_stacks+=("$stack")
|
run_stack "$stack" "$action" || failed_stacks+=("$stack")
|
||||||
done
|
done
|
||||||
@@ -503,7 +503,6 @@ run_all_sequential() {
|
|||||||
list_stacks() {
|
list_stacks() {
|
||||||
local stack group_name status
|
local stack group_name status
|
||||||
|
|
||||||
warn_unassigned
|
|
||||||
printf '%-24s %-16s %s\n' "STACK" "GROUP" "STATUS"
|
printf '%-24s %-16s %s\n' "STACK" "GROUP" "STATUS"
|
||||||
printf '%-24s %-16s %s\n' "------------------------" "----------------" "---------------"
|
printf '%-24s %-16s %s\n' "------------------------" "----------------" "---------------"
|
||||||
|
|
||||||
@@ -546,7 +545,6 @@ list_images() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
warn_unassigned
|
|
||||||
for stack in "${MANAGED_STACKS[@]}"; do
|
for stack in "${MANAGED_STACKS[@]}"; do
|
||||||
stack_images "$stack" || ((failed += 1))
|
stack_images "$stack" || ((failed += 1))
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in new issue
Block a user