podman/test/system/600-completion.bats

387 lines
15 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
# 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
# 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-)"
# 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
}