#!/usr/bin/env bash # # integration-fixtures.sh — throwaway, self-contained fixtures for the v2-integration workflow (#757). # # up start SQL Server, PostgreSQL (seeded), QuestDB, opc-plc and a port-NAT opc-plc as containers named # otopcua-ci-*, wait until each is usable, and print the env exports the # *.IntegrationTests fixtures read (also appended to $GITHUB_ENV when set). # down remove those containers (idempotent; touches nothing else). # # Deliberately NOT the shared 10.100.0.35 host: CI must not depend on that box, and these # containers are created and destroyed inside the job. Host ports are non-default so a # developer's own fixtures are never clobbered when the script is run locally. # # Reaching the containers: on the host (or a host-network job) that is 127.0.0.1; when the job # itself runs inside a container (/.dockerenv) the published ports live on the docker host, # i.e. the container's default gateway. Override with FIXTURE_HOST. set -euo pipefail SQL_PORT="${CI_SQL_PORT:-14340}" PG_PORT="${CI_PG_PORT:-15440}" QDB_PORT="${CI_QDB_PORT:-18812}" QDB_HTTP_PORT="${CI_QDB_HTTP_PORT:-19000}" OPC_PORT="${CI_OPC_PORT:-50100}" # #813 NAT opc-plc: published on OPC_NAT_PORT, listening on 50000 inside, advertising a private `.invalid` # host — so its GetEndpoints URLs are unreachable on BOTH host and port (NatPublishedEndpointBrowseTests). OPC_NAT_PORT="${CI_OPC_NAT_PORT:-50110}" # Throwaway-container-only credentials (never used against anything else). SA_PASSWORD="${CI_SA_PASSWORD:-Ci_throwaway_Pw1}" PG_PASSWORD="${CI_PG_PASSWORD:-Ci_throwaway_pg1}" NAMES=(otopcua-ci-mssql otopcua-ci-pg otopcua-ci-questdb otopcua-ci-opcplc otopcua-ci-opcplc-nat) ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # #841: CI keeps its OWN throwaway QuestDB (hermetic — never the shared /opt/otopcua-questdb fixture), but runs the # SAME image the shared fixture's compose file pins, read from that file so the two cannot drift. CI_QDB_IMAGE # overrides it only to trial an upgrade. QDB_COMPOSE="$ROOT/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.IntegrationTests/Docker/docker-compose.yml" questdb_image() { if [ -n "${CI_QDB_IMAGE:-}" ]; then echo "$CI_QDB_IMAGE"; return; fi local img img="$(sed -n 's/^[[:space:]]*image:[[:space:]]*\(questdb\/questdb:[^[:space:]]*\).*/\1/p' "$QDB_COMPOSE" | head -n 1)" if [ -z "$img" ]; then echo "no questdb/questdb: image line in $QDB_COMPOSE" >&2; return 1; fi echo "$img" } fixture_host() { if [ -n "${FIXTURE_HOST:-}" ]; then echo "$FIXTURE_HOST"; return; fi if [ -f /.dockerenv ] && command -v ip >/dev/null 2>&1; then ip route | awk '/^default/{print $3; exit}'; return fi echo 127.0.0.1 } down() { docker rm -f "${NAMES[@]}" >/dev/null 2>&1 || true; } wait_for() { # local what="$1" t="$2"; shift 2 local end=$((SECONDS + t)) until "$@" >/dev/null 2>&1; do if [ "$SECONDS" -ge "$end" ]; then echo "TIMEOUT waiting for $what" >&2; return 1; fi sleep 3 done echo "ready: $what" } up() { command -v docker >/dev/null || { echo "docker not available" >&2; exit 1; } local qdb_image; qdb_image="$(questdb_image)" down docker run -d --name otopcua-ci-mssql -e ACCEPT_EULA=Y -e MSSQL_PID=Developer \ -e "MSSQL_SA_PASSWORD=$SA_PASSWORD" -p "$SQL_PORT:1433" \ mcr.microsoft.com/mssql/server:2022-latest >/dev/null docker run -d --name otopcua-ci-pg -e "POSTGRES_PASSWORD=$PG_PASSWORD" -e POSTGRES_DB=sqlpollfixture \ -p "$PG_PORT:5432" postgres:17-alpine >/dev/null docker run -d --name otopcua-ci-questdb -p "$QDB_PORT:8812" -p "$QDB_HTTP_PORT:9000" \ "$qdb_image" >/dev/null docker run -d --name otopcua-ci-opcplc -p "$OPC_PORT:$OPC_PORT" \ mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn="$OPC_PORT" --ut --aa --alm >/dev/null # Mirrors tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/nat/docker-compose.yml. docker run -d --name otopcua-ci-opcplc-nat --hostname otopcua-ci-opcplc-nat-private -p "$OPC_NAT_PORT:50000" \ mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn=50000 --ph=otopcua-ci-opcplc-nat-private.invalid --ut --aa >/dev/null wait_for "postgres" 120 docker exec otopcua-ci-pg pg_isready -U postgres -d sqlpollfixture # pg_isready can answer during initdb's temp server; require a real query on the final server. wait_for "postgres query" 60 docker exec -e "PGPASSWORD=$PG_PASSWORD" otopcua-ci-pg \ psql -h 127.0.0.1 -U postgres -d sqlpollfixture -c 'select 1' docker exec -i -e "PGPASSWORD=$PG_PASSWORD" otopcua-ci-pg \ psql -v ON_ERROR_STOP=1 -v "app_password=$PG_PASSWORD" -h 127.0.0.1 -U postgres -d sqlpollfixture \ < "$ROOT/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/Docker/postgres/seed-pg.sql" >/dev/null echo "seeded: postgres" local sqlcmd=/opt/mssql-tools18/bin/sqlcmd wait_for "sql server" 300 docker exec otopcua-ci-mssql "$sqlcmd" -C -S localhost -U sa -P "$SA_PASSWORD" -Q 'SELECT 1' wait_for "questdb" 120 curl -fsS "http://$(fixture_host):$QDB_HTTP_PORT/exec?query=select%201" wait_for "opc-plc" 120 bash -c "exec 3<>/dev/tcp/$(fixture_host)/$OPC_PORT" wait_for "opc-plc (NAT)" 120 bash -c "exec 3<>/dev/tcp/$(fixture_host)/$OPC_NAT_PORT" # #818: hand the NAT server's PUBLIC certificate to NatPublishedEndpointBrowseTests (OPCUA_NAT_SERVER_CERT) so the # positive secure / host-name cases run here too. Copied out of the container — the trust channel is docker itself, # not the wire. The throwaway container makes a fresh certificate per run, so the committed shared-host copy cannot # be used; the private key never leaves the container. local nat_cert="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/otopcua-ci-fixtures/opc-plc-nat-server.der" mkdir -p "$(dirname "$nat_cert")" wait_for "opc-plc (NAT) certificate" 60 bash -c \ "docker exec otopcua-ci-opcplc-nat bash -c 'cat /app/pki/own/certs/*.der' > '$nat_cert' && [ -s '$nat_cert' ]" local h; h="$(fixture_host)" local -a exports=( "SQL_TEST_ENDPOINT=$h,$SQL_PORT" "SQL_TEST_USER=sa" "SQL_TEST_PASSWORD=$SA_PASSWORD" "SQL_PG_ENDPOINT=$h:$PG_PORT" "SQL_PG_PASSWORD=$PG_PASSWORD" "HISTORIAN_SQL_TEST_CONNECTION=Server=$h,$SQL_PORT;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=True;Encrypt=False" "HISTORIAN_QUESTDB_TEST_CONNECTION=Host=$h;Port=$QDB_PORT;Username=admin;Password=quest;Database=qdb;ServerCompatibilityMode=NoTypeLoading" "OPCUA_SIM_ENDPOINT=opc.tcp://$h:$OPC_PORT" "OPCUA_NAT_ENDPOINT=opc.tcp://$h:$OPC_NAT_PORT" "OPCUA_NAT_SERVER_CERT=$nat_cert" ) for e in "${exports[@]}"; do echo "export \"$e\"" [ -n "${GITHUB_ENV:-}" ] && echo "$e" >> "$GITHUB_ENV" done } case "${1:-}" in up) up ;; down) down ;; *) echo "usage: $0 up|down" >&2; exit 2 ;; esac