#!/bin/bash # # Test the Redis image. # # IMAGE_NAME specifies the name of the candidate image used for testing. # The image has to be available before this script is executed. # set -o errexit set -o nounset shopt -s nullglob [ "${DEBUG:-0}" -eq 1 ] && set -x test -n "${IMAGE_NAME-}" || { echo 'make sure $IMAGE_NAME is defined' && false ;} test -n "${VERSION-}" || { echo 'make sure $VERSION is defined' && false; } test -n "${OS-}" || { echo 'make sure $OS is defined' && false; } test_short_summary='' TESTSUITE_RESULT=0 TEST_LIST="\ run_container_creation_tests run_tests_no_root run_tests_no_pass run_tests_no_pass_altuid run_tests_no_root_altuid run_change_password_test run_doc_test " CIDFILE_DIR=$(mktemp --suffix=redis_test_cidfiles -d) function cleanup() { local cidfile for cidfile in $CIDFILE_DIR/* ; do local CONTAINER CONTAINER=$(cat $cidfile) echo "Stopping and removing container $CONTAINER..." docker stop $CONTAINER >/dev/null local exit_status exit_status=$(docker inspect -f '{{.State.ExitCode}}' $CONTAINER) if [ "$exit_status" != "0" ]; then echo "Inspecting container $CONTAINER" docker inspect $CONTAINER echo "Dumping logs for $CONTAINER" docker logs $CONTAINER fi docker rm -v $CONTAINER >/dev/null rm $cidfile echo "Done." done rmdir $CIDFILE_DIR echo "$test_short_summary" if [ $TESTSUITE_RESULT -eq 0 ] ; then echo "Tests for ${IMAGE_NAME} succeeded." else echo "Tests for ${IMAGE_NAME} failed." fi exit $TESTSUITE_RESULT } trap cleanup EXIT SIGINT check_result() { local result="$1" if [[ "$result" != "0" ]]; then TESTCASE_RESULT=1 fi return $result } function get_cid() { local id="$1" ; shift || return 1 echo $(cat "$CIDFILE_DIR/$id") } function get_container_ip() { local id="$1" ; shift docker inspect --format='{{.NetworkSettings.IPAddress}}' $(get_cid "$id") } function connection_works() { local container_ip="$1"; shift local password="$1"; shift if [ "$(redis_cmd "$container_ip" "$password" ping)" == "PONG" ] ; then return 0 fi return 1 } function redis_cmd() { local container_ip="$1"; shift local password="$1"; shift # if empty password is given, then no password will be specified docker run --rm "$IMAGE_NAME" redis-cli -h "$container_ip" ${password:+-a "$password"} "$@" } function test_connection() { local name=$1 ; shift local password=$1 ; shift local ip ip=$(get_container_ip $name) echo " Testing Redis connection to $ip (password='${password:-}')..." local max_attempts=10 local sleep_time=2 local i for i in $(seq $max_attempts); do echo " Trying to connect..." if connection_works "$ip" "$password" ; then echo " Success!" echo return 0 fi sleep $sleep_time done echo " Giving up: Failed to connect. Logs:" docker logs $(get_cid $name) return 1 } function test_redis() { local container_ip="$1" local password="$2" echo " Testing Redis (password='${password:-}')" redis_cmd "$container_ip" "$password" set a 1 >/dev/null check_result $? redis_cmd "$container_ip" "$password" set b 2 >/dev/null check_result $? test "$(redis_cmd "$container_ip" "$password" get b)" == '2' echo " Success!" echo } function create_container() { local name=$1 ; shift cidfile="$CIDFILE_DIR/$name" # create container with a cidfile in a directory for cleanup local container_id [ "${DEBUG:-0}" -eq 1 ] && echo "DEBUG: docker run ${DOCKER_ARGS:-} --cidfile $cidfile -d \"$@\" $IMAGE_NAME ${CONTAINER_ARGS:-}" >&2 container_id="$(docker run ${DOCKER_ARGS:-} --cidfile $cidfile -d "$@" $IMAGE_NAME ${CONTAINER_ARGS:-})" [ "${DEBUG:-0}" -eq 1 ] && echo "Created container $container_id" [ x"$container_id" == "x" ] && return 1 || return 0 } function run_change_password_test() { local tmpdir=$(mktemp -d) mkdir "${tmpdir}/data" && chmod -R a+rwx "${tmpdir}" # Create Redis container with persistent volume and set the initial password create_container "testpass1" -e REDIS_PASSWORD=foo \ -v ${tmpdir}:/var/lib/redis/data:Z check_result $? test_connection testpass1 foo check_result $? docker stop $(get_cid testpass1) >/dev/null # Create second container with changed password create_container "testpass2" -e REDIS_PASSWORD=bar \ -v ${tmpdir}:/var/lib/redis/data:Z check_result $? test_connection testpass2 bar check_result $? # The old password should not work anymore container_ip="$(get_container_ip testpass2)" connection_works "$container_ip" foo check_result $? } function assert_login_access() { local container_ip=$1; shift local PASS=$1 ; shift local success=$1 ; shift if connection_works "$container_ip" "$PASS" ; then if $success ; then echo " Connection ($PASS) access granted as expected" return 0 fi else if ! $success ; then echo " Connection ($PASS) access denied as expected" return 0 fi fi echo " Connection ($PASS) login assertion failed" return 1 } function assert_local_access() { local id="$1" ; shift docker exec $(get_cid "$id") bash -c 'redis-cli ping' } # Make sure the invocation of docker run fails. function assert_container_creation_fails() { # Time the docker run command. It should fail. If it doesn't fail, # redis will keep running so we kill it with SIGKILL to make sure # timeout returns a non-zero value. local ret=0 timeout -s 9 --preserve-status 60s docker run --rm "$@" $IMAGE_NAME >/dev/null || ret=$? # Timeout will exit with a high number. if [ $ret -gt 10 ]; then return 1 fi } function try_image_invalid_combinations() { assert_container_creation_fails -e REDIS_PASSWORD="pass with space" "$@" check_result $? } function run_container_creation_tests() { local ret echo " Testing image entrypoint usage" try_image_invalid_combinations ret=$? if [ $ret -eq 0 ]; then echo " Success!" else echo " Failed!" fi echo return $ret } test_scl_usage() { local name="$1" local run_cmd="$2" local expected="$3" echo " Testing the image SCL enable" local out out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${run_cmd}") if ! echo "${out}" | grep -q "${expected}"; then echo "ERROR[/bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" return 1 fi out=$(docker exec $(get_cid $name) /bin/bash -c "${run_cmd}" 2>&1) if ! echo "${out}" | grep -q "${expected}"; then echo "ERROR[exec /bin/bash -c "${run_cmd}"] Expected '${expected}', got '${out}'" return 1 fi out=$(docker exec $(get_cid $name) /bin/sh -ic "${run_cmd}" 2>&1) if ! echo "${out}" | grep -q "${expected}"; then echo "ERROR[exec /bin/sh -ic "${run_cmd}"] Expected '${expected}', got '${out}'" return 1 fi } run_doc_test() { local tmpdir=$(mktemp -d) local f echo " Testing documentation in the container image" # Extract the help.1 file from the container docker run --rm ${IMAGE_NAME} /bin/bash -c "cat /help.1" >${tmpdir}/help.1 # Check whether the help.1 file includes some important information for term in 6379 "REDIS\_PASSWORD" volume; do if ! cat ${tmpdir}/help.1 | grep -F -q -e "${term}" ; then echo "ERROR: File /help.1 does not include '${term}'." return 1 fi done # Check whether the file uses the correct format if ! file ${tmpdir}/help.1 | grep -q roff ; then echo "ERROR: /help.1 is not in troff or groff format" return 1 fi echo " Success!" echo } function run_tests() { local name=$1 ; shift local ret envs=${PASS:+"-e REDIS_PASSWORD=$PASS"} PASS=${PASS:-} create_container $name $envs ret=$? check_result $ret test_connection "$name" "$PASS" ret=$? check_result $ret # Only check version on rhel/centos builds if [ "$OS" != "fedora" ]; then echo " Testing scl usage" test_scl_usage $name 'redis-server --version' "$VERSION" check_result $? fi echo " Testing login accesses" local container_ip container_ip=$(get_container_ip $name) assert_login_access "$container_ip" "$PASS" true ret=$? check_result $ret if [ -n "$PASS" ] ; then assert_login_access "$container_ip" "${PASS}_foo" false check_result $? fi assert_local_access "$name" ret=$? check_result $ret if [ $ret -ne 0 ]; then echo " Local access FAILED." else echo " Local access SUCCESS." fi echo test_redis "$container_ip" "$PASS" check_result $? } function run_tests_no_root() { # Normal tests with password PASS=pass run_tests no_root } function run_tests_no_pass() { # Normal tests without password run_tests no_pass } function run_tests_no_pass_altuid() { # Test with arbitrary uid for the container without password DOCKER_ARGS="-u 12345" run_tests no_pass_altuid } function run_tests_no_root_altuid() { # Test with arbitrary uid for the container with password DOCKER_ARGS="-u 12345" PASS=pass run_tests no_root_altuid } function run_all_tests() { for test_case in $TEST_SET; do echo "Running test $test_case ...." TESTCASE_RESULT=0 $test_case check_result $? local test_msg if [ $TESTCASE_RESULT -eq 0 ]; then test_msg="[PASSED]" else test_msg="[FAILED]" TESTSUITE_RESULT=1 fi printf -v test_short_summary "%s %s for '%s' %s\n" "${test_short_summary}" "${test_msg}" "$test_case" [ -n "${FAIL_QUICKLY:-}" ] && cleanup "${APP_NAME}" && return 1 done; } TEST_SET=${TESTS:-$TEST_LIST} run_all_tests echo "Success!" cleanup