Ctrl K
ComunidadGuías…

smoketest.bash

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash

if ! command -v yq >/dev/null 2>&1 ; then
    echo "No 'yq' found"
    exit 1
fi

DIR="$(dirname "$(readlink -f "$0")")"
export DIR

FILES=(
    "${DIR}/compose/db.yml"
)

USE_USERHOSTS=${USE_USERHOSTS:-true}
PULL_IMAGES=${PULL_IMAGES:-true}
KEEP_VOLUMES=${KEEP_VOLUMES:-false}
OPEN_TABS=${OPEN_TABS:-false}

PRECREATE_BUCKETS=${PRECREATE_BUCKETS:-metadata,archivedrecordings,archivedreports,eventtemplates,probes,threaddumps,heapdumps,heapdumpreports,logs}

LOG_LEVEL=0
CRYOSTAT_HTTP_HOST=${CRYOSTAT_HTTP_HOST:-cryostat}
CRYOSTAT_HTTP_PORT=${CRYOSTAT_HTTP_PORT:-8080}
USE_PROXY=${USE_PROXY:-true}
DEPLOY_GRAFANA=${DEPLOY_GRAFANA:-true}
DRY_RUN=${DRY_RUN:-false}
USE_TLS=${USE_TLS:-true}
SAMPLE_APPS_USE_TLS=${SAMPLE_APPS_USE_TLS:-true}
ENFORCE_AGENT_TLS=${ENFORCE_AGENT_TLS:-false}

display_usage() {
    echo "Usage:"
    column -t -s% <<< "
    %-h% print this Help text.
    %-O% Offline mode, do not attempt to pull container images that are already present on this machine.
    %-p% disable auth Proxy.
    %-s [none,$(ls -1 ${DIR}/compose/s3-*.yml | xargs basename -a | grep -v none | sort | cut -d/ -f2 | cut -d. -f1 | cut -d- -f2 | paste -sd ',' -)]% S3 implementation to spin up (default \"seaweed\").
    %-G% exclude Grafana dashboard and jfr-datasource from deployment.
    %-r [replicas]% configure a cryostat-reports sidecar instance(s). Optional argument is the number of replicas, which defaults to 1.
    %-t [all|comma-list]% include sample applications for Testing. Leave blank or use \"all\" to deploy everything, otherwise use a comma-separated list from: $(find ${DIR}/compose/sample_apps -type f -name '*.yml' -exec basename {} \; | cut -d. -f1 | grep -v https | sort | tr '\n' ',' | sed 's/,$//')
    %-A% disable TLS on sample applications' Agents.
    %-V% do not discard data storage Volumes on exit.
    %-X% deploy additional development aid tools.
    %-c [/path/to/binary]% use specified Container engine (default \"\$(command -v podman)\").
    %-b% open a Browser tab for each running service's first mapped port (ex. auth proxy login, database viewer).
    %-n% do Not apply configuration changes, instead emit the compose YAML that would have been used to stdout.
    %-k% disable TLS (inseKure) on the auth proxy.
    %-v% enable Verbose logging. Can be passed multiple times to increase verbosity.
    "
}

s3=seaweed
container_engine="$(command -v podman)"
while getopts "hs:prGtAOVXc:bnkv" opt; do
    case $opt in
        h)
            display_usage
            exit 0
            ;;
        p)
            USE_PROXY=false
            ;;
        s)
            s3="${OPTARG}"
            ;;
        G)
            DEPLOY_GRAFANA=false
            ;;
        t)
            nextopt=${!OPTIND}
            deploy_all_samples=false
            if [[ -n $nextopt && $nextopt != -* ]] ; then
                OPTIND=$((OPTIND + 1))
                if [ "${nextopt}" = "all" ]; then
                    deploy_all_samples=true
                else
                    for sample in ${nextopt//,/ }; do
                        file="${DIR}/compose/sample_apps/${sample}.yml"
                        if [ ! -f "${file}" ]; then
                            echo "No such sample app file: ${file}"
                            exit 1
                        fi
                        FILES+=("${file}")
                    done
                fi
            else
                # there is no nextopt, ie `-t` was passed and there is no argument or the next argument is another flag
                deploy_all_samples=true
            fi
            if [ "${deploy_all_samples}" = "true" ]; then
                for sample in $(find "${DIR}/compose/sample_apps" -type f -exec basename {} \; | cut -d. -f1 | grep -v https); do
                    FILES+=("${DIR}/compose/sample_apps/${sample}.yml")
                done
            fi
            ;;
        A)
            SAMPLE_APPS_USE_TLS=false
            ENFORCE_AGENT_TLS=false
            ;;
        O)
            PULL_IMAGES=false
            ;;
        v)
            LOG_LEVEL=$((LOG_LEVEL+1))
            ;;
        V)
            KEEP_VOLUMES=true
            DATABASE_GENERATION=update
            ;;
        X)
            FILES+=("${DIR}/compose/db-viewer.yml")
            FILES+=("${DIR}/compose/prometheus.yml")
            ;;
        c)
            container_engine="${OPTARG}"
            ;;
        b)
            OPEN_TABS=true
            ;;
        r)
            FILES+=('./compose/reports.yml')
            nextopt=${!OPTIND}
            if [[ -n $nextopt && $nextopt != -* ]] ; then
                OPTIND=$((OPTIND + 1))
                REPORTS_REPLICAS="${nextopt}"
            else
                # there is no nextopt, ie `-r` was passed and there is no argument or the next argument is another flag
                REPORTS_REPLICAS=1
            fi
            ;;
        n)
            DRY_RUN=true
            ;;
        k)
            USE_TLS=false
            ;;
        *)
            display_usage
            exit 1
            ;;
    esac
done

if [ "${DEPLOY_GRAFANA}" = "true" ]; then
    FILES+=(
        "${DIR}/compose/cryostat-grafana.yml"
        "${DIR}/compose/jfr-datasource.yml"
    )
fi

if [ "${SAMPLE_APPS_USE_TLS}" = "true" ]; then
    for sample in "${FILES[@]}"; do
        if [[ ! "${sample}" =~ sample_apps ]]; then
            continue
        fi
        cfg="$(echo "${sample}" | sed -r 's/(.*).yml$/\1_https.yml/')"
        if [ -f "${cfg}" ]; then
            FILES+=("${cfg}")
        fi
    done
fi

CRYOSTAT_PROXY_PORT=8080
CRYOSTAT_PROXY_PROTOCOL=http
AUTH_PROXY_ALPHA_CONFIG_FILE=auth_proxy_alpha_config_http
if [ "${USE_PROXY}" = "true" ]; then
    FILES+=("${DIR}/compose/auth_proxy.yml")
    CRYOSTAT_HTTP_HOST=auth
    CRYOSTAT_HTTP_PORT=8181
    if [ "${USE_TLS}" = "true" ]; then
        FILES+=("${DIR}/compose/auth_proxy_https.yml")
        CRYOSTAT_PROXY_PORT=8443
        CRYOSTAT_PROXY_PROTOCOL=https
        AUTH_PROXY_ALPHA_CONFIG_FILE=auth_proxy_alpha_config_https
    fi
else
    FILES+=("${DIR}/compose/no_proxy.yml")
    if [ "${s3}" != "none" ]; then
        FILES+=("${DIR}/compose/s3_no_proxy.yml")
    fi
    if [ "${DEPLOY_GRAFANA}" = "true" ]; then
      FILES+=("${DIR}/compose/grafana_no_proxy.yml")
    fi
    GRAFANA_DASHBOARD_EXT_URL=http://grafana:3000/
fi
if [ $LOG_LEVEL = 0 ]; then
    CRYOSTAT_LOG_LEVEL=INFO
elif [ $LOG_LEVEL = 1 ]; then
    CRYOSTAT_LOG_LEVEL=DEBUG
elif [ $LOG_LEVEL = 2 ]; then
    CRYOSTAT_LOG_LEVEL=TRACE
else
    CRYOSTAT_LOG_LEVEL=ALL
fi
export CRYOSTAT_LOG_LEVEL
export CRYOSTAT_HTTP_HOST
export CRYOSTAT_HTTP_PORT
export GRAFANA_DASHBOARD_EXT_URL
export DATABASE_GENERATION
export CRYOSTAT_PROXY_PORT
export CRYOSTAT_PROXY_PROTOCOL
export REPORTS_REPLICAS

s3Manifest="${DIR}/compose/s3-${s3}.yml"
if [ ! -f "${s3Manifest}" ]; then
    echo "Unknown S3 selection: ${s3}"
    display_usage
    exit 2
fi
FILES+=("${s3Manifest}")
if [ "${s3}" = "none" ]; then
    STORAGE_PORT="4566"
else
    STORAGE_PORT="$(yq '.services.*.expose[0]' "${s3Manifest}" | grep -v null)"
fi
export STORAGE_PORT
export PRECREATE_BUCKETS

if [ "$(basename "${container_engine}")" = "podman" ]; then
    FILES+=("${DIR}/compose/cryostat.yml")
elif [ "$(basename "${container_engine}")" = "docker" ]; then
    FILES+=("${DIR}/compose/cryostat_docker.yml")
else
    echo "Unknown Container Engine selection: ${container_engine}"
    display_usage
    exit 2
fi

if [ -z "${S3_INSTANCE_ID}" ]; then
    S3_INSTANCE_ID="$(openssl rand -hex 5)"
fi
export S3_INSTANCE_ID

function bucketname() {
    echo "${1}-$(echo "${AWS_ACCESS_KEY_ID}" | head -c 6)-${S3_REGION}-${S3_INSTANCE_ID}" | tr -s '-' | head -c "${BUCKET_NAME_MAX_LENGTH:-63}"
}

if [ ! "${DRY_RUN}" = "true" ]; then
    set -xe
    CRYOSTAT_JAVA_OPTS="-XX:StartFlightRecording=filename=/tmp/onstart.jfr,name=onstart,settings=default,disk=true,maxage=5m -Dcom.sun.management.jmxremote.autodiscovery=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9091 -Dcom.sun.management.jmxremote.rmi.port=9091 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false"
    export CRYOSTAT_JAVA_OPTS
else
    CRYOSTAT_STORAGE_BUCKETS_ARCHIVES_NAME="$(bucketname 'archives')"
    export CRYOSTAT_STORAGE_BUCKETS_ARCHIVES_NAME
    CRYOSTAT_STORAGE_BUCKETS_ARCHIVED_REPORTS_NAME="$(bucketname 'archivedreports')"
    export CRYOSTAT_STORAGE_BUCKETS_ARCHIVED_REPORTS_NAME
    CRYOSTAT_STORAGE_BUCKETS_EVENT_TEMPLATES_NAME="$(bucketname 'eventtemplates')"
    export CRYOSTAT_STORAGE_BUCKETS_EVENT_TEMPLATES_NAME
    CRYOSTAT_STORAGE_BUCKETS_PROBE_TEMPLATES_NAME="$(bucketname 'probetemplates')"
    export CRYOSTAT_STORAGE_BUCKETS_PROBE_TEMPLATES_NAME
    CRYOSTAT_STORAGE_BUCKETS_HEAP_DUMPS_NAME="$(bucketname 'heapdumps')"
    export CRYOSTAT_STORAGE_BUCKETS_HEAP_DUMPS_NAME
    CRYOSTAT_STORAGE_BUCKETS_THREAD_DUMPS_NAME="$(bucketname 'threaddumps')"
    export CRYOSTAT_STORAGE_BUCKETS_THREAD_DUMPS_NAME
    CRYOSTAT_STORAGE_BUCKETS_METADATA_NAME="$(bucketname 'metadata')"
    export CRYOSTAT_STORAGE_BUCKETS_METADATA_NAME
fi

CMD=()
for file in "${FILES[@]}"; do
    CMD+=(-f "${file}")
done

createProxyCfgVolume() {
    "${container_engine}" volume create auth_proxy_cfg
    "${container_engine}" container create --name proxy_cfg_helper -v auth_proxy_cfg:/tmp registry.access.redhat.com/ubi9/ubi-micro
    local cfg
    cfg="$(mktemp)"
    chmod 644 "${cfg}"
    envsubst '$STORAGE_PORT' < "${DIR}/compose/${AUTH_PROXY_ALPHA_CONFIG_FILE}.yml" > "${cfg}"
    htpasswd="$(mktemp)"
    htpasswd -ibB "${htpasswd}" "${CRYOSTAT_USER:-user}" "${CRYOSTAT_PASS:-pass}"
    "${container_engine}" cp "${htpasswd}" proxy_cfg_helper:/tmp/auth_proxy_htpasswd
    "${container_engine}" cp "${cfg}" proxy_cfg_helper:/tmp/auth_proxy_alpha_config.yml
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export auth_proxy_cfg > auth_proxy_cfg.tar
    fi
    rm "${htpasswd}"
}

createProxyCertsVolume() {
    "${container_engine}" volume create auth_proxy_certs
    "${container_engine}" container create --name proxy_certs_helper -v auth_proxy_certs:/certs registry.access.redhat.com/ubi9/ubi-micro
    if [ -f "${DIR}/compose/auth_certs/certificate.pem" ] && [ -f "${DIR}/compose/auth_certs/private.key" ]; then
        chmod 644 "${DIR}/compose/auth_certs/private.key"
        "${container_engine}" cp "${DIR}/compose/auth_certs/certificate.pem" proxy_certs_helper:/certs/certificate.pem
        "${container_engine}" cp "${DIR}/compose/auth_certs/private.key" proxy_certs_helper:/certs/private.key
    fi
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export auth_proxy_certs > auth_proxy_certs.tar
    fi
}

createLocalstackCfgVolume() {
    "${container_engine}" volume create localstack_cfg
    "${container_engine}" container create --name localstack_cfg_helper -v localstack_cfg:/tmp registry.access.redhat.com/ubi9/ubi-micro
    "${container_engine}" cp "${DIR}/compose/localstack_buckets.sh" localstack_cfg_helper:/tmp
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export localstack_cfg > localstack_cfg.tar
    fi
}

createJmxTlsCertVolume() {
    "${container_engine}" volume create jmxtls_cfg
    "${container_engine}" container create --name jmxtls_cfg_helper -v jmxtls_cfg:/truststore registry.access.redhat.com/ubi9/ubi-micro
    if [ -d "${DIR}/truststore" ]; then
        "${container_engine}" cp "${DIR}/truststore" jmxtls_cfg_helper:/truststore
    fi
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export jmxtls_cfg > jmxtls_cfg.tar
    fi
}

createEventTemplateVolume() {
    "${container_engine}" volume create templates
    "${container_engine}" container create --name templates_helper -v templates:/templates registry.access.redhat.com/ubi9/ubi-micro
    if [ -d "${DIR}/templates" ]; then
        "${container_engine}" cp "${DIR}/templates" templates_helper:/templates
    fi
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export templates > templates.tar
    fi
}

createProbeTemplateVolume() {
    "${container_engine}" volume create probes
    "${container_engine}" container create --name probes_helper -v probes:/probes registry.access.redhat.com/ubi9/ubi-micro
    if [ -d "${DIR}/probes" ]; then
        "${container_engine}" cp "${DIR}/probes" probes_helper:/probes
    fi
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export probes > probes.tar
    fi
}

createCredentialVolume() {
    "${container_engine}" volume create credentials
    "${container_engine}" container create --name credentials_helper -v credentials:/credentials registry.access.redhat.com/ubi9/ubi-micro
    if [ -d "${DIR}/credentials" ]; then
        "${container_engine}" cp "${DIR}/credentials" credentials_helper:/credentials
    fi
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export credentials > credentials.tar
    fi
}

createPrometheusCfgVolume() {
    "${container_engine}" volume create prometheus_cfg
    "${container_engine}" container create --name prometheus_cfg_helper -v prometheus_cfg:/etc/prometheus registry.access.redhat.com/ubi9/ubi-micro
    "${container_engine}" cp "${DIR}/compose/prometheus_config.yml" prometheus_cfg_helper:/etc/prometheus/prometheus.yml
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export prometheus_cfg > prometheus_cfg.tar
    fi
}

createS3CertsVolume() {
    "${container_engine}" volume create s3_certs
    "${container_engine}" container create --name s3_certs_helper -v s3_certs:/certs registry.access.redhat.com/ubi9/ubi-micro
    if [ -f "${DIR}/compose/s3_certs/certificate.pem" ] && [ -f "${DIR}/compose/s3_certs/private.key" ]; then
        chmod 644 "${DIR}/compose/s3_certs/private.key"
        "${container_engine}" cp "${DIR}/compose/s3_certs/certificate.pem" s3_certs_helper:/certs/certificate.pem
        "${container_engine}" cp "${DIR}/compose/s3_certs/private.key" s3_certs_helper:/certs/private.key
    fi
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export s3_certs > s3_certs.tar
    fi
}

createDbCertsVolume() {
    "${container_engine}" volume create db_certs
    "${container_engine}" container create --name db_certs_helper -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro
    if [ -f "${DIR}/compose/db_certs/certificate.pem" ] && [ -f "${DIR}/compose/db_certs/private.key" ]; then
        "${container_engine}" cp "${DIR}/compose/db_certs/certificate.pem" db_certs_helper:/certs/certificate.pem
        "${container_engine}" cp "${DIR}/compose/db_certs/private.key" db_certs_helper:/certs/private.key
        # Set ownership to postgres user (UID 26) and permissions for PostgreSQL's requirements
        "${container_engine}" run --rm -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro chown -R 26:26 /certs
        "${container_engine}" run --rm -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro chmod 600 /certs/private.key
        "${container_engine}" run --rm -v db_certs:/certs registry.access.redhat.com/ubi9/ubi-micro chmod 644 /certs/certificate.pem
    fi
    if [ "${DRY_RUN}" = "true" ]; then
        "${container_engine}" volume export db_certs > db_certs.tar
    fi
}

createVolumes() {
    if [ "${USE_PROXY}" = "true" ]; then
        createProxyCfgVolume
    fi
    if [ "${USE_PROXY}" = "true" ] && [ "${USE_TLS}" = "true" ]; then
        sh "${DIR}/compose/auth_certs/generate.sh"
        createProxyCertsVolume
    fi
    if [ "${s3}" = "localstack" ]; then
        createLocalstackCfgVolume
    fi
    if [ "${s3}" = "seaweed" ]; then
        sh "${DIR}/compose/s3_certs/generate.sh"
        createS3CertsVolume
    fi
    sh "${DIR}/compose/db_certs/generate.sh"
    createDbCertsVolume
    createJmxTlsCertVolume
    createEventTemplateVolume
    createProbeTemplateVolume
    createCredentialVolume
    if printf '%s\n' "${FILES[@]}" | grep -q "prometheus.yml"; then
        createPrometheusCfgVolume
    fi
}

cleanupVolumes() {
    if [ "${USE_PROXY}" = "true" ]; then
        ${container_engine} rm proxy_cfg_helper || true
        ${container_engine} rm proxy_certs_helper || true
        ${container_engine} volume rm auth_proxy_cfg || true
        ${container_engine} volume rm auth_proxy_certs || true
    fi
    if [ "${s3}" = "localstack" ]; then
        ${container_engine} rm localstack_cfg_helper || true
        ${container_engine} volume rm localstack_cfg || true
    fi
    if [ "${s3}" = "seaweed" ]; then
        ${container_engine} rm s3_certs_helper || true
        ${container_engine} volume rm s3_certs || true
    fi
    ${container_engine} rm db_certs_helper || true
    ${container_engine} volume rm db_certs || true
    ${container_engine} rm jmxtls_cfg_helper || true
    ${container_engine} volume rm jmxtls_cfg || true
    ${container_engine} rm templates_helper || true
    ${container_engine} volume rm templates || true
    ${container_engine} rm probes_helper || true
    ${container_engine} volume rm probes || true
    ${container_engine} rm credentials_helper || true
    ${container_engine} volume rm credentials || true
    ${container_engine} rm prometheus_cfg_helper || true
    ${container_engine} volume rm prometheus_cfg || true
}

if [ "${KEEP_VOLUMES}" != "true" ]; then
    cleanupVolumes 1>&2
fi
if [ "${DRY_RUN}" = "true" ]; then
    set +xe
    "${container_engine}" compose \
        "${CMD[@]}" \
        config
    createVolumes 1>&2
    cleanupVolumes 1>&2
    exit 0
fi

PIDS=()

HOSTSFILE="${HOSTSFILE:-$HOME/.hosts}"

cleanup() {
    set +xe
    local downFlags=('--remove-orphans')
    if [ "${KEEP_VOLUMES}" != "true" ]; then
        downFlags=('--volumes')
    fi
    "${container_engine}" compose \
        "${CMD[@]}" \
        down "${downFlags[@]}"
    if [ "${SAMPLE_APPS_USE_TLS}" = "true" ]; then
        bash "${DIR}/compose/agent_certs/generate-agent-certs.bash" clean || true
    fi
    if [ "${USE_TLS}" = "true" ]; then
        rm "${DIR}/compose/auth_certs/certificate.pem" || true
        rm "${DIR}/compose/auth_certs/private.key" || true
    fi
    if [ "${s3}" = "seaweed" ]; then
        rm "${DIR}/compose/s3_certs/certificate.pem" || true
        rm "${DIR}/compose/s3_certs/private.key" || true
        rm "${DIR}/truststore/s3_storage.cer" || true
    fi
    rm "${DIR}/compose/db_certs/certificate.pem" || true
    rm "${DIR}/compose/db_certs/private.key" || true
    rm "${DIR}/truststore/database.cer" || true
    cleanupVolumes
    truncate -s 0 "${HOSTSFILE}"
    for i in "${PIDS[@]}"; do
        kill -0 "${i}" && kill "${i}"
    done
    set -xe
}
trap cleanup EXIT
cleanup

if [ "${SAMPLE_APPS_USE_TLS}" = "true" ]; then
    bash "${DIR}/compose/agent_certs/generate-agent-certs.bash" generate
fi

createVolumes

setupUserHosts() {
    # This requires https://github.com/figiel/hosts to work. See README.
    truncate -s 0 "${HOSTSFILE}"
    for file in "${FILES[@]}" ; do
        local hosts
        hosts="$(yq '.services.*.hostname' "${file}" | { grep -v null || test $? = 1 ; } | sed -e 's/^/localhost /')"
        echo "${hosts}" >> "${HOSTSFILE}"
    done
}
if [ "${USE_USERHOSTS}" = "true" ]; then
    setupUserHosts
fi

openBrowserTabs() {
    # TODO find a way to use 'podman wait --condition=healthy $containerId' instead of polling with curl
    set +xe
    local urls=()
    for file in "${FILES[@]}"; do
        local yaml
        yaml="$(yq '.services.* | [{"host": .hostname, "ports": .ports}]' "${file}")"
        local length
        length="$(echo "${yaml}" | yq 'length')"
        for (( i=0; i<"${length}"; i+=1 ))
        do
            local host
            local port
            if [ "${USE_USERHOSTS}" = "true" ]; then
                host="$(echo "${yaml}" | yq ".[${i}].host" | { grep -v null || test $? = 1 ; } )"
                if [ "${host}" = "auth" ]; then
                  host="localhost"
                fi
            else
                host="localhost"
            fi
            port="$(echo "${yaml}" | yq ".[${i}].ports[0]" | { grep -v null || test $? = 1 ; } | envsubst | cut -d: -f1)"
            if [ -n "${host}" ] && [ -n "${port}" ]; then
                urls+=("http://${host}:${port}")
            fi
        done
    done
    set -xe
    echo "Service URLs:" "${urls[@]}"
    for url in "${urls[@]}"; do
        (
            testSvc() {
              timeout 1s curl -s -f -o /dev/null "$1"
              local sc="$?"
              if [ "${sc}" = "0" ] || [ "${sc}" = "22" ]; then
                return 0
              else
                return "${sc}"
              fi
            }
            until testSvc "${url}"
            do
                sleep 5
            done
            xdg-open "${url}"
            echo "Opened ${url} in default browser."
        ) &
        PIDS+=($!)
    done
}
if [ "${OPEN_TABS}" = "true" ]; then
    openBrowserTabs
fi

if [ "${PULL_IMAGES}" = "true" ]; then
    IMAGES=()
    for file in "${FILES[@]}" ; do
        images="$(yq '.services.*.image' "${file}" | { grep -v null || test $? = 1; })"
        for img in ${images}; do
          IMAGES+=("$(eval echo "${img}")")
        done
    done
    "${container_engine}" pull "${IMAGES[@]}" || true
fi

"${container_engine}" compose \
    "${CMD[@]}" \
    up \
        --renew-anon-volumes \
        --remove-orphans \
        --abort-on-container-exit