mirror of
https://github.com/containers/podman.git
synced 2025-05-17 23:26:08 +08:00

**podman compose** is a thin wrapper around an external compose provider such as docker-compose or podman-compose. This means that `podman compose` is executing another tool that implements the compose functionality but sets up the environment in a way to let the compose provider communicate transparently with the local Podman socket. The specified options as well the command and argument are passed directly to the compose provider. The default compose providers are `docker-compose` and `podman-compose`. If installed, `docker-compose` takes precedence since it is the original implementation of the Compose specification and is widely used on the supported platforms (i.e., Linux, Mac OS, Windows). If you want to change the default behavior or have a custom installation path for your provider of choice, please change the `compose_provider` field in `containers.conf(5)`. You may also set the `PODMAN_COMPOSE_PROVIDER` environment variable. Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
416 lines
16 KiB
Bash
416 lines
16 KiB
Bash
#!/usr/bin/env bats -*- bats -*-
|
|
#
|
|
# Test podman shell completion
|
|
#
|
|
# Shell completion is provided via the cobra library
|
|
# It is implement by calling a hidden subcommand called "__complete"
|
|
#
|
|
|
|
load helpers
|
|
|
|
function setup() {
|
|
# $PODMAN may be a space-separated string, e.g. if we include a --url.
|
|
local -a podman_as_array=($PODMAN)
|
|
# __completeNoDesc must be the first arg if we running the completion cmd
|
|
# set the var for the run_completion function
|
|
PODMAN_COMPLETION="${podman_as_array[0]} __completeNoDesc ${podman_as_array[@]:1}"
|
|
|
|
basic_setup
|
|
}
|
|
|
|
# Returns true if we are able to podman-pause
|
|
function _can_pause() {
|
|
# Even though we're just trying completion, not an actual unpause,
|
|
# podman barfs with:
|
|
# Error: unpause is not supported for cgroupv1 rootless containers
|
|
if is_rootless && is_cgroupsv1; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
function check_shell_completion() {
|
|
local count=0
|
|
|
|
# Newline character; used for confirming string output
|
|
local nl="
|
|
"
|
|
|
|
for cmd in $(_podman_commands "$@"); do
|
|
# Skip the compose command which is calling `docker-compose --help`
|
|
# and hence won't match the assumptions made below.
|
|
if [[ "$cmd" == "compose" ]]; then
|
|
continue
|
|
fi
|
|
# Human-readable podman command string, with multiple spaces collapsed
|
|
name="podman"
|
|
if is_remote; then
|
|
name="podman-remote"
|
|
fi
|
|
command_string="$name $* $cmd"
|
|
command_string=${command_string// / } # 'podman x' -> 'podman x'
|
|
|
|
run_podman "$@" $cmd --help
|
|
local full_help="$output"
|
|
|
|
# The line immediately after 'Usage:' gives us a 1-line synopsis
|
|
usage=$(echo "$full_help" | grep -A1 '^Usage:' | tail -1)
|
|
assert "$usage" != "" "podman $cmd: no Usage message found"
|
|
|
|
# If usage ends in '[command]', recurse into subcommands
|
|
if expr "$usage" : '.*\[command\]$' >/dev/null; then
|
|
check_shell_completion "$@" $cmd
|
|
continue
|
|
fi
|
|
|
|
# Trim to command path so we only have the args
|
|
args="${usage/$command_string/}"
|
|
# Trim leading whitespaces
|
|
args="${args#"${args%%[![:space:]]*}"}"
|
|
|
|
# Extra args is used to match the correct argument number for the command
|
|
# This is important because some commands provide different suggestions based
|
|
# on the number of arguments.
|
|
extra_args=()
|
|
|
|
for arg in $args; do
|
|
|
|
match=false
|
|
i=0
|
|
while true; do
|
|
|
|
case $arg in
|
|
|
|
# If we have options than we need to check if we are getting flag completion
|
|
"[options]")
|
|
# skip this for remote it fails if a command only has the latest flag e.g podman top
|
|
if ! is_remote; then
|
|
run_completion "$@" $cmd "--"
|
|
# If this fails there is most likely a problem with the cobra library
|
|
is "${lines[0]}" "--.*" \
|
|
"$* $cmd: flag(s) listed in suggestions"
|
|
assert "${#lines[@]}" -gt 2 \
|
|
"$* $cmd: No flag suggestions"
|
|
_check_completion_end NoFileComp
|
|
fi
|
|
# continue the outer for args loop
|
|
continue 2
|
|
;;
|
|
|
|
*CONTAINER*)
|
|
# podman unpause fails early on rootless cgroupsv1
|
|
if [[ $cmd = "unpause" ]] && ! _can_pause; then
|
|
continue 2
|
|
fi
|
|
|
|
name=$random_container_name
|
|
# special case podman cp suggest containers names with a colon
|
|
if [[ $cmd = "cp" ]]; then
|
|
name="$name:"
|
|
fi
|
|
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
is "$output" ".*-$name${nl}" \
|
|
"$* $cmd: actual container listed in suggestions"
|
|
|
|
match=true
|
|
# resume
|
|
;;&
|
|
|
|
*POD*)
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
is "$output" ".*-$random_pod_name${nl}" \
|
|
"$* $cmd: actual pod listed in suggestions"
|
|
_check_completion_end NoFileComp
|
|
|
|
match=true
|
|
# resume
|
|
;;&
|
|
|
|
*IMAGE*)
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
is "$output" ".*localhost/$random_image_name:$random_image_tag${nl}" \
|
|
"$* $cmd: actual image listed in suggestions"
|
|
|
|
# check that we complete the image with tag after at least one char is typed
|
|
run_completion "$@" $cmd "${extra_args[@]}" "${random_image_name:0:1}"
|
|
is "$output" ".*$random_image_name:$random_image_tag${nl}" \
|
|
"$* $cmd: image name:tag included in suggestions"
|
|
|
|
# check that we complete the image id after at least two chars are typed
|
|
run_completion "$@" $cmd "${extra_args[@]}" "${random_image_id:0:2}"
|
|
is "$output" ".*$random_image_id${nl}" \
|
|
"$* $cmd: image id included in suggestions when two leading characters present in command line"
|
|
|
|
match=true
|
|
# resume
|
|
;;&
|
|
|
|
*NETWORK*)
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
is "$output" ".*$random_network_name${nl}" \
|
|
"$* $cmd: actual network listed in suggestions"
|
|
_check_completion_end NoFileComp
|
|
|
|
match=true
|
|
# resume
|
|
;;&
|
|
|
|
*VOLUME*)
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
is "$output" ".*$random_volume_name${nl}" \
|
|
"$* $cmd: actual volume listed in suggestions"
|
|
_check_completion_end NoFileComp
|
|
|
|
match=true
|
|
# resume
|
|
;;&
|
|
|
|
*REGISTRY*)
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
### FIXME how can we get the configured registries?
|
|
_check_completion_end NoFileComp
|
|
### FIXME this fails if no registries are configured
|
|
assert "${#lines[@]}" -gt 2 "$* $cmd: No REGISTRIES found in suggestions"
|
|
|
|
match=true
|
|
# resume
|
|
;;&
|
|
|
|
*SECRET*)
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
is "$output" ".*$random_secret_name${nl}" \
|
|
"$* $cmd: actual secret listed in suggestions"
|
|
_check_completion_end NoFileComp
|
|
|
|
match=true
|
|
# resume
|
|
;;&
|
|
|
|
*PATH* | *CONTEXT* | *FILE* | *COMMAND* | *ARG...* | *URI*)
|
|
# default shell completion should be done for everything which accepts a path
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
|
|
# cp is a special case it returns ShellCompDirectiveNoSpace
|
|
if [[ "$cmd" == "cp" ]]; then
|
|
_check_completion_end NoSpace
|
|
else
|
|
_check_completion_end Default
|
|
_check_no_suggestions
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
if [[ "$match" == "false" ]]; then
|
|
dprint "UNKNOWN arg: $arg for $command_string ${extra_args[*]}"
|
|
fi
|
|
;;
|
|
|
|
esac
|
|
|
|
# Increment the argument array
|
|
extra_args+=("arg")
|
|
|
|
i=$(($i + 1))
|
|
# If the argument ends with ...] than we accept 0...n args
|
|
# Loop three times to make sure we are not only completing the first arg
|
|
if [[ ! ${arg} =~ "..." ]] || [[ i -gt 3 ]]; then
|
|
break
|
|
fi
|
|
|
|
done
|
|
|
|
done
|
|
|
|
# If the command takes no more parameters make sure we are getting no completion
|
|
if [[ ! ${args##* } =~ "..." ]]; then
|
|
run_completion "$@" $cmd "${extra_args[@]}" ""
|
|
_check_completion_end NoFileComp
|
|
_check_no_suggestions
|
|
fi
|
|
|
|
done
|
|
|
|
}
|
|
|
|
# run the completion cmd
|
|
function run_completion() {
|
|
PODMAN="$PODMAN_COMPLETION" run_podman "$@"
|
|
}
|
|
|
|
# check for the given ShellCompDirective (always last line)
|
|
function _check_completion_end() {
|
|
is "${lines[-1]}" "Completion ended with directive: ShellCompDirective$1" "Completion has wrong ShellCompDirective set"
|
|
}
|
|
|
|
# Check that there are no suggestions in the output.
|
|
# We could only check stdout and not stderr but this is not possible with bats.
|
|
# By default we always have two extra lines at the end for the ShellCompDirective.
|
|
# Then we could also have other extra lines for debugging, they will always start
|
|
# with [Debug], e.g. `[Debug] [Error] no container with name or ID "t12" found: no such container`.
|
|
function _check_no_suggestions() {
|
|
if [ ${#lines[@]} -gt 2 ]; then
|
|
# Checking for line count is not enough since we may include additional debug output.
|
|
# Lines starting with [Debug] are allowed.
|
|
local i=0
|
|
length=$((${#lines[@]} - 2))
|
|
while [[ i -lt length ]]; do
|
|
assert "${lines[$i]:0:7}" == "[Debug]" "Unexpected non-Debug output line: ${lines[$i]}"
|
|
i=$((i + 1))
|
|
done
|
|
fi
|
|
}
|
|
|
|
|
|
@test "podman shell completion test" {
|
|
|
|
random_container_name=$(random_string 30)
|
|
random_pod_name=$(random_string 30)
|
|
random_image_name=$(random_string 30)
|
|
random_image_name=${random_image_name,,} # name must be lowercase
|
|
random_image_tag=$(random_string 5)
|
|
random_network_name=$(random_string 30)
|
|
random_volume_name=$(random_string 30)
|
|
random_secret_name=$(random_string 30)
|
|
random_secret_content=$(random_string 30)
|
|
secret_file=$PODMAN_TMPDIR/$(random_string 10)
|
|
|
|
echo $random_secret_content > $secret_file
|
|
|
|
# create a container for each state since some commands are only suggesting running container for example
|
|
run_podman create --name created-$random_container_name $IMAGE
|
|
run_podman run --name running-$random_container_name -d $IMAGE top
|
|
run_podman run --name pause-$random_container_name -d $IMAGE top
|
|
if _can_pause; then
|
|
run_podman pause pause-$random_container_name
|
|
fi
|
|
run_podman run --name exited-$random_container_name -d $IMAGE echo exited
|
|
|
|
# create pods for each state
|
|
run_podman pod create --name created-$random_pod_name
|
|
run_podman pod create --name running-$random_pod_name
|
|
run_podman pod create --name degraded-$random_pod_name
|
|
run_podman pod create --name exited-$random_pod_name
|
|
run_podman run -d --name running-$random_pod_name-con --pod running-$random_pod_name $IMAGE top
|
|
run_podman run -d --name degraded-$random_pod_name-con --pod degraded-$random_pod_name $IMAGE echo degraded
|
|
run_podman run -d --name exited-$random_pod_name-con --pod exited-$random_pod_name $IMAGE echo exited
|
|
run_podman pod stop exited-$random_pod_name
|
|
|
|
# create image name (just tag with new names no need to pull)
|
|
run_podman image tag $IMAGE $random_image_name:$random_image_tag
|
|
run_podman image list --format '{{.ID}}' --filter reference=$random_image_name
|
|
random_image_id="${lines[0]}"
|
|
|
|
# create network
|
|
run_podman network create $random_network_name
|
|
|
|
# create volume
|
|
run_podman volume create $random_volume_name
|
|
|
|
# create secret
|
|
run_podman secret create $random_secret_name $secret_file
|
|
|
|
# Called with no args -- start with 'podman --help'. check_shell_completion() will
|
|
# recurse for any subcommands.
|
|
check_shell_completion
|
|
|
|
# check inspect with format flag
|
|
run_completion inspect -f "{{."
|
|
assert "$output" =~ ".*^\{\{\.Args\}\}\$.*" "Defaulting to container type is completed"
|
|
|
|
run_completion inspect created-$random_container_name -f "{{."
|
|
assert "$output" =~ ".*^\{\{\.Args\}\}\$.*" "Container type is completed"
|
|
|
|
run_completion inspect $random_image_name -f "{{."
|
|
assert "$output" =~ ".*^\{\{\.Digest\}\}\$.*" "Image type is completed"
|
|
|
|
run_completion inspect $random_volume_name -f "{{."
|
|
assert "$output" =~ ".*^\{\{\.Anonymous\}\}\$.*" "Volume type is completed"
|
|
|
|
run_completion inspect created-$random_pod_name -f "{{."
|
|
assert "$output" =~ ".*^\{\{\.BlkioDeviceReadBps\}\}\$.*" "Pod type is completed"
|
|
|
|
run_completion inspect $random_network_name -f "{{."
|
|
assert "$output" =~ ".*^\{\{\.DNSEnabled\}\}\$.*" "Network type is completed"
|
|
|
|
# cleanup
|
|
run_podman secret rm $random_secret_name
|
|
rm -f $secret_file
|
|
|
|
run_podman volume rm $random_volume_name
|
|
|
|
run_podman network rm $random_network_name
|
|
|
|
run_podman image untag $IMAGE $random_image_name:$random_image_tag
|
|
|
|
for state in created running degraded exited; do
|
|
run_podman pod rm -t 0 --force $state-$random_pod_name
|
|
done
|
|
|
|
for state in created running pause exited; do
|
|
run_podman rm --force $state-$random_container_name
|
|
done
|
|
|
|
# Clean up the pod pause image
|
|
run_podman image list --format '{{.ID}} {{.Repository}}'
|
|
while read id name; do
|
|
if [[ "$name" =~ /podman-pause ]]; then
|
|
run_podman rmi $id
|
|
fi
|
|
done <<<"$output"
|
|
|
|
}
|
|
|
|
@test "podman shell completion for paths in container/image" {
|
|
skip_if_remote "mounting via remote does not work"
|
|
for cmd in create run; do
|
|
run_completion $cmd $IMAGE ""
|
|
assert "$output" =~ ".*^/etc/\$.*" "etc directory suggested (cmd: podman $cmd)"
|
|
assert "$output" =~ ".*^/home/\$.*" "home directory suggested (cmd: podman $cmd)"
|
|
assert "$output" =~ ".*^/root/\$.*" "root directory suggested (cmd: podman $cmd)"
|
|
|
|
# check completion for subdirectory
|
|
run_completion $cmd $IMAGE "/etc"
|
|
# It should be safe to assume the os-release file always exists in $IMAGE
|
|
assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc)"
|
|
# check completion for partial file name
|
|
run_completion $cmd $IMAGE "/etc/os-"
|
|
assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc/os-)"
|
|
|
|
# regression check for https://bugzilla.redhat.com/show_bug.cgi?id=2209809
|
|
# check for relative directory without slash in path.
|
|
run_completion $cmd $IMAGE "e"
|
|
assert "$output" =~ ".*^etc/\$.*" "etc dir suggested (cmd: podman $cmd e)"
|
|
|
|
# check completion with relative path components
|
|
# It is important the we will still use the image root and not escape to the host
|
|
run_completion $cmd $IMAGE "../../"
|
|
assert "$output" =~ ".*^../../etc/\$.*" "relative etc directory suggested (cmd: podman $cmd ../../)"
|
|
assert "$output" =~ ".*^../../home/\$.*" "relative home directory suggested (cmd: podman $cmd ../../)"
|
|
done
|
|
|
|
random_name=$(random_string 30)
|
|
random_file=$(random_string 30)
|
|
run_podman run --name $random_name $IMAGE sh -c "touch /tmp/$random_file && touch /tmp/${random_file}2 && mkdir /emptydir"
|
|
|
|
# check completion for podman cp
|
|
run_completion cp ""
|
|
assert "$output" =~ ".*^$random_name\:\$.*" "podman cp suggest container names"
|
|
|
|
run_completion cp "$random_name:"
|
|
assert "$output" =~ ".*^$random_name\:/etc/\$.*" "podman cp suggest paths in container"
|
|
|
|
run_completion cp "$random_name:/tmp"
|
|
assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest custom file in container"
|
|
|
|
run_completion cp "$random_name:/tmp/$random_file"
|
|
assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest /tmp/$random_file file in container"
|
|
assert "$output" =~ ".*^$random_name\:/tmp/${random_file}2\$.*" "podman cp suggest /tmp/${random_file}2 file in container"
|
|
|
|
run_completion cp "$random_name:/emptydir"
|
|
assert "$output" =~ ".*^$random_name\:/emptydir/\$.*ShellCompDirectiveNoSpace" "podman cp suggest empty dir with no space directive (:2)"
|
|
|
|
# cleanup container
|
|
run_podman rm $random_name
|
|
}
|