feat: accept stack folders as stack arguments
Folder references like qbittorrent/ or ./qbittorrent/ now select the same stack as the bare name, which matches shell tab completion. Paths are resolved against the stacks root and must point at a stack folder inside STACKS_DIR; folders elsewhere are rejected before any Docker command runs.
This commit is contained in:
1 parent
d0d254cd1d
commit
c002ec3244
1 file changed
+49
-1
@@ -71,6 +71,10 @@ usage() {
|
|||||||
cat <<EOF
|
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.
|
||||||
|
|
||||||
Lifecycle:
|
Lifecycle:
|
||||||
up [STACK] Create and start containers
|
up [STACK] Create and start containers
|
||||||
start [STACK] Start existing containers
|
start [STACK] Start existing containers
|
||||||
@@ -155,6 +159,42 @@ validate_name() {
|
|||||||
die "invalid $kind name: $value"
|
die "invalid $kind name: $value"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Rewrite a stack argument to its bare stack name in place. Plain names
|
||||||
|
# pass through unchanged. A folder reference such as ./stack/ or an
|
||||||
|
# absolute path must resolve, after following symlinks, to the stack
|
||||||
|
# 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 name path resolved canonical root
|
||||||
|
|
||||||
|
while [[ "$target" == */ ]]; do
|
||||||
|
target="${target%/}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Names without a path component are validated against the stacks.
|
||||||
|
if [[ "$target" != */* ]]; then
|
||||||
|
[[ -n "$target" ]] || die "invalid stack path: $original"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
name="${target##*/}"
|
||||||
|
[[ -n "$name" ]] || die "invalid stack path: $original"
|
||||||
|
root="$(readlink -f -- "$STACKS_DIR" 2>/dev/null)" ||
|
||||||
|
die "invalid STACKS_DIR: $STACKS_DIR"
|
||||||
|
path="$target"
|
||||||
|
[[ "$path" == /* ]] || path="$STACKS_DIR/$path"
|
||||||
|
resolved="$(readlink -f -- "$path" 2>/dev/null)" ||
|
||||||
|
die "stack folder not found: $original"
|
||||||
|
[[ -d "$resolved" ]] || die "stack folder not found: $original"
|
||||||
|
canonical="$(readlink -f -- "$root/$name" 2>/dev/null)"
|
||||||
|
[[ "$resolved" == "$canonical" ]] ||
|
||||||
|
die "not a stack folder in $root: $original"
|
||||||
|
|
||||||
|
target="$name"
|
||||||
|
}
|
||||||
|
|
||||||
# Set the maximum concurrency within each stage.
|
# 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"
|
||||||
@@ -170,6 +210,7 @@ add_stage() {
|
|||||||
local stack
|
local stack
|
||||||
local -n stages="$stages_name"
|
local -n stages="$stages_name"
|
||||||
local -n limits="$limits_name"
|
local -n limits="$limits_name"
|
||||||
|
local -a normalized=()
|
||||||
shift 3
|
shift 3
|
||||||
|
|
||||||
if [[ "${1:-}" == "--parallel" ]]; then
|
if [[ "${1:-}" == "--parallel" ]]; then
|
||||||
@@ -182,15 +223,17 @@ add_stage() {
|
|||||||
[[ $# -ge 1 ]] || die "$stage expects at least one stack"
|
[[ $# -ge 1 ]] || die "$stage expects at least one stack"
|
||||||
|
|
||||||
for stack in "$@"; do
|
for stack in "$@"; do
|
||||||
|
normalize_target stack
|
||||||
validate_name "stack" "$stack"
|
validate_name "stack" "$stack"
|
||||||
contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
||||||
die "stack is assigned more than once: $stack"
|
die "stack is assigned more than once: $stack"
|
||||||
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
||||||
die "stack is both assigned and excluded: $stack"
|
die "stack is both assigned and excluded: $stack"
|
||||||
CONFIGURED_STACKS+=("$stack")
|
CONFIGURED_STACKS+=("$stack")
|
||||||
|
normalized+=("$stack")
|
||||||
done
|
done
|
||||||
|
|
||||||
stages+=("$*")
|
stages+=("${normalized[*]}")
|
||||||
limits+=("$limit")
|
limits+=("$limit")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +246,7 @@ exclude() {
|
|||||||
|
|
||||||
local stack
|
local stack
|
||||||
for stack in "$@"; do
|
for stack in "$@"; do
|
||||||
|
normalize_target stack
|
||||||
validate_name "stack" "$stack"
|
validate_name "stack" "$stack"
|
||||||
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
||||||
die "stack is excluded more than once: $stack"
|
die "stack is excluded more than once: $stack"
|
||||||
@@ -707,6 +751,10 @@ main() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
if [[ -n "$TARGET" ]]; then
|
||||||
|
normalize_target TARGET
|
||||||
|
fi
|
||||||
|
|
||||||
load_config
|
load_config
|
||||||
discover_stacks
|
discover_stacks
|
||||||
validate_config_stacks
|
validate_config_stacks
|
||||||
|
|||||||
Reference in new issue
Block a user