#!/usr/bin/env bash ## This script runs CT (and necessary dependencies) in docker container(s) set -euo pipefail # ensure dir cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." help() { echo echo "-h|--help: To display this usage info" echo "--app lib_dir/app_name: For which app to run start docker-compose, and run common tests" echo "--console: Start EMQX in console mode but do not run test cases" echo "--attach: Attach to the Erlang docker container without running any test case" echo "--stop: Stop running containers for the given app" echo "--only-up: Only start the testbed but do not run CT" echo "--keep-up: Keep the testbed running after CT" echo "--ci: Set this flag in GitHub action to enforce no tests are skipped" echo "--: If any, all args after '--' are passed to rebar3 ct" echo " otherwise it runs the entire app's CT" } set +e if docker compose version; then DC='docker compose' elif command -v docker-compose; then DC='docker-compose' else echo 'Neither "docker compose" or "docker-compose" are available, stop.' exit 1 fi set -e WHICH_APP='novalue' CONSOLE='no' KEEP_UP='no' ONLY_UP='no' ATTACH='no' STOP='no' IS_CI='no' ODBC_REQUEST='no' UP='up' while [ "$#" -gt 0 ]; do case $1 in -h|--help) help exit 0 ;; --app) WHICH_APP="${2%/}" shift 2 ;; --only-up) ONLY_UP='yes' shift 1 ;; --keep-up) KEEP_UP='yes' shift 1 ;; --attach) ATTACH='yes' shift 1 ;; --stop) STOP='yes' shift 1 ;; --console) CONSOLE='yes' shift 1 ;; --ci) IS_CI='yes' UP='up --quiet-pull' shift 1 ;; --) shift 1 REBAR3CT="$*" shift $# ;; *) echo "unknown option $1" exit 1 ;; esac done if [ "${WHICH_APP}" = 'novalue' ]; then echo "must provide --app arg" help exit 1 fi if [ ! -d "${WHICH_APP}" ]; then echo "must provide an existing path for --app arg" help exit 1 fi ERLANG_CONTAINER='erlang' DOCKER_CT_ENVS_FILE="${WHICH_APP}/docker-ct" if [ -f "${WHICH_APP}/BSL.txt" ]; then if [ -n "${PROFILE:-}" ] && [ "${PROFILE}" != 'emqx-enterprise' ]; then echo "bad_profile: PROFILE=${PROFILE} will not work for app ${WHICH_APP}" exit 1 fi fi if [ -z "${PROFILE+x}" ]; then case "${WHICH_APP}" in apps/emqx) export PROFILE='emqx-enterprise' ;; apps/emqx_bridge) export PROFILE='emqx-enterprise' ;; # emqx_connector test suite is using kafka bridge which is only available in emqx-enterprise apps/emqx_connector) export PROFILE='emqx-enterprise' ;; apps/emqx_dashboard) export PROFILE='emqx-enterprise' ;; apps/emqx_rule_engine) export PROFILE='emqx-enterprise' ;; apps/*) if [[ -f "${WHICH_APP}/BSL.txt" ]]; then export PROFILE='emqx-enterprise' else export PROFILE='emqx' fi ;; *) export PROFILE="${PROFILE:-emqx}" ;; esac fi if [ -f "$DOCKER_CT_ENVS_FILE" ]; then # shellcheck disable=SC2002 CT_DEPS="$(cat "$DOCKER_CT_ENVS_FILE" | xargs)" fi CT_DEPS="${ERLANG_CONTAINER} ${CT_DEPS:-}" FILES=( ) for dep in ${CT_DEPS}; do case "${dep}" in erlang) FILES+=( '.ci/docker-compose-file/docker-compose.yaml' ) ;; toxiproxy) FILES+=( '.ci/docker-compose-file/docker-compose-toxiproxy.yaml' ) ;; influxdb) FILES+=( '.ci/docker-compose-file/docker-compose-influxdb-tcp.yaml' '.ci/docker-compose-file/docker-compose-influxdb-tls.yaml' ) ;; mongo) FILES+=( '.ci/docker-compose-file/docker-compose-mongo-single-tcp.yaml' '.ci/docker-compose-file/docker-compose-mongo-single-tls.yaml' ) ;; mongo_rs_sharded) FILES+=( '.ci/docker-compose-file/docker-compose-mongo-replicaset-tcp.yaml' '.ci/docker-compose-file/docker-compose-mongo-sharded-tcp.yaml' ) ;; redis) FILES+=( '.ci/docker-compose-file/docker-compose-redis-single-tcp.yaml' '.ci/docker-compose-file/docker-compose-redis-single-tls.yaml' '.ci/docker-compose-file/docker-compose-redis-sentinel-tcp.yaml' '.ci/docker-compose-file/docker-compose-redis-sentinel-tls.yaml' ) ;; redis_cluster) FILES+=( '.ci/docker-compose-file/docker-compose-redis-cluster-tcp.yaml' '.ci/docker-compose-file/docker-compose-redis-cluster-tls.yaml' ) ;; mysql) FILES+=( '.ci/docker-compose-file/docker-compose-mysql-tcp.yaml' '.ci/docker-compose-file/docker-compose-mysql-tls.yaml' ) ;; pgsql) FILES+=( '.ci/docker-compose-file/docker-compose-pgsql-tcp.yaml' '.ci/docker-compose-file/docker-compose-pgsql-tls.yaml' ) ;; kafka) FILES+=( '.ci/docker-compose-file/docker-compose-kafka.yaml' ) ;; tdengine) FILES+=( '.ci/docker-compose-file/docker-compose-tdengine-restful.yaml' ) ;; clickhouse) FILES+=( '.ci/docker-compose-file/docker-compose-clickhouse.yaml' ) ;; dynamo) FILES+=( '.ci/docker-compose-file/docker-compose-dynamo.yaml' ) ;; rocketmq) FILES+=( '.ci/docker-compose-file/docker-compose-rocketmq.yaml' '.ci/docker-compose-file/docker-compose-rocketmq-ssl.yaml' ) ;; cassandra) FILES+=( '.ci/docker-compose-file/docker-compose-cassandra.yaml' ) ;; sqlserver) ODBC_REQUEST='yes' FILES+=( '.ci/docker-compose-file/docker-compose-sqlserver.yaml' ) ;; opents) FILES+=( '.ci/docker-compose-file/docker-compose-opents.yaml' ) ;; pulsar) FILES+=( '.ci/docker-compose-file/docker-compose-pulsar.yaml' ) ;; oracle) FILES+=( '.ci/docker-compose-file/docker-compose-oracle.yaml' ) ;; iotdb) FILES+=( '.ci/docker-compose-file/docker-compose-iotdb.yaml' ) ;; rabbitmq) FILES+=( '.ci/docker-compose-file/docker-compose-rabbitmq.yaml' ) ;; minio) FILES+=( '.ci/docker-compose-file/docker-compose-minio-tcp.yaml' '.ci/docker-compose-file/docker-compose-minio-tls.yaml' ) ;; gcp_emulator) FILES+=( '.ci/docker-compose-file/docker-compose-gcp-emulator.yaml' ) ;; hstreamdb) FILES+=( '.ci/docker-compose-file/docker-compose-hstreamdb.yaml' ) ;; kinesis) FILES+=( '.ci/docker-compose-file/docker-compose-kinesis.yaml' ) ;; greptimedb) FILES+=( '.ci/docker-compose-file/docker-compose-greptimedb.yaml' ) ;; ldap) FILES+=( '.ci/docker-compose-file/docker-compose-ldap.yaml' ) ;; otel) FILES+=( '.ci/docker-compose-file/docker-compose-otel.yaml' ) ;; elasticsearch) FILES+=( '.ci/docker-compose-file/docker-compose-elastic-search-tls.yaml' ) ;; azurite) FILES+=( '.ci/docker-compose-file/docker-compose-azurite.yaml' ) ;; *) echo "unknown_ct_dependency $dep" exit 1 ;; esac done if [ "$ODBC_REQUEST" = 'yes' ]; then INSTALL_ODBC="./scripts/install-msodbc-driver.sh" else INSTALL_ODBC="echo 'msodbc driver not requested'" fi for file in "${FILES[@]}"; do DC="$DC -f $file" done DOCKER_USER="$(id -u)" export DOCKER_USER TTY='' if [[ -t 1 ]]; then TTY='-t' fi # ensure directory with secrets is created by current user before running compose mkdir -p /tmp/emqx-ci/emqx-shared-secret if [ "$STOP" = 'no' ]; then # some left-over log file has to be deleted before a new docker-compose up rm -f '.ci/docker-compose-file/redis/*.log' set +e # shellcheck disable=2086 # no quotes for UP $DC $UP -d --build --remove-orphans RESULT=$? if [ $RESULT -ne 0 ]; then mkdir -p _build/test/logs LOG='_build/test/logs/docker-compose.log' echo "Dumping docker-compose log to $LOG" $DC logs --no-color --timestamps > "$LOG" exit 1 fi set -e fi if [ "$DOCKER_USER" != "root" ]; then # the user must exist inside the container for `whoami` to work docker exec -i $TTY -u root:root "$ERLANG_CONTAINER" bash -c \ "useradd --uid $DOCKER_USER -M -d / emqx && \ mkdir -p /.cache /.hex /.mix && \ chown $DOCKER_USER /.cache /.hex /.mix && \ openssl rand -base64 -hex 16 > /.erlang.cookie && \ chown $DOCKER_USER /.erlang.cookie && \ chmod 0400 /.erlang.cookie && \ chown -R $DOCKER_USER /var/lib/secret && \ $INSTALL_ODBC" || true fi if [ "$ONLY_UP" = 'yes' ]; then exit 0 fi set +e if [ "$STOP" = 'yes' ]; then $DC down --remove-orphans elif [ "$ATTACH" = 'yes' ]; then docker exec -it "$ERLANG_CONTAINER" bash elif [ "$CONSOLE" = 'yes' ]; then docker exec -e PROFILE="$PROFILE" -i $TTY "$ERLANG_CONTAINER" bash -c "make run" else if [ -z "${REBAR3CT:-}" ]; then docker exec -e IS_CI="$IS_CI" \ -e PROFILE="$PROFILE" \ -e SUITEGROUP="${SUITEGROUP:-}" \ -e ENABLE_COVER_COMPILE="${ENABLE_COVER_COMPILE:-}" \ -e CT_COVER_EXPORT_PREFIX="${CT_COVER_EXPORT_PREFIX:-}" \ -i $TTY "$ERLANG_CONTAINER" \ bash -c "BUILD_WITHOUT_QUIC=1 make ${WHICH_APP}-ct" else # this is an ad-hoc run docker exec -e IS_CI="$IS_CI" \ -e PROFILE="$PROFILE" \ -i $TTY "$ERLANG_CONTAINER" \ bash -c "./rebar3 ct $REBAR3CT" fi RESULT=$? if [ "$RESULT" -ne 0 ]; then LOG='_build/test/logs/docker-compose.log' echo "Dumping docker-compose log to $LOG" $DC logs --no-color --timestamps > "$LOG" fi if [ "$KEEP_UP" != 'yes' ]; then $DC down fi exit "$RESULT" fi