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
|
||||
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:
|
||||
up [STACK] Create and start containers
|
||||
start [STACK] Start existing containers
|
||||
@@ -155,6 +159,42 @@ validate_name() {
|
||||
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.
|
||||
max_parallel() {
|
||||
[[ $# -eq 1 ]] || die "max_parallel expects exactly one value"
|
||||
@@ -170,6 +210,7 @@ add_stage() {
|
||||
local stack
|
||||
local -n stages="$stages_name"
|
||||
local -n limits="$limits_name"
|
||||
local -a normalized=()
|
||||
shift 3
|
||||
|
||||
if [[ "${1:-}" == "--parallel" ]]; then
|
||||
@@ -182,15 +223,17 @@ add_stage() {
|
||||
[[ $# -ge 1 ]] || die "$stage expects at least one stack"
|
||||
|
||||
for stack in "$@"; do
|
||||
normalize_target stack
|
||||
validate_name "stack" "$stack"
|
||||
contains "$stack" "${CONFIGURED_STACKS[@]}" &&
|
||||
die "stack is assigned more than once: $stack"
|
||||
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
||||
die "stack is both assigned and excluded: $stack"
|
||||
CONFIGURED_STACKS+=("$stack")
|
||||
normalized+=("$stack")
|
||||
done
|
||||
|
||||
stages+=("$*")
|
||||
stages+=("${normalized[*]}")
|
||||
limits+=("$limit")
|
||||
}
|
||||
|
||||
@@ -203,6 +246,7 @@ exclude() {
|
||||
|
||||
local stack
|
||||
for stack in "$@"; do
|
||||
normalize_target stack
|
||||
validate_name "stack" "$stack"
|
||||
contains "$stack" "${EXCLUDED_STACKS[@]}" &&
|
||||
die "stack is excluded more than once: $stack"
|
||||
@@ -707,6 +751,10 @@ main() {
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$TARGET" ]]; then
|
||||
normalize_target TARGET
|
||||
fi
|
||||
|
||||
load_config
|
||||
discover_stacks
|
||||
validate_config_stacks
|
||||
|
||||
Reference in new issue
Block a user