github.com/containers/podman/v5@v5.1.0-rc1/test/system/600-completion.bats (about) 1 #!/usr/bin/env bats -*- bats -*- 2 # 3 # Test podman shell completion 4 # 5 # Shell completion is provided via the cobra library 6 # It is implement by calling a hidden subcommand called "__complete" 7 # 8 9 load helpers 10 11 function setup() { 12 # $PODMAN may be a space-separated string, e.g. if we include a --url. 13 local -a podman_as_array=($PODMAN) 14 # __completeNoDesc must be the first arg if we running the completion cmd 15 # set the var for the run_completion function 16 PODMAN_COMPLETION="${podman_as_array[0]} __completeNoDesc ${podman_as_array[@]:1}" 17 18 basic_setup 19 } 20 21 # Returns true if we are able to podman-pause 22 function _can_pause() { 23 # Even though we're just trying completion, not an actual unpause, 24 # podman barfs with: 25 # Error: unpause is not supported for cgroupv1 rootless containers 26 if is_rootless && is_cgroupsv1; then 27 return 1 28 fi 29 return 0 30 } 31 32 function check_shell_completion() { 33 local count=0 34 35 # Newline character; used for confirming string output 36 local nl=" 37 " 38 39 for cmd in $(_podman_commands "$@"); do 40 # Skip the compose command which is calling `docker-compose --help` 41 # and hence won't match the assumptions made below. 42 if [[ "$cmd" == "compose" ]]; then 43 continue 44 fi 45 # Human-readable podman command string, with multiple spaces collapsed 46 name="podman" 47 if is_remote; then 48 name="podman-remote" 49 fi 50 command_string="$name $* $cmd" 51 command_string=${command_string// / } # 'podman x' -> 'podman x' 52 53 run_podman "$@" $cmd --help 54 local full_help="$output" 55 56 # The line immediately after 'Usage:' gives us a 1-line synopsis 57 usage=$(echo "$full_help" | grep -A1 '^Usage:' | tail -1) 58 assert "$usage" != "" "podman $cmd: no Usage message found" 59 60 # If usage ends in '[command]', recurse into subcommands 61 if expr "$usage" : '.*\[command\]$' >/dev/null; then 62 check_shell_completion "$@" $cmd 63 continue 64 fi 65 66 # Trim to command path so we only have the args 67 args="${usage/$command_string/}" 68 # Trim leading whitespaces 69 args="${args#"${args%%[![:space:]]*}"}" 70 71 # Extra args is used to match the correct argument number for the command 72 # This is important because some commands provide different suggestions based 73 # on the number of arguments. 74 extra_args=() 75 76 for arg in $args; do 77 78 match=false 79 i=0 80 while true; do 81 82 case $arg in 83 84 # If we have options than we need to check if we are getting flag completion 85 "[options]") 86 # skip this for remote it fails if a command only has the latest flag e.g podman top 87 if ! is_remote; then 88 run_completion "$@" $cmd "--" 89 # If this fails there is most likely a problem with the cobra library 90 is "${lines[0]}" "--.*" \ 91 "$* $cmd: flag(s) listed in suggestions" 92 assert "${#lines[@]}" -gt 2 \ 93 "$* $cmd: No flag suggestions" 94 _check_completion_end NoFileComp 95 fi 96 # continue the outer for args loop 97 continue 2 98 ;; 99 100 *CONTAINER*) 101 # podman unpause fails early on rootless cgroupsv1 102 if [[ $cmd = "unpause" ]] && ! _can_pause; then 103 continue 2 104 fi 105 106 name=$random_container_name 107 # special case podman cp suggest containers names with a colon 108 if [[ $cmd = "cp" ]]; then 109 name="$name:" 110 fi 111 112 run_completion "$@" $cmd "${extra_args[@]}" "" 113 is "$output" ".*-$name${nl}" \ 114 "$* $cmd: actual container listed in suggestions" 115 116 match=true 117 # resume 118 ;;& 119 120 *POD*) 121 run_completion "$@" $cmd "${extra_args[@]}" "" 122 is "$output" ".*-$random_pod_name${nl}" \ 123 "$* $cmd: actual pod listed in suggestions" 124 _check_completion_end NoFileComp 125 126 match=true 127 # resume 128 ;;& 129 130 *IMAGE*) 131 run_completion "$@" $cmd "${extra_args[@]}" "" 132 is "$output" ".*localhost/$random_image_name:$random_image_tag${nl}" \ 133 "$* $cmd: actual image listed in suggestions" 134 135 # check that we complete the image with tag after at least one char is typed 136 run_completion "$@" $cmd "${extra_args[@]}" "${random_image_name:0:1}" 137 is "$output" ".*$random_image_name:$random_image_tag${nl}" \ 138 "$* $cmd: image name:tag included in suggestions" 139 140 # check that we complete the image id after at least two chars are typed 141 run_completion "$@" $cmd "${extra_args[@]}" "${random_image_id:0:2}" 142 is "$output" ".*$random_image_id${nl}" \ 143 "$* $cmd: image id included in suggestions when two leading characters present in command line" 144 145 match=true 146 # resume 147 ;;& 148 149 *NETWORK*) 150 run_completion "$@" $cmd "${extra_args[@]}" "" 151 is "$output" ".*$random_network_name${nl}" \ 152 "$* $cmd: actual network listed in suggestions" 153 _check_completion_end NoFileComp 154 155 match=true 156 # resume 157 ;;& 158 159 *VOLUME*) 160 run_completion "$@" $cmd "${extra_args[@]}" "" 161 is "$output" ".*$random_volume_name${nl}" \ 162 "$* $cmd: actual volume listed in suggestions" 163 _check_completion_end NoFileComp 164 165 match=true 166 # resume 167 ;;& 168 169 *REGISTRY*) 170 run_completion "$@" $cmd "${extra_args[@]}" "" 171 ### FIXME how can we get the configured registries? 172 _check_completion_end NoFileComp 173 ### FIXME this fails if no registries are configured 174 assert "${#lines[@]}" -gt 2 "$* $cmd: No REGISTRIES found in suggestions" 175 176 match=true 177 # resume 178 ;;& 179 180 *SECRET*) 181 run_completion "$@" $cmd "${extra_args[@]}" "" 182 is "$output" ".*$random_secret_name${nl}" \ 183 "$* $cmd: actual secret listed in suggestions" 184 _check_completion_end NoFileComp 185 186 match=true 187 # resume 188 ;;& 189 190 *PATH* | *CONTEXT* | *FILE* | *COMMAND* | *ARG...* | *URI*) 191 # default shell completion should be done for everything which accepts a path 192 run_completion "$@" $cmd "${extra_args[@]}" "" 193 194 # cp is a special case it returns ShellCompDirectiveNoSpace 195 if [[ "$cmd" == "cp" ]]; then 196 _check_completion_end NoSpace 197 else 198 _check_completion_end Default 199 _check_no_suggestions 200 fi 201 ;; 202 203 *) 204 if [[ "$match" == "false" ]]; then 205 dprint "UNKNOWN arg: $arg for $command_string ${extra_args[*]}" 206 fi 207 ;; 208 209 esac 210 211 # Increment the argument array 212 extra_args+=("arg") 213 214 i=$(($i + 1)) 215 # If the argument ends with ...] than we accept 0...n args 216 # Loop three times to make sure we are not only completing the first arg 217 if [[ ! ${arg} =~ "..." ]] || [[ i -gt 3 ]]; then 218 break 219 fi 220 221 done 222 223 done 224 225 # If the command takes no more parameters make sure we are getting no completion 226 if [[ ! ${args##* } =~ "..." ]]; then 227 run_completion "$@" $cmd "${extra_args[@]}" "" 228 _check_completion_end NoFileComp 229 _check_no_suggestions 230 fi 231 232 done 233 234 } 235 236 # run the completion cmd 237 function run_completion() { 238 PODMAN="$PODMAN_COMPLETION" run_podman "$@" 239 } 240 241 # check for the given ShellCompDirective (always last line) 242 function _check_completion_end() { 243 is "${lines[-1]}" "Completion ended with directive: ShellCompDirective$1" "Completion has wrong ShellCompDirective set" 244 } 245 246 # Check that there are no suggestions in the output. 247 # We could only check stdout and not stderr but this is not possible with bats. 248 # By default we always have two extra lines at the end for the ShellCompDirective. 249 # Then we could also have other extra lines for debugging, they will always start 250 # with [Debug], e.g. `[Debug] [Error] no container with name or ID "t12" found: no such container`. 251 function _check_no_suggestions() { 252 if [ ${#lines[@]} -gt 2 ]; then 253 # Checking for line count is not enough since we may include additional debug output. 254 # Lines starting with [Debug] are allowed. 255 local i=0 256 length=$((${#lines[@]} - 2)) 257 while [[ i -lt length ]]; do 258 assert "${lines[$i]:0:7}" == "[Debug]" "Unexpected non-Debug output line: ${lines[$i]}" 259 i=$((i + 1)) 260 done 261 fi 262 } 263 264 265 @test "podman shell completion test" { 266 267 random_container_name=$(random_string 30) 268 random_pod_name=$(random_string 30) 269 random_image_name=$(random_string 30) 270 random_image_name=${random_image_name,,} # name must be lowercase 271 random_image_tag=$(random_string 5) 272 random_network_name=$(random_string 30) 273 random_volume_name=$(random_string 30) 274 random_secret_name=$(random_string 30) 275 random_secret_content=$(random_string 30) 276 secret_file=$PODMAN_TMPDIR/$(random_string 10) 277 278 echo $random_secret_content > $secret_file 279 280 # create a container for each state since some commands are only suggesting running container for example 281 run_podman create --name created-$random_container_name $IMAGE 282 run_podman run --name running-$random_container_name -d $IMAGE top 283 run_podman run --name pause-$random_container_name -d $IMAGE top 284 if _can_pause; then 285 run_podman pause pause-$random_container_name 286 fi 287 run_podman run --name exited-$random_container_name -d $IMAGE echo exited 288 289 # create pods for each state 290 run_podman pod create --name created-$random_pod_name 291 run_podman pod create --name running-$random_pod_name 292 run_podman pod create --name degraded-$random_pod_name 293 run_podman pod create --name exited-$random_pod_name 294 run_podman run -d --name running-$random_pod_name-con --pod running-$random_pod_name $IMAGE top 295 run_podman run -d --name degraded-$random_pod_name-con --pod degraded-$random_pod_name $IMAGE echo degraded 296 run_podman run -d --name exited-$random_pod_name-con --pod exited-$random_pod_name $IMAGE echo exited 297 run_podman pod stop exited-$random_pod_name 298 299 # create image name (just tag with new names no need to pull) 300 run_podman image tag $IMAGE $random_image_name:$random_image_tag 301 run_podman image list --format '{{.ID}}' --filter reference=$random_image_name 302 random_image_id="${lines[0]}" 303 304 # create network 305 run_podman network create $random_network_name 306 307 # create volume 308 run_podman volume create $random_volume_name 309 310 # create secret 311 run_podman secret create $random_secret_name $secret_file 312 313 # Called with no args -- start with 'podman --help'. check_shell_completion() will 314 # recurse for any subcommands. 315 check_shell_completion 316 317 # check inspect with format flag 318 run_completion inspect -f "{{." 319 assert "$output" =~ ".*^\{\{\.Args\}\}\$.*" "Defaulting to container type is completed" 320 321 run_completion inspect created-$random_container_name -f "{{." 322 assert "$output" =~ ".*^\{\{\.Args\}\}\$.*" "Container type is completed" 323 324 run_completion inspect $random_image_name -f "{{." 325 assert "$output" =~ ".*^\{\{\.Digest\}\}\$.*" "Image type is completed" 326 327 run_completion inspect $random_volume_name -f "{{." 328 assert "$output" =~ ".*^\{\{\.Anonymous\}\}\$.*" "Volume type is completed" 329 330 run_completion inspect created-$random_pod_name -f "{{." 331 assert "$output" =~ ".*^\{\{\.BlkioDeviceReadBps\}\}\$.*" "Pod type is completed" 332 333 run_completion inspect $random_network_name -f "{{." 334 assert "$output" =~ ".*^\{\{\.DNSEnabled\}\}\$.*" "Network type is completed" 335 336 # cleanup 337 run_podman secret rm $random_secret_name 338 rm -f $secret_file 339 340 run_podman volume rm $random_volume_name 341 342 run_podman network rm $random_network_name 343 344 run_podman image untag $IMAGE $random_image_name:$random_image_tag 345 346 for state in created running degraded exited; do 347 run_podman pod rm -t 0 --force $state-$random_pod_name 348 done 349 350 for state in created running pause exited; do 351 run_podman rm --force $state-$random_container_name 352 done 353 354 # Clean up the pod pause image 355 run_podman image list --format '{{.ID}} {{.Repository}}' 356 while read id name; do 357 if [[ "$name" =~ /podman-pause ]]; then 358 run_podman rmi $id 359 fi 360 done <<<"$output" 361 362 } 363 364 @test "podman shell completion for paths in container/image" { 365 skip_if_remote "mounting via remote does not work" 366 for cmd in create run; do 367 run_completion $cmd $IMAGE "" 368 assert "$output" =~ ".*^/etc/\$.*" "etc directory suggested (cmd: podman $cmd)" 369 assert "$output" =~ ".*^/home/\$.*" "home directory suggested (cmd: podman $cmd)" 370 assert "$output" =~ ".*^/root/\$.*" "root directory suggested (cmd: podman $cmd)" 371 372 # check completion for subdirectory 373 run_completion $cmd $IMAGE "/etc" 374 # It should be safe to assume the os-release file always exists in $IMAGE 375 assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc)" 376 # check completion for partial file name 377 run_completion $cmd $IMAGE "/etc/os-" 378 assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc/os-)" 379 380 # regression check for https://bugzilla.redhat.com/show_bug.cgi?id=2209809 381 # check for relative directory without slash in path. 382 run_completion $cmd $IMAGE "e" 383 assert "$output" =~ ".*^etc/\$.*" "etc dir suggested (cmd: podman $cmd e)" 384 385 # check completion with relative path components 386 # It is important the we will still use the image root and not escape to the host 387 run_completion $cmd $IMAGE "../../" 388 assert "$output" =~ ".*^../../etc/\$.*" "relative etc directory suggested (cmd: podman $cmd ../../)" 389 assert "$output" =~ ".*^../../home/\$.*" "relative home directory suggested (cmd: podman $cmd ../../)" 390 done 391 392 random_name=$(random_string 30) 393 random_file=$(random_string 30) 394 run_podman run --name $random_name $IMAGE sh -c "touch /tmp/$random_file && touch /tmp/${random_file}2 && mkdir /emptydir" 395 396 # check completion for podman cp 397 run_completion cp "" 398 assert "$output" =~ ".*^$random_name\:\$.*" "podman cp suggest container names" 399 400 run_completion cp "$random_name:" 401 assert "$output" =~ ".*^$random_name\:/etc/\$.*" "podman cp suggest paths in container" 402 403 run_completion cp "$random_name:/tmp" 404 assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest custom file in container" 405 406 run_completion cp "$random_name:/tmp/$random_file" 407 assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest /tmp/$random_file file in container" 408 assert "$output" =~ ".*^$random_name\:/tmp/${random_file}2\$.*" "podman cp suggest /tmp/${random_file}2 file in container" 409 410 run_completion cp "$random_name:/emptydir" 411 assert "$output" =~ ".*^$random_name\:/emptydir/\$.*ShellCompDirectiveNoSpace" "podman cp suggest empty dir with no space directive (:2)" 412 413 # cleanup container 414 run_podman rm $random_name 415 }