Files
orrisroot 7211663fa7
Validate / lint (push) Successful in 42s
Validate / import-test (push) Successful in 7s
fix: reach Zabbix API from container-based CI runners
Gitea act_runner executes jobs inside a container that talks to the host
Docker daemon, so ports published by compose are not reachable via
127.0.0.1. When running inside a container, attach it to the compose
network and use the zabbix-web service name. Allow overriding the
endpoint with ZBX_API_URL and dump compose status/logs on timeout.
2026-11-06 14:48:30 +09:00

225 lines
6.3 KiB
Bash
Executable File

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