diff --git a/s2i/bin/assemble b/s2i/bin/assemble index c6515b5..26fcf6d 100755 --- a/s2i/bin/assemble +++ b/s2i/bin/assemble @@ -41,6 +41,13 @@ mv /tmp/src/* "$HOME" # set permissions for any installed artifacts fix-permissions /opt/app-root -P +# We have to first upgrade pip to at least 19.3 because: +# * pip < 9 does not support different packages' versions for Python 2/3 +# * pip < 19.3 does not support manylinux2014 wheels. Only manylinux2014 wheels +# support platforms like ppc64le, aarch64 or armv7 +echo "---> Upgrading pip to version 19.3.1 ..." +pip install -U "pip==19.3.1" + if [[ ! -z "$UPGRADE_PIP_TO_LATEST" ]]; then echo "---> Upgrading pip to latest version ..." pip install -U pip setuptools wheel diff --git a/test/npm-virtualenv-uwsgi-test-app/app.sh b/test/npm-virtualenv-uwsgi-test-app/app.sh index 473e990..2659e06 100755 --- a/test/npm-virtualenv-uwsgi-test-app/app.sh +++ b/test/npm-virtualenv-uwsgi-test-app/app.sh @@ -2,23 +2,6 @@ # Test virtualenv environment and pip upgrade -# Get the latest stable version of the package released on PyPI -function get_latest_stable_version() { - echo $(curl -sL 'https://pypi.org/pypi/'$1'/json' | python -c \ -""" -import sys -import json -from pip._vendor.packaging.version import parse -versions = [parse(v) for v in json.load(sys.stdin)['releases'].keys()] -print(str((sorted([v for v in versions if not v.is_prerelease])[-1]))) -""") -} - -# Get version of the package installed on the system -function get_installed_version() { - echo $(pip freeze --all | grep $1 | cut -d"=" -f3) -} - echo "Testing that the virtual environment's Python is being used ..." if [ "$(which python)" != "/opt/app-root/bin/python" ]; then echo "ERROR: Initialization of the virtual environment failed." @@ -28,7 +11,10 @@ fi echo "Testing UPGRADE_PIP_TO_LATEST=1 (set in .s2i/environment) ..." packages=("pip" "setuptools" "wheel") for pkg in ${packages[@]}; do - if [ $(get_latest_stable_version $pkg) != $(get_installed_version $pkg) ]; then + # grep returns exit code 1 if the output contains only one line starting + # with "Requirement already …" which means that the package is updated + python -m pip install -U --no-python-version-warning $pkg 2>&1 | grep -v "^Requirement already up-to-date: " + if [ $? -eq 0 ]; then echo "ERROR: Failed to upgrade '$pkg' to the latest version." exit 1 fi diff --git a/test/run-openshift b/test/run-openshift index d59e199..87ac440 100755 --- a/test/run-openshift +++ b/test/run-openshift @@ -16,16 +16,23 @@ set -exo nounset test -n "${IMAGE_NAME-}" || false 'make sure $IMAGE_NAME is defined' test -n "${VERSION-}" || false 'make sure $VERSION is defined' +if [ -z "${EPHEMERAL_TEMPLATES:-}" ]; then + EPHEMERAL_TEMPLATES=" +https://raw.githubusercontent.com/sclorg/django-ex/master/openshift/templates/django-postgresql.json \ +https://raw.githubusercontent.com/openshift/origin/master/examples/quickstarts/django-postgresql.json" +fi + ct_os_cluster_up ct_os_test_s2i_app "${IMAGE_NAME}" "${THISDIR}/standalone-test-app" . "Hello World from standalone WSGI application!" ct_os_test_s2i_app "${IMAGE_NAME}" "https://github.com/sclorg/django-ex.git" . 'Welcome to your Django application on OpenShift' -ct_os_test_template_app "${IMAGE_NAME}" \ - https://raw.githubusercontent.com/sclorg/django-ex/master/openshift/templates/django-postgresql.json \ - python \ - 'Welcome to your Django application on OpenShift' \ - 8080 http 200 "-p SOURCE_REPOSITORY_REF=master -p PYTHON_VERSION=${VERSION} -p POSTGRESQL_VERSION=9.6 -p NAME=python-testing" \ - "centos/postgresql-96-centos7|postgresql:9.6" - +for template in $EPHEMERAL_TEMPLATES; do + ct_os_test_template_app "$IMAGE_NAME" \ + "$template" \ + python \ + 'Welcome to your Django application on OpenShift' \ + 8080 http 200 "-p SOURCE_REPOSITORY_REF=master -p PYTHON_VERSION=${VERSION} -p POSTGRESQL_VERSION=9.6 -p NAME=python-testing" \ + "centos/postgresql-96-centos7|postgresql:9.6" +done diff --git a/test/test-lib-openshift.sh b/test/test-lib-openshift.sh index 28136a3..f62eab6 100644 --- a/test/test-lib-openshift.sh +++ b/test/test-lib-openshift.sh @@ -1,5 +1,7 @@ +# shellcheck shell=bash # some functions are used from test-lib.sh, that is usually in the same dir -source $(dirname ${BASH_SOURCE[0]})/test-lib.sh +# shellcheck source=/dev/null +source "$(dirname "${BASH_SOURCE[0]}")"/test-lib.sh # Set of functions for testing docker images in OpenShift using 'oc' command @@ -13,8 +15,10 @@ OS_CLUSTER_STARTED_BY_TEST=0 function ct_os_cleanup() { if [ $OS_TESTSUITE_RESULT -eq 0 ] ; then + # shellcheck disable=SC2153 echo "OpenShift tests for ${IMAGE_NAME} succeeded." else + # shellcheck disable=SC2153 echo "OpenShift tests for ${IMAGE_NAME} failed." fi } @@ -26,8 +30,11 @@ function ct_os_cleanup() { # * VERSION specifies the major version of the MariaDB in format of X.Y # * OS specifies RHEL version (e.g. OS=rhel7) function ct_os_check_compulsory_vars() { + # shellcheck disable=SC2016 test -n "${IMAGE_NAME-}" || ( echo 'make sure $IMAGE_NAME is defined' >&2 ; exit 1) + # shellcheck disable=SC2016 test -n "${VERSION-}" || ( echo 'make sure $VERSION is defined' >&2 ; exit 1) + # shellcheck disable=SC2016 test -n "${OS-}" || ( echo 'make sure $OS is defined' >&2 ; exit 1) } @@ -44,9 +51,9 @@ function ct_os_get_status() { # Returns status of all objects and logs from all pods. function ct_os_print_logs() { ct_os_get_status - while read pod_name; do + while read -r pod_name; do echo "INFO: printing logs for pod ${pod_name}" - oc logs ${pod_name} + oc logs "${pod_name}" done < <(oc get pods --no-headers=true -o custom-columns=NAME:.metadata.name) } @@ -65,13 +72,15 @@ function ct_os_enable_print_logs() { # hostname -I returns and de-prioritizes IP addresses commonly used for local # addressing. The rest of addresses are taken as public with higher probability. function ct_get_public_ip() { - local hostnames=$(hostname -I) + local hostnames local public_ip='' local found_ip + hostnames=$(hostname -I) for guess_exp in '127\.0\.0\.1' '192\.168\.[0-9\.]*' '172\.[0-9\.]*' \ '10\.[0-9\.]*' '[0-9\.]*' ; do found_ip=$(echo "${hostnames}" | grep -oe "${guess_exp}") if [ -n "${found_ip}" ] ; then + # shellcheck disable=SC2001 hostnames=$(echo "${hostnames}" | sed -e "s/${found_ip}//") public_ip="${found_ip}" fi @@ -157,7 +166,7 @@ function ct_os_get_pod_ip() { function ct_os_check_pod_readiness() { local pod_prefix="${1}" ; shift local status="${1}" ; shift - test "$(ct_os_get_pod_status ${pod_prefix})" == "${status}" + test "$(ct_os_get_pod_status "${pod_prefix}")" == "${status}" } # ct_os_wait_pod_ready POD_PREFIX TIMEOUT @@ -172,7 +181,7 @@ function ct_os_wait_pod_ready() { echo -n "Waiting for ${pod_prefix} pod becoming ready ..." while ! ct_os_check_pod_readiness "${pod_prefix}" "true" ; do echo -n "." - [ ${SECONDS} -gt ${timeout} ] && echo " FAIL" && return 1 + [ "${SECONDS}" -gt "${timeout}" ] && echo " FAIL" && return 1 sleep 3 done echo " DONE" @@ -188,10 +197,10 @@ function ct_os_wait_rc_ready() { local timeout="${1}" ; shift SECONDS=0 echo -n "Waiting for ${pod_prefix} pod becoming ready ..." - while ! test "$((oc get --no-headers statefulsets; oc get --no-headers rc) 2>/dev/null \ + while ! test "$( (oc get --no-headers statefulsets; oc get --no-headers rc) 2>/dev/null \ | grep "^${pod_prefix}" | awk '$2==$3 {print "ready"}')" == "ready" ; do echo -n "." - [ ${SECONDS} -gt ${timeout} ] && echo " FAIL" && return 1 + [ "${SECONDS}" -gt "${timeout}" ] && echo " FAIL" && return 1 sleep 3 done echo " DONE" @@ -206,7 +215,7 @@ function ct_os_wait_rc_ready() { function ct_os_deploy_pure_image() { local image="${1}" ; shift # ignore error exit code, because oc new-app returns error when image exists - oc new-app ${image} "$@" || : + oc new-app "${image}" "$@" || : # let openshift cluster to sync to avoid some race condition errors sleep 3 } @@ -267,13 +276,14 @@ function _ct_os_get_uniq_project_name() { # The OPENSHIFT_CLUSTER_PULLSECRET_PATH environment variable can be set # to contain a path to a k8s secret definition which will be used # to authenticate to image registries. +# shellcheck disable=SC2120 function ct_os_new_project() { if [ "${CT_SKIP_NEW_PROJECT:-false}" == 'true' ] ; then echo "Creating project skipped." return fi local project_name="${1:-$(_ct_os_get_uniq_project_name)}" ; shift || : - oc new-project ${project_name} + oc new-project "${project_name}" # let openshift cluster to sync to avoid some race condition errors sleep 3 if test -n "${OPENSHIFT_CLUSTER_PULLSECRET_PATH:-}" -a -e "${OPENSHIFT_CLUSTER_PULLSECRET_PATH:-}"; then @@ -289,6 +299,7 @@ function ct_os_new_project() { # -------------------- # Deletes the specified project in the openshfit # Arguments: project - project name, uses the current project if omitted +# shellcheck disable=SC2120 function ct_os_delete_project() { if [ "${CT_SKIP_NEW_PROJECT:-false}" == 'true' ] ; then echo "Deleting project skipped, cleaning objects only." @@ -307,7 +318,7 @@ function ct_os_delete_project() { # Handy when we have one project and want to run more tests. function ct_delete_all_objects() { for x in bc builds dc is isimage istag po pv pvc rc routes secrets svc ; do - oc delete $x --all + oc delete "$x" --all done # for some objects it takes longer to be really deleted, so a dummy sleep # to avoid some races when other test can see not-yet-deleted objects and can fail @@ -322,8 +333,10 @@ function ct_delete_all_objects() { function ct_os_docker_login() { [ -n "${REGISTRY_ADDRESS:-}" ] && "REGISTRY_ADDRESS set, not trying to docker login." && return 0 # docker login fails with "404 page not found" error sometimes, just try it more times - for i in `seq 12` ; do - docker login -u developer -p $(oc whoami -t) ${REGISRTY_ADDRESS:-172.30.1.1:5000} && return 0 || : + # shellcheck disable=SC2034 + for i in $(seq 12) ; do + # shellcheck disable=SC2015 + docker login -u developer -p "$(oc whoami -t)" "${REGISRTY_ADDRESS:-172.30.1.1:5000}" && return 0 || : sleep 5 done return 1 @@ -340,11 +353,13 @@ function ct_os_upload_image() { local input_name="${1}" ; shift local image_name=${input_name##*/} local imagestream=${1:-$image_name:latest} - local output_name="${REGISRTY_ADDRESS:-172.30.1.1:5000}/$(oc project -q)/$imagestream" + local output_name + + output_name="${REGISRTY_ADDRESS:-172.30.1.1:5000}/$(oc project -q)/$imagestream" ct_os_docker_login - docker tag ${input_name} ${output_name} - docker push ${output_name} + docker tag "${input_name}" "${output_name}" + docker push "${output_name}" } # ct_os_is_tag_exists IS_NAME TAG @@ -412,7 +427,7 @@ function ct_os_cluster_up() { ct_os_set_path_oc "${cluster_version}" fi - mkdir -p ${dir}/{config,data,pv} + mkdir -p "${dir}"/{config,data,pv} case $(oc version| head -n 1) in "oc v3.1"?.*) oc cluster up --base-dir="${dir}/data" --public-hostname="${cluster_ip}" @@ -473,11 +488,13 @@ function ct_os_logged_in() { # In the end the PATH variable is changed, so the other tests can still use just 'oc'. # Arguments: oc_version - X.Y part of the version of OSE (e.g. 3.9) function ct_os_set_path_oc() { - local oc_version=$(ct_os_get_latest_ver $1) + local oc_version local oc_path + oc_version=$(ct_os_get_latest_ver "$1") + if oc version | grep -q "oc ${oc_version%.*}." ; then - echo "Binary oc found already available in version ${oc_version}: `which oc` Doing noting." + echo "Binary oc found already available in version ${oc_version}: $(command -v oc) Doing noting." return 0 fi @@ -492,7 +509,7 @@ function ct_os_set_path_oc() { oc_path="/tmp/oc-${oc_version}-bin" ct_os_download_upstream_oc "${oc_version}" "${oc_path}" fi - if [ -z "${oc_path}/oc" ] ; then + if [ -z "${oc_path}" ] ; then echo "ERROR: oc not found installed, nor downloaded" >&1 return 1 fi @@ -501,7 +518,7 @@ function ct_os_set_path_oc() { echo "ERROR: something went wrong, oc located at ${oc_path}, but oc of version ${oc_version} not found in PATH ($PATH)" >&1 return 1 else - echo "PATH set correctly, binary oc found in version ${oc_version}: `which oc`" + echo "PATH set correctly, binary oc found in version ${oc_version}: $(command -v oc)" fi } @@ -539,7 +556,7 @@ function ct_os_download_upstream_oc() { # download, unpack the binaries and then put them into output directory echo "Downloading https://github.com/openshift/origin/releases/download/${oc_version}/${tarball} into ${output_dir}/" >&2 - curl -sL https://github.com/openshift/origin/releases/download/${oc_version}/"${tarball}" | tar -C "${output_dir}" -xz + curl -sL https://github.com/openshift/origin/releases/download/"${oc_version}"/"${tarball}" | tar -C "${output_dir}" -xz mv -f "${output_dir}"/"${tarball%.tar.gz}"/* "${output_dir}/" rmdir "${output_dir}"/"${tarball%.tar.gz}" @@ -569,17 +586,18 @@ function ct_os_test_s2i_app_func() { local service_name="${image_name_no_namespace}-testing" local image_tagged="${image_name_no_namespace}:${VERSION}" - if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then + if [ $# -lt 4 ] || [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] || [ -z "${4}" ]; then echo "ERROR: ct_os_test_s2i_app_func() requires at least 4 arguments that cannot be emtpy." >&2 return 1 fi + # shellcheck disable=SC2119 ct_os_new_project # Create a specific imagestream tag for the image so that oc cannot use anything else if [ "${CT_SKIP_UPLOAD_IMAGE:-false}" == 'true' ] ; then if [ -n "${import_image}" ] ; then echo "Importing image ${import_image} as ${image_name}:${VERSION}" - oc import-image ${image_name}:${VERSION} --from ${import_image} --confirm + oc import-image "${image_name}":"${VERSION}" --from "${import_image}" --confirm else echo "Uploading and importing image skipped." fi @@ -597,6 +615,7 @@ function ct_os_test_s2i_app_func() { app_param=$(ct_obtain_input "${app}") fi + # shellcheck disable=SC2086 ct_os_deploy_s2i_image "${image_tagged}" "${app_param}" \ --context-dir="${context_dir}" \ --name "${service_name}" \ @@ -611,8 +630,12 @@ function ct_os_test_s2i_app_func() { ct_os_wait_pod_ready "${service_name}" 300 - local ip=$(ct_os_get_service_ip "${service_name}") - local check_command_exp=$(echo "$check_command" | sed -e "s//$ip/g") + local ip + local check_command_exp + + ip=$(ct_os_get_service_ip "${service_name}") + # shellcheck disable=SC2001 + check_command_exp=$(echo "$check_command" | sed -e "s//$ip/g") echo " Checking APP using $check_command_exp ..." local result=0 @@ -624,6 +647,7 @@ function ct_os_test_s2i_app_func() { echo " Check failed." fi + # shellcheck disable=SC2119 ct_os_delete_project return $result } @@ -652,7 +676,7 @@ function ct_os_test_s2i_app() { local oc_args=${8:-} local import_image=${9:-} - if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then + if [ $# -lt 4 ] || [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] || [ -z "${4}" ]; then echo "ERROR: ct_os_test_s2i_app() requires at least 4 arguments that cannot be emtpy." >&2 return 1 fi @@ -689,7 +713,7 @@ function ct_os_test_template_app_func() { local other_images=${6:-} local import_image=${7:-} - if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then + if [ $# -lt 4 ] || [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] || [ -z "${4}" ]; then echo "ERROR: ct_os_test_template_app_func() requires at least 4 arguments that cannot be emtpy." >&2 return 1 fi @@ -697,13 +721,14 @@ function ct_os_test_template_app_func() { local service_name="${name_in_template}-testing" local image_tagged="${name_in_template}:${VERSION}" + # shellcheck disable=SC2119 ct_os_new_project # Create a specific imagestream tag for the image so that oc cannot use anything else if [ "${CT_SKIP_UPLOAD_IMAGE:-false}" == 'true' ] ; then if [ -n "${import_image}" ] ; then echo "Importing image ${import_image} as ${image_name}:${VERSION}" - oc import-image ${image_name}:${VERSION} --from ${import_image} --confirm + oc import-image "${image_name}":"${VERSION}" --from "${import_image}" --confirm else echo "Uploading and importing image skipped." fi @@ -714,7 +739,7 @@ function ct_os_test_template_app_func() { ct_os_upload_image "${image_name}" "${image_tagged}" # upload also other images, that template might need (list of pairs in the format | - local images_tags_a + local image_tag_a local i_t for i_t in ${other_images} ; do echo "${i_t}" @@ -727,17 +752,26 @@ function ct_os_test_template_app_func() { # get the template file from remote or local location; if not found, it is # considered an internal template name, like 'mysql', so use the name # explicitly - local local_template=$(ct_obtain_input "${template}" 2>/dev/null || echo "--template=${template}") - local namespace=${CT_NAMESPACE:-$(oc project -q)} - oc new-app ${local_template} \ + local local_template + local namespace + + namespace=${CT_NAMESPACE:-$(oc project -q)} + + local_template=$(ct_obtain_input "${template}" 2>/dev/null || echo "--template=${template}") + # shellcheck disable=SC2086 + oc new-app "${local_template}" \ --name "${name_in_template}" \ -p NAMESPACE="${namespace}" \ ${oc_args} ct_os_wait_pod_ready "${service_name}" 300 - local ip=$(ct_os_get_service_ip "${service_name}") - local check_command_exp=$(echo "$check_command" | sed -e "s//$ip/g") + local ip + local check_command_exp + + ip=$(ct_os_get_service_ip "${service_name}") + # shellcheck disable=SC2001 + check_command_exp=$(echo "$check_command" | sed -e "s//$ip/g") echo " Checking APP using $check_command_exp ..." local result=0 @@ -749,6 +783,7 @@ function ct_os_test_template_app_func() { echo " Check failed." fi + # shellcheck disable=SC2119 ct_os_delete_project return $result } @@ -783,7 +818,7 @@ function ct_os_test_template_app() { local other_images=${9:-} local import_image=${10:-} - if [ $# -lt 4 ] || [ -z "${1}" -o -z "${2}" -o -z "${3}" -o -z "${4}" ]; then + if [ $# -lt 4 ] || [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] || [ -z "${4}" ]; then echo "ERROR: ct_os_test_template_app() requires at least 4 arguments that cannot be emtpy." >&2 return 1 fi @@ -815,6 +850,7 @@ ct_os_test_image_update() { local ip="" check_command_exp="" echo "Running image update test for: $image_name" + # shellcheck disable=SC2119 ct_os_new_project # Get current image from repository and create an imagestream @@ -839,6 +875,7 @@ ct_os_test_image_update() { check_command_exp=${check_function///$ip} ct_assert_cmd_success "$check_command_exp" + # shellcheck disable=SC2119 ct_os_delete_project } @@ -870,7 +907,9 @@ EOF SECONDS=0 echo -n "Waiting for command POD ." while [ $SECONDS -lt 180 ] ; do + # shellcheck disable=SC2016 sout="$(ct_os_cmd_image_run 'echo $((11*11))' 2>/dev/null)" + # shellcheck disable=SC2015 grep -q '^121$' <<< "$sout" && echo "DONE" && return 0 || : sleep 3 echo -n "." @@ -913,31 +952,32 @@ ct_os_test_response_internal() { local result=1 local status local response_code - local response_file=$(mktemp /tmp/ct_test_response_XXXXXX) + local response_file local util_image_name='python:3.6' + response_file=$(mktemp /tmp/ct_test_response_XXXXXX) ct_os_deploy_cmd_image "${util_image_name}" - while [ ${attempt} -le ${max_attempts} ]; do - ct_os_cmd_image_run "curl --connect-timeout 10 -s -w '%{http_code}' '${url}'" >${response_file} && status=0 || status=1 - if [ ${status} -eq 0 ]; then - response_code=$(cat ${response_file} | tail -c 3) + while [ "${attempt}" -le "${max_attempts}" ]; do + ct_os_cmd_image_run "curl --connect-timeout 10 -s -w '%{http_code}' '${url}'" >"${response_file}" && status=0 || status=1 + if [ "${status}" -eq 0 ]; then + response_code=$(tail -c 3 "${response_file}") if [ "${response_code}" -eq "${expected_code}" ]; then result=0 fi - cat ${response_file} | grep -qP -e "${body_regexp}" || result=1; + grep -qP -e "${body_regexp}" "${response_file}" || result=1; # Some services return 40x code until they are ready, so let's give them # some chance and not end with failure right away # Do not wait if we already have expected outcome though - if [ ${result} -eq 0 -o ${attempt} -gt ${ignore_error_attempts} -o ${attempt} -eq ${max_attempts} ] ; then + if [ "${result}" -eq 0 ] || [ "${attempt}" -gt "${ignore_error_attempts}" ] || [ "${attempt}" -eq "${max_attempts}" ] ; then break fi fi - attempt=$(( ${attempt} + 1 )) - sleep ${sleep_time} + attempt=$(( attempt + 1 )) + sleep "${sleep_time}" done - rm -f ${response_file} - return ${result} + rm -f "${response_file}" + return "${result}" } # ct_os_get_image_from_pod @@ -946,7 +986,8 @@ ct_os_test_response_internal() { # Argument: pod_prefix - prefix or full name of the pod to get image from ct_os_get_image_from_pod() { local pod_prefix=$1 ; shift - local pod_name=$(ct_os_get_pod_name $pod_prefix) + local pod_name + pod_name=$(ct_os_get_pod_name "$pod_prefix") oc get "po/${pod_name}" -o yaml | sed -ne 's/^\s*image:\s*\(.*\)\s*$/\1/ p' | head -1 } @@ -972,10 +1013,14 @@ function ct_os_check_cmd_internal() { local output local ret - local ip=$(ct_os_get_service_ip "${service_name}") - local check_command_exp=$(echo "$check_command" | sed -e "s//$ip/g") + local ip + local check_command_exp - ct_os_deploy_cmd_image $(ct_os_get_image_from_pod "${util_image_name##*/}" | head -n 1) + ip=$(ct_os_get_service_ip "${service_name}") + # shellcheck disable=SC2001 + check_command_exp=$(echo "$check_command" | sed -e "s//$ip/g") + + ct_os_deploy_cmd_image "$(ct_os_get_image_from_pod "${util_image_name##*/}" | head -n 1)" SECONDS=0 echo -n "Waiting for ${service_name} service becoming ready ..." @@ -988,7 +1033,7 @@ function ct_os_check_cmd_internal() { return 0 fi echo -n "." - [ ${SECONDS} -gt ${timeout} ] && break + [ ${SECONDS} -gt "${timeout}" ] && break sleep 3 done echo " FAIL" diff --git a/test/test-lib.sh b/test/test-lib.sh index c996317..674eafd 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -1,3 +1,4 @@ +# shellcheck shell=bash # # Test a container image. # @@ -24,20 +25,21 @@ EXPECTED_EXIT_CODE=0 # Uses: $CID_FILE_DIR - path to directory containing cid_files # Uses: $EXPECTED_EXIT_CODE - expected container exit code function ct_cleanup() { - for cid_file in $CID_FILE_DIR/* ; do - local container=$(cat $cid_file) + for cid_file in "$CID_FILE_DIR"/* ; do + local container + container=$(cat "$cid_file") : "Stopping and removing container $container..." - docker stop $container - exit_status=$(docker inspect -f '{{.State.ExitCode}}' $container) + docker stop "$container" + exit_status=$(docker inspect -f '{{.State.ExitCode}}' "$container") if [ "$exit_status" != "$EXPECTED_EXIT_CODE" ]; then : "Dumping logs for $container" - docker logs $container + docker logs "$container" fi - docker rm -v $container - rm $cid_file + docker rm -v "$container" + rm "$cid_file" done - rmdir $CID_FILE_DIR + rmdir "$CID_FILE_DIR" : "Done." } @@ -55,7 +57,7 @@ function ct_enable_cleanup() { # Uses: $CID_FILE_DIR - path to directory containing cid_files function ct_get_cid() { local name="$1" ; shift || return 1 - echo $(cat "$CID_FILE_DIR/$name") + cat "$CID_FILE_DIR/$name" } # ct_get_cip [id] @@ -64,7 +66,7 @@ function ct_get_cid() { # Argument: id - container id function ct_get_cip() { local id="$1" ; shift - docker inspect --format='{{.NetworkSettings.IPAddress}}' $(ct_get_cid "$id") + docker inspect --format='{{.NetworkSettings.IPAddress}}' "$(ct_get_cid "$id")" } # ct_wait_for_cid [cid_file] @@ -79,9 +81,9 @@ function ct_wait_for_cid() { local attempt=1 local result=1 while [ $attempt -le $max_attempts ]; do - [ -f $cid_file ] && [ -s $cid_file ] && return 0 + [ -f "$cid_file" ] && [ -s "$cid_file" ] && return 0 : "Waiting for container start..." - attempt=$(( $attempt + 1 )) + attempt=$(( attempt + 1 )) sleep $sleep_time done return 1 @@ -100,30 +102,32 @@ function ct_assert_container_creation_fails() { local cid_file=assert set +e local old_container_args="${CONTAINER_ARGS-}" + # we really work with CONTAINER_ARGS as with a string + # shellcheck disable=SC2124 CONTAINER_ARGS="$@" - ct_create_container $cid_file - if [ $? -eq 0 ]; then - local cid=$(ct_get_cid $cid_file) + if ct_create_container "$cid_file" ; then + local cid + cid=$(ct_get_cid "$cid_file") - while [ "$(docker inspect -f '{{.State.Running}}' $cid)" == "true" ] ; do + while [ "$(docker inspect -f '{{.State.Running}}' "$cid")" == "true" ] ; do sleep 2 - attempt=$(( $attempt + 1 )) - if [ $attempt -gt $max_attempts ]; then - docker stop $cid + attempt=$(( attempt + 1 )) + if [ "$attempt" -gt "$max_attempts" ]; then + docker stop "$cid" ret=1 break fi done - exit_status=$(docker inspect -f '{{.State.ExitCode}}' $cid) + exit_status=$(docker inspect -f '{{.State.ExitCode}}' "$cid") if [ "$exit_status" == "0" ]; then ret=1 fi - docker rm -v $cid - rm $CID_FILE_DIR/$cid_file + docker rm -v "$cid" + rm "$CID_FILE_DIR/$cid_file" fi - [ ! -z $old_container_args ] && CONTAINER_ARGS="$old_container_args" + [ -n "$old_container_args" ] && CONTAINER_ARGS="$old_container_args" set -e - return $ret + return "$ret" } # ct_create_container [name, command] @@ -139,9 +143,10 @@ function ct_assert_container_creation_fails() { function ct_create_container() { local cid_file="$CID_FILE_DIR/$1" ; shift # create container with a cidfile in a directory for cleanup - docker run --cidfile="$cid_file" -d ${CONTAINER_ARGS:-} $IMAGE_NAME "$@" - ct_wait_for_cid $cid_file || return 1 - : "Created container $(cat $cid_file)" + # shellcheck disable=SC2086 + docker run --cidfile="$cid_file" -d ${CONTAINER_ARGS:-} "$IMAGE_NAME" "$@" + ct_wait_for_cid "$cid_file" || return 1 + : "Created container $(cat "$cid_file")" } # ct_scl_usage_old [name, command, expected] @@ -159,19 +164,19 @@ function ct_scl_usage_old() { local expected="$3" local out="" : " Testing the image SCL enable" - out=$(docker run --rm ${IMAGE_NAME} /bin/bash -c "${command}") + out=$(docker run --rm "${IMAGE_NAME}" /bin/bash -c "${command}") if ! echo "${out}" | grep -q "${expected}"; then - echo "ERROR[/bin/bash -c "${command}"] Expected '${expected}', got '${out}'" >&2 + echo "ERROR[/bin/bash -c \"${command}\"] Expected '${expected}', got '${out}'" >&2 return 1 fi - out=$(docker exec $(ct_get_cid $name) /bin/bash -c "${command}" 2>&1) + out=$(docker exec "$(ct_get_cid "$name")" /bin/bash -c "${command}" 2>&1) if ! echo "${out}" | grep -q "${expected}"; then - echo "ERROR[exec /bin/bash -c "${command}"] Expected '${expected}', got '${out}'" >&2 + echo "ERROR[exec /bin/bash -c \"${command}\"] Expected '${expected}', got '${out}'" >&2 return 1 fi - out=$(docker exec $(ct_get_cid $name) /bin/sh -ic "${command}" 2>&1) + out=$(docker exec "$(ct_get_cid "$name")" /bin/sh -ic "${command}" 2>&1) if ! echo "${out}" | grep -q "${expected}"; then - echo "ERROR[exec /bin/sh -ic "${command}"] Expected '${expected}', got '${out}'" >&2 + echo "ERROR[exec /bin/sh -ic \"${command}\"] Expected '${expected}', got '${out}'" >&2 return 1 fi } @@ -183,22 +188,24 @@ function ct_scl_usage_old() { # Argument: strings - strings expected to appear in the documentation # Uses: $IMAGE_NAME - name of the image being tested function ct_doc_content_old() { - local tmpdir=$(mktemp -d) + local tmpdir + tmpdir=$(mktemp -d) local f : " Testing documentation in the container image" # Extract the help files from the container + # shellcheck disable=SC2043 for f in help.1 ; do - docker run --rm ${IMAGE_NAME} /bin/bash -c "cat /${f}" >${tmpdir}/$(basename ${f}) + docker run --rm "${IMAGE_NAME}" /bin/bash -c "cat /${f}" >"${tmpdir}/$(basename "${f}")" # Check whether the files contain some important information - for term in $@ ; do - if ! cat ${tmpdir}/$(basename ${f}) | grep -F -q -e "${term}" ; then + for term in "$@" ; do + if ! grep -F -q -e "${term}" "${tmpdir}/$(basename "${f}")" ; then echo "ERROR: File /${f} does not include '${term}'." >&2 return 1 fi done # Check whether the files use the correct format for term in TH PP SH ; do - if ! grep -q "^\.${term}" ${tmpdir}/help.1 ; then + if ! grep -q "^\.${term}" "${tmpdir}/help.1" ; then echo "ERROR: /help.1 is probably not in troff or groff format, since '${term}' is missing." >&2 return 1 fi @@ -222,7 +229,7 @@ function ct_mount_ca_file() # mount CA file only if NPM_REGISTRY variable is present. local mount_parameter="" if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then - mount_parameter="-v $(full_ca_file_path):$(full_ca_file_path):ro,Z" + mount_parameter="-v $(full_ca_file_path):$(full_ca_file_path):Z" fi echo "$mount_parameter" } @@ -244,21 +251,37 @@ function ct_build_s2i_npm_variables() # -------------------- # Checks existance of the npm tool and runs it. function ct_npm_works() { - local tmpdir=$(mktemp -d) + local tmpdir + tmpdir=$(mktemp -d) : " Testing npm in the container image" - docker run --rm ${IMAGE_NAME} /bin/bash -c "npm --version" >${tmpdir}/version - if [ $? -ne 0 ] ; then + cid_file="${tmpdir}/cid" + if ! docker run --rm "${IMAGE_NAME}" /bin/bash -c "npm --version" >"${tmpdir}/version" ; then echo "ERROR: 'npm --version' does not work inside the image ${IMAGE_NAME}." >&2 return 1 fi - docker run $(ct_mount_ca_file) --rm ${IMAGE_NAME} /bin/bash -c "npm install jquery && test -f node_modules/jquery/src/jquery.js" + # shellcheck disable=SC2046 + docker run -d $(ct_mount_ca_file) --rm --cidfile="$cid_file" "${IMAGE_NAME}-testapp" - if [ $? -ne 0 ] ; then + # Wait for the container to write it's CID file + ct_wait_for_cid "$cid_file" || return 1 + + if ! docker exec "$(cat "$cid_file")" /bin/bash -c "npm --verbose install jquery && test -f node_modules/jquery/src/jquery.js" >"${tmpdir}/jquery" 2>&1 ; then echo "ERROR: npm could not install jquery inside the image ${IMAGE_NAME}." >&2 return 1 fi + if [ -n "$NPM_REGISTRY" ] && [ -f "$(full_ca_file_path)" ]; then + if ! grep -qo "$NPM_REGISTRY" "${tmpdir}/jquery"; then + echo "ERROR: Internal repository is NOT set. Even it is requested." + return 1 + fi + fi + + if [ -f "$cid_file" ]; then + docker stop "$(cat "$cid_file")" + rm "$cid_file" + fi : " Success!" } @@ -301,8 +324,10 @@ ct_path_foreach () function ct_run_test_list() { for test_case in $TEST_LIST; do : "Running test $test_case" - [ -f test/$test_case ] && source test/$test_case - [ -f ../test/$test_case ] && source ../test/$test_case + # shellcheck source=/dev/null + [ -f "test/$test_case" ] && source "test/$test_case" + # shellcheck source=/dev/null + [ -f "../test/$test_case" ] && source "../test/$test_case" $test_case done; } @@ -318,9 +343,9 @@ function ct_run_test_list() { ct_gen_self_signed_cert_pem() { local output_dir=$1 ; shift local base_name=$1 ; shift - mkdir -p ${output_dir} - openssl req -newkey rsa:2048 -nodes -keyout ${output_dir}/${base_name}-key.pem -subj '/C=GB/ST=Berkshire/L=Newbury/O=My Server Company' > ${base_name}-req.pem - openssl req -new -x509 -nodes -key ${output_dir}/${base_name}-key.pem -batch > ${output_dir}/${base_name}-cert-selfsigned.pem + mkdir -p "${output_dir}" + openssl req -newkey rsa:2048 -nodes -keyout "${output_dir}"/"${base_name}"-key.pem -subj '/C=GB/ST=Berkshire/L=Newbury/O=My Server Company' > "${base_name}"-req.pem + openssl req -new -x509 -nodes -key "${output_dir}"/"${base_name}"-key.pem -batch > "${output_dir}"/"${base_name}"-cert-selfsigned.pem } # ct_obtain_input FILE|DIR|URL @@ -336,7 +361,8 @@ function ct_obtain_input() { # Try to use same extension for the temporary file if possible [[ "${extension}" =~ ^[a-z0-9]*$ ]] && extension=".${extension}" || extension="" - local output=$(mktemp "/var/tmp/test-input-XXXXXX$extension") + local output + output=$(mktemp "/var/tmp/test-input-XXXXXX$extension") if [ -f "${input}" ] ; then cp -f "${input}" "${output}" elif [ -d "${input}" ] ; then @@ -373,27 +399,28 @@ ct_test_response() { local result=1 local status local response_code - local response_file=$(mktemp /tmp/ct_test_response_XXXXXX) - while [ ${attempt} -le ${max_attempts} ]; do - curl --connect-timeout 10 -s -w '%{http_code}' "${url}" >${response_file} && status=0 || status=1 - if [ ${status} -eq 0 ]; then - response_code=$(cat ${response_file} | tail -c 3) + local response_file + response_file=$(mktemp /tmp/ct_test_response_XXXXXX) + while [ "${attempt}" -le "${max_attempts}" ]; do + curl --connect-timeout 10 -s -w '%{http_code}' "${url}" >"${response_file}" && status=0 || status=1 + if [ "${status}" -eq 0 ]; then + response_code=$(tail -c 3 "${response_file}") if [ "${response_code}" -eq "${expected_code}" ]; then result=0 fi - cat ${response_file} | grep -qP -e "${body_regexp}" || result=1; + grep -qP -e "${body_regexp}" "${response_file}" || result=1; # Some services return 40x code until they are ready, so let's give them # some chance and not end with failure right away # Do not wait if we already have expected outcome though - if [ ${result} -eq 0 -o ${attempt} -gt ${ignore_error_attempts} -o ${attempt} -eq ${max_attempts} ] ; then + if [ "${result}" -eq 0 ] || [ "${attempt}" -gt "${ignore_error_attempts}" ] || [ "${attempt}" -eq "${max_attempts}" ] ; then break fi fi - attempt=$(( ${attempt} + 1 )) - sleep ${sleep_time} + attempt=$(( attempt + 1 )) + sleep "${sleep_time}" done - rm -f ${response_file} - return ${result} + rm -f "${response_file}" + return "${result}" } # ct_registry_from_os OS @@ -404,7 +431,7 @@ ct_registry_from_os() { local registry="" case $1 in rhel*) - registry=registry.access.redhat.com + registry=registry.redhat.io ;; *) registry=docker.io @@ -413,6 +440,32 @@ ct_registry_from_os() { echo "$registry" } + # ct_get_public_image_name OS BASE_IMAGE_NAME VERSION +# ---------------- +# Transform the arguments into public image name +# Argument: OS - string containing the os version +# Argument: BASE_IMAGE_NAME - string containing the base name of the image as defined in the Makefile +# Argument: VERSION - string containing the version of the image as defined in the Makefile +ct_get_public_image_name() { + local os=$1; shift + local base_image_name=$1; shift + local version=$1; shift + + local public_image_name + local registry + + registry=$(ct_registry_from_os "$os") + if [ "x$os" == "xrhel7" ]; then + public_image_name=$registry/rhscl/$base_image_name-${version//./}-rhel7 + elif [ "x$os" == "xrhel8" ]; then + public_image_name=$registry/rhel8/$base_image_name-${version//./} + elif [ "x$os" == "xcentos7" ]; then + public_image_name=$registry/centos/$base_image_name-${version//./}-centos7 + fi + + echo "$public_image_name" +} + # ct_assert_cmd_success CMD # ---------------- # Evaluates [cmd] and fails if it does not succeed. @@ -555,9 +608,9 @@ EOF # Add in artifacts if doing an incremental build if $incremental; then - echo "RUN mkdir /tmp/artifacts" >>"$df_name" - echo "ADD artifacts.tar /tmp/artifacts" >>"$df_name" - echo "RUN chown -R $user_id:0 /tmp/artifacts" >>"$df_name" + { echo "RUN mkdir /tmp/artifacts" + echo "ADD artifacts.tar /tmp/artifacts" + echo "RUN chown -R $user_id:0 /tmp/artifacts" ; } >>"$df_name" fi echo "USER $user_id" >>"$df_name" @@ -578,8 +631,23 @@ EOF mount_options=$(echo "$s2i_args" | grep -o -e '\(-v\)[[:space:]]\.*\S*' || true) # Run the build and tag the result + # shellcheck disable=SC2086 docker build $mount_options -f "$df_name" --no-cache=true -t "$dst_image" . ) } +# ct_check_image_availability PUBLIC_IMAGE_NAME +# ---------------------------- +# Pull an image from the public repositories to see if the image is already available. +# Argument: PUBLIC_IMAGE_NAME - string containing the public name of the image to pull +ct_check_image_availability() { + local public_image_name=$1; + + # Try pulling the image to see if it is accessible + if ! docker pull "$public_image_name" &>/dev/null; then + echo "$public_image_name could not be downloaded via 'docker'" + return 1 + fi +} + # vim: set tabstop=2:shiftwidth=2:expandtab: