blob: 54418d413abde350b6508e27381558b66f6b8726 [file]
#!/usr/bin/env bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
set -Eeuo pipefail
# Most assertions below are a bare `jq -e ... >/dev/null`, so `set -e` used to
# end the run with no output at all and a CI log that said only "exit code 1".
# Name the command and line that failed instead; the trap fires once per frame,
# so the innermost assertion comes first and its callers follow.
trap 'echo "FAILED at ${BASH_SOURCE[0]}:${LINENO}: ${BASH_COMMAND}" >&2' ERR
DOCKER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASSWORD="ci-compose-password"
SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
PD_SECRET="ci-compose-pd-secret"
VERSION="ci-version"
RENDER_HUBBLE_IMAGE="example.invalid/hugegraph/hubble:ci"
DATASOURCE="jdbc:h2:file:/hubble/data/hubble;DB_CLOSE_ON_EXIT=FALSE"
CURL_TIMEOUTS=(--connect-timeout 5 --max-time 15)
ACTIVE_PROJECT=""
ACTIVE_FILES=()
RENDER_DIR=""
# How the render sees HG_SERVER_STARTUP_TIMEOUT_S. Stripped by default, so a
# baseline render shows the Compose default whatever the developer exported;
# render_with_timeout swaps in a value. Kept as one array rather than a second
# copy of the environment below, so a variable added there reaches both renders.
STARTUP_TIMEOUT_ENV=(-u HG_SERVER_STARTUP_TIMEOUT_S)
compose_auth() {
env "${STARTUP_TIMEOUT_ENV[@]}" \
HUGEGRAPH_VERSION="${VERSION}" \
HUBBLE_IMAGE="${RENDER_HUBBLE_IMAGE}" \
HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \
HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \
HG_PD_AUTH_SECRET_KEY="${PD_SECRET}" \
docker compose "$@"
}
render() {
local output="$1"
shift
compose_auth "$@" config --format json > "${output}"
}
# Same render with an explicit host value, to prove the variable reaches the
# Server environment rather than only defaulting there. An empty value is a
# set value and must survive to the container, which is what separates the
# Compose files' "-" from ":-".
render_with_timeout() {
local output="$1" timeout="$2"
shift 2
(
STARTUP_TIMEOUT_ENV=(HG_SERVER_STARTUP_TIMEOUT_S="${timeout}")
compose_auth "$@" config --format json
) > "${output}"
}
assert_startup_timeout() {
local rendered="$1" expected="$2" service
shift 2
for service in "$@"; do
jq -e --arg s "${service}" --arg v "${expected}" \
'.services[$s].environment.HG_SERVER_STARTUP_TIMEOUT_S == $v' \
"${rendered}" >/dev/null || {
echo "expected ${service} HG_SERVER_STARTUP_TIMEOUT_S=${expected}" \
"in ${rendered}" >&2
return 1
}
done
}
assert_file_property() {
local file="$1"
local property="$2"
grep -Fqx "${property}" "${file}"
}
assert_common() {
local rendered="$1"
local services="$2"
local volumes="$3"
jq -e \
--arg password "${PASSWORD}" \
--arg secret "${SECRET}" \
--argjson services "${services}" \
--argjson volumes "${volumes}" '
(.services | keys) == $services and
(.volumes | keys) == $volumes and
(.networks | keys) == ["hg-net"] and
all(.networks[]; .external != true) and
all(.volumes[]; .external != true) and
all(.services[];
(.networks | keys) == ["hg-net"] and
.healthcheck.test[0] == "CMD-SHELL") and
all(
[.services | to_entries[] |
select(.key | startswith("server")) |
.value.environment][];
.PASSWORD == $password and
.HG_SERVER_AUTH_TOKEN_SECRET == $secret)
' "${rendered}" >/dev/null
}
assert_hubble() {
local rendered="$1"
local config="$2"
shift 2
local dependencies
dependencies="$(printf '%s\n' "$@" | jq -Rsc 'split("\n")[:-1] | sort')"
jq -e \
--arg datasource "${DATASOURCE}" \
--arg config "/docker/conf/hubble/${config}" \
--argjson dependencies "${dependencies}" '
.services.hubble.image == "example.invalid/hugegraph/hubble:ci" and
.services.hubble.pull_policy == "missing" and
.services.hubble.environment.SPRING_DATASOURCE_URL == $datasource and
any(.services.hubble.ports[];
.target == 8088 and .published == "8088" and
.host_ip == "127.0.0.1") and
(.services.hubble.depends_on | keys | sort) == $dependencies and
all(.services.hubble.depends_on[];
.condition == "service_healthy") and
any(.services.hubble.volumes[];
.type == "volume" and .source == "hubble-data" and
.target == "/hubble/data") and
any(.services.hubble.volumes[];
.type == "bind" and (.source | endswith($config)) and
.target == "/hubble/conf/hugegraph-hubble.properties" and
.read_only == true) and
(.services.hubble.healthcheck.test[1] |
contains("http://localhost:8088/about") and
contains("\"status\":200") and
contains("\"name\":\"hugegraph-hubble\""))
' "${rendered}" >/dev/null
}
# The two *.local.properties files are generated by set-hubble-pd-password.sh
# and gitignored. Their binds must disable host-path creation, so a first
# `docker compose up` that skipped the generator fails instead of getting an
# empty directory mounted over Hubble's config.
#
# This reads the Compose file rather than the render on purpose. compose-go
# tags ServiceVolumeBind.CreateHostPath `json:"create_host_path,omitempty"`
# (checked in compose-go v2.7.0, the version inside the Compose 2.38.2 that
# ubuntu-24.04 runners ship), and omitempty drops a false bool, so
# `config --format json` emits the same `"bind": {}` for the long syntax with
# create_host_path disabled and for the short syntax this pins away from. The
# render simply cannot carry this contract on that version.
assert_hubble_bind_pinned() {
local file="$1" properties="$2"
grep -Fq "source: ./conf/hubble/${properties}" "${file}" || {
echo "${file}: the ${properties} mount is not in long bind syntax" >&2
return 1
}
grep -Fq "create_host_path: false" "${file}" || {
echo "${file}: the ${properties} bind does not disable" \
"create_host_path" >&2
return 1
}
if grep -Fq "./conf/hubble/${properties}:/hubble/conf" "${file}"; then
echo "${file}: the ${properties} mount is back on the short bind" \
"syntax, which lets Docker create an empty directory there" >&2
return 1
fi
}
assert_standalone() {
local rendered="$1"
assert_common "${rendered}" \
'["hubble","server"]' \
'["hubble-data","server-data"]'
assert_hubble "${rendered}" "standalone.properties" server
jq -e '
.services.server.image == "hugegraph/hugegraph:ci-version" and
.services.server.pull_policy == "missing" and
.services.server.healthcheck.test[1] ==
"curl -fsS http://localhost:8080/versions >/dev/null" and
any(.services.server.volumes[];
.source == "server-data" and
.target == "/hugegraph-server/rocksdb-data")
' "${rendered}" >/dev/null
assert_file_property "${DOCKER_DIR}/conf/hubble/standalone.properties" \
"pd.enabled=false"
assert_file_property "${DOCKER_DIR}/conf/hubble/standalone.properties" \
"server.direct_url=http://server:8080"
}
assert_hstore() {
local rendered="$1"
assert_common "${rendered}" \
'["hubble","pd","server","store"]' \
'["hubble-data","pd-data","store-data"]'
assert_hubble "${rendered}" "hstore.local.properties" server
assert_hubble_bind_pinned "${DOCKER_DIR}/docker-compose-hstore.yml" \
"hstore.local.properties"
jq -e '
.services.pd.image == "hugegraph/pd:ci-version" and
.services.store.image == "hugegraph/store:ci-version" and
.services.server.image == "hugegraph/server:ci-version" and
all([.services.pd, .services.store, .services.server][];
.pull_policy == "missing") and
.services.server.environment.HG_SERVER_BACKEND == "hstore" and
.services.server.environment.HG_SERVER_PD_PEERS == "pd:8686" and
.services.server.environment.HG_SERVER_CLUSTER == "hg" and
.services.server.environment.HG_SERVER_USE_PD == "true" and
.services.server.environment.HG_SERVER_REST_URL ==
"http://server:8080" and
.services.server.healthcheck.test[1] ==
"curl -fsS http://server:8080/versions >/dev/null" and
any(.services.pd.volumes[];
.source == "pd-data" and
.target == "/hugegraph-pd/pd_data") and
any(.services.store.volumes[];
.source == "store-data" and
.target == "/hugegraph-store/storage")
' "${rendered}" >/dev/null
assert_file_property "${DOCKER_DIR}/conf/hubble/hstore.local.properties" \
"pd.peers=pd:8686"
assert_file_property "${DOCKER_DIR}/conf/hubble/hstore.local.properties" \
"operations.store.allowed_targets=[http://store:8520]"
}
assert_ha() {
local rendered="$1"
assert_common "${rendered}" \
'["hubble","pd0","pd1","pd2","server0","server1","server2","store0","store1","store2"]' \
'["hg-pd0-data","hg-pd1-data","hg-pd2-data","hg-store0-data","hg-store1-data","hg-store2-data","hubble-data"]'
assert_hubble "${rendered}" "hstore-ha.local.properties" \
server0 server1 server2
assert_hubble_bind_pinned \
"${DOCKER_DIR}/docker-compose-3pd-3store-3server.yml" \
"hstore-ha.local.properties"
jq -e '
all([.services.pd0, .services.pd1, .services.pd2][];
.image == "hugegraph/pd:ci-version" and
.pull_policy == "missing") and
all([.services.store0, .services.store1, .services.store2][];
.image == "hugegraph/store:ci-version" and
.pull_policy == "missing") and
all([.services.server0, .services.server1, .services.server2][];
.image == "hugegraph/server:ci-version" and
.pull_policy == "missing" and
.environment.STORE_REST == "store0:8520" and
.environment.HG_SERVER_BACKEND == "hstore" and
.environment.HG_SERVER_PD_PEERS ==
"pd0:8686,pd1:8686,pd2:8686" and
.environment.HG_SERVER_CLUSTER == "hg" and
.environment.HG_SERVER_USE_PD == "true" and
.environment.HG_SERVER_INIT_STORE_ENABLED == "false" and
.environment.HG_SERVER_REQUIRE_AUTH_TOKEN_SECRET == "true") and
[.services.server0.environment.HG_SERVER_REST_URL,
.services.server1.environment.HG_SERVER_REST_URL,
.services.server2.environment.HG_SERVER_REST_URL] ==
["http://server0:8080",
"http://server1:8080",
"http://server2:8080"] and
all([.services.server0, .services.server1, .services.server2][];
.healthcheck.test[1] ==
"curl -fsS http://$$(hostname):8080/versions >/dev/null" and
.healthcheck.interval == "10s" and
.healthcheck.timeout == "5s" and
.healthcheck.retries == 30 and
.healthcheck.start_period == "1m0s")
' "${rendered}" >/dev/null
assert_file_property "${DOCKER_DIR}/conf/hubble/hstore-ha.local.properties" \
"pd.peers=pd0:8686,pd1:8686,pd2:8686"
assert_file_property "${DOCKER_DIR}/conf/hubble/hstore-ha.local.properties" \
"operations.store.allowed_targets=[http://store0:8520,http://store1:8520,http://store2:8520]"
}
assert_dev_override() {
local rendered="$1"
local override="$2"
assert_common "${rendered}" \
'["hubble","pd","server","store"]' \
'["hubble-data","pd-data","store-data"]'
jq -e '
.services.pd.image == "hugegraph/pd:dev" and
.services.store.image == "hugegraph/store:dev" and
.services.server.image == "hugegraph/server:dev" and
all([.services.pd, .services.store, .services.server][];
.pull_policy == "build" and .build != null) and
.services.hubble.image == "example.invalid/hugegraph/hubble:ci" and
.services.hubble.build == null
' "${rendered}" >/dev/null
jq -e '
(.services | keys) == ["pd","server","store"] and
(.networks | keys) == ["default"] and .volumes == null and
all(.services[];
.build != null and .image != null and
.pull_policy == "build" and
.environment == null and
(.networks | keys) == ["default"] and
.volumes == null)
' "${override}" >/dev/null
}
cleanup() {
if [[ -n "${ACTIVE_PROJECT}" ]]; then
compose_active down -v --remove-orphans >/dev/null 2>&1 || true
fi
restore_hubble_configs
[[ -z "${RENDER_DIR}" ]] || rm -rf "${RENDER_DIR}"
}
# The HStore topologies mount conf/hubble/<name>.local.properties, which is
# generated and untracked. Generate both with the CI secret before any render
# or `up`; the binds pin create_host_path: false, so a missing file makes
# Compose refuse to start. A developer's own local files are put back
# afterwards.
HUBBLE_BACKUP_DIR=""
prepare_hubble_configs() {
local name
HUBBLE_BACKUP_DIR="$(mktemp -d)"
for name in hstore hstore-ha; do
local f="${DOCKER_DIR}/conf/hubble/${name}.local.properties"
[[ ! -f "${f}" ]] || cp -p "${f}" "${HUBBLE_BACKUP_DIR}/${name}.local.properties"
"${DOCKER_DIR}/set-hubble-pd-password.sh" "${name}" "${PD_SECRET}" >/dev/null
done
}
restore_hubble_configs() {
[[ -n "${HUBBLE_BACKUP_DIR}" ]] || return 0
local name
for name in hstore hstore-ha; do
local f="${DOCKER_DIR}/conf/hubble/${name}.local.properties"
if [[ -f "${HUBBLE_BACKUP_DIR}/${name}.local.properties" ]]; then
cp -p "${HUBBLE_BACKUP_DIR}/${name}.local.properties" "${f}"
else
rm -f "${f}"
fi
done
rm -rf "${HUBBLE_BACKUP_DIR}"
HUBBLE_BACKUP_DIR=""
}
# set-hubble-pd-password.sh must survive the characters a sed replacement
# would mangle, keep the rest of the example, produce a world-readable file
# for the read-only mount, and refuse an empty secret or unknown topology.
hubble_password_helper_check() {
local f="${DOCKER_DIR}/conf/hubble/hstore.local.properties"
"${DOCKER_DIR}/set-hubble-pd-password.sh" hstore 'a&b#c\d' >/dev/null
local line mode
line=$(grep '^operations\.pd\.password=' "${f}")
mode=$(stat -c '%a' "${f}" 2>/dev/null || stat -f '%Lp' "${f}")
[[ "${line}" == 'operations.pd.password=a&b#c\\d' ]] || {
echo "set-hubble-pd-password.sh mangled the secret: ${line}" >&2; exit 1; }
[[ "${mode}" == "644" ]] || {
echo "set-hubble-pd-password.sh wrote mode ${mode}, Hubble could not read it" >&2; exit 1; }
grep -q '^pd.server=pd:8620$' "${f}" || {
echo "set-hubble-pd-password.sh dropped the example's other properties" >&2; exit 1; }
! "${DOCKER_DIR}/set-hubble-pd-password.sh" hstore '' 2>/dev/null || {
echo "set-hubble-pd-password.sh accepted an empty secret" >&2; exit 1; }
! "${DOCKER_DIR}/set-hubble-pd-password.sh" nope 'x' 2>/dev/null || {
echo "set-hubble-pd-password.sh accepted an unknown topology" >&2; exit 1; }
# Hubble decodes this file as ISO-8859-1 and PD compares UTF-8 bytes, so a
# non-ASCII secret would be a permanent 401 with nothing in any log.
! "${DOCKER_DIR}/set-hubble-pd-password.sh" hstore 'pässwörd' 2>/dev/null || {
echo "set-hubble-pd-password.sh accepted a non-ASCII secret" >&2; exit 1; }
# java.util.Properties drops whitespace after the separator, so a leading
# space has to survive as an escape or Hubble reads a shortened secret.
"${DOCKER_DIR}/set-hubble-pd-password.sh" hstore ' lead' >/dev/null
line=$(grep '^operations\.pd\.password=' "${f}")
[[ "${line}" == 'operations.pd.password=\ lead' ]] || {
echo "set-hubble-pd-password.sh left a leading space unescaped: ${line}" >&2
exit 1; }
# put the CI value back for the render/smoke that follows
"${DOCKER_DIR}/set-hubble-pd-password.sh" hstore "${PD_SECRET}" >/dev/null
}
run_render() {
RENDER_DIR="$(mktemp -d)"
trap cleanup EXIT INT TERM
prepare_hubble_configs
render "${RENDER_DIR}/standalone.json" \
-f "${DOCKER_DIR}/docker-compose.yml"
render "${RENDER_DIR}/hstore.json" \
-f "${DOCKER_DIR}/docker-compose-hstore.yml"
render "${RENDER_DIR}/ha.json" \
-f "${DOCKER_DIR}/docker-compose-3pd-3store-3server.yml"
render "${RENDER_DIR}/dev.json" \
-f "${DOCKER_DIR}/docker-compose-hstore.yml" \
-f "${DOCKER_DIR}/docker-compose.dev.yml"
render "${RENDER_DIR}/override.json" \
-f "${DOCKER_DIR}/docker-compose.dev.yml"
assert_standalone "${RENDER_DIR}/standalone.json"
assert_hstore "${RENDER_DIR}/hstore.json"
assert_ha "${RENDER_DIR}/ha.json"
assert_dev_override "${RENDER_DIR}/dev.json" \
"${RENDER_DIR}/override.json"
# An absent host value renders the entrypoint default in every topology and
# a host value overrides it, so the container budget can be lowered to meet
# the healthcheck budget without editing the Compose files. Raising it past
# that budget still needs a file edit: the healthcheck literals give every
# Server about 360 seconds, and the documented "up -d --wait" gives up
# there, so this knob aligns the two downward only.
assert_startup_timeout "${RENDER_DIR}/standalone.json" 120 server
assert_startup_timeout "${RENDER_DIR}/hstore.json" 120 server
assert_startup_timeout "${RENDER_DIR}/ha.json" 120 server0 server1 server2
render_with_timeout "${RENDER_DIR}/standalone-timeout.json" 450 \
-f "${DOCKER_DIR}/docker-compose.yml"
render_with_timeout "${RENDER_DIR}/hstore-timeout.json" 450 \
-f "${DOCKER_DIR}/docker-compose-hstore.yml"
render_with_timeout "${RENDER_DIR}/ha-timeout.json" 450 \
-f "${DOCKER_DIR}/docker-compose-3pd-3store-3server.yml"
assert_startup_timeout "${RENDER_DIR}/standalone-timeout.json" 450 server
assert_startup_timeout "${RENDER_DIR}/hstore-timeout.json" 450 server
assert_startup_timeout "${RENDER_DIR}/ha-timeout.json" 450 \
server0 server1 server2
# The unset and override cases above hold under ":-" too. Only an empty
# host value separates the spellings, and it has to reach the container so
# the entrypoint can reject it, as README section 6 promises.
render_with_timeout "${RENDER_DIR}/standalone-empty.json" "" \
-f "${DOCKER_DIR}/docker-compose.yml"
render_with_timeout "${RENDER_DIR}/hstore-empty.json" "" \
-f "${DOCKER_DIR}/docker-compose-hstore.yml"
render_with_timeout "${RENDER_DIR}/ha-empty.json" "" \
-f "${DOCKER_DIR}/docker-compose-3pd-3store-3server.yml"
assert_startup_timeout "${RENDER_DIR}/standalone-empty.json" "" server
assert_startup_timeout "${RENDER_DIR}/hstore-empty.json" "" server
assert_startup_timeout "${RENDER_DIR}/ha-empty.json" "" \
server0 server1 server2
hubble_password_helper_check
echo "Compose render contracts passed"
}
compose_active() {
env -u HG_SERVER_STARTUP_TIMEOUT_S \
HUGEGRAPH_VERSION="${HUGEGRAPH_VERSION:-latest}" \
HUBBLE_IMAGE="${HUBBLE_IMAGE:-hugegraph/hubble:latest}" \
HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \
HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \
HG_PD_AUTH_SECRET_KEY="${PD_SECRET}" \
COMPOSE_PROGRESS=plain \
docker compose -p "${ACTIVE_PROJECT}" "${ACTIVE_FILES[@]}" "$@"
}
diagnose() {
compose_active ps || true
compose_active logs --no-color --tail 200 || true
}
http_status() {
curl "${CURL_TIMEOUTS[@]}" -sS -o /dev/null -w '%{http_code}' "$@"
}
wait_hubble_mode() {
local expected_pd="$1"
local expected_auth="$2"
local response=""
local _
for _ in {1..30}; do
response="$(curl "${CURL_TIMEOUTS[@]}" -fsS \
http://localhost:8088/api/v1.3/config || true)"
if jq -e --argjson expected_pd "${expected_pd}" \
--argjson expected_auth "${expected_auth}" '
.status == 200 and
.data.pd_enabled == $expected_pd and
.data.auth_enabled == $expected_auth and
.data.server_capabilities_verified == true
' <<<"${response}" >/dev/null 2>&1; then
return
fi
sleep 2
done
echo "Hubble authentication detection failed: ${response}" >&2
return 1
}
check_hubble_login() {
local response
response="$(curl "${CURL_TIMEOUTS[@]}" -fsS \
-H "Content-Type: application/json" \
--data "{\"user_name\":\"admin\",\"user_password\":\"${PASSWORD}\"}" \
http://localhost:8088/api/v1.3/auth/login)"
jq -e '.status == 200 and .data.user_name == "admin"' \
<<<"${response}" >/dev/null
}
check_hubble_anonymous() {
curl "${CURL_TIMEOUTS[@]}" -fsS \
http://localhost:8088/api/v1.3/auth/status |
jq -e '.status == 200 and .data.level == "ANONYMOUS"' >/dev/null
curl "${CURL_TIMEOUTS[@]}" -fsS \
http://localhost:8088/api/v1.3/auth/context |
jq -e '
.status == 200 and
.data.mode == "NON_AUTH" and
.data.role == "ANONYMOUS"
' >/dev/null
}
smoke() {
local name="$1"
local expected_pd="$2"
local expected_auth="$3"
shift 3
ACTIVE_PROJECT="hg-ci-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-0}-$$-${name}"
ACTIVE_FILES=()
while (($#)); do
ACTIVE_FILES+=(-f "$1")
shift
done
if ! compose_active up -d --wait --wait-timeout 600; then
diagnose
return 1
fi
curl "${CURL_TIMEOUTS[@]}" -fsS \
http://localhost:8080/versions >/dev/null
if [[ "${expected_auth}" == true ]]; then
[[ "$(http_status \
http://localhost:8080/graphspaces/DEFAULT/graphs)" == 401 ]]
[[ "$(http_status -u "admin:${PASSWORD}" \
http://localhost:8080/graphspaces/DEFAULT/graphs)" == 200 ]]
else
[[ "$(http_status \
http://localhost:8080/graphspaces/DEFAULT/graphs)" == 200 ]]
fi
curl "${CURL_TIMEOUTS[@]}" -fsS http://localhost:8088/about |
jq -e '.status == 200 and .data.name == "hugegraph-hubble"' >/dev/null
wait_hubble_mode "${expected_pd}" "${expected_auth}"
if [[ "${expected_auth}" == true ]]; then
check_hubble_login
else
check_hubble_anonymous
fi
compose_active down -v --remove-orphans
ACTIVE_PROJECT=""
ACTIVE_FILES=()
echo "Compose smoke passed: ${name}"
}
run_smoke() {
trap cleanup EXIT INT TERM
prepare_hubble_configs
smoke standalone false true "${DOCKER_DIR}/docker-compose.yml"
smoke hstore true true "${DOCKER_DIR}/docker-compose-hstore.yml"
}
run_smoke_auth_off() {
PASSWORD=""
trap cleanup EXIT INT TERM
prepare_hubble_configs
smoke standalone-anon false false "${DOCKER_DIR}/docker-compose.yml"
smoke hstore-anon true false "${DOCKER_DIR}/docker-compose-hstore.yml"
}
case "${1:-}" in
render)
run_render
;;
smoke)
run_smoke
;;
smoke-auth-off)
run_smoke_auth_off
;;
all)
run_render
run_smoke
;;
*)
echo "Usage: $0 {render|smoke|smoke-auth-off|all}" >&2
exit 2
;;
esac