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  }