confgraph 0.2.0__tar.gz → 0.3.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- confgraph-0.3.7/.github/workflows/publish.yml +28 -0
- confgraph-0.3.7/.github/workflows/redos-scan.yml +48 -0
- confgraph-0.3.7/.gitignore +21 -0
- confgraph-0.3.7/.python-version +1 -0
- confgraph-0.3.7/CHANGELOG.md +67 -0
- confgraph-0.3.7/CLAUDE.md +109 -0
- confgraph-0.3.7/INTERFACE.md +85 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/PKG-INFO +20 -10
- {confgraph-0.2.0 → confgraph-0.3.7}/README.md +8 -1
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/analysis/dependency_resolver.py +82 -1
- confgraph-0.3.7/confgraph/change_ir.py +3546 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/cli.py +164 -154
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/builder.py +11 -1
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/html.py +21 -11
- confgraph-0.3.7/confgraph/loader.py +410 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/__init__.py +2 -0
- confgraph-0.3.7/confgraph/models/aaa.py +90 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/acl.py +23 -1
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/base.py +21 -4
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/bgp.py +224 -15
- confgraph-0.3.7/confgraph/models/cdp.py +28 -0
- confgraph-0.3.7/confgraph/models/community_list.py +138 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/crypto.py +92 -0
- confgraph-0.3.7/confgraph/models/dhcp.py +51 -0
- confgraph-0.3.7/confgraph/models/dns.py +32 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/eigrp.py +16 -0
- confgraph-0.3.7/confgraph/models/evpn.py +124 -0
- confgraph-0.3.7/confgraph/models/interface.py +651 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/isis.py +12 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/line.py +22 -2
- confgraph-0.3.7/confgraph/models/lldp.py +32 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/logging_config.py +9 -0
- confgraph-0.3.7/confgraph/models/mpls.py +46 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/multicast.py +42 -0
- confgraph-0.3.7/confgraph/models/netflow.py +125 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/ntp.py +11 -0
- confgraph-0.3.7/confgraph/models/object_group.py +55 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/ospf.py +115 -37
- confgraph-0.3.7/confgraph/models/panos_zone.py +21 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/parsed_config.py +172 -3
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/qos.py +32 -4
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/rip.py +4 -1
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/static_route.py +4 -0
- confgraph-0.3.7/confgraph/models/stp.py +46 -0
- confgraph-0.3.7/confgraph/models/topology.py +44 -0
- confgraph-0.3.7/confgraph/models/vlan.py +45 -0
- confgraph-0.3.7/confgraph/models/vpc.py +51 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/vrf.py +29 -0
- confgraph-0.3.7/confgraph/models/vxlan.py +81 -0
- confgraph-0.3.7/confgraph/parsers/__init__.py +123 -0
- confgraph-0.3.7/confgraph/parsers/base.py +1405 -0
- confgraph-0.3.7/confgraph/parsers/eos_parser.py +2291 -0
- confgraph-0.3.7/confgraph/parsers/ios_parser.py +12952 -0
- confgraph-0.3.7/confgraph/parsers/iosxr_parser.py +2729 -0
- confgraph-0.3.7/confgraph/parsers/junos_groups.py +153 -0
- confgraph-0.3.7/confgraph/parsers/junos_hierarchy.py +403 -0
- confgraph-0.3.7/confgraph/parsers/junos_parser.py +1922 -0
- confgraph-0.3.7/confgraph/parsers/nxos_parser.py +2575 -0
- confgraph-0.3.7/confgraph/parsers/panos_parser.py +1344 -0
- confgraph-0.3.7/confgraph/parsers/panos_xml.py +355 -0
- confgraph-0.3.7/confgraph/tombstones.py +362 -0
- confgraph-0.3.7/confgraph/topology/__init__.py +22 -0
- confgraph-0.3.7/confgraph/topology/exporters.py +1010 -0
- confgraph-0.3.7/confgraph/topology/graph.py +612 -0
- confgraph-0.3.7/confgraph/topology/ingest.py +356 -0
- confgraph-0.3.7/confgraph/utils/__init__.py +0 -0
- confgraph-0.3.7/confgraph/utils/escaping.py +52 -0
- confgraph-0.3.7/confgraph/utils/interface.py +374 -0
- confgraph-0.3.7/dist-0.3.7/.gitignore +1 -0
- confgraph-0.3.7/docs/ADDING_NEW_OS_SUPPORT.md +202 -0
- confgraph-0.3.7/docs/ARCHITECTURE.md +211 -0
- confgraph-0.3.7/docs/CLI_USAGE.md +101 -0
- confgraph-0.3.7/docs/EOS_PARSER_SUPPORT.md +723 -0
- confgraph-0.3.7/docs/IOSXR_PARSER_SUPPORT.md +607 -0
- confgraph-0.3.7/docs/IOS_PARSER_SUPPORT.md +985 -0
- confgraph-0.3.7/docs/JUNOS_PARSER_SUPPORT.md +684 -0
- confgraph-0.3.7/docs/NXOS_PARSER_SUPPORT.md +540 -0
- confgraph-0.3.7/docs/PANOS_PARSER_SUPPORT.md +742 -0
- confgraph-0.3.7/docs/PARSER_SUPPORT_MATRIX.md +411 -0
- confgraph-0.3.7/docs/bgp_cluster_view.png +0 -0
- confgraph-0.3.7/docs/default_view.png +0 -0
- confgraph-0.3.7/docs/specific_node_highlight.png +0 -0
- confgraph-0.3.7/docs/supported_os_types.md +9 -0
- confgraph-0.3.7/main.py +4 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/pyproject.toml +11 -5
- confgraph-0.3.7/samples/complex_router.ios +288 -0
- confgraph-0.3.7/samples/eos.txt +368 -0
- confgraph-0.3.7/samples/ios.txt +276 -0
- confgraph-0.3.7/samples/ios01.txt +342 -0
- confgraph-0.3.7/samples/ios_xe.txt +334 -0
- confgraph-0.3.7/samples/ios_xr.txt +341 -0
- confgraph-0.3.7/samples/iosxr_test.cfg +169 -0
- confgraph-0.3.7/samples/junos_test.cfg +240 -0
- confgraph-0.3.7/samples/nxos.txt +309 -0
- confgraph-0.3.7/samples/panos_sample.xml +775 -0
- confgraph-0.3.7/tools/redos_baseline.json +3 -0
- confgraph-0.3.7/tools/redos_scan.json +8 -0
- confgraph-0.3.7/tools/redos_scan.py +201 -0
- confgraph-0.3.7/uv.lock +519 -0
- confgraph-0.2.0/confgraph/models/community_list.py +0 -68
- confgraph-0.2.0/confgraph/models/interface.py +0 -277
- confgraph-0.2.0/confgraph/parsers/__init__.py +0 -18
- confgraph-0.2.0/confgraph/parsers/base.py +0 -536
- confgraph-0.2.0/confgraph/parsers/eos_parser.py +0 -889
- confgraph-0.2.0/confgraph/parsers/ios_parser.py +0 -4167
- confgraph-0.2.0/confgraph/parsers/iosxr_parser.py +0 -1235
- confgraph-0.2.0/confgraph/parsers/junos_hierarchy.py +0 -307
- confgraph-0.2.0/confgraph/parsers/junos_parser.py +0 -1190
- confgraph-0.2.0/confgraph/parsers/nxos_parser.py +0 -564
- confgraph-0.2.0/confgraph.egg-info/PKG-INFO +0 -126
- confgraph-0.2.0/confgraph.egg-info/SOURCES.txt +0 -57
- confgraph-0.2.0/confgraph.egg-info/dependency_links.txt +0 -1
- confgraph-0.2.0/confgraph.egg-info/entry_points.txt +0 -2
- confgraph-0.2.0/confgraph.egg-info/requires.txt +0 -4
- confgraph-0.2.0/confgraph.egg-info/top_level.txt +0 -1
- confgraph-0.2.0/setup.cfg +0 -4
- {confgraph-0.2.0 → confgraph-0.3.7}/LICENSE +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/analysis/__init__.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/__init__.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/assets/cytoscape-dagre.min.js +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/assets/cytoscape.min.js +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/assets/dagre.min.js +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/__init__.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/base.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/graph/exporters/json.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/banner.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/bfd.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/eem.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/ipsla.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/nat.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/object_tracking.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/prefix_list.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/route_map.py +0 -0
- {confgraph-0.2.0 → confgraph-0.3.7}/confgraph/models/snmp.py +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to GitHub Packages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*.*.*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
packages: write
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
|
|
21
|
+
- name: Build wheel
|
|
22
|
+
run: uv build --wheel
|
|
23
|
+
|
|
24
|
+
- name: Publish to GitHub Packages
|
|
25
|
+
run: |
|
|
26
|
+
uv publish \
|
|
27
|
+
--publish-url https://pypi.pkg.github.com/verigraphs/ \
|
|
28
|
+
--token ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: redos-scan
|
|
2
|
+
|
|
3
|
+
# CCR-0126: scan the parser regexes for catastrophic backtracking with a real
|
|
4
|
+
# analyser (regexploit) and fail on a NEWLY introduced pathological pattern.
|
|
5
|
+
# The current baseline is clean (0 findings over 784 patterns); this gate keeps
|
|
6
|
+
# it that way. Runs on every change to the parsers or the scan itself, plus a
|
|
7
|
+
# weekly sweep and manual dispatch. All within this repo — no secrets needed.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [main]
|
|
12
|
+
paths:
|
|
13
|
+
- "confgraph/parsers/**"
|
|
14
|
+
- "tools/redos_scan.py"
|
|
15
|
+
- "tools/redos_baseline.json"
|
|
16
|
+
- ".github/workflows/redos-scan.yml"
|
|
17
|
+
pull_request:
|
|
18
|
+
paths:
|
|
19
|
+
- "confgraph/parsers/**"
|
|
20
|
+
- "tools/redos_scan.py"
|
|
21
|
+
- "tools/redos_baseline.json"
|
|
22
|
+
- ".github/workflows/redos-scan.yml"
|
|
23
|
+
schedule:
|
|
24
|
+
- cron: "0 8 * * 1" # Mondays 08:00 UTC
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
redos:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
|
|
33
|
+
- uses: astral-sh/setup-uv@v5
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.11"
|
|
36
|
+
|
|
37
|
+
# `uv run --extra redos` resolves confgraph + regexploit into uv's managed
|
|
38
|
+
# project env and runs there. (Not `uv pip install --system`: setup-uv
|
|
39
|
+
# provisions a uv-managed interpreter that refuses --system installs.)
|
|
40
|
+
- name: ReDoS gate
|
|
41
|
+
run: uv run --extra redos python tools/redos_scan.py --check
|
|
42
|
+
|
|
43
|
+
- name: Upload scan artifact
|
|
44
|
+
if: always()
|
|
45
|
+
uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: redos-scan
|
|
48
|
+
path: tools/redos_scan.json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
*.egg-info/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.pyc
|
|
5
|
+
.DS_Store
|
|
6
|
+
*.html
|
|
7
|
+
.claude/
|
|
8
|
+
temp/
|
|
9
|
+
Self_Sustain_Parser_sys_design_v3.md
|
|
10
|
+
configz.code-workspace
|
|
11
|
+
docs/ConfigZ_Skills_Layer_Addendum_v3.1.md
|
|
12
|
+
docs/_from_management_for_open_source.txt
|
|
13
|
+
docs/poojan_notes.txt
|
|
14
|
+
docs/self_sustaining_parser_system_design_v1.md
|
|
15
|
+
docs/PRODUCT_ONE_PAGER.md
|
|
16
|
+
_internal_docs/
|
|
17
|
+
|
|
18
|
+
# Enterprise-only — lives in confgraph-entrp repo
|
|
19
|
+
confgraph/simulation/
|
|
20
|
+
samples/sim_topo/
|
|
21
|
+
tests/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `confgraph` are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
|
|
5
|
+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.3.6] - 2026-07-27
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Op-primary parsers (IOS, NX-OS, EOS) no longer emit legacy tombstone
|
|
14
|
+
strings** into `ParsedConfig.no_commands`, `InterfaceConfig.no_commands`, or
|
|
15
|
+
`BGPConfig.no_commands` (CCR-0110 Phase E). This closes the one-release
|
|
16
|
+
deprecation window opened in 0.3.5: those fields are now **empty** on
|
|
17
|
+
op-primary parses. The Change-IR operation model (`ParsedConfig.change_ops`)
|
|
18
|
+
is unaffected — every removal that previously produced a tombstone string is
|
|
19
|
+
still emitted as a native `ChangeOp` with real provenance, so the composed
|
|
20
|
+
ChangeSet is byte-identical to 0.3.5.
|
|
21
|
+
|
|
22
|
+
**IOS-XR is excepted** (pending Phase 5): its deletion capability is carried
|
|
23
|
+
by derived tombstone strings (no native op sits behind them), so IOS-XR
|
|
24
|
+
continues to populate `no_commands` for its top-level and interface
|
|
25
|
+
deletions. **Three residual derived-only strings also survive on all OSes**
|
|
26
|
+
(each has no native op behind it — the string carries the deletion):
|
|
27
|
+
the whole-process `no router bgp <asn>` delete (`process:bgp:<asn>`) and the
|
|
28
|
+
OSPF area type resets (`field:ospf:<pid>:area:<n>:stub_reset` and
|
|
29
|
+
`...:nssa_reset`). All other OSPF-area deletion variants are op-backed and
|
|
30
|
+
emit no string.
|
|
31
|
+
|
|
32
|
+
The ops-to-legacy shim (`confgraph.change_ir.encode_legacy_shim`) is **not**
|
|
33
|
+
wired to refill the deprecated fields (owner-confirmed zero external
|
|
34
|
+
consumers, zero remaining engine readers). It survives as a **codec
|
|
35
|
+
artifact** — the inverse deriver that reconstructs the byte-exact legacy
|
|
36
|
+
string vocabulary from the composed ChangeSet — pinned against frozen
|
|
37
|
+
pre-removal goldens by the shim identity suite.
|
|
38
|
+
|
|
39
|
+
**Migration:** read change intent from `ParsedConfig.change_ops` (Change-IR)
|
|
40
|
+
instead of the `no_commands` string containers. The fields remain on the
|
|
41
|
+
model (empty) until removed at the next major version.
|
|
42
|
+
|
|
43
|
+
## [0.3.5] - 2026-07-26
|
|
44
|
+
|
|
45
|
+
### Deprecated
|
|
46
|
+
|
|
47
|
+
- **`ParsedConfig.no_commands`, `InterfaceConfig.no_commands`, and
|
|
48
|
+
`BGPConfig.no_commands` are deprecated** (CCR-0025 Phase 4 / CCR-0110). These
|
|
49
|
+
tombstone fields record `no ...` negation lines in the legacy string
|
|
50
|
+
vocabulary. They are superseded by the Change-IR operation model
|
|
51
|
+
(`ParsedConfig.change_ops`), which represents the same intent as structured
|
|
52
|
+
`ChangeOp`s with real provenance.
|
|
53
|
+
|
|
54
|
+
The `CONFGRAPH_CHANGE_IR` default flipped to `ops` (2026-07-19). This release
|
|
55
|
+
starts the deprecation window: the fields stay **populated but deprecated for
|
|
56
|
+
one release cycle** (revised from two — CCR-0025 §8.2, owner decision
|
|
57
|
+
2026-07-25) — during the window they continue to carry what the parsers emit;
|
|
58
|
+
when parser tombstone emission retires (CCR-0110 Phase E) they are refilled by
|
|
59
|
+
the ops-to-legacy shim (`confgraph.change_ir.encode_legacy_shim`),
|
|
60
|
+
byte-identical, continuously verified by the shim's in-suite identity sweep.
|
|
61
|
+
In the release after this window the fields are emptied; they are removed
|
|
62
|
+
from the model only at the next **major** version, preserving schema
|
|
63
|
+
compatibility until then. `confgraph`'s own platform and engine consumers
|
|
64
|
+
already stopped reading the fields at the default flip.
|
|
65
|
+
|
|
66
|
+
**Migration:** read change intent from `ParsedConfig.change_ops` (Change-IR)
|
|
67
|
+
instead of the `no_commands` string containers.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Engineering Standards for This Project
|
|
2
|
+
|
|
3
|
+
## Core Principle: Holistic Fixes, Never Patch Work
|
|
4
|
+
|
|
5
|
+
Every fix or feature must address the **root cause**, not the symptom. Before writing a single line of code, complete the full understanding and planning phase below. A patch is any change that fixes the immediate case while leaving the structural problem intact — patches are not acceptable.
|
|
6
|
+
|
|
7
|
+
A structural fix has one test: **adding the next similar thing (field, protocol, deletion type, scenario) requires exactly one code change per architectural layer.** If it requires changes in two places *within the same layer*, the fix is still a patch.
|
|
8
|
+
|
|
9
|
+
When a fix legitimately spans two architectural layers (e.g. parser + merger), one entry per layer is the correct answer — not a patch. The test is whether those entries live in designated registries (one each) or whether they require editing business logic functions that were not supposed to grow.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Mandatory Process for Every Fix or Feature
|
|
14
|
+
|
|
15
|
+
### Step 1 — Understand the problem fully before touching code
|
|
16
|
+
|
|
17
|
+
- Read the failing scenario, test, or bug report completely.
|
|
18
|
+
- Identify the **symptom** (what breaks) and the **root cause** (why it breaks structurally).
|
|
19
|
+
- Ask: is this a one-off missing case, or is there a class of similar cases that will keep appearing? If there is a class, the fix must address the class, not just the instance.
|
|
20
|
+
- If the root cause is unclear, read more code. Do not guess.
|
|
21
|
+
|
|
22
|
+
### Step 2 — Read the relevant code and understand intent
|
|
23
|
+
|
|
24
|
+
- Read every file that touches the affected area — parser, model, merger, simulator, tests.
|
|
25
|
+
- Understand what the code was designed to do, not just what it currently does. Look at existing patterns (registries, shared helpers, base class hooks) and understand why they were built that way.
|
|
26
|
+
- Identify the **single point of truth** for the feature you're changing. All logic of a given kind should live in one place; if it's duplicated, the duplication is itself a structural problem.
|
|
27
|
+
- Read the `_internal_docs/` directory if the area has a design document. The intent matters as much as the current implementation.
|
|
28
|
+
|
|
29
|
+
### Step 3 — Design before coding
|
|
30
|
+
|
|
31
|
+
- Write out in plain text: what is the root cause, what is the target end-state, and what files and methods change.
|
|
32
|
+
- For every proposed change ask: does adding a new field / protocol / deletion type / scenario require touching this file again? If yes, redesign until the answer is no.
|
|
33
|
+
- Identify all **call sites and callers** of the code you're changing. A fix that breaks a caller is not a fix.
|
|
34
|
+
- Check whether the change affects both `confgraph/` (core parser/model) and `confgraph_entrp/` (simulation/merger). Changes to shared models or parsers ripple into the simulator.
|
|
35
|
+
- Only proceed to coding once the design is clear and complete.
|
|
36
|
+
|
|
37
|
+
### Step 4 — Define acceptance criteria before writing code
|
|
38
|
+
|
|
39
|
+
Before touching any file, write down:
|
|
40
|
+
- What must be true after the fix that is not true now (the positive test cases)?
|
|
41
|
+
- What must still be true that was true before (the regression cases)?
|
|
42
|
+
- What is the **parity check** — the test that proves the fix is structural and not a patch (e.g., adding a second field of the same type requires zero new code)?
|
|
43
|
+
|
|
44
|
+
### Step 5 — Implement the solution
|
|
45
|
+
|
|
46
|
+
- Follow the design from Step 3 exactly. If implementation reveals a flaw in the design, stop, go back to Step 3, and redesign. Do not improvise patches mid-implementation.
|
|
47
|
+
- Build shared helpers before rewriting callers — never leave callers in a partially-migrated state.
|
|
48
|
+
- One logical change per commit area. Do not mix unrelated fixes.
|
|
49
|
+
- Do not add error handling, fallbacks, or validation for scenarios that cannot happen.
|
|
50
|
+
- Do not add abstraction for hypothetical future requirements. Solve the class of problem that exists now.
|
|
51
|
+
|
|
52
|
+
### Step 6 — Build tests that prove structural correctness
|
|
53
|
+
|
|
54
|
+
Tests must verify the fix is structural, not just that the immediate case passes:
|
|
55
|
+
|
|
56
|
+
- **Positive test**: the specific broken case now works.
|
|
57
|
+
- **Parity test**: a second instance of the same class (second protocol, second field, second deletion type) works without any additional code changes.
|
|
58
|
+
- **Regression test**: all existing scenarios produce identical output to before the fix.
|
|
59
|
+
- **Negative test**: the system behaves correctly when the input is absent or malformed.
|
|
60
|
+
|
|
61
|
+
Run tests before declaring the fix complete. If a regression appears, fix it before moving on.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Concrete Anti-Patterns to Reject
|
|
66
|
+
|
|
67
|
+
These are signs that a fix is patch work:
|
|
68
|
+
|
|
69
|
+
| Pattern | Why it's a patch |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Adding a new `if text == "new-field":` clause to a method that already has 10 similar clauses | The method is growing without a shared abstraction; the next field requires the same change |
|
|
72
|
+
| Adding a new tombstone type by adding a new `elif tombstone.startswith("new:")` block inside a protocol-specific method | Tombstone handling should live in a shared registry; protocol-specific code should not need to grow |
|
|
73
|
+
| Fixing a bug in one parsing path without checking whether the parallel path has the same bug | The two paths will diverge again on the next feature |
|
|
74
|
+
| Adding a new scenario by working around a parser or merger limitation rather than fixing the limitation | The limitation will block the next scenario too |
|
|
75
|
+
| Adding detection logic for a new trigger type when the underlying issue is that detection should be outcome-based | The trigger list will grow indefinitely |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Project-Specific Structural Rules
|
|
80
|
+
|
|
81
|
+
### Parser layer (`confgraph/parsers/`)
|
|
82
|
+
|
|
83
|
+
- Every OS parser that has parallel code paths for the same concept (global vs VRF, IOS vs IOS-XR) must delegate to a **shared helper method**. The helper is the single source of truth. Callers call the helper; they do not re-implement the logic.
|
|
84
|
+
- Adding a new `BGPNeighbor` field requires exactly one change in one parser method. If it requires changes in two methods, the paths have diverged and must be unified first.
|
|
85
|
+
- Tombstone emission for nested `no` commands must be driven by a registry (`confgraph/tombstones.py`), not by per-protocol or per-method detection code. `parse_deletion_commands()` is the canonical traversal point.
|
|
86
|
+
|
|
87
|
+
### Merger layer (`confgraph_entrp/simulation/merger.py`)
|
|
88
|
+
|
|
89
|
+
- The deletion registry `_DELETION_RULES` is the single place to add new tombstone handlers. Adding a new tombstone type requires exactly one entry in `_DELETION_RULES` and one handler function — no changes to `_apply_deletions()` itself.
|
|
90
|
+
- Field-level tombstone resolution uses `_reset_fields_from_tombstones()`. Do not add per-field special-case code in `_merge_bgp_instances()` or other merge functions.
|
|
91
|
+
- Nested path navigation (for hierarchical tombstones) must use an explicit accessor registry, not eval or getattr chains.
|
|
92
|
+
|
|
93
|
+
### Causal chain detection (`confgraph_entrp/simulation/snapshot.py`)
|
|
94
|
+
|
|
95
|
+
- Adjacency loss detection must be **outcome-based** (compare adj_links before and after), not **trigger-based** (gate detection on whether a specific trigger type fired). A new class of protocol-config change must not require a new Layer 0x detector.
|
|
96
|
+
- The reason resolver (`_resolve_adjacency_reason`) is the single place to add new reason strings.
|
|
97
|
+
|
|
98
|
+
### Scenario testing (`demo_files/`)
|
|
99
|
+
|
|
100
|
+
- Scenarios must test the real config change they describe. If a scenario uses a workaround because the parser or merger cannot handle the real command, the correct action is to fix the parser or merger — not to redesign the scenario.
|
|
101
|
+
- Every scenario fix that reveals a structural gap must be accompanied by a fix to the structural gap, not just a scenario redesign.
|
|
102
|
+
|
|
103
|
+
### Change requests (CCRs)
|
|
104
|
+
|
|
105
|
+
Change requests against this repo are tracked in the platform repo:
|
|
106
|
+
`~/ai/confgraph-platform/_internal_docs/core_change_requests/`. To file or update
|
|
107
|
+
one from this session, use the **`ccr` skill** (`.claude/skills/ccr/SKILL.md`) — it
|
|
108
|
+
covers scaffolding via `ccr.py`, validation, and the commit conventions. When a
|
|
109
|
+
commit here implements a CCR, end its message with the trailer `CCR: CCR-00XX`.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# confgraph — Public Interface Contract
|
|
2
|
+
|
|
3
|
+
This file documents the interface that external tools (e.g. configz-validator) depend on.
|
|
4
|
+
**Update this file whenever any of the below changes.**
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Package root
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
confgraph/
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Parsers
|
|
15
|
+
|
|
16
|
+
| Class | Module |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `BaseParser` | `confgraph.parsers.base` |
|
|
19
|
+
| `ParseError` | `confgraph.parsers.base` |
|
|
20
|
+
| `IOSParser` | `confgraph.parsers.ios_parser` |
|
|
21
|
+
| `EOSParser` | `confgraph.parsers.eos_parser` |
|
|
22
|
+
| `NXOSParser` | `confgraph.parsers.nxos_parser` |
|
|
23
|
+
| `IOSXRParser` | `confgraph.parsers.iosxr_parser` |
|
|
24
|
+
|
|
25
|
+
All parsers expose a single entry point:
|
|
26
|
+
```python
|
|
27
|
+
parsed: ParsedConfig = ParserClass(config_text: str).parse()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Models root
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
confgraph/models/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## ParsedConfig fields
|
|
37
|
+
|
|
38
|
+
| Field | Type |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `source_os` | `OSType` |
|
|
41
|
+
| `hostname` | `str \| None` |
|
|
42
|
+
| `vrfs` | `list[VRFConfig]` |
|
|
43
|
+
| `interfaces` | `list[InterfaceConfig]` |
|
|
44
|
+
| `bgp_instances` | `list[BGPConfig]` |
|
|
45
|
+
| `ospf_instances` | `list[OSPFConfig]` |
|
|
46
|
+
| `isis_instances` | `list[ISISConfig]` |
|
|
47
|
+
| `eigrp_instances` | `list[EIGRPConfig]` |
|
|
48
|
+
| `rip_instances` | `list[RIPConfig]` |
|
|
49
|
+
| `route_maps` | `list[RouteMapConfig]` |
|
|
50
|
+
| `prefix_lists` | `list[PrefixListConfig]` |
|
|
51
|
+
| `static_routes` | `list[StaticRoute]` |
|
|
52
|
+
| `acls` | `list[ACLConfig]` |
|
|
53
|
+
| `community_lists` | `list[CommunityListConfig]` |
|
|
54
|
+
| `as_path_lists` | `list[ASPathListConfig]` |
|
|
55
|
+
| `class_maps` | `list[ClassMapConfig]` |
|
|
56
|
+
| `policy_maps` | `list[PolicyMapConfig]` |
|
|
57
|
+
| `lines` | `list[LineConfig]` |
|
|
58
|
+
| `ip_sla_operations` | `list[IPSLAOperation]` |
|
|
59
|
+
| `eem_applets` | `list[EEMApplet]` |
|
|
60
|
+
| `object_tracks` | `list[ObjectTrack]` |
|
|
61
|
+
| `ntp` | `NTPConfig \| None` |
|
|
62
|
+
| `snmp` | `SNMPConfig \| None` |
|
|
63
|
+
| `syslog` | `SyslogConfig \| None` |
|
|
64
|
+
| `banners` | `BannerConfig \| None` |
|
|
65
|
+
| `nat` | `NATConfig \| None` |
|
|
66
|
+
| `crypto` | `CryptoConfig \| None` |
|
|
67
|
+
| `bfd` | `BFDConfig \| None` |
|
|
68
|
+
| `multicast` | `MulticastConfig \| None` |
|
|
69
|
+
| `raw_config` | `str` |
|
|
70
|
+
| `unrecognized_blocks` | `list[UnrecognizedBlock]` |
|
|
71
|
+
|
|
72
|
+
## OSType enum
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from confgraph.models.base import OSType
|
|
76
|
+
# Values: OSType.IOS, OSType.IOS_XE, OSType.IOS_XR, OSType.NXOS, OSType.EOS
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## BaseConfigObject
|
|
80
|
+
|
|
81
|
+
All model objects (VRFConfig, BGPConfig, etc.) inherit from `BaseConfigObject` which provides:
|
|
82
|
+
- `object_id: str`
|
|
83
|
+
- `raw_lines: list[str]`
|
|
84
|
+
- `line_numbers: list[int]`
|
|
85
|
+
- `source_os: OSType`
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: confgraph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Local-first network config dependency mapper — parse, visualize, and lint vendor configs
|
|
5
|
-
Author-email: Poojan Patel <poojanpatel42@gmail.com>
|
|
6
|
-
License-Expression: Apache-2.0
|
|
7
5
|
Project-URL: Homepage, https://github.com/verigraphs/confgraph
|
|
8
6
|
Project-URL: Repository, https://github.com/verigraphs/confgraph
|
|
9
7
|
Project-URL: Bug Tracker, https://github.com/verigraphs/confgraph/issues
|
|
10
|
-
|
|
8
|
+
Author-email: Poojan Patel <poojanpatel42@gmail.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: arista,bgp,cisco,config,graph,network,ospf,visualization
|
|
11
12
|
Classifier: Development Status :: 3 - Alpha
|
|
12
13
|
Classifier: Intended Audience :: System Administrators
|
|
13
14
|
Classifier: Intended Audience :: Telecommunications Industry
|
|
@@ -17,13 +18,15 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
18
|
Classifier: Topic :: System :: Networking
|
|
18
19
|
Classifier: Topic :: Utilities
|
|
19
20
|
Requires-Python: >=3.11
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENSE
|
|
22
21
|
Requires-Dist: ciscoconfparse2>=0.9.16
|
|
22
|
+
Requires-Dist: click>=8.1
|
|
23
23
|
Requires-Dist: networkx>=3.6.1
|
|
24
24
|
Requires-Dist: pydantic>=2.12.5
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Provides-Extra: redos
|
|
28
|
+
Requires-Dist: regexploit; extra == 'redos'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
27
30
|
|
|
28
31
|
# confgraph
|
|
29
32
|
|
|
@@ -71,6 +74,7 @@ Troubleshooting a BGP flap? The BGP cluster automatically pulls in related route
|
|
|
71
74
|
| Cisco NX-OS | `NXOSParser` |
|
|
72
75
|
| Arista EOS | `EOSParser` |
|
|
73
76
|
| Juniper JunOS | `JunOSParser` |
|
|
77
|
+
| Palo Alto PAN-OS | `PANOSParser` |
|
|
74
78
|
|
|
75
79
|
## Try it instantly
|
|
76
80
|
|
|
@@ -84,6 +88,8 @@ Pre-generated maps for all supported platforms — open any in your browser, no
|
|
|
84
88
|
| Cisco NX-OS | [samples/nxos.txt](samples/nxos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/nxos.html) |
|
|
85
89
|
| Arista EOS | [samples/eos.txt](samples/eos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/eos.html) |
|
|
86
90
|
| Juniper JunOS | [samples/junos_test.cfg](samples/junos_test.cfg) | [Live demo](https://verigraphs.github.io/confgraph/samples/junos_test.html) |
|
|
91
|
+
| Cisco IOS (full) | [samples/ios01.txt](samples/ios01.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/ios01.html) |
|
|
92
|
+
| Palo Alto PAN-OS | [samples/panos_sample.xml](samples/panos_sample.xml) | [Live demo](https://verigraphs.github.io/confgraph/samples/panos_sample.html) |
|
|
87
93
|
|
|
88
94
|
## Install
|
|
89
95
|
|
|
@@ -101,7 +107,7 @@ uvx confgraph map router.txt
|
|
|
101
107
|
|
|
102
108
|
## Protocols parsed
|
|
103
109
|
|
|
104
|
-
**BGP** · **OSPF** · **ACLs** · **NAT** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS ·
|
|
110
|
+
**BGP** · **OSPF** · **ACLs** · **NAT** · **Crypto/IPsec** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS · BFD · IP SLA · EEM · Object tracking · Multicast · Security zones (PAN-OS)
|
|
105
111
|
|
|
106
112
|
## Use as a library
|
|
107
113
|
|
|
@@ -117,6 +123,10 @@ print(parsed.route_maps)
|
|
|
117
123
|
|
|
118
124
|
**Local-first by design.** confgraph never sends your config files anywhere. All parsing, graph generation, and analysis run entirely on your machine. The output is a single self-contained HTML file with no external requests — no CDN, no analytics, no telemetry. This means it can be safely moved to and used within air-gapped management jump-hosts where internet access is prohibited.
|
|
119
125
|
|
|
126
|
+
## CLI Reference
|
|
127
|
+
|
|
128
|
+
See [`docs/CLI_USAGE.md`](docs/CLI_USAGE.md) for all commands, options, and examples.
|
|
129
|
+
|
|
120
130
|
## Contributing
|
|
121
131
|
|
|
122
132
|
Contributions welcome — new parsers, bug fixes, additional protocol coverage. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) to get started.
|
|
@@ -44,6 +44,7 @@ Troubleshooting a BGP flap? The BGP cluster automatically pulls in related route
|
|
|
44
44
|
| Cisco NX-OS | `NXOSParser` |
|
|
45
45
|
| Arista EOS | `EOSParser` |
|
|
46
46
|
| Juniper JunOS | `JunOSParser` |
|
|
47
|
+
| Palo Alto PAN-OS | `PANOSParser` |
|
|
47
48
|
|
|
48
49
|
## Try it instantly
|
|
49
50
|
|
|
@@ -57,6 +58,8 @@ Pre-generated maps for all supported platforms — open any in your browser, no
|
|
|
57
58
|
| Cisco NX-OS | [samples/nxos.txt](samples/nxos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/nxos.html) |
|
|
58
59
|
| Arista EOS | [samples/eos.txt](samples/eos.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/eos.html) |
|
|
59
60
|
| Juniper JunOS | [samples/junos_test.cfg](samples/junos_test.cfg) | [Live demo](https://verigraphs.github.io/confgraph/samples/junos_test.html) |
|
|
61
|
+
| Cisco IOS (full) | [samples/ios01.txt](samples/ios01.txt) | [Live demo](https://verigraphs.github.io/confgraph/samples/ios01.html) |
|
|
62
|
+
| Palo Alto PAN-OS | [samples/panos_sample.xml](samples/panos_sample.xml) | [Live demo](https://verigraphs.github.io/confgraph/samples/panos_sample.html) |
|
|
60
63
|
|
|
61
64
|
## Install
|
|
62
65
|
|
|
@@ -74,7 +77,7 @@ uvx confgraph map router.txt
|
|
|
74
77
|
|
|
75
78
|
## Protocols parsed
|
|
76
79
|
|
|
77
|
-
**BGP** · **OSPF** · **ACLs** · **NAT** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS ·
|
|
80
|
+
**BGP** · **OSPF** · **ACLs** · **NAT** · **Crypto/IPsec** · VRF · IS-IS · EIGRP · RIP · Route-maps · Prefix-lists · Community lists · AS-path lists · Static routes · NTP · SNMP · Syslog · Banners · QoS · BFD · IP SLA · EEM · Object tracking · Multicast · Security zones (PAN-OS)
|
|
78
81
|
|
|
79
82
|
## Use as a library
|
|
80
83
|
|
|
@@ -90,6 +93,10 @@ print(parsed.route_maps)
|
|
|
90
93
|
|
|
91
94
|
**Local-first by design.** confgraph never sends your config files anywhere. All parsing, graph generation, and analysis run entirely on your machine. The output is a single self-contained HTML file with no external requests — no CDN, no analytics, no telemetry. This means it can be safely moved to and used within air-gapped management jump-hosts where internet access is prohibited.
|
|
92
95
|
|
|
96
|
+
## CLI Reference
|
|
97
|
+
|
|
98
|
+
See [`docs/CLI_USAGE.md`](docs/CLI_USAGE.md) for all commands, options, and examples.
|
|
99
|
+
|
|
93
100
|
## Contributing
|
|
94
101
|
|
|
95
102
|
Contributions welcome — new parsers, bug fixes, additional protocol coverage. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) to get started.
|
|
@@ -21,6 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
from pydantic import BaseModel, Field
|
|
22
22
|
|
|
23
23
|
from confgraph.models.parsed_config import ParsedConfig
|
|
24
|
+
from confgraph.utils.interface import is_discard_interface
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
# ---------------------------------------------------------------------------
|
|
@@ -95,6 +96,10 @@ class DependencyResolver:
|
|
|
95
96
|
self._class_maps = {cm.name: cm for cm in config.class_maps}
|
|
96
97
|
self._policy_maps = {pm.name: pm for pm in config.policy_maps}
|
|
97
98
|
self._ip_sla_ops = {op.sla_id: op for op in config.ip_sla_operations}
|
|
99
|
+
self._ospf_instances = {
|
|
100
|
+
f"{o.process_id}" + (f" vrf {o.vrf}" if o.vrf else ""): o
|
|
101
|
+
for o in config.ospf_instances
|
|
102
|
+
}
|
|
98
103
|
|
|
99
104
|
# Track which named objects have been referenced (for orphan detection)
|
|
100
105
|
self._referenced: dict[str, set[str]] = {
|
|
@@ -121,6 +126,7 @@ class DependencyResolver:
|
|
|
121
126
|
links.extend(self._resolve_eigrp())
|
|
122
127
|
links.extend(self._resolve_rip())
|
|
123
128
|
links.extend(self._resolve_interfaces())
|
|
129
|
+
links.extend(self._resolve_vrfs())
|
|
124
130
|
links.extend(self._resolve_route_maps())
|
|
125
131
|
links.extend(self._resolve_static_routes())
|
|
126
132
|
links.extend(self._resolve_ntp())
|
|
@@ -132,6 +138,7 @@ class DependencyResolver:
|
|
|
132
138
|
links.extend(self._resolve_ipsla())
|
|
133
139
|
links.extend(self._resolve_object_tracking())
|
|
134
140
|
links.extend(self._resolve_multicast())
|
|
141
|
+
links.extend(self._resolve_zones())
|
|
135
142
|
|
|
136
143
|
orphaned = self._find_orphans()
|
|
137
144
|
return DependencyReport(links=links, orphaned=orphaned)
|
|
@@ -302,10 +309,31 @@ class DependencyResolver:
|
|
|
302
309
|
links.append(self._link(
|
|
303
310
|
"interface", iface.name, "unnumbered_source", "interface", iface.unnumbered_source,
|
|
304
311
|
))
|
|
312
|
+
# PAN-OS BGP-over-IPSec-VPN underlay (CCR-0116): a tunnel.N carrying a
|
|
313
|
+
# resolved physical egress depends on that egress interface, so the
|
|
314
|
+
# graph chains bgp_instance -> tunnel.N -> ethernet1/N. Field-driven,
|
|
315
|
+
# so no OS branching: non-PAN-OS interfaces leave it None.
|
|
316
|
+
if iface.tunnel_underlay_interface:
|
|
317
|
+
links.append(self._link(
|
|
318
|
+
"interface", iface.name, "tunnel_underlay",
|
|
319
|
+
"interface", iface.tunnel_underlay_interface,
|
|
320
|
+
))
|
|
321
|
+
# Surface the IPSec/IKE hop the tunnel rides as an edge to the
|
|
322
|
+
# per-device crypto node (only when crypto config was parsed, so no
|
|
323
|
+
# ghost node is invented).
|
|
324
|
+
if iface.tunnel_ike_gateway and self._config.crypto is not None:
|
|
325
|
+
links.append(self._link(
|
|
326
|
+
"interface", iface.name, "tunnel_ike_gateway", "crypto", "crypto",
|
|
327
|
+
))
|
|
305
328
|
if iface.acl_in:
|
|
306
329
|
links.append(self._link("interface", iface.name, "acl_in", "acl", iface.acl_in))
|
|
307
330
|
if iface.acl_out:
|
|
308
331
|
links.append(self._link("interface", iface.name, "acl_out", "acl", iface.acl_out))
|
|
332
|
+
if iface.ospf_process_id is not None:
|
|
333
|
+
ospf_node_id = f"{iface.ospf_process_id}" + (f" vrf {iface.vrf}" if iface.vrf else "")
|
|
334
|
+
links.append(self._link(
|
|
335
|
+
"interface", iface.name, "ospf_process", "ospf_instance", ospf_node_id,
|
|
336
|
+
))
|
|
309
337
|
return links
|
|
310
338
|
|
|
311
339
|
# ------------------------------------------------------------------
|
|
@@ -334,6 +362,14 @@ class DependencyResolver:
|
|
|
334
362
|
def _infer_match_ref_type(match_type: str) -> str | None:
|
|
335
363
|
"""Map a route-map match_type string to its referenced object type."""
|
|
336
364
|
mt = match_type.lower()
|
|
365
|
+
# An INLINE match references nothing: its values are literal patterns or
|
|
366
|
+
# prefixes, not the names of objects. Cisco matches an as-path by naming
|
|
367
|
+
# an as-path access-list; PAN-OS carries the regex in the policy rule
|
|
368
|
+
# itself. Inferring a reference from those values would manufacture a
|
|
369
|
+
# dangling ref for every such rule — the harm this method exists to
|
|
370
|
+
# avoid. Parsers signal the inline case with a "-regex" suffix.
|
|
371
|
+
if mt.endswith("-regex") or mt.endswith(" regex"):
|
|
372
|
+
return None
|
|
337
373
|
if "prefix-list" in mt:
|
|
338
374
|
return "prefix_list"
|
|
339
375
|
if "as-path" in mt:
|
|
@@ -355,13 +391,41 @@ class DependencyResolver:
|
|
|
355
391
|
links.append(self._link(
|
|
356
392
|
"static_route", str(sr.destination), "vrf", "vrf", sr.vrf,
|
|
357
393
|
))
|
|
358
|
-
|
|
394
|
+
# Null0 / NullN and each vendor's discard target are blackhole
|
|
395
|
+
# pseudo-interfaces: traffic is dropped, no InterfaceConfig exists.
|
|
396
|
+
# They are routing sinks, not resolvable objects, so emit no link
|
|
397
|
+
# (skipping avoids a false dangling-interface ERROR). See
|
|
398
|
+
# confgraph.utils.interface.is_discard_interface for the vendor table.
|
|
399
|
+
if sr.next_hop_interface and not is_discard_interface(sr.next_hop_interface):
|
|
359
400
|
links.append(self._link(
|
|
360
401
|
"static_route", str(sr.destination),
|
|
361
402
|
"next_hop_interface", "interface", sr.next_hop_interface,
|
|
362
403
|
))
|
|
363
404
|
return links
|
|
364
405
|
|
|
406
|
+
# ------------------------------------------------------------------
|
|
407
|
+
# VRFs
|
|
408
|
+
# ------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
def _resolve_vrfs(self) -> list[DependencyLink]:
|
|
411
|
+
"""Resolve VRF import/export route-map references.
|
|
412
|
+
|
|
413
|
+
Without this, VRF route-maps are never marked referenced and are
|
|
414
|
+
wrongly reported as orphaned. Where a VRF has no route-map, no edge
|
|
415
|
+
is emitted.
|
|
416
|
+
"""
|
|
417
|
+
links: list[DependencyLink] = []
|
|
418
|
+
for vrf in self._config.vrfs:
|
|
419
|
+
if vrf.route_map_import:
|
|
420
|
+
links.append(self._link(
|
|
421
|
+
"vrf", vrf.name, "route_map_import", "route_map", vrf.route_map_import,
|
|
422
|
+
))
|
|
423
|
+
if vrf.route_map_export:
|
|
424
|
+
links.append(self._link(
|
|
425
|
+
"vrf", vrf.name, "route_map_export", "route_map", vrf.route_map_export,
|
|
426
|
+
))
|
|
427
|
+
return links
|
|
428
|
+
|
|
365
429
|
# ------------------------------------------------------------------
|
|
366
430
|
# Orphan detection
|
|
367
431
|
# ------------------------------------------------------------------
|
|
@@ -422,8 +486,13 @@ class DependencyResolver:
|
|
|
422
486
|
"acl": self._acls,
|
|
423
487
|
"vrf": self._vrfs,
|
|
424
488
|
"interface": self._interfaces,
|
|
489
|
+
"ospf_instance": self._ospf_instances,
|
|
425
490
|
"class_map": self._class_maps,
|
|
426
491
|
"policy_map": self._policy_maps,
|
|
492
|
+
# The crypto config is a per-device singleton node named "crypto"
|
|
493
|
+
# (GraphBuilder). A tunnel→crypto underlay hop (CCR-0116) resolves
|
|
494
|
+
# iff that node exists.
|
|
495
|
+
"crypto": {"crypto": self._config.crypto} if self._config.crypto else {},
|
|
427
496
|
}.get(ref_type, {})
|
|
428
497
|
return ref_name in index
|
|
429
498
|
|
|
@@ -659,3 +728,15 @@ class DependencyResolver:
|
|
|
659
728
|
if mc.vrf:
|
|
660
729
|
links.append(self._link("multicast", "multicast", "vrf", "vrf", mc.vrf))
|
|
661
730
|
return links
|
|
731
|
+
|
|
732
|
+
# ------------------------------------------------------------------
|
|
733
|
+
# PAN-OS Zones
|
|
734
|
+
# ------------------------------------------------------------------
|
|
735
|
+
|
|
736
|
+
def _resolve_zones(self) -> list[DependencyLink]:
|
|
737
|
+
"""Emit zone → interface edges for PAN-OS zone configurations."""
|
|
738
|
+
links: list[DependencyLink] = []
|
|
739
|
+
for zone in self._config.zones:
|
|
740
|
+
for iface_name in zone.interfaces:
|
|
741
|
+
links.append(self._link("zone", zone.name, "interface", "interface", iface_name))
|
|
742
|
+
return links
|