Release Tests: release/1.5.0 #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Collection Tests | |
| run-name: "Release Tests: ${{ github.head_ref || github.ref_name }}" | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| - release/* | |
| workflow_dispatch: | |
| inputs: | |
| test_type: | |
| description: 'Test type' | |
| required: true | |
| default: 'lint' | |
| type: choice | |
| options: | |
| - lint | |
| - sanity | |
| - unit | |
| - integration | |
| - all | |
| ansible_lint_version: | |
| description: 'ansible-lint version to use for lint/all (empty = latest)' | |
| required: false | |
| default: '' | |
| type: string | |
| ansible_core: | |
| description: 'ansible-core version(s), as a JSON array, e.g. ["2.20"] or ["2.18","2.19"]' | |
| required: false | |
| default: '["2.18","2.19","2.20"]' | |
| type: string | |
| zabbix_version: | |
| description: 'Zabbix version (for integration tests)' | |
| required: false | |
| default: '7.0' | |
| type: choice | |
| options: | |
| - '7.0' | |
| - '7.4' | |
| permissions: | |
| contents: read | |
| jobs: | |
| init: | |
| name: "Initialize workflow" | |
| runs-on: ubuntu-latest | |
| outputs: | |
| test_type: ${{ inputs.test_type || 'all' }} | |
| ansible_lint_version: ${{ inputs.ansible_lint_version || ''}} | |
| ansible_core: ${{ inputs.ansible_core || '["2.18","2.19","2.20"]'}} | |
| zabbix_version: ${{ inputs.zabbix_version || '7.0' }} | |
| steps: | |
| - name: Init workflow | |
| run: | | |
| # Init workflow | |
| echo "Workflow initialized" | |
| test: | |
| name: "${{ needs.init.outputs.test_type }} / ansible-core ${{ matrix.ansible_version }} / zbx ${{ needs.init.outputs.zabbix_version }}" | |
| runs-on: ansible-test-collection | |
| timeout-minutes: 180 | |
| needs: init | |
| env: | |
| TEST_TYPE: ${{ needs.init.outputs.test_type }} | |
| ZABBIX_VERSION: ${{ needs.init.outputs.zabbix_version }} | |
| SRC_DIR: ${{ github.workspace }} | |
| COLLECTIONS_PATH: /home/runner/.ansible/collections | |
| COLLECTION_DIR: /home/runner/.ansible/collections/ansible_collections/zabbix/zabbix | |
| LANG: en_US.UTF-8 | |
| LC_ALL: en_US.UTF-8 | |
| COMPOSE_FILE: .github/docker-compose.yml | |
| # cancel-in-progress only on pull_request; preserve scheduled and dispatch runs. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-${{ needs.init.outputs.test_type }}-${{ matrix.ansible_version }} | |
| cancel-in-progress: true | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ansible_version: ${{ needs.init.outputs.ansible_core && fromJSON(needs.init.outputs.ansible_core) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@f9e715a95fcd1f9253f77dd28f11e88d2d6460c7 # v6.0.3 | |
| # shellcheck is not needed on the host: "ansible-test sanity --docker" ships its own toolchain. | |
| - name: Set up system dependencies | |
| env: | |
| COMPOSE_VERSION: v5.1.4 | |
| run: | | |
| # Set up system dependencies | |
| echo "::group::Install locales" | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends locales | |
| sudo locale-gen en_US.UTF-8 | |
| sudo update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 | |
| echo "::endgroup::" | |
| if ! docker compose version >/dev/null 2>&1; then | |
| echo "::group::Install Docker Compose plugin" | |
| arch=$(uname -m) | |
| case "$arch" in | |
| x86_64) arch=x86_64 ;; | |
| aarch64) arch=aarch64 ;; | |
| *) echo "Unsupported arch for docker compose plugin: $arch"; exit 1 ;; | |
| esac | |
| mkdir -p ~/.docker/cli-plugins | |
| curl -sSL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-${arch}" \ | |
| -o ~/.docker/cli-plugins/docker-compose | |
| chmod +x ~/.docker/cli-plugins/docker-compose | |
| echo "::endgroup::" | |
| fi | |
| # Reads CONTROLLER_PYTHON_VERSIONS from ansible-test's constants on stable-{ver}; | |
| # picks the highest supported Python. Falls back to 3.13 on any error. | |
| - name: Resolve controller Python for ansible-core ${{ matrix.ansible_version }} | |
| id: pyver | |
| env: | |
| ANSIBLE_CORE_VERSION: ${{ matrix.ansible_version }} | |
| run: | | |
| # Resolve controller Python for ansible-core | |
| if ! [[ "$ANSIBLE_CORE_VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then | |
| echo "Unexpected ansible-core version format: ${ANSIBLE_CORE_VERSION}" >&2 | |
| echo "python-version=3.13" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| pyver=$(curl -fsSL \ | |
| "https://raw.githubusercontent.com/ansible/ansible/stable-${ANSIBLE_CORE_VERSION}/test/lib/ansible_test/_util/target/common/constants.py" \ | |
| | python3 -c " | |
| import sys, re | |
| m = re.search(r'CONTROLLER_PYTHON_VERSIONS\s*=\s*\(([^)]+)\)', sys.stdin.read()) | |
| if m: | |
| vs = sorted([v.strip().strip(\"'\\\"\") for v in m.group(1).split(',') | |
| if re.match(r'^[0-9]+\.[0-9]+$', v.strip().strip(\"'\\\"\"))], | |
| key=lambda v: tuple(int(x) for x in v.split('.'))) | |
| print(vs[-1] if vs else '3.13') | |
| else: | |
| print('3.13') | |
| " 2>/dev/null || echo '3.13') | |
| echo "Resolved Python: ${pyver}" | |
| echo "python-version=${pyver}" >> "$GITHUB_OUTPUT" | |
| - name: Set up Python ${{ steps.pyver.outputs.python-version }} | |
| uses: actions/setup-python@2e3e4b15a884dc73a63f962bff250a855150a234 # v6.2.0 | |
| with: | |
| python-version: ${{ steps.pyver.outputs.python-version }} | |
| cache-dependency-path: requirements.txt | |
| cache: pip | |
| # Free-form inputs passed via env to avoid shell injection on self-hosted runner. | |
| - name: Install ansible-core ${{ matrix.ansible_version }} and tools | |
| env: | |
| ANSIBLE_CORE_VERSION: ${{ matrix.ansible_version }} | |
| ANSIBLE_LINT_VERSION: ${{ needs.init.outputs.ansible_lint_version }} | |
| run: | | |
| # Install ansible-core | |
| lint_req="ansible-lint" | |
| [ -n "$ANSIBLE_LINT_VERSION" ] && lint_req="ansible-lint==${ANSIBLE_LINT_VERSION}" | |
| echo "::group::Install ansible-core and tools" | |
| pip install \ | |
| "ansible-core~=${ANSIBLE_CORE_VERSION}.0" \ | |
| "$lint_req" \ | |
| antsibull-changelog \ | |
| "netaddr>=0.8" \ | |
| "Jinja2>=3.1.2" \ | |
| pytest pytest-xdist pytest-mock | |
| echo "::endgroup::" | |
| echo "::group::Installed Python requirements" | |
| pip install -r "$SRC_DIR/requirements.txt" | |
| echo "::endgroup::" | |
| # --no-deps: Galaxy dependencies are handled by the next dedicated step. | |
| - name: Install collection | |
| working-directory: ${{ env.SRC_DIR }} | |
| run: | | |
| # Install collection | |
| ansible-galaxy collection install ./ -p "$COLLECTIONS_PATH" -f --no-deps | |
| # --upgrade: always installs latest Galaxy versions to catch dependency drift early. | |
| - name: Install Galaxy dependencies | |
| run: | | |
| # Install Galaxy dependencies | |
| ansible-galaxy collection install \ | |
| ansible.utils \ | |
| ansible.posix \ | |
| ansible.netcommon \ | |
| oracle.oci \ | |
| -p "$COLLECTIONS_PATH" --upgrade | |
| - name: Show test environment | |
| run: | | |
| # Show test environment | |
| echo "::group::Test environment" | |
| echo "commit : $(git log --oneline -1)" | |
| echo "ansible-core : $(ansible --version | head -1)" | |
| echo "python : $(python --version)" | |
| echo "test type : $TEST_TYPE" | |
| echo "ref : ${{ github.ref_name }} (${{ github.sha }})" | |
| echo "zabbix : $ZABBIX_VERSION" | |
| echo "::endgroup::" | |
| - name: Validate collection structure | |
| working-directory: ${{ env.COLLECTION_DIR }} | |
| run: | | |
| # Validate collection structure | |
| [ -f MANIFEST.json ] || { echo "MANIFEST.json not found"; exit 1; } | |
| [ -d roles ] || { echo "roles directory not found"; exit 1; } | |
| [ -d plugins ] || { echo "plugins directory not found"; exit 1; } | |
| - name: Validate changelog | |
| working-directory: ${{ env.SRC_DIR }} | |
| if: env.TEST_TYPE == 'lint' || env.TEST_TYPE == 'all' | |
| run: antsibull-changelog lint | |
| # ZABBIX_VERSION (workflow-level env) is interpolated by compose into image tags. | |
| - name: Start Zabbix stack | |
| id: start_zabbix | |
| if: env.TEST_TYPE == 'integration' || env.TEST_TYPE == 'all' | |
| continue-on-error: ${{ env.TEST_TYPE == 'all' }} | |
| run: | | |
| # --wait ensures containers pass healthchecks; API readiness (incl. DB upgrade) | |
| # is verified separately by "Wait for Zabbix API". | |
| docker compose -f "$COMPOSE_FILE" up -d --wait --wait-timeout 120 postgres zabbix-server zabbix-web | |
| # Tests connect to "testhost" on port 80 (httpapi/inventory default). | |
| - name: Configure testhost | |
| if: env.TEST_TYPE == 'integration' || env.TEST_TYPE == 'all' | |
| run: | | |
| # Configure testhost | |
| grep -q testhost /etc/hosts || echo "127.0.0.1 testhost" | sudo tee -a /etc/hosts | |
| # Lint runs from SRC_DIR (full source tree), not the installed collection. | |
| - name: Lint (ansible-lint) | |
| id: test_lint | |
| if: env.TEST_TYPE == 'lint' || env.TEST_TYPE == 'all' | |
| continue-on-error: ${{ env.TEST_TYPE == 'all' }} | |
| timeout-minutes: 15 | |
| working-directory: ${{ env.SRC_DIR }} | |
| run: | | |
| # Run ansible-lint | |
| echo "::group::Running ansible-lint" | |
| ansible-lint -v | |
| echo "::endgroup::" | |
| # ansible-test < 2.18 can't locate its K8s pod container via Docker | |
| # (ContainerNotFoundError); fall back to --python for those versions. | |
| - name: Sanity tests | |
| id: test_sanity | |
| if: env.TEST_TYPE == 'sanity' || env.TEST_TYPE == 'all' | |
| continue-on-error: ${{ env.TEST_TYPE == 'all' }} | |
| timeout-minutes: 30 | |
| working-directory: ${{ env.COLLECTION_DIR }} | |
| env: | |
| ANSIBLE_CORE_VERSION: ${{ matrix.ansible_version }} | |
| CONTROLLER_PYTHON: ${{ steps.pyver.outputs.python-version }} | |
| run: | | |
| # Sanity tests | |
| echo "::group::Running sanity tests" | |
| IFS='.' read -r _maj _min <<< "$ANSIBLE_CORE_VERSION" | |
| if (( _maj == 2 && _min < 18 )); then | |
| ansible-test sanity --python "$CONTROLLER_PYTHON" -v | |
| else | |
| ansible-test sanity --docker -v | |
| fi | |
| echo "::endgroup::" | |
| # Same --docker / --python split as sanity. | |
| - name: Unit tests | |
| id: test_unit | |
| if: env.TEST_TYPE == 'unit' || env.TEST_TYPE == 'all' | |
| continue-on-error: ${{ env.TEST_TYPE == 'all' }} | |
| timeout-minutes: 30 | |
| working-directory: ${{ env.COLLECTION_DIR }} | |
| env: | |
| ANSIBLE_CORE_VERSION: ${{ matrix.ansible_version }} | |
| CONTROLLER_PYTHON: ${{ steps.pyver.outputs.python-version }} | |
| run: | | |
| # Unit tests | |
| echo "::group::Running unit tests" | |
| IFS='.' read -r _maj _min <<< "$ANSIBLE_CORE_VERSION" | |
| if (( _maj == 2 && _min < 18 )); then | |
| ansible-test units --python "$CONTROLLER_PYTHON" -v | |
| else | |
| ansible-test units --docker -v | |
| fi | |
| echo "::endgroup::" | |
| - name: Wait for Zabbix API | |
| id: wait_zabbix | |
| if: env.TEST_TYPE == 'integration' || env.TEST_TYPE == 'all' | |
| continue-on-error: ${{ env.TEST_TYPE == 'all' }} | |
| timeout-minutes: 7 | |
| run: | | |
| # Wait for Zabbix API | |
| echo "Waiting for Zabbix API to be ready (incl. DB upgrade)..." | |
| echo "::group::Polling Zabbix API for readiness" | |
| for i in $(seq 1 60); do | |
| response=$(curl -sf --connect-timeout 5 http://localhost:8080/api_jsonrpc.php \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"jsonrpc":"2.0","method":"user.login","params":{"username":"Admin","password":"zabbix"},"id":1}' || true) | |
| if echo "$response" | grep -q '"result"'; then | |
| echo "::notice::Zabbix API is ready (login OK)" | |
| docker compose -f "$COMPOSE_FILE" logs zabbix-server | tail -10 | |
| echo "::endgroup::" | |
| exit 0 | |
| fi | |
| if [ $((i % 6)) -eq 0 ]; then | |
| echo "attempt $i/60 - last response: $response" | |
| docker compose -f "$COMPOSE_FILE" ps | |
| fi | |
| sleep 5 | |
| done | |
| echo "::endgroup::" | |
| echo "::error::Zabbix API not responding after 5 minutes" | |
| echo "::group::Zabbix Server Logs" | |
| docker compose -f "$COMPOSE_FILE" logs zabbix-server | tail -50 | |
| echo "::endgroup::" | |
| echo "::group::Zabbix Web Logs" | |
| docker compose -f "$COMPOSE_FILE" logs zabbix-web | tail -30 | |
| echo "::endgroup::" | |
| exit 1 | |
| # Skip if Zabbix never became ready — avoids burning the 60-minute test timeout. | |
| - name: Integration tests | |
| id: test_integration | |
| if: >- | |
| (env.TEST_TYPE == 'integration' || env.TEST_TYPE == 'all') && | |
| steps.wait_zabbix.outcome == 'success' | |
| continue-on-error: ${{ env.TEST_TYPE == 'all' }} | |
| timeout-minutes: 60 | |
| working-directory: ${{ env.COLLECTION_DIR }} | |
| run: | | |
| # Integration tests | |
| echo "::group::Running integration tests" | |
| ansible-test integration -v | |
| echo "::endgroup::" | |
| # Single gate: in 'all' mode each test step has continue-on-error; this step fails the job if any did. | |
| - name: Check test results | |
| if: always() && env.TEST_TYPE == 'all' | |
| run: | | |
| # Check test results | |
| echo "::group::Checking test results" | |
| failed=() | |
| [ "${{ steps.test_lint.outcome }}" = "failure" ] && failed+=("lint") | |
| [ "${{ steps.test_sanity.outcome }}" = "failure" ] && failed+=("sanity") | |
| [ "${{ steps.test_unit.outcome }}" = "failure" ] && failed+=("unit") | |
| [ "${{ steps.start_zabbix.outcome }}" = "failure" ] && failed+=("integration (stack start)") | |
| [ "${{ steps.wait_zabbix.outcome }}" = "failure" ] && failed+=("integration (zabbix api)") | |
| [ "${{ steps.test_integration.outcome }}" = "failure" ] && failed+=("integration") | |
| if (( ${#failed[@]} > 0 )); then | |
| echo "Failed test types: ${failed[*]}" | |
| exit 1 | |
| fi | |
| echo "::endgroup::" | |
| echo "::notice::All test types passed." | |
| - name: Collect logs on failure | |
| if: failure() && (env.TEST_TYPE == 'integration' || env.TEST_TYPE == 'all') | |
| run: | | |
| # Collect logs on failure | |
| echo "::group::Docker Containers" | |
| docker ps -a | |
| echo "::endgroup::" | |
| echo "::group::Zabbix Server Logs" | |
| docker compose -f "$COMPOSE_FILE" logs zabbix-server | tail -100 | |
| echo "::endgroup::" | |
| echo "::group::Zabbix Web Logs" | |
| docker compose -f "$COMPOSE_FILE" logs zabbix-web | tail -50 | |
| echo "::endgroup::" | |
| echo "::group::PostgreSQL Logs" | |
| docker compose -f "$COMPOSE_FILE" logs postgres | tail -30 | |
| echo "::endgroup::" |