github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/test/apiv2/test-apiv2 (about) 1 #!/usr/bin/env bash 2 # 3 # Usage: test-apiv2 [PORT] 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 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 # like # Compare, but allowing patterns 112 ########## 113 function like() { 114 local actual=$1 115 local expect=$2 116 local testname=$3 117 118 if expr "$actual" : "$expect" &>/dev/null; then 119 # On success, include expected value; this helps readers understand 120 # (but don't show enormous multi-line output like 'generate kube') 121 blurb=$(head -n1 <<<"$actual") 122 _show_ok 1 "$testname ('$blurb') ~ $expect" 123 return 124 fi 125 _show_ok 0 "$testname" "~ $expect" "$actual" 126 } 127 128 ############## 129 # _show_ok # Helper for is() and like(): displays 'ok' or 'not ok' 130 ############## 131 function _show_ok() { 132 local ok=$1 133 local testname=$2 134 135 # If output is a tty, colorize pass/fail 136 local red= 137 local green= 138 local reset= 139 local bold= 140 if [ -t 1 ]; then 141 red='\e[31m' 142 green='\e[32m' 143 reset='\e[0m' 144 bold='\e[1m' 145 fi 146 147 _bump $testcounter_file 148 count=$(<$testcounter_file) 149 150 # "skip" is a special case of "ok". Assume that our caller has included 151 # the magical '# skip - reason" comment string. 152 if [[ $ok == "skip" ]]; then 153 # colon-plus: replace green with yellow, but only if green is non-null 154 green="${green:+\e[33m}" 155 ok=1 156 fi 157 if [ $ok -eq 1 ]; then 158 echo -e "${green}ok $count ${TEST_CONTEXT} $testname${reset}" 159 echo "ok $count ${TEST_CONTEXT} $testname" >>$LOG 160 return 161 fi 162 163 # Failed 164 local expect=$3 165 local actual=$4 166 echo -e "${red}not ok $count ${TEST_CONTEXT} $testname${reset}" 167 echo -e "${red}# expected: $expect${reset}" 168 echo -e "${red}# actual: ${bold}$actual${reset}" 169 170 echo "not ok $count ${TEST_CONTEXT} $testname" >>$LOG 171 echo " expected: $expect" >>$LOG 172 173 _bump $failures_file 174 } 175 176 ########### 177 # _bump # Increment a counter in a file 178 ########### 179 function _bump() { 180 local file=$1 181 182 count=$(<$file) 183 echo $(( $count + 1 )) >| $file 184 } 185 186 ############# 187 # jsonify # convert 'foo=bar,x=y' to json {"foo":"bar","x":"y"} 188 ############# 189 function jsonify() { 190 # convert each to double-quoted form 191 local -a settings_out 192 for i in "$@"; do 193 # Each argument is of the form foo=bar. Separate into left and right. 194 local lhs 195 local rhs 196 IFS='=' read lhs rhs <<<"$i" 197 198 if [[ $rhs =~ \" || $rhs == true || $rhs == false || $rhs =~ ^-?[0-9]+$ ]]; then 199 # rhs has been pre-formatted for JSON or a non-string, do not change it 200 : 201 elif [[ $rhs == False ]]; then 202 # JSON boolean is lowercase only 203 rhs=false 204 elif [[ $rhs == True ]]; then 205 # JSON boolean is lowercase only 206 rhs=true 207 else 208 rhs="\"${rhs}\"" 209 fi 210 settings_out+=("\"${lhs}\":${rhs}") 211 done 212 213 # ...and wrap inside braces, with comma separator if multiple fields 214 (IFS=','; echo "{${settings_out[*]}}") 215 } 216 217 ####### 218 # t # Main test helper 219 ####### 220 function t() { 221 local method=$1; shift 222 local path=$1; shift 223 local -a curl_args 224 local content_type="application/json" 225 226 local testname="$method $path" 227 # POST and PUT requests may be followed by one or more key=value pairs. 228 # Slurp the command line until we see a 3-digit status code. 229 if [[ $method = "POST" || $method == "PUT" ]]; then 230 local -a post_args 231 for arg; do 232 case "$arg" in 233 *=*) post_args+=("$arg"); 234 shift;; 235 *.tar) curl_args+=(--data-binary @$arg); 236 content_type="application/x-tar"; 237 shift;; 238 application/*) content_type="$arg"; 239 shift;; 240 [1-9][0-9][0-9]) break;; 241 *) die "Internal error: invalid POST arg '$arg'" ;; 242 esac 243 done 244 if [[ -z "$curl_args" ]]; then 245 curl_args+=(-d $(jsonify ${post_args[@]})) 246 testname="$testname [${curl_args[@]}]" 247 fi 248 fi 249 250 # entrypoint path can include a descriptive comment; strip it off 251 path=${path%% *} 252 253 local url=$path 254 if ! [[ $path =~ ^'http://' ]]; then 255 # path may include JSONish params that curl will barf on; url-encode them 256 path="${path//'['/%5B}" 257 path="${path//']'/%5D}" 258 path="${path//'{'/%7B}" 259 path="${path//'}'/%7D}" 260 path="${path//':'/%3A}" 261 262 # If given path begins with /, use it as-is; otherwise prepend /version/ 263 url=http://$HOST:$PORT 264 case "$path" in 265 /*) url="$url$path" ;; 266 libpod/*) url="$url/v4.0.0/$path" ;; 267 *) url="$url/v1.41/$path" ;; 268 esac 269 fi 270 271 # curl -X HEAD but without --head seems to wait for output anyway 272 if [[ $method == "HEAD" ]]; then 273 curl_args+=("--head") 274 fi 275 276 if [ -n "$REGISTRY_CONFIG_HEADER" ]; then 277 curl_args+=(-H "X-Registry-Config: $REGISTRY_CONFIG_HEADER") 278 fi 279 280 local expected_code=$1; shift 281 282 # Log every action we do 283 echo "-------------------------------------------------------------" >>$LOG 284 echo "\$ $testname" >>$LOG 285 rm -f $WORKDIR/curl.* 286 # -s = silent, but --write-out 'format' gives us important response data 287 # The hairy "{ ...;rc=$?; } || :" lets us capture curl's exit code and 288 # give a helpful diagnostic if it fails. 289 { response=$(curl -s -X $method "${curl_args[@]}" \ 290 -H "Content-type: $content_type" \ 291 --dump-header $WORKDIR/curl.headers.out \ 292 --write-out '%{http_code}^%{content_type}^%{time_total}' \ 293 -o $WORKDIR/curl.result.out "$url"); rc=$?; } || : 294 295 # Any error from curl is instant bad news, from which we can't recover 296 if [[ $rc -ne 0 ]]; then 297 die "curl failure ($rc) on $url - cannot continue" 298 fi 299 300 # Show returned headers (without trailing ^M or empty lines) in log file. 301 # Sometimes -- I can't remember why! -- we don't get headers. 302 if [[ -e $WORKDIR/curl.headers.out ]]; then 303 tr -d '\015' < $WORKDIR/curl.headers.out | egrep '.' >>$LOG 304 fi 305 306 IFS='^' read actual_code content_type time_total <<<"$response" 307 printf "X-Response-Time: ${time_total}s\n\n" >>$LOG 308 309 # Log results, if text. If JSON, filter through jq for readability. 310 if [[ $content_type =~ /octet ]]; then 311 output="[$(file --brief $WORKDIR/curl.result.out)]" 312 echo "$output" >>$LOG 313 elif [[ -e $WORKDIR/curl.result.out ]]; then 314 # Output from /logs sometimes includes NULs. Strip them. 315 output=$(tr -d '\0' < $WORKDIR/curl.result.out) 316 317 if [[ $content_type =~ application/json ]] && [[ $method != "HEAD" ]]; then 318 jq . <<<"$output" >>$LOG 319 else 320 echo "$output" >>$LOG 321 fi 322 else 323 output= 324 echo "[no output]" >>$LOG 325 fi 326 327 # Test return code 328 is "$actual_code" "$expected_code" "$testname : status" 329 330 # Special case: 204/304, by definition, MUST NOT return content (rfc2616) 331 if [[ $expected_code = 204 || $expected_code = 304 ]]; then 332 if [ -n "$*" ]; then 333 die "Internal error: ${expected_code} status returns no output; fix your test." 334 fi 335 if [ -n "$output" ]; then 336 _show_ok 0 "$testname: ${expected_code} status returns no output" "''" "$output" 337 fi 338 return 339 fi 340 341 local i 342 343 # Special case: if response code does not match, dump the response body 344 # and skip all further subtests. 345 if [[ $actual_code != $expected_code ]]; then 346 echo -e "# response: $output" 347 for i; do 348 _show_ok skip "$testname: $i # skip - wrong return code" 349 done 350 return 351 fi 352 353 for i; do 354 if expr "$i" : "[^=~]\+=.*" >/dev/null; then 355 # Exact match on json field 356 json_field=$(expr "$i" : "\([^=]*\)=") 357 expect=$(expr "$i" : '[^=]*=\(.*\)') 358 actual=$(jq -r "$json_field" <<<"$output") 359 is "$actual" "$expect" "$testname : $json_field" 360 elif expr "$i" : "[^=~]\+~.*" >/dev/null; then 361 # regex match on json field 362 json_field=$(expr "$i" : "\([^~]*\)~") 363 expect=$(expr "$i" : '[^~]*~\(.*\)') 364 actual=$(jq -r "$json_field" <<<"$output") 365 like "$actual" "$expect" "$testname : $json_field" 366 else 367 # Direct string comparison 368 is "$output" "$i" "$testname : output" 369 fi 370 done 371 } 372 373 ################### 374 # start_service # Run the socket listener 375 ################### 376 service_pid= 377 function start_service() { 378 # If there's a listener on the port, nothing for us to do 379 { exec 3<> /dev/tcp/$HOST/$PORT; } &>/dev/null && return 380 381 test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN" 382 383 if [ "$HOST" != "localhost" ]; then 384 die "Cannot start service on non-localhost ($HOST)" 385 fi 386 387 echo "rootdir: "$WORKDIR 388 # Some tests use shortnames; force registry override to work around 389 # docker.io throttling. 390 # FIXME esm revisit pulling expected images re: shortnames caused tests to fail 391 # env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf 392 $PODMAN_BIN \ 393 --root $WORKDIR/server_root --syslog=true \ 394 system service \ 395 --time 0 \ 396 tcp:127.0.0.1:$PORT \ 397 &> $WORKDIR/server.log & 398 service_pid=$! 399 400 wait_for_port $HOST $PORT 401 } 402 403 function stop_service() { 404 # Stop the server 405 if [[ -n $service_pid ]]; then 406 kill $service_pid || : 407 wait $service_pid || : 408 fi 409 } 410 411 #################### 412 # start_registry # Run a local registry 413 #################### 414 REGISTRY_PORT= 415 REGISTRY_USERNAME= 416 REGISTRY_PASSWORD= 417 REGISTRY_CONFIG_HEADER= 418 function start_registry() { 419 # We can be invoked multiple times, e.g. from different subtests, but 420 # let's assume that once started we only kill it at the end of tests. 421 if [[ -n "$REGISTRY_PORT" ]]; then 422 return 423 fi 424 425 REGISTRY_PORT=$(random_port) 426 REGISTRY_USERNAME=u$(random_string 7) 427 REGISTRY_PASSWORD=p$(random_string 7) 428 REGISTRY_CONFIG_HEADER=$(echo "{\"localhost:${REGISTRY_PORT}\":{\"username\":\"${REGISTRY_USERNAME}\",\"password\":\"${REGISTRY_PASSWORD}\"}}" | base64 --wrap=0) 429 430 local REGDIR=$WORKDIR/registry 431 local AUTHDIR=$REGDIR/auth 432 mkdir -p $AUTHDIR 433 434 mkdir -p ${REGDIR}/{root,runroot} 435 local PODMAN_REGISTRY_ARGS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot" 436 437 # Give it three tries, to compensate for network flakes 438 podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE || 439 podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE || 440 podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE 441 442 # Create a local cert and credentials 443 # FIXME: is there a hidden "--quiet" flag? This is too noisy. 444 openssl req -newkey rsa:4096 -nodes -sha256 \ 445 -keyout $AUTHDIR/domain.key -x509 -days 2 \ 446 -out $AUTHDIR/domain.crt \ 447 -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \ 448 -addext subjectAltName=DNS:localhost 449 htpasswd -Bbn ${REGISTRY_USERNAME} ${REGISTRY_PASSWORD} \ 450 > $AUTHDIR/htpasswd 451 452 # Run the registry, and wait for it to come up 453 podman ${PODMAN_REGISTRY_ARGS} run -d \ 454 -p ${REGISTRY_PORT}:5000 \ 455 --name registry \ 456 -v $AUTHDIR:/auth:Z \ 457 -e "REGISTRY_AUTH=htpasswd" \ 458 -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ 459 -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ 460 -e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt \ 461 -e REGISTRY_HTTP_TLS_KEY=/auth/domain.key \ 462 ${REGISTRY_IMAGE} 463 464 wait_for_port localhost $REGISTRY_PORT 10 465 } 466 467 function stop_registry() { 468 local REGDIR=${WORKDIR}/registry 469 if [[ -d $REGDIR ]]; then 470 local OPTS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot" 471 podman $OPTS stop -f -t 0 -a 472 473 # rm/rmi are important when running rootless: without them we 474 # get EPERMS in tmpdir cleanup because files are owned by subuids. 475 podman $OPTS rm -f -a 476 podman $OPTS rmi -f -a 477 fi 478 } 479 480 ################# 481 # random_port # Random open port; arg is range (min-max), default 5000-5999 482 ################# 483 function random_port() { 484 local range=${1:-5000-5999} 485 486 local port 487 for port in $(shuf -i ${range}); do 488 if ! { exec 5<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then 489 echo $port 490 return 491 fi 492 done 493 494 die "Could not find open port in range $range" 495 } 496 497 ################### 498 # random_string # Pseudorandom alphanumeric string of given length 499 ################### 500 function random_string() { 501 local length=${1:-10} 502 head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length 503 } 504 505 ################### 506 # wait_for_port # Returns once port is available on host 507 ################### 508 function wait_for_port() { 509 local host=$1 # Probably "localhost" 510 local port=$2 # Numeric port 511 local _timeout=${3:-5} # Optional; default to 5 seconds 512 513 local path=/dev/tcp/$host/$port 514 515 # Wait 516 local i=$_timeout 517 while [ $i -gt 0 ]; do 518 { exec 3<> /dev/tcp/$host/$port; } &>/dev/null && return 519 sleep 1 520 i=$(( $i - 1 )) 521 done 522 die "Timed out (${_timeout}s) waiting for service ($path)" 523 } 524 525 ############ 526 # podman # Needed by some test scripts to invoke the actual podman binary 527 ############ 528 function podman() { 529 echo "\$ $PODMAN_BIN $*" >>$WORKDIR/output.log 530 # env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf \ 531 $PODMAN_BIN --root $WORKDIR/server_root "$@" >>$WORKDIR/output.log 2>&1 532 } 533 534 #################### 535 # root, rootless # Is server rootless? 536 #################### 537 ROOTLESS= 538 function root() { 539 ! rootless 540 } 541 542 function rootless() { 543 if [[ -z $ROOTLESS ]]; then 544 ROOTLESS=$(curl -s http://$HOST:$PORT/v1.40/info | jq .Rootless) 545 fi 546 test "$ROOTLESS" = "true" 547 } 548 549 # True if cgroups v2 are enabled 550 function have_cgroupsv2() { 551 cgroup_type=$(stat -f -c %T /sys/fs/cgroup) 552 test "$cgroup_type" = "cgroup2fs" 553 } 554 555 # END infrastructure code 556 ############################################################################### 557 # BEGIN sanity checks 558 559 for tool in curl jq podman; do 560 type $tool &>/dev/null || die "$ME: Required tool '$tool' not found" 561 done 562 563 # END sanity checks 564 ############################################################################### 565 # BEGIN entry handler (subtest invoker) 566 567 echo '============================= test session starts ==============================' 568 echo "podman client -- $(curl --version)" 569 570 # Identify the tests to run. If called with args, use those as globs. 571 tests_to_run=() 572 if [ -n "$*" ]; then 573 shopt -s nullglob 574 for i; do 575 match=(${TESTS_DIR}/*${i}*.at) 576 if [ ${#match} -eq 0 ]; then 577 die "No match for $TESTS_DIR/*$i*.at" 578 fi 579 tests_to_run+=("${match[@]}") 580 done 581 shopt -u nullglob 582 else 583 tests_to_run=($TESTS_DIR/*.at) 584 fi 585 echo -e "collected ${#tests_to_run[@]} items\n" 586 587 start_service 588 589 for i in ${tests_to_run[@]}; do 590 TEST_CONTEXT="[$(basename $i .at)]" 591 592 # Clear output from 'podman' helper 593 >| $WORKDIR/output.log 594 595 source $i 596 done 597 598 # END entry handler 599 ############################################################################### 600 601 clean_up_server 602 603 test_count=$(<$testcounter_file) 604 failure_count=$(<$failures_file) 605 606 if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then 607 rm -rf $WORKDIR 608 fi 609 610 echo "1..${test_count}" 611 612 exit $failure_count