Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci-plugin-catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@ on:
paths:
- "tests/test_plugin_catalog.py"
- "tools/plugin_catalog.py"
- "tools/validate_ci_selection.py"
- "pyproject.toml"
- "plugins/python/**"
- "plugins/rust/python-package/*/Cargo.toml"
- "plugins/rust/python-package/*/plugin-manifest.yaml"
- ".github/workflows/ci-plugin-catalog.yaml"
- ".github/workflows/ci-rust-python-package.yaml"
- ".github/workflows/release-rust-python-package.yaml"
- ".github/workflows/ci-python-package.yaml"
- ".github/workflows/release-python-package.yaml"
pull_request:
branches: [main]
paths:
- "tests/test_plugin_catalog.py"
- "tools/plugin_catalog.py"
- "tools/validate_ci_selection.py"
- "pyproject.toml"
- "plugins/python/**"
- "plugins/rust/python-package/*/Cargo.toml"
- "plugins/rust/python-package/*/plugin-manifest.yaml"
- ".github/workflows/ci-plugin-catalog.yaml"
- ".github/workflows/ci-rust-python-package.yaml"
- ".github/workflows/release-rust-python-package.yaml"
- ".github/workflows/ci-python-package.yaml"
- ".github/workflows/release-python-package.yaml"

concurrency:
group: ci-plugin-catalog-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref_name }}
Expand Down
206 changes: 206 additions & 0 deletions .github/workflows/ci-python-package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
name: CI Python Package Plugins

on:
push:
branches: [main]
paths:
- "Makefile"
- "pyproject.toml"
- "uv.lock"
- "plugins/python/**"
- "plugins/tests/**"
- "tools/**"
- "tests/**"
- ".github/workflows/ci-python-package.yaml"
- ".github/workflows/release-python-package.yaml"
pull_request:
branches: [main]
paths:
- "Makefile"
- "pyproject.toml"
- "uv.lock"
- "plugins/python/**"
- "plugins/tests/**"
- "tools/**"
- "tests/**"
- ".github/workflows/ci-python-package.yaml"
- ".github/workflows/release-python-package.yaml"
workflow_dispatch:

concurrency:
group: ci-python-package-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read
pull-requests: read

jobs:
validate-and-detect:
runs-on: ubuntu-latest
outputs:
python_plugins: ${{ steps.detect.outputs.python_plugins }}
has_python_plugins: ${{ steps.detect.outputs.has_python_plugins }}
python_plugin_count: ${{ steps.detect.outputs.python_plugin_count }}
python_release_validation_tags: ${{ steps.detect.outputs.python_release_validation_tags }}
has_python_release_validation_tags: ${{ steps.detect.outputs.has_python_release_validation_tags }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

- id: detect
shell: bash
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
selection="$(python3 tools/plugin_catalog.py ci-selection . diff "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}")"
elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
selection="$(python3 tools/plugin_catalog.py ci-selection . all '' '')"
elif [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then
selection="$(python3 tools/plugin_catalog.py ci-selection . all '' '')"
else
selection="$(python3 tools/plugin_catalog.py ci-selection . diff "${{ github.event.before }}" "${{ github.sha }}")"
fi

selection="$(printf '%s' "${selection}" | python3 tools/validate_ci_selection.py)"
python_plugins="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(json.dumps(json.load(sys.stdin)["python_plugins"]))')"
has_python_plugins="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(str(json.load(sys.stdin)["has_python_plugins"]).lower())')"
python_plugin_count="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(json.load(sys.stdin)["python_plugin_count"])')"
python_release_validation_tags="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(json.dumps(json.load(sys.stdin)["python_release_validation_tags"]))')"
has_python_release_validation_tags="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(str(json.load(sys.stdin)["has_python_release_validation_tags"]).lower())')"
{
echo "python_plugins=${python_plugins}"
echo "has_python_plugins=${has_python_plugins}"
echo "python_plugin_count=${python_plugin_count}"
echo "python_release_validation_tags=${python_release_validation_tags}"
echo "has_python_release_validation_tags=${has_python_release_validation_tags}"
} >> "$GITHUB_OUTPUT"

build-test:
needs: validate-and-detect
if: needs.validate-and-detect.outputs.has_python_plugins == 'true'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
plugin: ${{ fromJson(needs.validate-and-detect.outputs.python_plugins) }}
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

- name: Install uv
run: python -m pip install uv==0.9.30

- name: Sync plugin environment
working-directory: plugins/python/${{ matrix.plugin }}
run: make sync

- name: Plugin CI build verification
if: matrix.os == 'ubuntu-latest'
working-directory: plugins/python/${{ matrix.plugin }}
run: make ci-build

- name: Plugin CI verification
if: matrix.os != 'ubuntu-latest'
working-directory: plugins/python/${{ matrix.plugin }}
run: make ci

release-validation:
if: github.event_name == 'pull_request' && needs.validate-and-detect.outputs.has_python_release_validation_tags == 'true'
needs: validate-and-detect
strategy:
fail-fast: false
matrix:
tag: ${{ fromJson(needs.validate-and-detect.outputs.python_release_validation_tags) }}
permissions:
contents: read
pull-requests: read
id-token: write
uses: ./.github/workflows/release-python-package.yaml
with:
tag: ${{ matrix.tag }}
repository: testpypi
publish_enabled: false

create-release-tags:
needs:
- validate-and-detect
- build-test
- release-validation
if: >-
${{
always() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
needs.validate-and-detect.outputs.has_python_release_validation_tags == 'true' &&
needs.build-test.result == 'success' &&
(needs.release-validation.result == 'success' || needs.release-validation.result == 'skipped')
}}
runs-on: ubuntu-latest
permissions:
actions: write
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Create release tags for bumped plugin versions
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAGS: ${{ needs.validate-and-detect.outputs.python_release_validation_tags }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
while IFS= read -r tag; do
if [[ ! "${tag}" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "ERROR: unexpected characters in tag '${tag}'; aborting." >&2
exit 1
fi
if remote_sha="$(git ls-remote --exit-code --tags origin "refs/tags/${tag}" | awk '{print $1}')"; then
if [[ "${remote_sha}" != "${GITHUB_SHA}" ]]; then
echo "FATAL: tag '${tag}' already exists at ${remote_sha}, expected ${GITHUB_SHA}." >&2
exit 1
fi
echo "Tag ${tag} already exists remotely at ${GITHUB_SHA}; skipping tag creation."
else
if git show-ref --verify --quiet "refs/tags/${tag}"; then
local_sha="$(git rev-list -n 1 "${tag}")"
if [[ "${local_sha}" != "${GITHUB_SHA}" ]]; then
echo "FATAL: local tag '${tag}' exists at ${local_sha}, expected ${GITHUB_SHA}." >&2
exit 1
fi
else
git tag "${tag}" "${GITHUB_SHA}"
fi
git push origin "refs/tags/${tag}" || {
if remote_sha="$(git ls-remote --exit-code --tags origin "refs/tags/${tag}" | awk '{print $1}')" &&
[[ "${remote_sha}" == "${GITHUB_SHA}" ]]; then
echo "Tag ${tag} already exists remotely at ${GITHUB_SHA}; skipping."
else
echo "FATAL: push of '${tag}' failed." >&2
exit 1
fi
}
fi
gh workflow run release-python-package.yaml \
--ref "${tag}" \
-f "tag=${tag}" \
-f repository=pypi \
-f publish_enabled=true
release_run_url="$(gh run list -w release-python-package.yaml --limit 1 --json url --jq '.[0].url')"
echo "Dispatched PyPI release workflow for ${tag}: ${release_run_url}"
done < <(python3 -c 'import json, os; [print(tag) for tag in json.loads(os.environ["RELEASE_TAGS"])]')
33 changes: 24 additions & 9 deletions .github/workflows/ci-rust-python-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ jobs:
has_mutation_cargo_packages: ${{ steps.detect.outputs.has_mutation_cargo_packages }}
release_validation_tags: ${{ steps.detect.outputs.release_validation_tags }}
has_release_validation_tags: ${{ steps.detect.outputs.has_release_validation_tags }}
rust_plugins: ${{ steps.detect.outputs.rust_plugins }}
has_rust_plugins: ${{ steps.detect.outputs.has_rust_plugins }}
rust_plugin_count: ${{ steps.detect.outputs.rust_plugin_count }}
rust_release_validation_tags: ${{ steps.detect.outputs.rust_release_validation_tags }}
has_rust_release_validation_tags: ${{ steps.detect.outputs.has_rust_release_validation_tags }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand Down Expand Up @@ -98,6 +103,11 @@ jobs:
has_mutation_cargo_packages="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(str(json.load(sys.stdin)["has_mutation_cargo_packages"]).lower())')"
release_validation_tags="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(json.dumps(json.load(sys.stdin)["release_validation_tags"]))')"
has_release_validation_tags="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(str(json.load(sys.stdin)["has_release_validation_tags"]).lower())')"
rust_plugins="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(json.dumps(json.load(sys.stdin)["rust_plugins"]))')"
has_rust_plugins="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(str(json.load(sys.stdin)["has_rust_plugins"]).lower())')"
rust_plugin_count="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(json.load(sys.stdin)["rust_plugin_count"])')"
rust_release_validation_tags="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(json.dumps(json.load(sys.stdin)["rust_release_validation_tags"]))')"
has_rust_release_validation_tags="$(printf '%s' "${selection}" | python3 -c 'import json, sys; print(str(json.load(sys.stdin)["has_rust_release_validation_tags"]).lower())')"
if [[ "${has_plugins}" == "false" ]]; then
has_plugins_output="false"
else
Expand All @@ -114,16 +124,21 @@ jobs:
echo "has_mutation_cargo_packages=${has_mutation_cargo_packages_output}"
echo "release_validation_tags=${release_validation_tags}"
echo "has_release_validation_tags=${has_release_validation_tags}"
echo "rust_plugins=${rust_plugins}"
echo "has_rust_plugins=${has_rust_plugins}"
echo "rust_plugin_count=${rust_plugin_count}"
echo "rust_release_validation_tags=${rust_release_validation_tags}"
echo "has_rust_release_validation_tags=${has_rust_release_validation_tags}"
} >> "$GITHUB_OUTPUT"

build-test:
needs: validate-and-detect
if: needs.validate-and-detect.outputs.has_plugins == 'true'
if: needs.validate-and-detect.outputs.has_rust_plugins == 'true'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
plugin: ${{ fromJson(needs.validate-and-detect.outputs.plugins) }}
plugin: ${{ fromJson(needs.validate-and-detect.outputs.rust_plugins) }}
runs-on: ${{ matrix.os }}
defaults:
run:
Expand Down Expand Up @@ -264,7 +279,7 @@ jobs:

coverage:
needs: validate-and-detect
if: needs.validate-and-detect.outputs.has_plugins == 'true'
if: needs.validate-and-detect.outputs.has_rust_plugins == 'true'
runs-on: ubuntu-latest
defaults:
run:
Expand Down Expand Up @@ -305,7 +320,7 @@ jobs:
env:
CARGO_PACKAGES: ${{ needs.validate-and-detect.outputs.cargo_packages }}
NEXTEST_PROFILE: ci
PLUGINS: ${{ needs.validate-and-detect.outputs.plugins }}
PLUGINS: ${{ needs.validate-and-detect.outputs.rust_plugins }}
PYO3_PYTHON: python
run: |
mkdir -p coverage
Expand Down Expand Up @@ -333,7 +348,7 @@ jobs:

- name: Enforce per-plugin coverage floor
env:
PLUGINS: ${{ needs.validate-and-detect.outputs.plugins }}
PLUGINS: ${{ needs.validate-and-detect.outputs.rust_plugins }}
run: python3 tools/plugin_catalog.py coverage-check . coverage/cobertura.xml 90.00 "${PLUGINS}"

- name: Upload coverage to Codecov
Expand Down Expand Up @@ -370,12 +385,12 @@ jobs:
cargo doc "${cargo_args[@]}" --lib --no-deps --document-private-items

release-validation:
if: github.event_name == 'pull_request' && needs.validate-and-detect.outputs.has_release_validation_tags == 'true'
if: github.event_name == 'pull_request' && needs.validate-and-detect.outputs.has_rust_release_validation_tags == 'true'
needs: validate-and-detect
strategy:
fail-fast: false
matrix:
tag: ${{ fromJson(needs.validate-and-detect.outputs.release_validation_tags) }}
tag: ${{ fromJson(needs.validate-and-detect.outputs.rust_release_validation_tags) }}
permissions:
contents: read
pull-requests: read
Expand All @@ -400,7 +415,7 @@ jobs:
always() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
needs.validate-and-detect.outputs.has_release_validation_tags == 'true' &&
needs.validate-and-detect.outputs.has_rust_release_validation_tags == 'true' &&
needs.build-test.result == 'success' &&
needs.security-policy.result == 'success' &&
needs.coverage.result == 'success' &&
Expand All @@ -420,7 +435,7 @@ jobs:
- name: Create release tags for bumped plugin versions
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAGS: ${{ needs.validate-and-detect.outputs.release_validation_tags }}
RELEASE_TAGS: ${{ needs.validate-and-detect.outputs.rust_release_validation_tags }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
Expand Down
18 changes: 14 additions & 4 deletions .github/workflows/plugin-maintenance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
plugins=(
rust_plugins=(
encoded_exfil_detection
pii_filter
rate_limiter
Expand All @@ -71,14 +71,25 @@ jobs:
sql_sanitizer
url_reputation
)
for plugin in "${plugins[@]}"; do
for plugin in "${rust_plugins[@]}"; do
echo "::group::Testing ${plugin}"
pushd "plugins/rust/python-package/${plugin}"
make sync
NEXTEST_PROFILE=ci make ci
popd
echo "::endgroup::"
done
python_plugins=(
ica_metering_exporter
)
for plugin in "${python_plugins[@]}"; do
echo "::group::Testing ${plugin}"
pushd "plugins/python/${plugin}"
make sync
make ci
popd
echo "::endgroup::"
done

# --- Open PR if lock files changed ---

Expand All @@ -95,10 +106,9 @@ jobs:
### Changes
- `cargo update` applied to Rust workspace (`Cargo.lock`)
- `uv lock --upgrade` applied to root Python workspace (`uv.lock`)
- `uv lock --upgrade` applied to `sql_sanitizer` plugin lock file

### Validation performed in CI
- All 7 plugins built and tested on `ubuntu-latest`
- All 8 managed plugins built and tested on `ubuntu-latest`
- `cargo deny check advisories` passed — no new CVEs

### Reviewer checklist
Expand Down
Loading