fix the compose call: it never worked outside --dry-run
Setting IFS to a newline to pass the environment also stopped the command line from splitting, so '-f a -f b' went out as a single argument and the two words 'docker compose' were looked up as one program name. Every real command was broken; only --dry-run, which prints a string, looked right. The variables are now exported in a subshell instead of going through env(1), which keeps values with spaces intact and leaves the command line to normal word splitting. The tests only checked what --dry-run printed, which is why they missed it. spec/unit/compose_spec.sh now asserts the argv that is actually executed, for both the plugin and the standalone binary.
This commit is contained in:
+19
-10
@@ -39,22 +39,31 @@ myos_compose() {
|
|||||||
done
|
done
|
||||||
_dir=$(dirname "$_first")
|
_dir=$(dirname "$_first")
|
||||||
|
|
||||||
# variables the compose files reference, plus the network names they default
|
# the variables the compose files reference, plus the network names: those
|
||||||
# on (networks.yml is appended after the scan, so its variables are added here)
|
# live in networks.yml, which is appended after the scan
|
||||||
# shellcheck disable=SC2086 # both are deliberate word lists
|
# shellcheck disable=SC2086 # both are deliberate word lists
|
||||||
_vars=$(myos_env_vars $_files)
|
_vars=$(myos_env_vars $_files)
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
_envargs=$(myos_env_export $_vars DOCKER_NETWORK_DEFAULT DOCKER_NETWORK_PRIVATE DOCKER_NETWORK_PUBLIC COMPOSE_SERVICE_NAME)
|
_envargs=$(myos_env_export $_vars DOCKER_NETWORK_DEFAULT DOCKER_NETWORK_PRIVATE DOCKER_NETWORK_PUBLIC COMPOSE_SERVICE_NAME)
|
||||||
|
|
||||||
if [ "${DRYRUN:-false}" = true ]; then
|
if [ "${DRYRUN:-false}" = true ]; then
|
||||||
|
# shellcheck disable=SC2086 # printed, not executed
|
||||||
printf '%s%s -p %s --project-directory %s %s\n' "$_bin" "$_fargs" "$_project" "$_dir" "$*"
|
printf '%s%s -p %s --project-directory %s %s\n' "$_bin" "$_fargs" "$_project" "$_dir" "$*"
|
||||||
else
|
return 0
|
||||||
_IFS=$IFS; IFS='
|
|
||||||
'
|
|
||||||
# shellcheck disable=SC2046,SC2086 # deliberate word splitting on IFS=newline
|
|
||||||
env $_envargs $_bin --ansi=auto $_fargs -p "$_project" --project-directory "$_dir" "$@"
|
|
||||||
_rc=$?
|
|
||||||
IFS=$_IFS
|
|
||||||
return $_rc
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Export the variables in a subshell rather than through env(1): a value may
|
||||||
|
# contain spaces, and the command line must still be split on spaces (the
|
||||||
|
# compose binary can be the two words "docker compose").
|
||||||
|
(
|
||||||
|
while IFS= read -r _kv; do
|
||||||
|
[ -n "$_kv" ] || continue
|
||||||
|
# shellcheck disable=SC2163 # _kv is a NAME=value pair, not a name
|
||||||
|
export "$_kv"
|
||||||
|
done <<EOF
|
||||||
|
$_envargs
|
||||||
|
EOF
|
||||||
|
# shellcheck disable=SC2086 # _bin and _fargs are deliberate word lists
|
||||||
|
exec $_bin --ansi=auto $_fargs -p "$_project" --project-directory "$_dir" "$@"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Mock docker for myos tests: logs every call, answers a few read-only queries deterministically.
|
# Mock docker for myos tests: logs every call, answers a few read-only queries deterministically.
|
||||||
[ -n "${MYOS_DOCKER_LOG:-}" ] && printf 'docker %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
if [ -n "${MYOS_DOCKER_LOG:-}" ]; then
|
||||||
|
printf 'docker %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
||||||
|
# the environment is recorded too, so a test can assert what was exported
|
||||||
|
env > "$(dirname "$MYOS_DOCKER_LOG")/env.log"
|
||||||
|
fi
|
||||||
case "$1 $2" in
|
case "$1 $2" in
|
||||||
"compose version") echo "${MOCK_COMPOSE_VERSION:-2.29.0}" ;;
|
"compose version") echo "${MOCK_COMPOSE_VERSION:-2.29.0}" ;;
|
||||||
"compose config") echo "# mock compose config" ;;
|
"compose config") echo "# mock compose config" ;;
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
[ -n "${MYOS_DOCKER_LOG:-}" ] && printf 'docker-compose %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
if [ -n "${MYOS_DOCKER_LOG:-}" ]; then
|
||||||
|
printf 'docker-compose %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
||||||
|
# the environment is recorded too, so a test can assert what was exported
|
||||||
|
env > "$(dirname "$MYOS_DOCKER_LOG")/env.log"
|
||||||
|
fi
|
||||||
case "$1" in version) echo "${MOCK_COMPOSE_VERSION:-2.29.0}" ;; config) echo "# mock compose config" ;; *) : ;; esac
|
case "$1" in version) echo "${MOCK_COMPOSE_VERSION:-2.29.0}" ;; config) echo "# mock compose config" ;; *) : ;; esac
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#shellcheck shell=sh
|
||||||
|
Include lib/str.sh
|
||||||
|
Include lib/core.sh
|
||||||
|
Include lib/tags.sh
|
||||||
|
Include lib/config.sh
|
||||||
|
Include lib/compose.sh
|
||||||
|
|
||||||
|
# These assertions look at what is actually executed, not at what --dry-run
|
||||||
|
# prints: the two used to disagree, because the execution path set IFS to a
|
||||||
|
# newline and so never split "-f a -f b", nor the two words "docker compose".
|
||||||
|
Describe 'lib/compose.sh execution'
|
||||||
|
setup() {
|
||||||
|
MYOS_TMP=$(mktemp -d "${TMPDIR:-/tmp}/myos-compose.XXXXXX")
|
||||||
|
MYOS_TMP=$(cd "$MYOS_TMP" && pwd -P)
|
||||||
|
printf 'services:\n a:\n image: ${IMAGE}\n' > "$MYOS_TMP/a.yml"
|
||||||
|
printf 'services:\n b:\n image: alpine\n' > "$MYOS_TMP/b.yml"
|
||||||
|
# exported, otherwise the mock (a child process) never sees it
|
||||||
|
export MYOS_DOCKER_LOG=$MYOS_TMP/docker.log
|
||||||
|
export PATH=$SPEC_DIR/support/bin:$PATH
|
||||||
|
DRYRUN=false
|
||||||
|
IMAGE=alpine:3.20
|
||||||
|
unset MYOS_COMPOSE_BIN 2>/dev/null || true
|
||||||
|
}
|
||||||
|
cleanup() { rm -rf "$MYOS_TMP"; }
|
||||||
|
BeforeEach setup
|
||||||
|
AfterEach cleanup
|
||||||
|
|
||||||
|
logged() { cat "$MYOS_DOCKER_LOG"; }
|
||||||
|
|
||||||
|
two_files() { myos_compose demo "$(printf '%s\n%s' "$MYOS_TMP/a.yml" "$MYOS_TMP/b.yml")" -- config; }
|
||||||
|
|
||||||
|
It 'passes every -f as its own argument'
|
||||||
|
When call two_files
|
||||||
|
The status should be success
|
||||||
|
The result of function logged should include "-f $MYOS_TMP/a.yml -f $MYOS_TMP/b.yml"
|
||||||
|
The result of function logged should include "-p demo"
|
||||||
|
The result of function logged should include "--project-directory $MYOS_TMP"
|
||||||
|
The result of function logged should end with "config"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'splits the two words of the docker compose plugin'
|
||||||
|
MYOS_COMPOSE_BIN="docker compose"
|
||||||
|
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||||
|
The status should be success
|
||||||
|
The result of function logged should start with "docker compose --ansi=auto"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'runs the docker-compose binary as one word'
|
||||||
|
MYOS_COMPOSE_BIN="docker-compose"
|
||||||
|
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||||
|
The status should be success
|
||||||
|
The result of function logged should start with "docker-compose --ansi=auto"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'passes the variables the compose files reference'
|
||||||
|
MYOS_COMPOSE_BIN="docker-compose"
|
||||||
|
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||||
|
The status should be success
|
||||||
|
The contents of file "$MYOS_TMP/env.log" should include "IMAGE=alpine:3.20"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'keeps a value that contains spaces in one piece'
|
||||||
|
MYOS_COMPOSE_BIN="docker-compose"
|
||||||
|
IMAGE="alpine with spaces"
|
||||||
|
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||||
|
The status should be success
|
||||||
|
The contents of file "$MYOS_TMP/env.log" should include "IMAGE=alpine with spaces"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'refuses to run without a compose file'
|
||||||
|
When call myos_compose demo "" -- config
|
||||||
|
The status should equal 3
|
||||||
|
The stderr should include "no compose file"
|
||||||
|
End
|
||||||
|
End
|
||||||
Reference in New Issue
Block a user