github.com/containers/podman/v5@v5.1.0-rc1/test/apiv2/test-apiv2 (about)

     1  #!/usr/bin/env bash
     2  #
     3  # Usage: test-apiv2 testglob
     4  #
     5  # DEVELOPER NOTE: you almost certainly don't need to play in here. See README.
     6  #
     7  ME=$(basename $0)
     8  
     9  ###############################################################################
    10  # BEGIN stuff you can but probably shouldn't customize
    11  
    12  PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"}
    13  PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"}
    14  PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"alpine_labels"}
    15  PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"latest"}
    16  PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG"
    17  
    18  IMAGE=$PODMAN_TEST_IMAGE_FQN
    19  
    20  REGISTRY_IMAGE="${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/registry:2.7"
    21  
    22  # END   stuff you can but probably shouldn't customize
    23  ###############################################################################
    24  # BEGIN setup
    25  
    26  TMPDIR=${TMPDIR:-/tmp}
    27  WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX)
    28  
    29  # Log of all HTTP requests and responses; always make '.log' point to latest
    30  LOGBASE=${TMPDIR}/$ME.log
    31  LOG=${LOGBASE}.$(date +'%Y%m%dT%H%M%S')
    32  ln -sf $LOG $LOGBASE
    33  
    34  HOST=localhost
    35  PORT=${PODMAN_SERVICE_PORT:-8081}
    36  
    37  # Keep track of test count and failures in files, not variables, because
    38  # variables don't carry back up from subshells.
    39  testcounter_file=$WORKDIR/.testcounter
    40  failures_file=$WORKDIR/.failures
    41  
    42  echo 0 >$testcounter_file
    43  echo 0 >$failures_file
    44  
    45  # Where the tests live
    46  TESTS_DIR=$(realpath $(dirname $0))
    47  
    48  # As of 2021-11 podman has one external helper binary, rootlessport, needed
    49  # for rootless networking.
    50  if [[ -z "$CONTAINERS_HELPER_BINARY_DIR" ]]; then
    51      export CONTAINERS_HELPER_BINARY_DIR=$(realpath ${TESTS_DIR}/../../bin)
    52  fi
    53  
    54  # Path to podman binary
    55  PODMAN_BIN=${PODMAN:-${CONTAINERS_HELPER_BINARY_DIR}/podman}
    56  
    57  # Cleanup handlers
    58  clean_up_server() {
    59      if [ -n "$service_pid" ]; then
    60          # Remove any containers and images; this prevents the following warning:
    61          #  'rm: cannot remove '/.../overlay': Device or resource busy
    62          podman rm -a
    63          podman rmi -af
    64  
    65          stop_registry --cleanup
    66          stop_service
    67      fi
    68  }
    69  
    70  # Any non-test-related error, be it syntax or podman-command, fails here.
    71  err_handler() {
    72      echo "Fatal error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}"
    73      echo "Log:"
    74      sed -e 's/^/  >/' <$WORKDIR/output.log
    75      echo "Bailing."
    76      clean_up_server
    77  }
    78  
    79  trap err_handler ERR
    80  
    81  # END   setup
    82  ###############################################################################
    83  # BEGIN infrastructure code - the helper functions used in tests themselves
    84  
    85  #########
    86  #  die  #  Exit error with a message to stderr
    87  #########
    88  function die() {
    89      echo "$ME: $*" >&2
    90      clean_up_server
    91      exit 1
    92  }
    93  
    94  ########
    95  #  is  #  Simple comparison
    96  ########
    97  function is() {
    98      local actual=$1
    99      local expect=$2
   100      local testname=$3
   101  
   102      if [ "$actual" = "$expect" ]; then
   103          # On success, include expected value; this helps readers understand
   104          _show_ok 1 "$testname=$expect"
   105          return
   106      fi
   107      _show_ok 0 "$testname" "$expect" "$actual"
   108  }
   109  
   110  ############
   111  #  is_not  #  Simple disequality
   112  ############
   113  function is_not() {
   114      local actual=$1
   115      local expect_not=$2
   116      local testname=$3
   117  
   118      if [ "$actual" != "$expect_not" ]; then
   119          # On success, include expected value; this helps readers understand
   120          _show_ok 1 "$testname!=$expect"
   121          return
   122      fi
   123      _show_ok 0 "$testname" "!= $expect" "$actual"
   124  }
   125  
   126  ##########
   127  #  like  #  Compare, but allowing patterns
   128  ##########
   129  function like() {
   130      local actual=$1
   131      local expect=$2
   132      local testname=$3
   133  
   134      if expr "$actual" : "$expect" &>/dev/null; then
   135          # On success, include expected value; this helps readers understand
   136          # (but don't show enormous multi-line output like 'generate kube')
   137          blurb=$(head -n1 <<<"$actual")
   138          _show_ok 1 "$testname ('$blurb') ~ $expect"
   139          return
   140      fi
   141      _show_ok 0 "$testname" "~ $expect" "$actual"
   142  }
   143  
   144  ##############
   145  #  _show_ok  #  Helper for is() and like(): displays 'ok' or 'not ok'
   146  ##############
   147  function _show_ok() {
   148      local ok=$1
   149      # Exec tests include control characters; filter them out
   150      # Also filter out timestamps, to get consistent test names in logs
   151      local testname=$(tr -d \\012 <<<"$2"|sed -e 's/since=[0-9.]\+/since=(T)/'|cat -vT)
   152  
   153      # If output is a tty, colorize pass/fail
   154      local red=
   155      local green=
   156      local reset=
   157      local bold=
   158      if [ -t 1 ]; then
   159          red='\e[31m'
   160          green='\e[32m'
   161          reset='\e[0m'
   162          bold='\e[1m'
   163      fi
   164  
   165      _bump $testcounter_file
   166      count=$(<$testcounter_file)
   167  
   168      # "skip" is a special case of "ok". Assume that our caller has included
   169      # the magical '# skip - reason" comment string.
   170      if [[ $ok == "skip" ]]; then
   171          # colon-plus: replace green with yellow, but only if green is non-null
   172          green="${green:+\e[33m}"
   173          ok=1
   174      fi
   175      if [ $ok -eq 1 ]; then
   176          echo -e "${green}ok $count ${TEST_CONTEXT} $testname${reset}"
   177          echo    "ok $count ${TEST_CONTEXT} $testname" >>$LOG
   178          return
   179      fi
   180  
   181      # Failed
   182      local expect=$3
   183      local actual=$4
   184      echo -e "${red}not ok $count ${TEST_CONTEXT} $testname${reset}"
   185      echo -e "${red}#  expected: $expect${reset}"
   186      echo -e "${red}#    actual: ${bold}$actual${reset}"
   187  
   188      echo    "not ok $count ${TEST_CONTEXT} $testname" >>$LOG
   189      echo    "  expected: $expect"                     >>$LOG
   190  
   191      _bump $failures_file
   192  }
   193  
   194  ###########
   195  #  _bump  #  Increment a counter in a file
   196  ###########
   197  function _bump() {
   198      local file=$1
   199  
   200      count=$(<$file)
   201      echo $(( $count + 1 )) >| $file
   202  }
   203  
   204  #############
   205  #  jsonify  #  convert 'foo=bar,x=y' to json {"foo":"bar","x":"y"}
   206  #############
   207  function jsonify() {
   208      # convert each to double-quoted form
   209      local -a settings_out
   210      for i in "$@"; do
   211          # Each argument is of the form foo=bar. Separate into left and right.
   212          local lhs
   213          local rhs
   214          IFS='=' read lhs rhs <<<"$i"
   215  
   216          if [[ $rhs =~ \" || $rhs == true || $rhs == false || $rhs =~ ^-?[0-9]+$ ]]; then
   217              # rhs has been pre-formatted for JSON or a non-string, do not change it
   218              :
   219          elif [[ $rhs == False ]]; then
   220              # JSON boolean is lowercase only
   221              rhs=false
   222          elif [[ $rhs == True ]]; then
   223              # JSON boolean is lowercase only
   224              rhs=true
   225          else
   226              rhs="\"${rhs}\""
   227          fi
   228          settings_out+=("\"${lhs}\":${rhs}")
   229      done
   230  
   231      # ...and wrap inside braces, with comma separator if multiple fields
   232      (IFS=','; echo "{${settings_out[*]}}")
   233  }
   234  
   235  #######
   236  #  t  #  Main test helper
   237  #######
   238  function t() {
   239      local method=$1; shift
   240      local path=$1; shift
   241      local -a curl_args form_args
   242      local content_type="application/json"
   243  
   244      local testname="$method $path"
   245  
   246      # POST and PUT requests may be followed by one or more key=value pairs.
   247      # Slurp the command line until we see a 3-digit status code.
   248      if [[ $method = "POST" || $method == "PUT" || $method == "DELETE" ]]; then
   249          local -a post_args
   250  
   251          if [[ $method == "POST" ]]; then
   252              function _add_curl_args() { curl_args+=(--data-binary @$1); }
   253          else
   254              function _add_curl_args() { curl_args+=(--upload-file $1); }
   255          fi
   256  
   257          for arg; do
   258              case "$arg" in
   259                  # This is just some hack to avoid adding `-d {}` to curl for endpoints where we really need an empty body.
   260                  # --disable makes curl not lookup the curlrc file, it shouldn't affect the tests in any way.
   261                  -)                curl_args+=(--disable);
   262                                    shift;;
   263                  --form=*)         form_args+=(--form);
   264                                    form_args+=("${arg#--form=}");
   265                                    content_type="multipart/form-data";
   266                                    shift;;
   267                  *=*)              post_args+=("$arg");
   268                                    shift;;
   269                  *.json)           _add_curl_args $arg;
   270                                    content_type="application/json";
   271                                    shift;;
   272                  *.tar)            _add_curl_args $arg;
   273                                    content_type="application/x-tar";
   274                                    shift;;
   275                  *.yaml)           _add_curl_args $arg;
   276                                    shift;;
   277                  application/*)    content_type="$arg";
   278                                    shift;;
   279                  [1-9][0-9][0-9])  break;;
   280                  *)                die "Internal error: invalid POST arg '$arg'" ;;
   281              esac
   282          done
   283          if [[ -z "${curl_args[*]}" && -z "${form_args[*]}" ]]; then
   284              curl_args=(-d $(jsonify ${post_args[*]}))
   285              testname="$testname [${curl_args[*]}]"
   286          elif [[ -z "${curl_args[*]}" ]]; then
   287              curl_args=(--form request.json=$(jsonify ${post_args[*]}) "${form_args[@]}")
   288              testname="$testname [${curl_args[*]} ${form_args[*]}]"
   289          fi
   290      fi
   291  
   292      # entrypoint path can include a descriptive comment; strip it off
   293      path=${path%% *}
   294  
   295      local url=$path
   296      if ! [[ $path =~ ^'http://' ]]; then
   297          # path may include JSONish params that curl will barf on; url-encode them
   298          path="${path//'['/%5B}"
   299          path="${path//']'/%5D}"
   300          path="${path//'{'/%7B}"
   301          path="${path//'}'/%7D}"
   302          path="${path//':'/%3A}"
   303  
   304          # If given path begins with /, use it as-is; otherwise prepend /version/
   305          url=http://$HOST:$PORT
   306          case "$path" in
   307          /*) url="$url$path" ;;
   308          libpod/*) url="$url/v4.0.0/$path" ;;
   309          *)  url="$url/v1.41/$path" ;;
   310          esac
   311      fi
   312  
   313      # curl -X HEAD but without --head seems to wait for output anyway
   314      if [[ $method == "HEAD" ]]; then
   315          curl_args+=("--head")
   316      fi
   317  
   318      # If this is set, we're *expecting* curl to time out
   319      if [[ -n "$APIV2_TEST_EXPECT_TIMEOUT" ]]; then
   320          curl_args+=("-m" $APIV2_TEST_EXPECT_TIMEOUT)
   321      fi
   322  
   323      local expected_code=$1; shift
   324  
   325      if [[ "$expected_code" == "101" ]]; then
   326          curl_args+=("-H" "Connection: upgrade" "-H" "Upgrade: tcp")
   327      fi
   328  
   329      # Log every action we do
   330      echo "-------------------------------------------------------------" >>$LOG
   331      echo "\$ $testname"                                                  >>$LOG
   332      rm -f $WORKDIR/curl.*
   333      # The --write-out 'format' gives us important response data.
   334      # The hairy "{ ...;rc=$?; } || :" lets us capture curl's exit code and
   335      # give a helpful diagnostic if it fails.
   336      : > $WORKDIR/curl.result.out
   337      : > $WORKDIR/curl.result.err
   338      { response=$(curl -X $method "${curl_args[@]}"               \
   339                      -H "Content-type: $content_type"             \
   340                      --dump-header $WORKDIR/curl.headers.out      \
   341                      -v --stderr $WORKDIR/curl.result.err         \
   342                      --write-out '%{http_code}^%{content_type}^%{time_total}' \
   343                      -o $WORKDIR/curl.result.out "$url"); rc=$?; } || :
   344      if [ -n "$PODMAN_TESTS_DUMP_TRACES" ]; then
   345          # Dump the results we got back, exactly as we got them back.
   346          echo "\$ begin stdout"                                                      >>$LOG
   347          test -s $WORKDIR/curl.result.out && od -t x1c $WORKDIR/curl.result.out 2>&1 >>$LOG
   348          echo "\$ end stdout"                                                        >>$LOG
   349          echo "\$ begin stderr"                                                      >>$LOG
   350          test -s $WORKDIR/curl.result.err && cat $WORKDIR/curl.result.err            >>$LOG
   351          echo "\$ end stderr"                                                        >>$LOG
   352          echo "\$ begin response code^content_type^time_total"                       >>$LOG
   353          od -t x1c <<< "$response"                                                   >>$LOG
   354          echo "\$ end response"                                                      >>$LOG
   355      fi
   356  
   357      # Special case: this means we *expect and want* a timeout
   358      if [[ -n "$APIV2_TEST_EXPECT_TIMEOUT" ]]; then
   359          # Hardcoded. See curl(1) for list of exit codes
   360          if [[ $rc -eq 28 ]]; then
   361              _show_ok 1 "$testname: curl timed out (expected)"
   362          else
   363              _show_ok 0 "$testname: expected curl to time out; it did not"
   364          fi
   365          return
   366      fi
   367  
   368      # Any error from curl is instant bad news, from which we can't recover
   369      if [[ $rc -ne 0 ]]; then
   370          die "curl failure ($rc) on $url - cannot continue. args=${curl_args[*]}"
   371      fi
   372  
   373      # Show returned headers (without trailing ^M or empty lines) in log file.
   374      # Sometimes -- I can't remember why! -- we don't get headers.
   375      if [[ -e $WORKDIR/curl.headers.out ]]; then
   376          tr -d '\015' < $WORKDIR/curl.headers.out | grep -E '.' >>$LOG
   377      fi
   378  
   379      IFS='^' read actual_code content_type time_total <<<"$response"
   380      printf "X-Response-Time: ${time_total}s\n\n" >>$LOG
   381  
   382      # Log results, if text. If JSON, filter through jq for readability.
   383      if [[ $content_type =~ /octet ]]; then
   384          output="[$(file --brief $WORKDIR/curl.result.out)]"
   385          echo "$output" >>$LOG
   386      elif [[ -e $WORKDIR/curl.result.out ]]; then
   387          # Output from /logs sometimes includes NULs. Strip them.
   388          output=$(tr -d '\0' < $WORKDIR/curl.result.out)
   389  
   390          if [[ $content_type =~ application/json ]] && [[ $method != "HEAD" ]]; then
   391              jq . <<<"$output" >>$LOG
   392          else
   393              echo "$output" >>$LOG
   394          fi
   395      else
   396          output=
   397          echo "[no output]" >>$LOG
   398      fi
   399  
   400      # Test return code
   401      is "$actual_code" "$expected_code" "$testname : status"
   402  
   403      # Special case: 204/304, by definition, MUST NOT return content (rfc2616)
   404      if [[ $expected_code = 204 || $expected_code = 304 ]]; then
   405          if [ -n "$*" ]; then
   406              die "Internal error: ${expected_code} status returns no output; fix your test."
   407          fi
   408          if [ -n "$output" ]; then
   409              _show_ok 0 "$testname: ${expected_code} status returns no output" "''" "$output"
   410          fi
   411          return
   412      fi
   413  
   414      local i
   415  
   416      # Special case: if response code does not match, dump the response body
   417      # and skip all further subtests.
   418      if [[ "$actual_code" != "$expected_code" ]]; then
   419          echo -e "#  response: $output"
   420          for i; do
   421              _show_ok skip "$testname: $i # skip - wrong return code"
   422          done
   423          return
   424      fi
   425  
   426      for i; do
   427          if expr "$i" : '[^\!]\+\!=.\+' >/dev/null; then
   428              # Disequality on json field
   429              json_field=$(expr "$i" : '\([^!]*\)!')
   430              expect_not=$(expr "$i" : '[^\!]*\!=\(.*\)')
   431              actual=$(jq -r "$json_field" <<<"$output")
   432              is_not "$actual" "$expect_not" "$testname : $json_field"
   433          elif expr "$i" : "[^=~]\+=.*" >/dev/null; then
   434              # Exact match on json field
   435              json_field=$(expr "$i" : "\([^=]*\)=")
   436              expect=$(expr "$i" : '[^=]*=\(.*\)')
   437              actual=$(jq -r "$json_field" <<<"$output")
   438              is "$actual" "$expect" "$testname : $json_field"
   439          elif expr "$i" : "[^=~]\+~.*" >/dev/null; then
   440              # regex match on json field
   441              json_field=$(expr "$i" : "\([^~]*\)~")
   442              expect=$(expr "$i" : '[^~]*~\(.*\)')
   443              actual=$(jq -r "$json_field" <<<"$output")
   444              like "$actual" "$expect" "$testname : $json_field"
   445          else
   446              # Direct string comparison
   447              is "$output" "$i" "$testname : output"
   448          fi
   449      done
   450  }
   451  
   452  ###################
   453  #  start_service  #  Run the socket listener
   454  ###################
   455  service_pid=
   456  function start_service() {
   457      # If there's a listener on the port, nothing for us to do
   458      { exec 3<> /dev/tcp/$HOST/$PORT; } &>/dev/null && return
   459  
   460      test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN"
   461  
   462      if [ "$HOST" != "localhost" ]; then
   463          die "Cannot start service on non-localhost ($HOST)"
   464      fi
   465  
   466      # FIXME: EXPERIMENTAL: 2022-06-13: podman rootless needs a namespace. If
   467      # system-service is the first podman command run (as is the case in CI)
   468      # this will happen as a fork-exec, where the parent podman creates the
   469      # namespace and the child is the server. Then, when stop_service() kills
   470      # the parent, the child (server) happily stays alive and ruins subsequent
   471      # tests that try to restart service with different settings.
   472      # Workaround: run an unshare to get namespaces initialized.
   473      if [[ $(id -u) != 0 ]]; then
   474          $PODMAN_BIN unshare true
   475      fi
   476  
   477      CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf \
   478          $PODMAN_BIN \
   479          --root $WORKDIR/server_root --syslog=true \
   480          system service \
   481          --time 0 \
   482          tcp:127.0.0.1:$PORT \
   483          &> $WORKDIR/server.log &
   484      service_pid=$!
   485      echo "# started service, pid $service_pid"
   486  
   487      wait_for_port $HOST $PORT
   488  }
   489  
   490  function stop_service() {
   491      # Stop the server
   492      if [[ -n $service_pid ]]; then
   493          kill $service_pid || :
   494          wait $service_pid || :
   495          echo "# stopped service, pid $service_pid"
   496      fi
   497      service_pid=
   498  
   499      if { exec 3<> /dev/tcp/$HOST/$PORT; } &>/dev/null; then
   500          echo "# WARNING: stop_service: Service still running on port $PORT"
   501      fi
   502  
   503  }
   504  
   505  ####################
   506  #  start_registry  #  Run a local registry
   507  ####################
   508  REGISTRY_PORT=
   509  REGISTRY_USERNAME=
   510  REGISTRY_PASSWORD=
   511  function start_registry() {
   512      # We can be called multiple times, but each time should start a new
   513      # registry container with (possibly) different configuration. That
   514      # means that all callers must be responsible for invoking stop_registry.
   515      if [[ -n "$REGISTRY_PORT" ]]; then
   516          die "start_registry invoked twice in succession, without stop_registry"
   517      fi
   518  
   519      # First arg is auth type (default: "none", but can also be "htpasswd")
   520      local auth="${1:-none}"
   521  
   522      REGISTRY_PORT=$(random_port)
   523  
   524      local REGDIR=$WORKDIR/registry
   525      local AUTHDIR=$REGDIR/auth
   526      mkdir -p $AUTHDIR
   527  
   528      mkdir -p ${REGDIR}/{root,runroot}
   529      local PODMAN_REGISTRY_ARGS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot"
   530  
   531      # Give it three tries, to compensate for network flakes
   532      podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE ||
   533          podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE ||
   534          podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE
   535  
   536      # Create a local cert (no need to do this more than once)
   537      if [[ ! -e $AUTHDIR/domain.key ]]; then
   538          # FIXME: is there a hidden "--quiet" flag? This is too noisy.
   539          openssl req -newkey rsa:4096 -nodes -sha256 \
   540                  -keyout $AUTHDIR/domain.key -x509 -days 2 \
   541                  -out $AUTHDIR/domain.crt \
   542                  -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \
   543                  -addext subjectAltName=DNS:localhost
   544      fi
   545  
   546      # If invoked with auth=htpasswd, create credentials
   547      REGISTRY_USERNAME=
   548      REGISTRY_PASSWORD=
   549      declare -a registry_auth_params=(-e "REGISTRY_AUTH=$auth")
   550      if [[ "$auth" = "htpasswd" ]]; then
   551          REGISTRY_USERNAME=u$(random_string 7)
   552          REGISTRY_PASSWORD=p$(random_string 7)
   553  
   554          htpasswd -Bbn ${REGISTRY_USERNAME} ${REGISTRY_PASSWORD} \
   555                   > $AUTHDIR/htpasswd
   556  
   557          registry_auth_params+=(
   558              -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm"
   559              -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd"
   560          )
   561      fi
   562  
   563      # Run the registry, and wait for it to come up
   564      podman ${PODMAN_REGISTRY_ARGS} run -d \
   565             -p ${REGISTRY_PORT}:5000 \
   566             --name registry \
   567             -v $AUTHDIR:/auth:Z \
   568             "${registry_auth_params[@]}" \
   569             -e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt \
   570             -e REGISTRY_HTTP_TLS_KEY=/auth/domain.key \
   571             ${REGISTRY_IMAGE}
   572  
   573      wait_for_port localhost $REGISTRY_PORT 10
   574      echo "# started registry (auth=$auth) on port $PORT"
   575  }
   576  
   577  function stop_registry() {
   578      local REGDIR=${WORKDIR}/registry
   579      if [[ -d $REGDIR ]]; then
   580          local OPTS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot"
   581          podman $OPTS stop -i -t 0 registry
   582  
   583          # rm/rmi are important when running rootless: without them we
   584          # get EPERMS in tmpdir cleanup because files are owned by subuids.
   585          podman $OPTS rm -f -i registry
   586          if [[ "$1" = "--cleanup" ]]; then
   587              podman $OPTS rmi -f -a
   588          fi
   589          echo "# stopped registry on port $PORT"
   590      fi
   591  
   592      REGISTRY_PORT=
   593      REGISTRY_USERNAME=
   594      REGISTRY_PASSWORD=
   595  }
   596  
   597  #################
   598  #  random_port  #  Random open port; arg is range (min-max), default 5000-5999
   599  #################
   600  function random_port() {
   601      local range=${1:-5000-5999}
   602  
   603      local port
   604      for port in $(shuf -i ${range}); do
   605          if ! { exec 5<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
   606              echo $port
   607              return
   608          fi
   609      done
   610  
   611      die "Could not find open port in range $range"
   612  }
   613  
   614  ###################
   615  #  random_string  #  Pseudorandom alphanumeric string of given length
   616  ###################
   617  function random_string() {
   618      local length=${1:-10}
   619      head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
   620  }
   621  
   622  ###################
   623  #  wait_for_port  #  Returns once port is available on host
   624  ###################
   625  function wait_for_port() {
   626      local host=$1                      # Probably "localhost"
   627      local port=$2                      # Numeric port
   628      local _timeout=${3:-5}             # Optional; default to 5 seconds
   629  
   630      local path=/dev/tcp/$host/$port
   631  
   632      # Wait
   633      local i=$_timeout
   634      while [ $i -gt 0 ]; do
   635          { exec 3<> /dev/tcp/$host/$port; } &>/dev/null && return
   636          sleep 1
   637          i=$(( $i - 1 ))
   638      done
   639      die "Timed out (${_timeout}s) waiting for service ($path)"
   640  }
   641  
   642  ############
   643  #  podman  #  Needed by some test scripts to invoke the actual podman binary
   644  ############
   645  function podman() {
   646      echo "\$ $PODMAN_BIN $*"                           >>$WORKDIR/output.log
   647  #    env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf \
   648          $PODMAN_BIN --root $WORKDIR/server_root "$@"   >>$WORKDIR/output.log 2>&1
   649  }
   650  
   651  ####################
   652  #  root, rootless  #  Is server rootless?
   653  ####################
   654  ROOTLESS=
   655  function root() {
   656      ! rootless
   657  }
   658  
   659  function rootless() {
   660      if [[ -z $ROOTLESS ]]; then
   661          ROOTLESS=$(curl -s http://$HOST:$PORT/v1.40/info | jq .Rootless)
   662      fi
   663      test "$ROOTLESS" = "true"
   664  }
   665  
   666  # True if cgroups v2 are enabled
   667  function have_cgroupsv2() {
   668      cgroup_type=$(stat -f -c %T /sys/fs/cgroup)
   669      test "$cgroup_type" = "cgroup2fs"
   670  }
   671  
   672  # END   infrastructure code
   673  ###############################################################################
   674  # BEGIN sanity checks
   675  
   676  for tool in curl jq podman; do
   677      type $tool &>/dev/null || die "$ME: Required tool '$tool' not found"
   678  done
   679  
   680  # END   sanity checks
   681  ###############################################################################
   682  # BEGIN entry handler (subtest invoker)
   683  
   684  echo '============================= test session starts =============================='
   685  echo "podman client -- $(curl --version)"
   686  
   687  # Identify the tests to run. If called with args, use those as globs.
   688  tests_to_run=()
   689  if [ -n "$*" ]; then
   690      shopt -s nullglob
   691      for i; do
   692          match=(${TESTS_DIR}/*${i}*.at)
   693          if [ ${#match} -eq 0 ]; then
   694              die "No match for $TESTS_DIR/*$i*.at"
   695          fi
   696          tests_to_run+=("${match[@]}")
   697      done
   698      shopt -u nullglob
   699  else
   700      tests_to_run=($TESTS_DIR/*.at)
   701  fi
   702  echo -e "collected ${#tests_to_run[@]} items\n"
   703  
   704  start_service
   705  
   706  for i in "${tests_to_run[@]}"; do
   707      TEST_CONTEXT="[$(basename $i .at)]"
   708  
   709      # Clear output from 'podman' helper
   710      truncate --size=0 $WORKDIR/output.log
   711  
   712      source $i
   713  done
   714  
   715  # END   entry handler
   716  ###############################################################################
   717  
   718  clean_up_server
   719  
   720  test_count=$(<$testcounter_file)
   721  failure_count=$(<$failures_file)
   722  
   723  if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then
   724      rm -rf $WORKDIR
   725  fi
   726  
   727  echo "1..${test_count}"
   728  
   729  exit $failure_count