#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" COMPOSE_FILE="$ROOT/tests/compose.yaml" env_value() { local key="$1" local default="$2" local value="${!key:-}" if [[ -z "$value" && -f "$ROOT/tests/.env" ]]; then value="$(sed -n "s/^${key}=//p" "$ROOT/tests/.env" | tail -n 1)" fi printf '%s' "${value:-$default}" } PROJECT="$(env_value ZBX_TEST_PROJECT zabbix-templates-test)" WEB_PORT="$(env_value ZBX_WEB_PORT 8080)" API_URL="$(env_value ZBX_API_URL "")" # When this script runs inside a container (e.g. a Gitea act_runner job # container talking to the host Docker daemon), ports published by compose are # bound on the Docker host, not in this container, so 127.0.0.1 cannot reach # them. In that case, attach this container to the compose network and talk to # the web frontend by its service name instead. SELF_CONTAINER="" if [[ -z "$API_URL" ]]; then if [[ -f /.dockerenv || -f /run/.containerenv ]]; then SELF_CONTAINER="$(hostname)" API_URL="http://zabbix-web:8080/api_jsonrpc.php" else API_URL="http://127.0.0.1:${WEB_PORT}/api_jsonrpc.php" fi fi command -v docker >/dev/null 2>&1 || { echo "ERROR: docker is required." >&2 exit 1 } docker compose version >/dev/null 2>&1 || { echo "ERROR: docker compose is required." >&2 exit 1 } command -v curl >/dev/null 2>&1 || { echo "ERROR: curl is required." >&2 exit 1 } command -v jq >/dev/null 2>&1 || { echo "ERROR: jq is required." >&2 exit 1 } mapfile -t files < <( find "$ROOT/templates" -type f \( -name '*.yaml' -o -name '*.yml' \) \ | grep -E '/[0-9]+\.[0-9]+/' || true ) if [[ ${#files[@]} -eq 0 ]]; then echo "No YAML files to import." exit 0 fi compose=( docker compose --project-directory "$ROOT/tests" -f "$COMPOSE_FILE" -p "$PROJECT" ) if [[ -f "$ROOT/tests/.env" ]]; then compose+=(--env-file "$ROOT/tests/.env") fi NETWORK="${PROJECT}_default" network_connected=0 cleanup() { local status=$? if [[ $network_connected -eq 1 ]]; then docker network disconnect "$NETWORK" "$SELF_CONTAINER" >/dev/null 2>&1 || true fi if [[ "${KEEP_STACK:-0}" == "1" ]]; then echo "Zabbix stack left running (KEEP_STACK=1)." echo "To stop it: docker compose --project-directory \"$ROOT/tests\" -f \"$COMPOSE_FILE\" -p \"$PROJECT\" down -v" else "${compose[@]}" down -v >/dev/null 2>&1 || true fi exit "$status" } trap cleanup EXIT echo "Starting the Zabbix test stack..." "${compose[@]}" up -d if [[ -n "$SELF_CONTAINER" ]]; then echo "Running inside a container; connecting $SELF_CONTAINER to $NETWORK..." docker network connect "$NETWORK" "$SELF_CONTAINER" network_connected=1 fi echo "API endpoint: $API_URL" echo "Waiting for the API..." ready=0 server_version="" for _ in {1..90}; do response="$(curl -sS -m 5 -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"apiinfo.version","params":{},"id":1}' \ "$API_URL" 2>/dev/null || true)" server_version="$(jq -r '.result // empty' <<<"$response" 2>/dev/null || true)" if [[ -n "$server_version" ]]; then ready=1 break fi sleep 2 done if [[ $ready -ne 1 ]]; then echo "ERROR: The Zabbix API did not become ready." >&2 "${compose[@]}" ps >&2 || true "${compose[@]}" logs --tail 50 >&2 || true echo "Logs: docker compose --project-directory \"$ROOT/tests\" -f \"$COMPOSE_FILE\" -p \"$PROJECT\" logs" >&2 exit 1 fi server_minor="$(cut -d. -f1-2 <<<"$server_version")" echo "Zabbix version: $server_version" mapfile -t versions < <( printf '%s\n' "${files[@]}" | grep -oE '/[0-9]+\.[0-9]+/' | tr -d '/' | sort -u ) server_major="${server_minor%%.*}" server_patch="${server_minor##*.}" server_key=$((10#$server_major * 100 + 10#$server_patch)) selected_version="" selected_key=-1 for version in "${versions[@]}"; do major="${version%%.*}" minor="${version##*.}" key=$((10#$major * 100 + 10#$minor)) if (( key <= server_key && key > selected_key )); then selected_version="$version" selected_key=$key fi done if [[ -z "$selected_version" ]]; then echo "No YAML files compatible with Zabbix $server_version." exit 0 fi echo "Using version directory: $selected_version" mapfile -t version_files < <( printf '%s\n' "${files[@]}" | grep -F "/$selected_version/" || true ) files=("${version_files[@]}") echo "Logging in..." login_response="$( jq -n \ --arg user "$(env_value ZBX_USER Admin)" \ --arg password "$(env_value ZBX_PASSWORD zabbix)" \ '{ jsonrpc: "2.0", method: "user.login", params: {username: $user, password: $password}, id: 1 }' \ | curl -sS --fail-with-body \ -H 'Content-Type: application/json' \ --data-binary @- \ "$API_URL" )" token="$(jq -r '.result // empty' <<<"$login_response")" if [[ -z "$token" ]]; then echo "ERROR: Login failed." >&2 jq . <<<"$login_response" >&2 exit 1 fi rules="$(jq -n '{ templates: {createMissing: true, updateExisting: true}, template_groups: {createMissing: true, updateExisting: true}, templateLinkage: {createMissing: true, deleteMissing: false}, items: {createMissing: true, updateExisting: true, deleteMissing: false}, discoveryRules: {createMissing: true, updateExisting: true, deleteMissing: false}, triggers: {createMissing: true, updateExisting: true, deleteMissing: false}, graphs: {createMissing: true, updateExisting: true, deleteMissing: false}, httptests: {createMissing: true, updateExisting: true, deleteMissing: false}, templateDashboards: {createMissing: true, updateExisting: true, deleteMissing: false}, valueMaps: {createMissing: true, updateExisting: true, deleteMissing: false} }')" for file in "${files[@]}"; do rel="${file#"$ROOT/"}" echo "Importing: $rel" response="$( jq -n \ --rawfile source "$file" \ --argjson rules "$rules" \ '{ jsonrpc: "2.0", method: "configuration.import", params: { format: "yaml", rules: $rules, source: $source }, id: 1 }' \ | curl -sS --fail-with-body \ -H "Authorization: Bearer ${token}" \ -H 'Content-Type: application/json' \ --data-binary @- \ "$API_URL" )" if ! jq -e '.result == true' >/dev/null 2>&1 <<<"$response"; then echo "ERROR: Import failed: $rel" >&2 jq . <<<"$response" >&2 exit 1 fi done echo "Import test passed: ${#files[@]} file(s)"