Repository navigation
FIX: count attacks without converters as no_converters in analytics #9792
Workflow file for this run
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
| # Tests Docker image builds for devcontainer and production | |
| name: docker_build | |
| on: | |
| push: | |
| branches: | |
| - "main" | |
| - "releases/v*" | |
| pull_request: | |
| branches: | |
| - "main" | |
| - "releases/**" | |
| merge_group: | |
| workflow_dispatch: | |
| inputs: | |
| pypiVersion: | |
| description: 'Exact published PyPI version (defaults to the latest stable release)' | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| defaults: | |
| run: | |
| shell: bash | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| validate-compose-bindings: | |
| name: Validate Compose Bindings | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Require loopback-only published ports | |
| working-directory: docker | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $env:PYRIT_SOURCE_COMMIT = git rev-parse --verify HEAD | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Cannot determine source commit." | |
| } | |
| $sourceStatus = git status --porcelain | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Cannot determine source dirty state." | |
| } | |
| $env:PYRIT_SOURCE_DIRTY = if ($sourceStatus) { 'true' } else { 'false' } | |
| $json = docker compose --file docker-compose.yaml --profile gui --profile jupyter config --format json | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Docker Compose configuration resolution failed." | |
| } | |
| $services = ($json | ConvertFrom-Json).services | |
| $expectedPorts = @{ 'pyrit-gui' = 8000; 'pyrit-jupyter' = 8888 } | |
| foreach ($service in $expectedPorts.Keys) { | |
| $port = $expectedPorts[$service] | |
| $bindings = @($services.$service.ports) | |
| if ($bindings.Count -ne 1 -or | |
| $bindings[0].host_ip -ne '127.0.0.1' -or | |
| $bindings[0].published -ne "$port" -or | |
| $bindings[0].target -ne $port -or | |
| $bindings[0].protocol -ne 'tcp') { | |
| throw "Expected only 127.0.0.1:${port}:${port}/tcp for ${service}; got $($bindings | ConvertTo-Json -Compress)." | |
| } | |
| Write-Output "${service}: verified 127.0.0.1:${port}:${port}/tcp" | |
| } | |
| build-and-test-local: | |
| name: Build and test (local) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| devcontainer: ${{ steps.devcontainer.outcome }} | |
| production: ${{ steps.production.outcome }} | |
| import: ${{ steps.import.outcome }} | |
| gui: ${{ steps.gui.outcome }} | |
| jupyter: ${{ steps.jupyter.outcome }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Record source provenance | |
| id: provenance | |
| run: | | |
| source_status=$(git status --porcelain) | |
| if [[ -n "$source_status" ]]; then | |
| echo "::error::Source checkout must be clean for compatibility stamping." | |
| exit 1 | |
| fi | |
| commit=$(git rev-parse HEAD) | |
| echo "commit=$commit" >> "$GITHUB_OUTPUT" | |
| - name: Set up cached devcontainer builder | |
| uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | |
| - name: Build devcontainer image | |
| id: devcontainer | |
| uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 | |
| with: | |
| context: .devcontainer | |
| file: .devcontainer/Dockerfile | |
| push: false | |
| tags: pyrit-devcontainer:latest | |
| load: true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Select the daemon's builder explicitly: the cached builder cannot see loaded images. | |
| - name: Build production image (local) | |
| id: production | |
| uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 | |
| with: | |
| builder: default | |
| context: . | |
| file: docker/Dockerfile | |
| push: false | |
| tags: pyrit:local-test | |
| load: true | |
| build-args: | | |
| BASE_IMAGE=pyrit-devcontainer:latest | |
| PYRIT_SOURCE=local | |
| GIT_COMMIT=${{ steps.provenance.outputs.commit }} | |
| GIT_MODIFIED=false | |
| # Run every smoke check after a successful build, even if another check fails. | |
| - name: Test PyRIT import (local) | |
| id: import | |
| if: ${{ !cancelled() && steps.production.outcome == 'success' }} | |
| timeout-minutes: 3 | |
| run: exec bash docker/smoke_test.sh pyrit:local-test import | |
| - name: Test GUI mode (local) | |
| id: gui | |
| if: ${{ !cancelled() && steps.production.outcome == 'success' }} | |
| timeout-minutes: 3 | |
| run: exec bash docker/smoke_test.sh pyrit:local-test gui | |
| - name: Test Jupyter mode (local) | |
| id: jupyter | |
| if: ${{ !cancelled() && steps.production.outcome == 'success' }} | |
| timeout-minutes: 3 | |
| run: exec bash docker/smoke_test.sh pyrit:local-test jupyter | |
| build-and-test-pypi: | |
| name: Build and test (PyPI) | |
| runs-on: ubuntu-latest | |
| # Restore main-push checks after validating 1.2.0: https://github.com/microsoft/PyRIT/issues/3007 | |
| if: github.event_name == 'workflow_dispatch' | |
| outputs: | |
| production: ${{ steps.production.outcome }} | |
| import: ${{ steps.import.outcome }} | |
| gui: ${{ steps.gui.outcome }} | |
| jupyter: ${{ steps.jupyter.outcome }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Select PyPI release | |
| id: pypi-version | |
| timeout-minutes: 5 | |
| env: | |
| PYRIT_PYPI_VERSION: ${{ inputs.pypiVersion }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version=$(python3 build_scripts/select_pypi_version.py --version "${PYRIT_PYPI_VERSION:-}") | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "Testing published PyPI release: $version" | |
| - name: Set up cached devcontainer builder | |
| uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | |
| - name: Build devcontainer image | |
| id: devcontainer | |
| uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 | |
| with: | |
| context: .devcontainer | |
| file: .devcontainer/Dockerfile | |
| push: false | |
| tags: pyrit-devcontainer:latest | |
| load: true | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Build production image (PyPI) | |
| id: production | |
| uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 | |
| with: | |
| builder: default | |
| context: . | |
| file: docker/Dockerfile | |
| push: false | |
| tags: pyrit:pypi-test | |
| load: true | |
| build-args: | | |
| BASE_IMAGE=pyrit-devcontainer:latest | |
| PYRIT_SOURCE=pypi | |
| PYRIT_VERSION=${{ steps.pypi-version.outputs.version }} | |
| - name: Test PyRIT import (PyPI) | |
| id: import | |
| if: ${{ !cancelled() && steps.production.outcome == 'success' }} | |
| timeout-minutes: 3 | |
| run: exec bash docker/smoke_test.sh pyrit:pypi-test import | |
| - name: Test GUI mode (PyPI) | |
| id: gui | |
| if: ${{ !cancelled() && steps.production.outcome == 'success' }} | |
| timeout-minutes: 3 | |
| run: exec bash docker/smoke_test.sh pyrit:pypi-test gui | |
| - name: Test Jupyter mode (PyPI) | |
| id: jupyter | |
| if: ${{ !cancelled() && steps.production.outcome == 'success' }} | |
| timeout-minutes: 3 | |
| run: exec bash docker/smoke_test.sh pyrit:pypi-test jupyter | |
| # Keep the existing required check names. A failed/cancelled execution job or | |
| # a missing/skipped stage must never produce a successful compatibility check. | |
| local-checks: | |
| name: ${{ matrix.name }} | |
| needs: build-and-test-local | |
| if: ${{ always() }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: Build Devcontainer | |
| output: devcontainer | |
| - name: Build Production (local) | |
| output: production | |
| - name: Test Import (local) | |
| output: import | |
| - name: Test GUI (local) | |
| output: gui | |
| - name: Test Jupyter (local) | |
| output: jupyter | |
| steps: | |
| - name: Check execution and stage results | |
| env: | |
| JOB_RESULT: ${{ needs.build-and-test-local.result }} | |
| STAGE_RESULT: ${{ needs.build-and-test-local.outputs[matrix.output] }} | |
| run: | | |
| echo "Docker execution: $JOB_RESULT" | |
| echo "Requested stage: $STAGE_RESULT" | |
| if [[ "$JOB_RESULT" != success || "$STAGE_RESULT" != success ]]; then | |
| echo "::error::Both the Docker execution job and this stage must succeed. See Build and test (local)." | |
| exit 1 | |
| fi | |
| # Literal names remain visible when PyPI is disabled. Job-level conditions | |
| # preserve skipped conclusions without starting runners for disabled gates. | |
| pypi-production-check: | |
| name: Build Production (PyPI) | |
| needs: build-and-test-pypi | |
| if: ${{ always() && (github.event_name == 'workflow_dispatch') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check execution and stage results | |
| env: | |
| JOB_RESULT: ${{ needs.build-and-test-pypi.result }} | |
| STAGE_RESULT: ${{ needs.build-and-test-pypi.outputs.production }} | |
| run: | | |
| echo "Docker execution: $JOB_RESULT" | |
| echo "Requested stage: $STAGE_RESULT" | |
| if [[ "$JOB_RESULT" != success || "$STAGE_RESULT" != success ]]; then | |
| echo "::error::Both the Docker execution job and this stage must succeed. See Build and test (PyPI)." | |
| exit 1 | |
| fi | |
| pypi-import-check: | |
| name: Test Import (PyPI) | |
| needs: build-and-test-pypi | |
| if: ${{ always() && (github.event_name == 'workflow_dispatch') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check execution and stage results | |
| env: | |
| JOB_RESULT: ${{ needs.build-and-test-pypi.result }} | |
| STAGE_RESULT: ${{ needs.build-and-test-pypi.outputs.import }} | |
| run: | | |
| echo "Docker execution: $JOB_RESULT" | |
| echo "Requested stage: $STAGE_RESULT" | |
| if [[ "$JOB_RESULT" != success || "$STAGE_RESULT" != success ]]; then | |
| echo "::error::Both the Docker execution job and this stage must succeed. See Build and test (PyPI)." | |
| exit 1 | |
| fi | |
| pypi-gui-check: | |
| name: Test GUI (PyPI) | |
| needs: build-and-test-pypi | |
| if: ${{ always() && (github.event_name == 'workflow_dispatch') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check execution and stage results | |
| env: | |
| JOB_RESULT: ${{ needs.build-and-test-pypi.result }} | |
| STAGE_RESULT: ${{ needs.build-and-test-pypi.outputs.gui }} | |
| run: | | |
| echo "Docker execution: $JOB_RESULT" | |
| echo "Requested stage: $STAGE_RESULT" | |
| if [[ "$JOB_RESULT" != success || "$STAGE_RESULT" != success ]]; then | |
| echo "::error::Both the Docker execution job and this stage must succeed. See Build and test (PyPI)." | |
| exit 1 | |
| fi | |
| pypi-jupyter-check: | |
| name: Test Jupyter (PyPI) | |
| needs: build-and-test-pypi | |
| if: ${{ always() && (github.event_name == 'workflow_dispatch') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check execution and stage results | |
| env: | |
| JOB_RESULT: ${{ needs.build-and-test-pypi.result }} | |
| STAGE_RESULT: ${{ needs.build-and-test-pypi.outputs.jupyter }} | |
| run: | | |
| echo "Docker execution: $JOB_RESULT" | |
| echo "Requested stage: $STAGE_RESULT" | |
| if [[ "$JOB_RESULT" != success || "$STAGE_RESULT" != success ]]; then | |
| echo "::error::Both the Docker execution job and this stage must succeed. See Build and test (PyPI)." | |
| exit 1 | |
| fi |