Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
dd23066
test: record automatic naming performance baseline
marcinpsk Aug 23, 2026
5ba6398
test: isolate virtual chassis callback baseline
marcinpsk Aug 23, 2026
8327c05
docs: record interface family architecture decisions
marcinpsk Aug 23, 2026
a26d191
test: preserve profiler database errors
marcinpsk Aug 23, 2026
d87bd1d
ci: audit workflows and parallelize tests
marcinpsk Aug 23, 2026
d466d23
refactor: extract rule selection
marcinpsk Aug 23, 2026
c986495
fix: address performance review findings
marcinpsk Aug 23, 2026
a40fe73
fix: isolate signal performance measurements
marcinpsk Aug 23, 2026
ccfd618
fix: scrub untagged dollar literals
marcinpsk Aug 23, 2026
4deee17
refactor: extract naming evaluation
marcinpsk Aug 23, 2026
7ea7845
refactor: execute installed family plans
marcinpsk Aug 23, 2026
10b570c
fix: propagate installed family failures
marcinpsk Aug 23, 2026
87c1233
test: cover flat family execution on older NetBox
marcinpsk Aug 23, 2026
8099650
fix: preserve installed family compatibility
marcinpsk Aug 23, 2026
538aa5e
fix: retain legacy family selection
marcinpsk Aug 23, 2026
0e62d3e
fix: read leftover interfaces from the module database alias
marcinpsk Aug 25, 2026
3d0100d
fix: bound the virtual-chassis position matchers
marcinpsk Aug 25, 2026
3119f2b
refactor: split flat-family candidate discovery
marcinpsk Aug 25, 2026
099f62b
refactor: build channelized families through family plans
marcinpsk Aug 25, 2026
624c679
fix: revalidate the module topology before building a family
marcinpsk Aug 25, 2026
8463f61
fix: lock only the interfaces the reconciliation restores
marcinpsk Aug 25, 2026
f625e3b
refactor: plan prediction and preview from prospective families
marcinpsk Aug 25, 2026
b569811
fix: classify a prospective channel the way the apply path does
marcinpsk Aug 25, 2026
556475b
refactor: apply a rule batch family by family
marcinpsk Aug 25, 2026
270a93a
fix: keep a family's own channels out of the flat-expansion count
marcinpsk Aug 25, 2026
27e772e
test: measure the channelized per-module cost over the modules it adds
marcinpsk Aug 25, 2026
6ecece3
fix: release the batch template cache when pinning it fails
marcinpsk Aug 28, 2026
408b10e
refactor: convert flat families through conversion plans
marcinpsk Aug 28, 2026
666d7c6
test: write out-of-band renames the way another actor writes them
marcinpsk Aug 28, 2026
247ea82
refactor: install module families through the family package
marcinpsk Aug 28, 2026
803d6c0
fix: guard leftover interfaces before equivalent families collapse
marcinpsk Aug 28, 2026
a3249ea
refactor: rename device-level families through the family package
marcinpsk Aug 28, 2026
3f1cc49
docs: describe the contracted family architecture
marcinpsk Aug 28, 2026
b82000b
refactor: describe every previewed name with one immutable value
marcinpsk Aug 28, 2026
6e0f6ec
fix: validate the comparison tool's artifact paths
marcinpsk Aug 28, 2026
d2f104c
perf: record the interface-family after artifact and its comparison
marcinpsk Aug 28, 2026
b652523
perf: label a performance artifact with what it measured
marcinpsk Aug 28, 2026
648793c
fix: report the PostgreSQL version the artifacts actually record
marcinpsk Aug 28, 2026
9625d9b
fix: report the planner settings status in both compared columns
marcinpsk Aug 28, 2026
06630ba
docs: name the component that converts and fix the comparison sentence
marcinpsk Aug 28, 2026
63bec29
docs: attribute the raised statement counts to every table that rose
marcinpsk Aug 28, 2026
19fefd3
fix: refuse a module-type pattern that backtracks exponentially
marcinpsk Aug 29, 2026
6fcbe40
fix: accept a bounded repeat over an ambiguous body
marcinpsk Aug 29, 2026
89142bd
perf: record the host load each performance run was taken under
marcinpsk Aug 29, 2026
c440a2d
fix: make the performance runner record reproducible statement counts
marcinpsk Aug 29, 2026
569f771
perf: re-measure both sides with the reproducible runner
marcinpsk Aug 29, 2026
1650403
docs: report the database work behind the raised statement counts
marcinpsk Aug 29, 2026
632c1da
fix: harden family planning and performance evidence
marcinpsk Aug 30, 2026
09b70d6
fix: validate performance artifact boundaries
marcinpsk Aug 30, 2026
0f58917
fix: distribute the performance contract
marcinpsk Aug 30, 2026
546cf66
Merge branch 'main' into perf/issue-74-signal-baseline
marcinpsk Aug 30, 2026
8aad3f8
fix: resolve performance quality findings
marcinpsk Aug 30, 2026
0f7b961
fix: address late performance review findings
marcinpsk Aug 30, 2026
636bebc
fix: address final family review findings
marcinpsk Aug 30, 2026
7b41580
test: retain measured signal performance evidence
marcinpsk Aug 30, 2026
7ff85eb
fix: keep deferred conversion action available
marcinpsk Aug 30, 2026
55dee2a
fix: retain failed family outcomes
marcinpsk Aug 30, 2026
c23ffc5
fix: preserve database failure boundary
marcinpsk Aug 30, 2026
9152082
test: identify interface name collision race
marcinpsk Aug 30, 2026
c303259
test: validate performance evidence claims
marcinpsk Aug 30, 2026
9fdf078
fix: enforce regex safety during rule loading
marcinpsk Aug 31, 2026
d61edd0
refactor: centralize scoped regex flags
marcinpsk Aug 31, 2026
f8e33e0
fix: reject ambiguous regex sequences
marcinpsk Aug 31, 2026
6bdd959
fix: preserve deterministic regex sequences
marcinpsk Aug 31, 2026
cc2183d
fix: compose regex ambiguity boundaries
marcinpsk Aug 31, 2026
514fffc
fix: make regex safety syntax neutral
marcinpsk Aug 31, 2026
1ca453a
fix: preserve regex boundary semantics
marcinpsk Aug 31, 2026
bf6b990
fix: close regex ambiguity edge cases
marcinpsk Aug 31, 2026
6bfcccc
fix: model regex category languages
marcinpsk Aug 31, 2026
6ba7af9
fix: execute stored regex patterns with RE2
marcinpsk Aug 31, 2026
0ac1fac
fix: audit legacy regex semantics before RE2
marcinpsk Aug 31, 2026
091f848
fix: detect counted repeat drift before RE2
marcinpsk Aug 31, 2026
b6efded
fix: audit negated POSIX regex classes
marcinpsk Aug 31, 2026
913a507
docs: match conversion and SQL narratives to the code
marcinpsk Aug 31, 2026
88d8162
test: cover the channel-family preflight reason
marcinpsk Aug 31, 2026
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
4 changes: 2 additions & 2 deletions .devcontainer/scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ apt-get update -qq
apt-get install -y -qq net-tools git
# Dev tools used by the agent loop and pre-commit hooks. Keep in sync with
# the `dev` extras in pyproject.toml — at minimum, anything invoked by:
# - test + coverage runs: pytest, pytest-django, pytest-cov
# - test + coverage runs: pytest, pytest-django, pytest-cov, pytest-xdist
# - pre-commit hooks (.pre-commit-config.yaml): ruff, pre-commit, reuse
$PIP_CMD install pytest pytest-django pytest-cov ruff pre-commit reuse
$PIP_CMD install pytest pytest-django pytest-cov pytest-xdist ruff pre-commit reuse

# Install GitHub CLI
if ! command -v gh >/dev/null 2>&1; then
Expand Down
28 changes: 13 additions & 15 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@

## Project overview

NetBox plugin that automatically renames interfaces when modules (transceivers, line cards, converters) are installed into device module bays. It hooks into Django's `post_save` signal on `dcim.Module` to apply configurable renaming rules with template variable substitution and arithmetic expression support.
NetBox plugin that automatically renames interfaces when modules (transceivers, line cards, converters) are installed into device module bays. It also reapplies rules when a module type or a device's virtual-chassis position changes. Django signals defer the work until the surrounding transaction commits.

Requires NetBox ≥ 4.2.0 and Python ≥ 3.12. Licensed under Apache-2.0 (REUSE-compliant).
Requires NetBox ≥ 4.3.0 and Python ≥ 3.12. Licensed under Apache-2.0 (REUSE-compliant).

## Architecture

This follows the standard [NetBox plugin pattern](https://netboxlabs.com/docs/netbox/en/stable/plugins/development/):

- **`models.py`** — Single model `InterfaceNameRule` linking a module type (nullable FK, required only in exact mode) to a name template, with optional scoping to parent module type, device type, and/or platform. Rules are matched most-specific-first.
- **`signals.py`** — Two signal handlers:
- `post_save` on `dcim.Module` (primary path): fires on `created=True`, defers renaming to `on_commit` so interfaces exist in DB first. This is the **only** path that runs during normal module installation because NetBox creates interfaces via `bulk_create()`.
- `pre_save` on `dcim.Interface` (defence-in-depth): would rename before INSERT, but **does NOT fire** during normal module installation because `bulk_create()` skips `pre_save` signals. Only fires when interfaces are created individually via `Interface.save()` (scripts, custom code, etc.).

Lazily imports the engine to avoid circular imports during Django startup.
- **`engine.py`** — Core logic: two-tier rule lookup (`_find_matching_rule` first tries an exact FK match across 4 specificity levels, then falls back to regex matching with `re.fullmatch()` across the same 4 levels), template variable building from module bay hierarchy, and interface renaming/breakout creation. The `evaluate_name_template` function supports `{variable}` substitution followed by safe AST-based arithmetic evaluation of remaining brace expressions.
- **`api/`** — DRF REST API using NetBox's `NetBoxModelViewSet` and `NetBoxModelSerializer`.
- **`views.py`, `urls.py`, `tables.py`, `forms.py`, `filters.py`, `navigation.py`** — Standard NetBox UI CRUD views.
- **`utils.py`** — Feature detection for gating (e.g., `{module_path}` token support detected via `dcim.constants.MODULE_PATH_TOKEN` import).
- **`models.py`**: Defines `InterfaceNameRule`. A rule can select an exact module type or a regex pattern, add parent, device, and platform scopes, and describe flat or channelized breakout output.
- **`signals.py`**: Handles `pre_save` and `post_save` for `dcim.Module` and `dcim.Device`. It records prior state, schedules work with `transaction.on_commit()`, and catches failures at the deferred callback boundary. It intentionally does not connect to `dcim.Interface` because NetBox creates module interfaces with `bulk_create()`. It also connects the optional LibreNMS prediction signal when that plugin is installed.
- **`rule_selection.py`**: Loads and fingerprints enabled rules, separates exact and regex candidates, applies scope priority, and pins one cached snapshot across batch work.
- **`naming.py`**: Builds variables from the module-bay hierarchy and evaluates templates. It replaces known variables, parses the remaining integer arithmetic, and evaluates only supported AST nodes.
- **`family/`**: Owns the interface-family domain model, discovery, planning, execution, structural creation, conversion, name collision checks, and NetBox capability detection.
- **`engine.py`**: Orchestrates rule application, prediction, virtual-chassis reapply, preview, and batch operations. It keeps stable entry points while delegating rule selection, naming, and family behavior to their owning modules.
- **`api/` and `graphql/`**: Expose NetBox REST and GraphQL integrations.
- **`views.py`, `urls.py`, `tables.py`, `forms.py`, `filters.py`, `navigation.py`, `jobs.py`**: Provide NetBox UI and background-job integrations.

The signal handler → engine import is intentionally lazy to ensure Django models are fully loaded before use.

Expand Down Expand Up @@ -89,9 +87,9 @@ The `reuse-lint` pre-commit hook validates compliance on every commit.
## Key conventions

- All views, forms, serializers, and tables inherit from NetBox's base classes (`NetBoxModel`, `NetBoxModelViewSet`, `NetBoxModelForm`, etc.) — always use these, not raw Django/DRF equivalents. Non-model forms are the exception: NetBox 4.x dropped `BootstrapMixin` and styles every form through its own widget templates (`FORM_RENDERER = TemplatesSetting`), so a plain form subclasses `django.forms.Form`, exactly as NetBox's own `ConfirmationForm`/`BulkRenameForm` do.
- Template variables use Python `str.format()` syntax: `{slot}`, `{bay_position}`, `{bay_position_num}`, `{parent_bay_position}`, `{sfp_slot}`, `{base}`, `{channel}`, `{module_path}` (gated via `utils.supports_module_path()` using import-based feature detection).
- Arithmetic inside braces is evaluated via `ast.parse` with a strict allowlist of AST node types — never use `eval()` directly on user input.
- Template variables use braces: `{slot}`, `{bay_position}`, `{bay_position_num}`, `{parent_bay_position}`, `{sfp_slot}`, `{base}`, `{channel}`, and `{vc_position}`. `naming.py` replaces known variables explicitly before it parses arithmetic.
- Arithmetic inside braces is parsed with `ast.parse` and evaluated recursively for the supported integer operators. Never use `eval()` on user input.
- The `tags` field on `InterfaceNameRule` uses `related_name="+"` to avoid reverse accessor clashes with other plugins.
- Rule matching uses **two tiers** within each priority level — exact FK match first, then regex (`re.fullmatch()`) fallback. Priority levels (applied in both tiers): (module_type + parent + device) → (module_type + parent) → (module_type + device) → (module_type only).
- Rule matching uses two tiers. Exact module-type rules take priority over regex rules. Within each tier, `rule_selection.py` applies the parent, device, and platform scope score, then the documented tie breakers.
- Add new rules to the appropriate vendor-specific file under `contrib/` (`cisco.yaml`, `juniper.yaml`, `linux.yaml`, `ufispace.yaml`, `ufispace-device-type.yaml`, `converters.yaml`) — keep them updated when adding new rule patterns.
- Commits follow [Conventional Commits](https://www.conventionalcommits.org/) format, enforced by pre-commit hook.
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
cooldown:
default-days: 7

- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
github-actions:
patterns:
Expand All @@ -25,8 +29,12 @@ updates:
directory: "/.devcontainer" # Location of devcontainer.json
schedule:
interval: "weekly"
cooldown:
default-days: 7

- package-ecosystem: "docker-compose" # See documentation for possible values
directory: "/.devcontainer" # Location of docker-compose.yml
schedule:
interval: "weekly"
cooldown:
default-days: 7
2 changes: 2 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/coverage-badge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
name: Update coverage report

on:
# zizmor: ignore[dangerous-triggers] the job accepts only a successful push from this
# repository's main branch, and never consumes artifacts from pull-request code.
workflow_run:
workflows: ["Test with supported NetBox and Python versions"]
types: [completed]
Expand Down Expand Up @@ -66,6 +68,8 @@ jobs:
fi

- name: Checkout gh-pages
# zizmor: ignore[artipacked] this job pushes the coverage report to gh-pages,
# so this checkout has to keep its credential.
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: gh-pages
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/lint-format.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ on:
jobs:
format-and-lint:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand All @@ -22,13 +26,17 @@ jobs:
python-version: '3.12'

- name: Install dependencies
run: uv pip install --system ruff
run: 'uv pip install --system --only-binary=:all: pre-commit==4.5.1 ruff==0.16.0'
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Run Ruff linting
run: ruff check .

- name: Run Ruff formatting check
run: ruff format --check .

# Runs the pinned hook from .pre-commit-config.yaml, so the version lives in one place.
- name: Audit the workflows
run: pre-commit run --all-files zizmor

- name: Run devcontainer script tests
run: bash .devcontainer/scripts/tests/test-debug-toolbar-patches.sh
2 changes: 2 additions & 0 deletions .github/workflows/mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
contents: write
steps:
- name: Checkout repository
# zizmor: ignore[artipacked] mkdocs gh-deploy pushes to gh-pages, so this
# checkout has to keep its credential.
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-title.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name: Validate PR title

on:
pull_request_target:
pull_request:
types: [opened, edited, synchronize]

permissions:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/publish-pypi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ jobs:
build:
name: Build distribution 📦
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag || github.ref }}
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
pull-requests: write
steps:
- name: Checkout
# zizmor: ignore[artipacked] semantic-release pushes the release commit and tag,
# so this checkout has to keep its credential.
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
Expand Down
11 changes: 8 additions & 3 deletions .github/workflows/test-netbox-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ concurrency:
jobs:
test-netbox-main:
runs-on: ubuntu-latest
permissions:
contents: read

strategy:
fail-fast: false
Expand Down Expand Up @@ -49,6 +51,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: netbox-InterfaceNameRules-plugin
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand All @@ -64,12 +67,13 @@ jobs:
repository: "netbox-community/netbox"
path: netbox
ref: main
persist-credentials: false

- name: Install NetBox and plugin
working-directory: netbox-InterfaceNameRules-plugin
run: |
uv pip install --system -r ../netbox/requirements.txt
uv pip install --system pytest pytest-django tblib
uv pip install --system --only-binary=:all: pytest==9.0.2 pytest-django==4.12.0 pytest-xdist==3.8.0 tblib==3.2.2
uv pip install --system -e .

- name: Set up NetBox configuration
Expand All @@ -96,8 +100,9 @@ jobs:
EOF

- name: Run tests
working-directory: netbox/netbox
working-directory: netbox-InterfaceNameRules-plugin
env:
NETBOX_CONFIGURATION: netbox.configuration
PYTHONPATH: ${{ github.workspace }}/netbox/netbox
run: |
python manage.py test netbox_interface_name_rules --verbosity=2
pytest -n auto netbox_interface_name_rules -o pythonpath=../netbox/netbox
27 changes: 16 additions & 11 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ concurrency:
jobs:
test-netbox:
runs-on: ubuntu-latest
permissions:
contents: read

strategy:
fail-fast: false
Expand Down Expand Up @@ -66,6 +68,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: netbox-InterfaceNameRules-plugin
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
Expand All @@ -81,12 +84,13 @@ jobs:
repository: "netbox-community/netbox"
path: netbox
ref: ${{ matrix.netbox-version }}
persist-credentials: false

- name: Install NetBox and plugin
working-directory: netbox-InterfaceNameRules-plugin
run: |
uv pip install --system -r ../netbox/requirements.txt
uv pip install --system pytest pytest-django tblib coverage
uv pip install --system --only-binary=:all: pytest==9.0.2 pytest-cov==7.0.0 pytest-django==4.12.0 pytest-xdist==3.8.0 tblib==3.2.2
uv pip install --system -e .

- name: Set up NetBox configuration
Expand All @@ -113,30 +117,31 @@ jobs:
EOF

- name: Run tests with coverage
working-directory: netbox/netbox
working-directory: netbox-InterfaceNameRules-plugin
env:
NETBOX_CONFIGURATION: netbox.configuration
COVERAGE_RCFILE: ../../netbox-InterfaceNameRules-plugin/pyproject.toml
PYTHONPATH: ${{ github.workspace }}/netbox/netbox
COVERAGE_RCFILE: pyproject.toml
# Turns the channelization tests' skipUnless guard into an assertion on the leg that must
# have the feature, so a broken probe cannot silently skip the whole file.
EXPECT_NETBOX_CHANNELIZATION: ${{ matrix.netbox-version == 'feature' && '1' || '' }}
# Moving branches can change query plans upstream. Record their counts in this disposable
# checkout so these cells report plugin compatibility failures instead of baseline drift.
UPDATE_QUERY_COUNTS: ${{ matrix.experimental && '1' || '' }}
run: |
coverage run manage.py test netbox_interface_name_rules --verbosity=2
coverage report
pytest -n auto netbox_interface_name_rules --cov=netbox_interface_name_rules --cov-report=term-missing \
-o pythonpath=../netbox/netbox
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Generate coverage report
if: matrix.python-version == '3.12' && matrix.netbox-version == 'v4.5.3'
working-directory: netbox/netbox
working-directory: netbox-InterfaceNameRules-plugin
env:
COVERAGE_RCFILE: ../../netbox-InterfaceNameRules-plugin/pyproject.toml
COVERAGE_RCFILE: pyproject.toml
run: |
mkdir -p ../../coverage-report
coverage json -o ../../coverage-report/coverage.json
coverage html -d ../../coverage-report/htmlcov
coverage xml -o ../../coverage-report/coverage.xml
mkdir -p ../coverage-report
coverage json -o ../coverage-report/coverage.json
coverage html -d ../coverage-report/htmlcov
coverage xml -o ../coverage-report/coverage.xml

- name: Upload coverage report
if: matrix.python-version == '3.12' && matrix.netbox-version == 'v4.5.3'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ venv/
.coverage.*
htmlcov/
coverage.xml
performance/baselines/*.json

# Ruff / linting
.ruff_cache/
Expand Down
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ repos:
- id: mixed-line-ending
args: [--fix=lf]

# GitHub Actions security audit. The lint workflow runs this same hook.
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.29.0
hooks:
- id: zizmor

# Python code quality
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.0
Expand Down
Loading