Skip to content

FIX: count attacks without converters as no_converters in analytics #9792

FIX: count attacks without converters as no_converters in analytics

FIX: count attacks without converters as no_converters in analytics #9792

Workflow file for this run

# 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