summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuriy Andamasov <yuriy@vyos.io>2026-04-26 17:25:59 +0300
committerGitHub <noreply@github.com>2026-04-26 17:25:59 +0300
commitfd71d68e4a1b379740a66db5ccdad5a30aa08efa (patch)
tree29377ed96c00acfae443a6c83693b581963b9c48
parentc9daf4285c3349064917fe25124929a942403e3c (diff)
parent80035465ca7345e9ba5aae8e6d49cf536bd75aa0 (diff)
downloadvyos.vyos-fd71d68e4a1b379740a66db5ccdad5a30aa08efa.tar.gz
vyos.vyos-fd71d68e4a1b379740a66db5ccdad5a30aa08efa.zip
Merge branch 'main' into test/vlan-unit-tests
-rw-r--r--.coderabbit.yaml591
-rw-r--r--.github/copilot-instructions.md49
-rw-r--r--.github/instructions/modules.instructions.md37
-rw-r--r--.github/instructions/tests.instructions.md114
-rw-r--r--.github/workflows/ah_token_refresh.yml2
-rw-r--r--.gitignore6
-rw-r--r--changelogs/fragments/coderabbit-config.yml3
-rw-r--r--changelogs/fragments/copilot-instructions.yml4
8 files changed, 805 insertions, 1 deletions
diff --git a/.coderabbit.yaml b/.coderabbit.yaml
new file mode 100644
index 00000000..38eae56f
--- /dev/null
+++ b/.coderabbit.yaml
@@ -0,0 +1,591 @@
+# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
+# CodeRabbit configuration for vyos/vyos.vyos Ansible network collection
+# Docs: https://docs.coderabbit.ai/guides/configure-coderabbit
+
+language: en-US
+early_access: false
+tone_instructions: >
+ Concise, technical, no filler. Focus on correctness, security, idempotency,
+ and Ansible conventions. Cite file paths and line numbers.
+
+reviews:
+ profile: chill
+ request_changes_workflow: false
+ high_level_summary: true
+ high_level_summary_placeholder: '@coderabbitai summary'
+ auto_title_placeholder: '@coderabbitai'
+ review_status: true
+ poem: false
+ collapse_walkthrough: true
+ changed_files_summary: true
+ sequence_diagrams: false
+ assess_linked_issues: true
+ related_issues: true
+ related_prs: true
+ suggested_labels: false
+ auto_apply_labels: false
+ suggested_reviewers: false
+
+ auto_review:
+ enabled: true
+ auto_incremental_review: true
+ drafts: false
+ base_branches:
+ - main
+ ignore_title_keywords:
+ - WIP
+ - DO NOT MERGE
+ - Bump
+
+ path_filters:
+ - '!**/__pycache__/**'
+ - '!**/*.pyc'
+ - '!**/*.egg-info/**'
+ - '!changelogs/changelog.yaml'
+ - '!.venv/**'
+ - '!.collections/**'
+ - '!.worktrees/**'
+
+ path_instructions:
+ # ── Global PR hygiene ──────────────────────────────────────────────
+ - path: '**'
+ instructions: |
+ This is the vyos.vyos Ansible network collection (namespace=vyos, name=vyos,
+ version=6.0.0). PR titles must follow the format `T{id}: description` referencing
+ a Phorge task at vyos.dev. Every PR must include a changelog fragment in
+ changelogs/fragments/ (YAML, valid keys: major_changes, minor_changes,
+ breaking_changes, deprecated_features, removed_features, security_fixes,
+ bugfixes, known_issues, doc_changes, trivial; plus release_summary as a
+ prelude section). Style: black line-length=100, isort
+ profile=black line_length=100, flake8 max-line-length=120. Do not suggest
+ 88-char wrapping.
+
+ # ── Module entry points ────────────────────────────────────────────
+ - path: 'plugins/modules/vyos_*.py'
+ instructions: |
+ Module entry points. Each file must contain three YAML triple-string blocks:
+ DOCUMENTATION, EXAMPLES, and RETURN — this is Ansible's documentation contract,
+ not Python docstrings. Verify:
+ - DOCUMENTATION includes: module, author, short_description, description,
+ version_added, extends_documentation_fragment (vyos.vyos.vyos), options with
+ types and descriptions, and a notes section listing tested VyOS versions.
+ - EXAMPLES has at least one working task per supported state.
+ - RETURN documents all return keys with description, returned, type, and sample.
+ - The module wires argspec, config, and facts classes correctly.
+ - State choices include the full set where applicable: merged, replaced,
+ overridden, deleted, gathered, parsed, rendered.
+ Do not add Python-style docstrings (def-level) to these files — the YAML blocks
+ are the canonical documentation.
+
+ # ── Argspec (auto-generated) ───────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/argspec/**'
+ instructions: |
+ Auto-generated by the Ansible resource module builder. These files carry a
+ "DO NOT EDIT" warning header. Do not suggest modifications to auto-generated
+ argspec files — changes will be overwritten. If the schema needs updating,
+ the resource module builder must regenerate it. Only flag issues if the
+ argument_spec dict has obvious type mismatches or missing required fields
+ that would cause runtime failures.
+
+ # ── Config classes ─────────────────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/config/**'
+ instructions: |
+ Config builders extending ansible.netcommon ConfigBase or ResourceModule.
+ These generate VyOS CLI commands from desired state. Verify:
+ - execute_module() handles all declared states correctly.
+ - set_config() and _set_config() process gathered facts and desired config
+ without data loss.
+ - Command generation produces valid VyOS CLI syntax (set/delete prefixes,
+ proper quoting of values with spaces).
+ - No silent swallowing of unknown keys — unknown config should raise or warn.
+ - Methods that compare current vs desired state handle empty/None gracefully.
+ Some older config files have auto-generated headers — do not restructure those.
+
+ # ── Facts classes ──────────────────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/facts/**'
+ instructions: |
+ Facts classes parse raw VyOS CLI output into structured dicts. Verify:
+ - Regex patterns handle edge cases (missing fields, empty values, quoted strings).
+ - populate() returns a clean dict even when device output is incomplete.
+ - get_device_data() uses the correct show command for the resource.
+ - facts/facts.py FACT_RESOURCE_SUBSETS and FACT_LEGACY_SUBSETS stay in sync
+ with available fact classes.
+ - Legacy facts (facts/legacy/) use run_commands(); resource facts use
+ get_resource_connection().
+
+ # ── RM Templates ───────────────────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/rm_templates/*.py'
+ instructions: |
+ Parser templates mapping structured data to VyOS CLI commands and vice versa.
+ Files with a `_14` suffix target VyOS 1.4+ behavior — do not suggest merging
+ them with the base version. Verify:
+ - _tmplt_* helper functions produce syntactically valid VyOS commands.
+ - Regex patterns in PARSERS list correctly capture all variations of the CLI
+ output (quoted values, optional fields, nested hierarchies).
+ - New templates include both set and delete command generation.
+ - compval/getval paths match the argspec structure.
+
+ # ── Cliconf plugin ─────────────────────────────────────────────────
+ - path: 'plugins/cliconf/vyos.py'
+ instructions: |
+ Low-level CLI abstraction for VyOS. Handles configure mode, commit, diff,
+ command execution. Changes here affect all modules. Verify:
+ - edit_config() enters configure mode and commits correctly.
+ - get_diff() returns accurate before/after config diffs.
+ - Error handling catches VyOS-specific error patterns (commit failures,
+ invalid commands).
+ - __rpc__ list matches actually implemented methods.
+
+ # ── Terminal plugin ────────────────────────────────────────────────
+ - path: 'plugins/terminal/vyos.py'
+ instructions: |
+ Terminal prompt detection and initialization. Changes affect connection
+ reliability. Verify regex patterns against actual VyOS prompt formats
+ (configure mode, operational mode, different shell variants). Do not
+ remove existing patterns without testing against all supported VyOS versions.
+
+ # ── Action plugin ──────────────────────────────────────────────────
+ - path: 'plugins/action/vyos.py'
+ instructions: |
+ Auto-proxies all modules to the device. Must validate network_cli connection
+ type. Symlinks from each module name point here. Keep minimal — logic belongs
+ in config classes, not the action plugin.
+
+ # ── Changelog fragments ────────────────────────────────────────────
+ - path: 'changelogs/fragments/*.{yaml,yml}'
+ instructions: |
+ Changelog fragments for ansible-changelog. Valid top-level keys:
+ major_changes, minor_changes, breaking_changes, deprecated_features,
+ removed_features, security_fixes, bugfixes, known_issues, doc_changes,
+ trivial. release_summary is a prelude section (one per release).
+ Fragment filename should be descriptive (e.g., fix-bgp-neighbor-timers.yml).
+ Use `trivial` for tooling/housekeeping. Entries should be complete sentences.
+
+ # ── CI workflows ───────────────────────────────────────────────────
+ - path: '.github/workflows/**'
+ instructions: |
+ CI pipeline: tests.yml (main CI with changelog, build, lint, sanity, unit jobs),
+ codecoverage.yml, release.yml (Galaxy + Automation Hub publish), check_label.yaml,
+ cla-check.yml. Changes to release.yml or ah_token_refresh.yml affect publishing
+ credentials — review with extra care. Do not remove the `all_green` aggregation
+ job from tests.yml.
+
+ # ── Unit tests ─────────────────────────────────────────────────────
+ - path: 'tests/unit/**'
+ instructions: |
+ Unit tests use pytest + unittest.TestCase via TestVyosModule base class.
+ Key patterns:
+ - All test classes inherit TestVyosModule (from vyos_module.py).
+ - setUp() creates and starts mock patches; tearDown() stops them.
+ - execute_module(failed, changed, commands, sort) is the primary assertion method.
+ - load_fixtures() is overridden per test class to wire mock return values.
+ - Fixture files (.cfg) go in tests/unit/modules/network/vyos/fixtures/.
+ - Use load_fixture(name) to read fixtures — never inline raw config strings.
+ - set_module_args(dict(...)) configures module input before execution.
+ Style: black line-length=100, assertions via self.assertEqual / self.assertIn /
+ execute_module kwargs. pytest-xdist runs tests in parallel (-n 2).
+
+ # ── Test fixtures ──────────────────────────────────────────────────
+ - path: 'tests/unit/modules/network/vyos/fixtures/**'
+ instructions: |
+ Raw VyOS CLI output files (.cfg). These are loaded by load_fixture() and
+ cached in memory. Format is VyOS `set ...` configuration syntax or show
+ command output. Fixture filenames follow the pattern:
+ vyos_{module}_config.cfg (base) or vyos_{module}_config_v14.cfg (VyOS 1.4+).
+ New fixtures must be syntactically valid VyOS config. Do not add JSON fixtures
+ unless the test explicitly requires JSON parsing.
+
+ # ── Collection metadata ────────────────────────────────────────────
+ - path: 'galaxy.yml'
+ instructions: |
+ Collection metadata. namespace=vyos, name=vyos. Version bumps must be
+ coordinated with release process. Dependency on ansible.netcommon>=2.5.1
+ is required. Do not add unnecessary dependencies.
+
+ - path: 'meta/runtime.yml'
+ instructions: |
+ Module redirects and tombstones. Adding a new module requires a redirect
+ entry (short name → FQCN). Tombstoned modules (logging, vyos_logging) must
+ not be un-tombstoned. requires_ansible must stay >=2.15.0 unless explicitly
+ bumping minimum version.
+
+ finishing_touches:
+ docstrings:
+ enabled: true
+ unit_tests:
+ enabled: true
+
+ tools:
+ github-checks:
+ enabled: true
+ timeout_ms: 90000
+ eslint:
+ enabled: false
+ biome:
+ enabled: false
+ actionlint:
+ enabled: true
+ yamllint:
+ enabled: true
+ markdownlint:
+ enabled: true
+ languagetool:
+ enabled: true
+ level: default
+ enabled_only: false
+ gitleaks:
+ enabled: true
+ checkov:
+ enabled: false
+ semgrep:
+ enabled: true
+ ast-grep:
+ essential_rules: true
+ ruff:
+ enabled: false
+
+chat:
+ auto_reply: true
+
+knowledge_base:
+ opt_out: false
+ learnings:
+ scope: auto
+ issues:
+ scope: auto
+ pull_requests:
+ scope: auto
+ linked_repositories:
+ - repository: "ansible/ansible"
+ instructions: >
+ Core Ansible framework. Reference for module_utils base classes,
+ plugin interfaces (cliconf, terminal, action), module documentation
+ conventions (DOCUMENTATION/EXAMPLES/RETURN YAML blocks), and
+ ansible-test sanity requirements.
+ - repository: "ansible-collections/ansible.netcommon"
+ instructions: >
+ Network common collection. Contains ConfigBase, ResourceModule,
+ FactsBase, NetworkTemplate, and get_resource_connection — the base
+ classes and utilities that vyos.vyos modules directly extend.
+
+code_generation:
+ docstrings:
+ language: en-US
+ path_instructions:
+ # ── Module entry points: YAML blocks, not Python docstrings ──────
+ - path: 'plugins/modules/vyos_*.py'
+ instructions: |
+ Do NOT generate Python-style docstrings for these files. Ansible modules
+ use YAML triple-string blocks: DOCUMENTATION, EXAMPLES, and RETURN.
+ If updating these blocks:
+ - DOCUMENTATION must include: module name, author, short_description,
+ description (list of strings), version_added, extends_documentation_fragment
+ (vyos.vyos.vyos), and a full options tree with type, description, and
+ choices/default where applicable. Include a notes section listing supported
+ VyOS versions (1.3.8, 1.4.1, 1.4.2, 1.5 rolling).
+ - EXAMPLES must show at least one task per supported state using FQCN
+ (vyos.vyos.vyos_<module>).
+ - RETURN must document: commands (list, always), before (dict, always),
+ after (dict, when changed), and any module-specific return values.
+ Keep version_added accurate — do not backdate.
+
+ # ── Argspec: skip auto-generated files ───────────────────────────
+ - path: 'plugins/module_utils/network/vyos/argspec/**'
+ instructions: |
+ Skip — these files are auto-generated by the Ansible resource module builder
+ and carry a "DO NOT EDIT" header. Do not generate or modify docstrings.
+
+ # ── Config classes ───────────────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/config/**'
+ instructions: |
+ Config builder classes extending ConfigBase or ResourceModule. Use
+ reStructuredText-style docstrings (Ansible/Sphinx convention):
+ def method(self, ...):
+ """Short description.
+
+ :param name: description
+ :type name: type
+ :rtype: type
+ :returns: description
+ """
+ Document: execute_module(), set_config(), get_<resource>_facts(), and any
+ method that generates CLI commands. Focus on what state transitions the
+ method handles and what CLI commands it may produce. Do not document trivial
+ __init__ that just calls super(). Some files have auto-generated headers —
+ keep docstrings minimal in those to avoid noise on regeneration.
+
+ # ── Facts classes ────────────────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/facts/**'
+ instructions: |
+ Facts parsers that convert VyOS CLI output to structured dicts. Use rST
+ docstrings. Document:
+ - populate(): what show commands it runs and the dict structure it returns.
+ - render_config() / get_device_data(): the CLI command used and expected
+ output format.
+ - Any regex-heavy parsing method: briefly note what CLI patterns it handles.
+ Skip __init__.py files.
+
+ # ── RM Templates ─────────────────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/rm_templates/*.py'
+ instructions: |
+ Parser template files with _tmplt_* helper functions and PARSERS lists.
+ Add a module-level docstring describing the resource and VyOS CLI hierarchy
+ covered. For _tmplt_* functions: one-line docstring stating the VyOS command
+ path generated (e.g., "Generate `set protocols bgp <asn> neighbor <addr>
+ timers ...` commands."). Do not document individual regex PARSERS entries —
+ the patterns are self-describing. Files with _14 suffix target VyOS 1.4+ —
+ note this in the module docstring.
+
+ # ── Cliconf plugin ──────────────────────────────────────────────
+ - path: 'plugins/cliconf/vyos.py'
+ instructions: |
+ Uses Ansible DOCUMENTATION block for plugin-level docs. For Python methods
+ use rST docstrings. Document: get_device_info(), edit_config(), get_config(),
+ get_diff(), commit(), discard_changes(). Note which methods enter/exit
+ configure mode.
+
+ # ── Terminal and action plugins ──────────────────────────────────
+ - path: 'plugins/terminal/vyos.py'
+ instructions: |
+ Minimal docstrings only. Document on_open_shell() and on_close_shell()
+ explaining what terminal parameters they set. Regex patterns are
+ self-documenting — do not add inline comments to each pattern.
+
+ - path: 'plugins/action/vyos.py'
+ instructions: |
+ Skip — thin proxy inheriting ActionNetworkModule. No docstrings needed.
+
+ # ── Utilities ────────────────────────────────────────────────────
+ - path: 'plugins/module_utils/network/vyos/utils/*.py'
+ instructions: |
+ Utility functions shared across modules. Use rST docstrings for any
+ function with non-obvious behavior. Document parameters, return types,
+ and side effects. Skip trivial one-liners.
+
+ # ── Test files ──────────────────────────────────────────────────
+ - path: 'tests/**'
+ instructions: |
+ Skip — test files do not need docstrings. Test method names should be
+ self-describing (test_vyos_<module>_<state>_<scenario>).
+
+ unit_tests:
+ path_instructions:
+ # ── Resource module tests ────────────────────────────────────────
+ - path: 'plugins/modules/vyos_interfaces.py'
+ instructions: &resource_module_tests |
+ Generate tests in tests/unit/modules/network/vyos/test_vyos_<module>.py.
+ Inherit from TestVyosModule (import from vyos_module.py in same directory).
+
+ Structure:
+ ```python
+ class TestVyos<Module>Module(TestVyosModule):
+ module = vyos_<module>
+
+ def setUp(self):
+ super().setUp()
+ # Mock get_resource_connection at BOTH levels:
+ self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils."
+ "network.common.cfg.base.get_resource_connection"
+ )
+ self.mock_get_resource_connection_facts = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils."
+ "network.common.facts.facts.get_resource_connection"
+ )
+ # Mock the facts get_device_data method:
+ self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network."
+ "vyos.facts.<resource>.<resource>.<FactsClass>.get_device_data"
+ )
+ # Start all patches and store references
+ self.execute_show_command = self.mock_execute_show_command.start()
+
+ def tearDown(self):
+ super().tearDown()
+ # Stop ALL patches
+
+ def load_fixtures(self, commands=None, filename=None):
+ def load_from_file(*args, **kwargs):
+ return load_fixture(filename or "vyos_<module>_config.cfg")
+ self.execute_show_command.side_effect = load_from_file
+ ```
+
+ Required test methods for each resource module:
+ - test_vyos_<module>_merged: config change, changed=True, verify commands list
+ - test_vyos_<module>_merged_idempotent: no-op, changed=False, commands=[]
+ - test_vyos_<module>_replaced: replaced state, changed=True
+ - test_vyos_<module>_replaced_idempotent: replaced no-op, changed=False
+ - test_vyos_<module>_overridden: full override, changed=True
+ - test_vyos_<module>_deleted: deletion, changed=True
+ - test_vyos_<module>_gathered: state=gathered, verify result["gathered"] dict
+ - test_vyos_<module>_rendered: state=rendered, verify result["rendered"] commands
+ - test_vyos_<module>_parsed: state=parsed with running_config, verify output
+
+ Assertions use self.execute_module(changed=True/False, commands=[...]).
+ Commands lists contain exact VyOS CLI strings: "set interfaces ethernet eth0 ...".
+ Use set_module_args(dict(config=[...], state="<state>")) before execute_module.
+ Create fixture files in tests/unit/modules/network/vyos/fixtures/ named
+ vyos_<module>_config.cfg with valid VyOS set-syntax configuration.
+ Use load_fixture() to read fixtures — never inline raw config.
+
+ - path: 'plugins/modules/vyos_l3_interfaces.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_lag_interfaces.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_lldp_global.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_lldp_interfaces.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_static_routes.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_firewall_rules.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_firewall_global.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_firewall_interfaces.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_ospfv2.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_ospfv3.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_ospf_interfaces.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_bgp_global.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_bgp_address_family.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_prefix_lists.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_route_maps.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_snmp_server.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_logging_global.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_ntp_global.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_hostname.py'
+ instructions: *resource_module_tests
+
+ - path: 'plugins/modules/vyos_vrf.py'
+ instructions: *resource_module_tests
+
+ # ── Legacy module tests ──────────────────────────────────────────
+ - path: 'plugins/modules/vyos_command.py'
+ instructions: &legacy_module_tests |
+ Generate tests in tests/unit/modules/network/vyos/test_vyos_<module>.py.
+ Inherit from TestVyosModule.
+
+ Legacy modules mock differently from resource modules:
+ ```python
+ class TestVyos<Module>Module(TestVyosModule):
+ module = vyos_<module>
+
+ def setUp(self):
+ super().setUp()
+ # Mock run_commands directly on the module:
+ self.mock_run_commands = patch(
+ "ansible_collections.vyos.vyos.plugins.modules."
+ "vyos_<module>.run_commands"
+ )
+ self.run_commands = self.mock_run_commands.start()
+ # Some also mock get_capabilities or get_config/load_config
+
+ def tearDown(self):
+ super().tearDown()
+ self.mock_run_commands.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ # Set run_commands return_value or side_effect
+ self.run_commands.return_value = [load_fixture(filename)]
+ ```
+
+ Legacy modules (vyos_command, vyos_config, vyos_facts, vyos_banner,
+ vyos_ping, vyos_system, vyos_user, vyos_vlan) do not use
+ resource module states. Test: successful execution, error handling,
+ idempotency where applicable, and specific module features (e.g.,
+ vyos_command wait_for/retries, vyos_config src/lines/match).
+ Use execute_module(changed=, commands=) for assertions.
+
+ - path: 'plugins/modules/vyos_config.py'
+ instructions: *legacy_module_tests
+
+ - path: 'plugins/modules/vyos_facts.py'
+ instructions: *legacy_module_tests
+
+ - path: 'plugins/modules/vyos_banner.py'
+ instructions: *legacy_module_tests
+
+ - path: 'plugins/modules/vyos_ping.py'
+ instructions: *legacy_module_tests
+
+ - path: 'plugins/modules/vyos_system.py'
+ instructions: *legacy_module_tests
+
+ - path: 'plugins/modules/vyos_user.py'
+ instructions: *legacy_module_tests
+
+ - path: 'plugins/modules/vyos_vlan.py'
+ instructions: *legacy_module_tests
+
+ # ── Test infrastructure — do not generate tests for these ────────
+ - path: 'tests/unit/modules/utils.py'
+ instructions: |
+ Skip — test infrastructure (ModuleTestCase base, set_module_args, exception
+ classes). Do not generate tests for test utilities.
+
+ - path: 'tests/unit/modules/conftest.py'
+ instructions: |
+ Skip — pytest fixtures (patch_ansible_module). Do not generate tests.
+
+ - path: 'tests/unit/modules/network/vyos/vyos_module.py'
+ instructions: |
+ Skip — TestVyosModule base class with execute_module(), load_fixture(),
+ and mock setup. Do not generate tests for the test base class.
+
+ - path: 'tests/unit/modules/network/vyos/fixtures/**'
+ instructions: |
+ Skip — raw VyOS CLI output fixtures. Not code, not testable.
+
+ # ── Non-module plugin code ───────────────────────────────────────
+ - path: 'plugins/module_utils/**'
+ instructions: |
+ Module utility code (argspec, config, facts, rm_templates, utils).
+ These are tested indirectly through module-level tests — the config
+ classes are exercised when test_vyos_<module>.py calls execute_module().
+ Do not generate separate unit tests for module_utils classes unless
+ a utility function in plugins/module_utils/network/vyos/utils/ has
+ complex standalone logic worth testing in isolation.
+
+ - path: 'plugins/cliconf/vyos.py'
+ instructions: |
+ Skip — cliconf plugin is tested via integration tests and indirectly
+ through module tests. Unit testing requires complex CliconfBase mocking
+ that provides little value over integration coverage.
+
+ - path: 'plugins/terminal/vyos.py'
+ instructions: |
+ Skip — terminal plugin regex patterns are validated through integration
+ tests against actual VyOS devices.
+
+ - path: 'plugins/action/vyos.py'
+ instructions: |
+ Skip — thin action proxy. Tested indirectly via module tests.
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 00000000..fc25faa1
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,49 @@
+# Copilot Review Instructions — vyos.vyos
+
+This is the `vyos.vyos` Ansible network collection for managing VyOS devices.
+Namespace `vyos`, name `vyos`, version `6.0.0`. All modules are prefixed `vyos_`.
+
+## Commit and PR standards
+
+- Every commit title must start with a Phorge task ID: `T<number>: description`.
+- Every PR must have exactly one changelog fragment in `changelogs/fragments/`.
+- PR descriptions that state a test count (e.g. "Add 8 unit tests") must match the actual number of test methods in the changed files. Flag mismatches.
+
+## Changelog fragments
+
+Fragments are YAML files under `changelogs/fragments/`. Valid top-level keys:
+
+| Key | Use for |
+|-----|---------|
+| `trivial` | Developer tooling, CI, housekeeping, formatting-only changes |
+| `bugfixes` | Bug fixes |
+| `minor_changes` | New features or user-visible improvements |
+| `major_changes` | Breaking changes |
+| `security_fixes` | Security fixes |
+| `doc_changes` | Documentation-only changes |
+
+Flag any fragment that uses `minor_changes` for what is actually developer tooling (linting, formatting, gitignore, test scaffolding). Those should use `trivial`.
+
+## Module architecture
+
+Two module families:
+
+**Resource modules** (`vyos_interfaces`, `vyos_firewall_rules`, `vyos_bgp_global`, etc.) follow a four-part structure under `plugins/module_utils/network/vyos/`:
+- `argspec/{resource}/` — argument spec
+- `config/{resource}/` — config builder
+- `facts/{resource}/` — facts parser
+- `rm_templates/{resource}.py` — regex/Jinja2 CLI templates
+
+Resource modules support all states: `merged`, `replaced`, `overridden`, `deleted`, `rendered`, `gathered`, `parsed`.
+
+**Legacy modules** (`vyos_vlan`, `vyos_config`, `vyos_command`, `vyos_user`, etc.) do not follow the resource module pattern.
+
+## VyOS CLI conventions
+
+- Set commands: `set interfaces ethernet eth0 address '192.0.2.1/24'`
+- Delete commands: `delete interfaces ethernet eth0 address '192.0.2.1/24'`
+- Quoting varies by context. In general, string values (descriptions, names, ELIN numbers) are single-quoted; boolean flags and bare keywords are not. However, address/prefix values may be quoted or unquoted depending on where they appear:
+ - Quoted: `address '192.0.2.1/24'`, `description 'my-iface'`, `elin '0000000911'`
+ - Unquoted: `address 192.0.2.1` (in firewall groups), `disable`, `mtu-ignore`, `vif 200`
+- When reviewing tests and fixtures, align with the quoting style used by surrounding fixtures rather than flagging a missing quote as an error.
+- Interface types: `ethernet`, `loopback`, `bonding`, `bridge`, `tunnel`, `wireguard`.
diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md
new file mode 100644
index 00000000..d7782e1e
--- /dev/null
+++ b/.github/instructions/modules.instructions.md
@@ -0,0 +1,37 @@
+---
+applyTo: "plugins/**,meta/runtime.yml"
+---
+
+# Plugin conventions
+
+## Module option descriptions
+
+- Must be complete English sentences ending with a period.
+- No grammar errors. Common mistake: "the number hops" should be "the number of hops".
+- Deprecated options must include `removed_in_version` and `removed_from_collection` fields.
+- Do not add new options with `deprecated: true` — remove deprecated options entirely.
+
+## rm_templates files
+
+Files in `plugins/module_utils/network/vyos/rm_templates/` define regex parsers and Jinja2 generators for a resource module. Each entry has:
+- `name` — unique identifier
+- `getval` — compiled regex with named groups
+- `setval` — Jinja2 template or callable producing a VyOS CLI command
+- `result` — dict mapping regex groups to facts structure
+- `shared` (optional) — bool, whether the template applies to a shared config block
+
+## meta/runtime.yml redirects
+
+Short-name redirects must point to the actual module name with the `vyos_` prefix. For example:
+```yaml
+snmp_server:
+ redirect: vyos.vyos.vyos_snmp_server # correct
+ # NOT: vyos.vyos.vyos_snmp_servers # wrong (pluralized)
+```
+
+Verify any changed redirect target exists as a real module file under `plugins/modules/`.
+
+## cliconf / terminal plugins
+
+`plugins/cliconf/vyos.py` — do not modify without understanding edit-mode and commit semantics.
+`plugins/terminal/vyos.py` — handles prompt detection; regex changes require testing against all supported VyOS versions listed in README.md.
diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md
new file mode 100644
index 00000000..150db41e
--- /dev/null
+++ b/.github/instructions/tests.instructions.md
@@ -0,0 +1,114 @@
+---
+applyTo: "tests/unit/**"
+---
+
+# Unit test conventions
+
+## Base class and structure
+
+All test classes inherit from `TestVyosModule` in `tests/unit/modules/network/vyos/vyos_module.py`.
+
+```python
+class TestVyosFooModule(TestVyosModule):
+ module = vyos_foo
+
+ def setUp(self): ...
+ def tearDown(self): ...
+ def load_fixtures(self, commands=None, filename=None): ...
+ def test_...(self): ...
+```
+
+## Mocking: resource modules
+
+Resource module tests require two framework-level patches in `setUp` with corresponding cleanup in `tearDown`:
+
+```python
+# in setUp:
+self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection"
+)
+self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
+
+self.mock_get_resource_connection_facts = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection"
+)
+self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
+
+# in tearDown:
+self.mock_get_resource_connection_config.stop()
+self.mock_get_resource_connection_facts.stop()
+```
+
+Most resource module tests also patch the facts class's `get_device_data` method directly and use it in `load_fixtures`:
+
+```python
+# in setUp:
+self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos."
+ "facts.{resource}.{resource}.{Resource}Facts.get_device_data"
+)
+self.execute_show_command = self.mock_execute_show_command.start()
+
+# in load_fixtures:
+def load_from_file(*args, **kwargs):
+ return load_fixture("vyos_{resource}_config.cfg")
+self.execute_show_command.side_effect = load_from_file
+```
+
+An alternative pattern sets `self.get_resource_connection_facts.return_value.get_config.return_value = fixture_data` in `load_fixtures` instead — both approaches work, but the `get_device_data` pattern is used by most existing tests.
+
+## Mocking: legacy modules
+
+Legacy modules (`vyos_vlan`, `vyos_config`, `vyos_command`, etc.) patch the specific helper the module calls, directly in the module under test. Which helper depends on the module:
+
+```python
+# vyos_command, vyos_facts, vyos_ping — patch run_commands
+self.mock_run_commands = patch("ansible_collections.vyos.vyos.plugins.modules.vyos_foo.run_commands")
+# vyos_config — patch get_config / load_config
+self.mock_load_config = patch("ansible_collections.vyos.vyos.plugins.modules.vyos_foo.load_config")
+```
+
+In `load_fixtures`, configure the mock using `side_effect` (for dynamic fixture loading) or `return_value` (for a fixed response):
+
+```python
+# side_effect — used by most existing legacy tests
+def load_from_file(*args, **kwargs):
+ return load_fixture("vyos_foo_config.cfg")
+self.run_commands.side_effect = load_from_file
+
+# return_value — acceptable for simple fixed responses
+self.run_commands.return_value = [SHOW_OUTPUT]
+```
+
+## load_fixtures and filename
+
+`execute_module()` always calls `load_fixtures()`. Never set mock return values inside a test method — they will be overwritten by the next `execute_module` call. Use the `filename` parameter to vary the fixture:
+
+```python
+def load_fixtures(self, commands=None, filename=None):
+ if filename == "empty":
+ self.run_commands.return_value = [EMPTY_OUTPUT]
+ else:
+ self.run_commands.return_value = [DEFAULT_OUTPUT]
+```
+
+Then call: `self.execute_module(changed=True, commands=commands, filename="empty")`
+
+## Fixture files
+
+Raw device CLI output lives in `tests/unit/modules/network/vyos/fixtures/` as `.cfg` files.
+Load with `load_fixture("vyos_foo_config.cfg")`.
+
+## Required test coverage for resource modules
+
+A complete resource module test file should cover all applicable states:
+- `merged` (including an idempotent case)
+- `replaced`
+- `overridden`
+- `deleted`
+- `rendered` — assert `result["rendered"]` matches expected CLI commands
+- `gathered` — assert `result["gathered"]` contains expected structured data
+- `parsed` — pass `running_config=raw_string` and assert `result["parsed"]`
+
+Flag test files that are missing `rendered`, `gathered`, or `parsed` tests without explanation.
+
diff --git a/.github/workflows/ah_token_refresh.yml b/.github/workflows/ah_token_refresh.yml
index 0346920e..0c8cba7b 100644
--- a/.github/workflows/ah_token_refresh.yml
+++ b/.github/workflows/ah_token_refresh.yml
@@ -7,7 +7,7 @@ on:
jobs:
refresh:
- uses: ansible/team-devtools/.github/workflows/ah_token_refresh.yml@v26.1.0
+ uses: ansible/team-devtools/.github/workflows/ah_token_refresh.yml@v26.2.0
with:
environment: release
secrets:
diff --git a/.gitignore b/.gitignore
index cbe347c4..1dbe8b0e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -120,3 +120,9 @@ changelogs/.plugin-cache.yaml
inventory.network
*.bak
+
+# Git worktrees
+.worktrees/
+
+# Claude Code
+.claude/
diff --git a/changelogs/fragments/coderabbit-config.yml b/changelogs/fragments/coderabbit-config.yml
new file mode 100644
index 00000000..9442337f
--- /dev/null
+++ b/changelogs/fragments/coderabbit-config.yml
@@ -0,0 +1,3 @@
+trivial:
+ - Add CodeRabbit review configuration with path-specific guidelines for review,
+ docstring generation, and unit test generation.
diff --git a/changelogs/fragments/copilot-instructions.yml b/changelogs/fragments/copilot-instructions.yml
new file mode 100644
index 00000000..c7af5c74
--- /dev/null
+++ b/changelogs/fragments/copilot-instructions.yml
@@ -0,0 +1,4 @@
+---
+trivial:
+ - Add Copilot custom review instructions to guide automated code review with
+ project-specific conventions for tests, changelog fragments, and module patterns.