| #!/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 |