# shellcheck shell=bash load 'libs/bats-support/load' load 'libs/bats-assert/load' # Helpful globals readonly TEMP_BASE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/toolbx" readonly TEMP_STORAGE_DIR="${TEMP_BASE_DIR}/system-test-storage" readonly IMAGE_CACHE_DIR="${BATS_SUITE_TMPDIR}/image-cache" readonly ROOTLESS_PODMAN_STORE_DIR="${TEMP_STORAGE_DIR}/storage" readonly ROOTLESS_PODMAN_RUNROOT_DIR="${TEMP_STORAGE_DIR}/runroot" readonly PODMAN_STORE_CONFIG_FILE="${TEMP_STORAGE_DIR}/storage.conf" readonly DOCKER_REG_ROOT="${TEMP_STORAGE_DIR}/docker-registry-root" readonly DOCKER_REG_CERTS_DIR="${BATS_SUITE_TMPDIR}/certs" readonly DOCKER_REG_AUTH_DIR="${BATS_SUITE_TMPDIR}/auth" readonly DOCKER_REG_URI="localhost:50000" readonly DOCKER_REG_NAME="docker-registry" # Podman and Toolbx commands to run readonly TOOLBX="${TOOLBX:-$(command -v toolbox)}" readonly TOOLBX_TEST_SYSTEM_TAGS_ALL="arch-fedora,commands-options,custom-image,runtime-environment,ubuntu" readonly TOOLBX_TEST_SYSTEM_TAGS="${TOOLBX_TEST_SYSTEM_TAGS:-$TOOLBX_TEST_SYSTEM_TAGS_ALL}" # Images declare -Ag IMAGES=([arch]="quay.io/toolbx/arch-toolbox" \ [busybox]="quay.io/toolbox_tests/busybox" \ [docker-reg]="quay.io/toolbox_tests/registry" \ [fedora]="registry.fedoraproject.org/fedora-toolbox" \ [rhel]="registry.access.redhat.com/ubi8/toolbox" \ [ubuntu]="quay.io/toolbx/ubuntu-toolbox") function cleanup_all() { podman rm --all --force >/dev/null podman rmi --all --force >/dev/null } function _setup_environment() { _setup_containers_storage check_xdg_runtime_dir } function _setup_containers_storage() { mkdir -p "${TEMP_STORAGE_DIR}" # Set up a storage config file for PODMAN echo -e "[storage]\n driver = \"overlay\"\n rootless_storage_path = \"${ROOTLESS_PODMAN_STORE_DIR}\"\n runroot = \"${ROOTLESS_PODMAN_RUNROOT_DIR}\"\n" > "${PODMAN_STORE_CONFIG_FILE}" export CONTAINERS_STORAGE_CONF="${PODMAN_STORE_CONFIG_FILE}" } function _clean_temporary_storage() { podman system reset --force >/dev/null rm --force --recursive "${ROOTLESS_PODMAN_STORE_DIR}" rm --force --recursive "${ROOTLESS_PODMAN_RUNROOT_DIR}" rm --force --recursive "${PODMAN_STORE_CONFIG_FILE}" rm --force --recursive "${TEMP_STORAGE_DIR}" } # Pulls an image using Podman and saves it to a image dir using Skopeo # # Parameters # ========== # - distro - os-release field ID (e.g., fedora, rhel) # - version - os-release field VERSION_ID (e.g., 33, 34, 8.4) # # Only use during test suite setup for caching all images to be used throughout # tests. function _pull_and_cache_distro_image() { local num_of_retries=5 local timeout=10 local cached=false local distro local version local image local image_archive distro="$1" version="$2" if [ -z "${IMAGES[$distro]+x}" ]; then fail "Requested distro (${distro}) does not have a matching image" return 1 fi image="${IMAGES[$distro]}" image_archive="${distro}-toolbox" if [[ $# -eq 2 ]]; then image="${image}:${version}" image_archive="${image_archive}-${version}" fi if [[ -d "${IMAGE_CACHE_DIR}/${image_archive}" ]] ; then return 0 fi if [ ! -d "${IMAGE_CACHE_DIR}" ]; then run mkdir -p "${IMAGE_CACHE_DIR}" assert_success fi local error_message local -i j local -i ret_val for ((j = 0; j < num_of_retries; j++)); do error_message="$( (skopeo copy --dest-compress \ "docker://${image}" \ "dir:${IMAGE_CACHE_DIR}/${image_archive}" >/dev/null) 2>&1)" ret_val="$?" if [ "$ret_val" -eq 0 ]; then cached=true break fi sleep "$timeout" done if ! $cached; then echo "Failed to cache image ${image} to ${IMAGE_CACHE_DIR}/${image_archive}" >&2 [ "$error_message" != "" ] && echo "$error_message" >&2 return "$ret_val" fi cleanup_all ret_val="$?" return "$ret_val" } # Removes the folder with cached images function _clean_cached_images() { rm --force --recursive "${IMAGE_CACHE_DIR}" } # Prepares a locally hosted image registry # # The registry is set up with Podman set to an alternative root. It won't # affect other containers or images in the default root. # # Instructions taken from https://docs.docker.com/registry/deploying/ function _setup_docker_registry() { # Create certificates for HTTPS # This is needed so that Podman does not have to be configured to work with # HTTP-only registries run mkdir -p "${DOCKER_REG_CERTS_DIR}" assert_success run openssl req \ -newkey rsa:4096 \ -nodes -sha256 \ -keyout "${DOCKER_REG_CERTS_DIR}"/domain.key \ -addext "subjectAltName= DNS:localhost" \ -x509 \ -days 365 \ -subj '/' \ -out "${DOCKER_REG_CERTS_DIR}"/domain.crt assert_success # Add certificate to Podman's trusted certificates (rootless) run mkdir -p "$HOME"/.config/containers/certs.d/"${DOCKER_REG_URI}" assert_success run cp "${DOCKER_REG_CERTS_DIR}"/domain.crt "$HOME"/.config/containers/certs.d/"${DOCKER_REG_URI}"/domain.crt assert_success # Create a registry user # username: user; password: user run mkdir -p "${DOCKER_REG_AUTH_DIR}" assert_success run htpasswd -Bbc "${DOCKER_REG_AUTH_DIR}"/htpasswd user user assert_success # Create separate Podman root run mkdir -p "${DOCKER_REG_ROOT}" assert_success # Pull Docker registry image run podman --root "${DOCKER_REG_ROOT}" pull "${IMAGES[docker-reg]}" assert_success # Create a Docker registry run podman --root "${DOCKER_REG_ROOT}" run \ --detach \ --env REGISTRY_AUTH=htpasswd \ --env REGISTRY_AUTH_HTPASSWD_PATH="/auth/htpasswd" \ --env REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ --env REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ --env REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ --name "${DOCKER_REG_NAME}" \ --privileged \ --publish 50000:5000 \ --rm \ --volume "${DOCKER_REG_AUTH_DIR}":/auth \ --volume "${DOCKER_REG_CERTS_DIR}":/certs \ "${IMAGES[docker-reg]}" assert_success run podman login \ --authfile "${TEMP_BASE_DIR}/authfile.json" \ --username user \ --password user \ "${DOCKER_REG_URI}" assert_success # Add fedora-toolbox:34 image to the registry run skopeo copy --dest-authfile "${TEMP_BASE_DIR}/authfile.json" \ dir:"${IMAGE_CACHE_DIR}"/fedora-toolbox-34 \ docker://"${DOCKER_REG_URI}"/fedora-toolbox:34 assert_success run rm "${TEMP_BASE_DIR}/authfile.json" assert_success } # Stop, removes and cleans after a locally hosted Docker registry function _clean_docker_registry() { # Stop Docker registry container if podman --root "$DOCKER_REG_ROOT" container exists "$DOCKER_REG_NAME"; then podman --root "${DOCKER_REG_ROOT}" stop --time 0 "${DOCKER_REG_NAME}" fi # Clean up Podman's registry root state podman --root "${DOCKER_REG_ROOT}" rm --all --force podman --root "${DOCKER_REG_ROOT}" rmi --all --force # Remove Docker registry dir rm --force --recursive "${DOCKER_REG_ROOT}" # Remove dir with created registry certificates rm --force --recursive "$HOME"/.config/containers/certs.d/"${DOCKER_REG_URI}" } function build_image_without_name() { echo -e "FROM scratch\n\nLABEL com.github.containers.toolbox=\"true\"" > "$BATS_TEST_TMPDIR"/Containerfile run podman build "$BATS_TEST_TMPDIR" assert_success assert_line --index 0 --partial "FROM scratch" assert_line --index 1 --partial "LABEL com.github.containers.toolbox=\"true\"" assert_line --index 2 --partial "COMMIT" assert_line --index 3 --regexp "^--> [a-f0-9]{6,64}$" # shellcheck disable=SC2154 last=$((${#lines[@]}-1)) assert_line --index "$last" --regexp "^[a-f0-9]{64}$" rm -f "$BATS_TEST_TMPDIR"/Containerfile echo "${lines[$last]}" } function check_bats_version() { local required_version required_version="$1" if ! old_version=$(printf "%s\n%s\n" "$BATS_VERSION" "$required_version" | sort --version-sort | head --lines 1); then return 1 fi if [ "$required_version" = "$old_version" ]; then return 0 fi return 1 } function get_busybox_image() { local image image="${IMAGES[busybox]}" echo "$image" return 0 } function get_default_image() { local distro local image local release distro="$(get_system_id)" release="$(get_system_version)" image="${IMAGES[$distro]}:$release" echo "$image" return 0 } # Copies an image from local storage to Podman's image store # # Call before creating any container. Network failures are not nice. # # An image has to be cached first. See _pull_and_cache_distro_image() # # Parameters: # =========== # - distro - os-release field ID (e.g., fedora, rhel) # - version - os-release field VERSION_ID (e.g., 33, 34, 8.4) function pull_distro_image() { local distro local version local image local image_archive distro="$1" version="$2" if [ -z "${IMAGES[$distro]+x}" ]; then fail "Requested distro (${distro}) does not have a matching image" return 1 fi image="${IMAGES[$distro]}" image_archive="${distro}-toolbox" if [[ -n $version ]]; then image="${image}:${version}" image_archive="${image_archive}-${version}" fi # No need to copy if the image is already available in Podman if podman image exists "${image}"; then return 0 fi # https://github.com/containers/skopeo/issues/547 for the options for containers-storage run skopeo copy "dir:${IMAGE_CACHE_DIR}/${image_archive}" "containers-storage:[overlay@$ROOTLESS_PODMAN_STORE_DIR+$ROOTLESS_PODMAN_STORE_DIR]${image}" # shellcheck disable=SC2154 if [ "$status" -ne 0 ]; then echo "Failed to load image ${image} from cache ${IMAGE_CACHE_DIR}/${image_archive}" assert_success fi return 0 } # Copies the system's default image to Podman's image store # # See pull_default_image() for more info. function pull_default_image() { pull_distro_image "$(get_system_id)" "$(get_system_version)" } function pull_default_image_and_copy() { pull_default_image local distro local version local image distro="$(get_system_id)" version="$(get_system_version)" image="${IMAGES[$distro]}:$version" # https://github.com/containers/skopeo/issues/547 for the options for containers-storage run skopeo copy \ "containers-storage:[overlay@$ROOTLESS_PODMAN_STORE_DIR+$ROOTLESS_PODMAN_STORE_DIR]$image" \ "containers-storage:[overlay@$ROOTLESS_PODMAN_STORE_DIR+$ROOTLESS_PODMAN_STORE_DIR]$image-copy" if [ "$status" -ne 0 ]; then echo "Failed to copy image $image to $image-copy" assert_success fi } # Creates a container with specific name, distro and version # # Pulling of an image is taken care of by the function # # Parameters: # =========== # - distro - os-release field ID (e.g., fedora, rhel) # - version - os-release field VERSION_ID (e.g., 33, 34, 8.4) # - container_name - name of the container function create_distro_container() { local distro local version local container_name distro="$1" version="$2" container_name="$3" pull_distro_image "${distro}" "${version}" "$TOOLBX" --assumeyes create --container "${container_name}" --distro "${distro}" --release "${version}" >/dev/null \ || fail "Toolbx couldn't create container '$container_name'" } # Creates a container with specific name matching the system # # Parameters: # =========== # - container_name - name of the container function create_container() { local container_name container_name="$1" create_distro_container "$(get_system_id)" "$(get_system_version)" "$container_name" } # Creates a default container function create_default_container() { pull_default_image "$TOOLBX" --assumeyes create >/dev/null \ || fail "Toolbx couldn't create default container" } function start_container() { local container_name container_name="$1" podman start "$container_name" >/dev/null \ || fail "Podman couldn't start the container '$container_name'" } # Checks if a Toolbx container started # # Parameters: # =========== # - container_name - name of the container # # Returns: # ======== # - 0 - container has not started # - 1 - container has started function container_started() { local container_name container_name="$1" local -i ret_val=1 start_container "$container_name" local -i j local num_of_retries=5 for ((j = 0; j < num_of_retries; j++)); do run --separate-stderr podman logs "$container_name" # shellcheck disable=SC2154 if [ "$status" -ne 0 ]; then fail "Failed to invoke 'podman logs'" ret_val="$status" break fi # Look for last line of the container startup log # shellcheck disable=SC2154 if echo "$output $stderr" | grep "Listening to file system and ticker events"; then ret_val=0 break fi sleep 1 done if [ "$ret_val" -ne 0 ]; then if [ "$j" -eq "$num_of_retries" ]; then fail "Failed to initialize container $container_name" fi [ "$output" != "" ] && echo "$output" [ "$stderr" != "" ] && echo "$stderr" >&2 fi return "$ret_val" } function stop_container() { local container_name container_name="$1" # Make sure the container is running before trying to stop it podman start "$container_name" >/dev/null \ || fail "Podman couldn't start the container '$container_name'" podman stop "$container_name" >/dev/null \ || fail "Podman couldn't stop the container '$container_name'" } # Returns the name of the latest created container function get_latest_container_name() { podman ps --latest --format "{{ .Names }}" } function list_images() { podman images --all --format "{{.ID}}" | wc --lines } function list_containers() { podman ps --all --quiet | wc --lines } # Returns the path to os-release function find_os_release() { if [[ -f "/etc/os-release" ]]; then echo "/etc/os-release" elif [[ -f "/usr/lib/os-release" ]]; then echo "/usr/lib/os-release" else echo "" fi } # Returns the content of field ID in os-release function get_system_id() ( local os_release os_release="$(find_os_release)" if [[ -z "$os_release" ]]; then echo "" return fi # shellcheck disable=SC1090 . "$os_release" echo "$ID" ) # Returns the content of field VERSION_ID in os-release function get_system_version() ( local os_release os_release="$(find_os_release)" if [[ -z "$os_release" ]]; then echo "" return fi # shellcheck disable=SC1090 . "$os_release" local system_version="$VERSION_ID" [ "$ID" = "arch" ] && system_version="latest" echo "$system_version" ) function is_fedora_rawhide() ( local os_release os_release="$(find_os_release)" [ -z "$os_release" ] && return 1 # shellcheck disable=SC1090 . "$os_release" [ "$ID" != "fedora" ] && return 1 [ "$REDHAT_BUGZILLA_PRODUCT_VERSION" != "rawhide" ] && return 1 return 0 ) # Set up the XDG_RUNTIME_DIR variable if not set function check_xdg_runtime_dir() { if [[ -z "${XDG_RUNTIME_DIR}" ]]; then export XDG_RUNTIME_DIR="/run/user/${UID}" fi }