diff options
| author | omnom62 <omnom62@outlook.com> | 2026-07-07 19:28:52 +1000 |
|---|---|---|
| committer | omnom62 <omnom62@outlook.com> | 2026-07-07 19:28:52 +1000 |
| commit | be24a31fb8a4dcdd7c237eee28299cc4a20e4f9c (patch) | |
| tree | 3ef69ebbfd8a15f09af454c590ac6a3260e0ecf3 | |
| parent | 7c57a114f8a6b63047b47817bd0ecc71ac3ad4cc (diff) | |
| download | rest.vyos-be24a31fb8a4dcdd7c237eee28299cc4a20e4f9c.tar.gz rest.vyos-be24a31fb8a4dcdd7c237eee28299cc4a20e4f9c.zip | |
T8332: vyos_system SIT and UAT
12 files changed, 555 insertions, 0 deletions
diff --git a/docs/vyos.rest.vyos_system_module.rst b/docs/vyos.rest.vyos_system_module.rst new file mode 100644 index 0000000..9b97920 --- /dev/null +++ b/docs/vyos.rest.vyos_system_module.rst @@ -0,0 +1,266 @@ +.. _vyos.rest.vyos_system_module: + + +********************* +vyos.rest.vyos_system +********************* + +**Manage system settings on VyOS devices using REST API** + + +Version added: 1.0.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Manages basic system settings on VyOS devices via the REST API. +- Covers hostname, domain name, name servers, and domain search. +- Uses REST API (``connection=httpapi``) instead of CLI. + + + + +Parameters +---------- + +.. raw:: html + + <table border=0 cellpadding=0 class="documentation-table"> + <tr> + <th colspan="1">Parameter</th> + <th>Choices/<font color="blue">Defaults</font></th> + <th width="100%">Comments</th> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>domain_name</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Device domain name.</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>domain_search</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=string</span> + </div> + </td> + <td> + </td> + <td> + <div>List of domain search suffixes.</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>host_name</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + </td> + <td> + <div>Device hostname.</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>name_server</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + / <span style="color: purple">elements=string</span> + </div> + </td> + <td> + </td> + <td> + <div>List of DNS name servers.</div> + <div style="font-size: small; color: darkgreen"><br/>aliases: name_servers</div> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="parameter-"></div> + <b>state</b> + <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a> + <div style="font-size: small"> + <span style="color: purple">string</span> + </div> + </td> + <td> + <ul style="margin: 0; padding: 0"><b>Choices:</b> + <li><div style="color: blue"><b>present</b> ←</div></li> + <li>absent</li> + </ul> + </td> + <td> + <div><code>present</code> applies the configuration.</div> + <div><code>absent</code> removes the configuration.</div> + </td> + </tr> + </table> + <br/> + + +Notes +----- + +.. note:: + - Requires ``ansible_connection=httpapi`` with the VyOS httpapi plugin. + - ``ansible_network_os`` must be set to ``vyos.rest.vyos``. + + + +Examples +-------- + +.. code-block:: yaml + + - name: Configure hostname and domain + vyos.rest.vyos_system: + host_name: router1 + domain_name: example.com + state: present + + - name: Configure name servers + vyos.rest.vyos_system: + name_server: + - 8.8.8.8 + - 8.8.4.4 + state: present + + - name: Configure domain search + vyos.rest.vyos_system: + domain_search: + - sub1.example.com + - sub2.example.com + state: present + + - name: Remove domain name and name servers + vyos.rest.vyos_system: + domain_name: example.com + name_server: + - 8.8.8.8 + state: absent + + + +Return Values +------------- +Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module: + +.. raw:: html + + <table border=0 cellpadding=0 class="documentation-table"> + <tr> + <th colspan="1">Key</th> + <th>Returned</th> + <th width="100%">Description</th> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>after</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td>when changed</td> + <td> + <div>System configuration after this module ran.</div> + <br/> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>before</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td>always</td> + <td> + <div>System configuration before this module ran.</div> + <br/> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>commands</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">list</span> + </div> + </td> + <td>always</td> + <td> + <div>List of API command tuples sent to the device.</div> + <br/> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>response</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">dictionary</span> + </div> + </td> + <td>when changes are applied</td> + <td> + <div>Raw API response.</div> + <br/> + </td> + </tr> + <tr> + <td colspan="1"> + <div class="ansibleOptionAnchor" id="return-"></div> + <b>saved</b> + <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a> + <div style="font-size: small"> + <span style="color: purple">boolean</span> + </div> + </td> + <td>when changes are applied</td> + <td> + <div>Whether the config was saved after changes.</div> + <br/> + </td> + </tr> + </table> + <br/><br/> + + +Status +------ + + +Authors +~~~~~~~ + +- VyOS Community (@vyos) diff --git a/tests/integration/targets/vyos_system/aliases b/tests/integration/targets/vyos_system/aliases new file mode 100644 index 0000000..cc0afef --- /dev/null +++ b/tests/integration/targets/vyos_system/aliases @@ -0,0 +1 @@ +network/vyos diff --git a/tests/integration/targets/vyos_system/defaults/main.yaml b/tests/integration/targets/vyos_system/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_system/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_system/tasks/httpapi.yaml b/tests/integration/targets/vyos_system/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_system/tasks/httpapi.yaml @@ -0,0 +1,21 @@ +--- +- name: Collect all httpapi test cases + ansible.builtin.find: + paths: "{{ role_path }}/tests/httpapi" + patterns: "{{ testcase }}.yaml" + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + ansible.builtin.set_fact: + test_items: "{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=httpapi) + ansible.builtin.include_tasks: "{{ test_case_to_run }}" + vars: + ansible_connection: ansible.netcommon.httpapi + ansible_network_os: vyos.rest.vyos + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/vyos_system/tasks/main.yaml b/tests/integration/targets/vyos_system/tasks/main.yaml new file mode 100644 index 0000000..b1f6193 --- /dev/null +++ b/tests/integration/targets/vyos_system/tasks/main.yaml @@ -0,0 +1,5 @@ +--- +- name: Run httpapi tests + ansible.builtin.include_tasks: httpapi.yaml + tags: + - httpapi diff --git a/tests/integration/targets/vyos_system/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_system/tests/httpapi/_remove_config.yaml new file mode 100644 index 0000000..7c11046 --- /dev/null +++ b/tests/integration/targets/vyos_system/tests/httpapi/_remove_config.yaml @@ -0,0 +1,11 @@ +--- +- name: Remove test system configuration + vyos.rest.vyos_system: + domain_name: lab.example.com + name_server: + - 8.8.8.8 + - 8.8.4.4 + domain_search: + - sub1.example.com + state: absent + ignore_errors: true diff --git a/tests/integration/targets/vyos_system/tests/httpapi/absent.yaml b/tests/integration/targets/vyos_system/tests/httpapi/absent.yaml new file mode 100644 index 0000000..da0857e --- /dev/null +++ b/tests/integration/targets/vyos_system/tests/httpapi/absent.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: START vyos_system absent integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Setup system configuration + vyos.rest.vyos_system: + domain_name: lab.example.com + name_server: + - 8.8.8.8 + state: present + + - name: Remove domain name + register: result + vyos.rest.vyos_system: + domain_name: lab.example.com + state: absent + + - assert: + that: + - result.changed == true + + - name: Remove domain name (IDEMPOTENT) + register: result + vyos.rest.vyos_system: + domain_name: lab.example.com + state: absent + + - assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_system/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_system/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..650ef1e --- /dev/null +++ b/tests/integration/targets/vyos_system/tests/httpapi/gathered.yaml @@ -0,0 +1,14 @@ +--- +- debug: + msg: START vyos_system gathered integration tests on connection={{ ansible_connection }} + +- block: + - name: Gather system configuration + register: result + vyos.rest.vyos_system: + state: present + + - assert: + that: + - result.before.host_name is defined + - result.before.host_name | length > 0 diff --git a/tests/integration/targets/vyos_system/tests/httpapi/present.yaml b/tests/integration/targets/vyos_system/tests/httpapi/present.yaml new file mode 100644 index 0000000..aced4f4 --- /dev/null +++ b/tests/integration/targets/vyos_system/tests/httpapi/present.yaml @@ -0,0 +1,47 @@ +--- +- debug: + msg: START vyos_system present integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Configure domain and name servers + register: result + vyos.rest.vyos_system: + domain_name: lab.example.com + name_server: + - 8.8.8.8 + - 8.8.4.4 + state: present + + - assert: + that: + - result.changed == true + + - name: Configure domain and name servers (IDEMPOTENT) + register: result + vyos.rest.vyos_system: + domain_name: lab.example.com + name_server: + - 8.8.8.8 + - 8.8.4.4 + state: present + + - assert: + that: + - result.changed == false + - result.commands == [] + + - name: Add domain search + register: result + vyos.rest.vyos_system: + domain_search: + - sub1.example.com + state: present + + - assert: + that: + - result.changed == true + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_system/vars/main.yaml b/tests/integration/targets/vyos_system/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_system/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/unit/fixtures/system_running.json b/tests/unit/fixtures/system_running.json new file mode 100644 index 0000000..b8a4969 --- /dev/null +++ b/tests/unit/fixtures/system_running.json @@ -0,0 +1,6 @@ +{ + "host-name": "vyos150", + "domain-name": "lab.example.com", + "name-server": ["8.8.8.8", "8.8.4.4"], + "domain-search": "sub1.example.com" +} diff --git a/tests/unit/modules/test_vyos_system.py b/tests/unit/modules/test_vyos_system.py new file mode 100644 index 0000000..1bd9cff --- /dev/null +++ b/tests/unit/modules/test_vyos_system.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json +import os +import unittest + +from unittest.mock import MagicMock + +from ansible_collections.vyos.rest.plugins.modules.vyos_system import ( + build_commands, + get_running_config, +) + + +_BASE = ["system"] + + +def load_fixture(filename): + fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures") + with open(os.path.join(fixtures_dir, filename)) as f: + return json.load(f) + + +class TestVyOSSystemGetRunning(unittest.TestCase): + + def setUp(self): + self.mock_vyos = MagicMock() + self.fixture = load_fixture("system_running.json") + self.mock_vyos.get_config = MagicMock(return_value=self.fixture) + + def test_parses_hostname(self): + result = get_running_config(self.mock_vyos) + self.assertEqual(result["host_name"], "vyos150") + + def test_parses_domain_name(self): + result = get_running_config(self.mock_vyos) + self.assertEqual(result["domain_name"], "lab.example.com") + + def test_parses_name_servers_list(self): + result = get_running_config(self.mock_vyos) + self.assertIn("8.8.8.8", result["name_server"]) + self.assertIn("8.8.4.4", result["name_server"]) + + def test_parses_domain_search_string(self): + result = get_running_config(self.mock_vyos) + self.assertIn("sub1.example.com", result["domain_search"]) + + def test_empty_config(self): + self.mock_vyos.get_config = MagicMock(return_value={}) + result = get_running_config(self.mock_vyos) + self.assertEqual(result, {}) + + +class TestVyOSSystemBuildCommands(unittest.TestCase): + + def _have(self): + return { + "host_name": "vyos150", + "domain_name": "lab.example.com", + "name_server": ["8.8.8.8", "8.8.4.4"], + "domain_search": ["sub1.example.com"], + } + + def test_present_idempotent(self): + config = { + "host_name": "vyos150", + "domain_name": "lab.example.com", + "name_server": ["8.8.8.8", "8.8.4.4"], + "domain_search": ["sub1.example.com"], + } + cmds = build_commands(config, self._have(), "present") + self.assertEqual(cmds, []) + + def test_present_new_hostname(self): + config = { + "host_name": "router1", + "domain_name": None, + "name_server": None, + "domain_search": None, + } + cmds = build_commands(config, self._have(), "present") + self.assertIn(("set", _BASE + ["host-name", "router1"]), cmds) + + def test_present_new_nameserver(self): + config = { + "host_name": None, + "domain_name": None, + "name_server": ["1.1.1.1"], + "domain_search": None, + } + cmds = build_commands(config, self._have(), "present") + self.assertIn(("set", _BASE + ["name-server", "1.1.1.1"]), cmds) + + def test_present_existing_nameserver_skipped(self): + config = { + "host_name": None, + "domain_name": None, + "name_server": ["8.8.8.8"], + "domain_search": None, + } + cmds = build_commands(config, self._have(), "present") + self.assertEqual(cmds, []) + + def test_absent_domain_name(self): + config = { + "host_name": None, + "domain_name": "lab.example.com", + "name_server": None, + "domain_search": None, + } + cmds = build_commands(config, self._have(), "absent") + self.assertIn(("delete", _BASE + ["domain-name"]), cmds) + + def test_absent_nameserver(self): + config = { + "host_name": None, + "domain_name": None, + "name_server": ["8.8.8.8"], + "domain_search": None, + } + cmds = build_commands(config, self._have(), "absent") + self.assertIn(("delete", _BASE + ["name-server", "8.8.8.8"]), cmds) + + def test_absent_nonexistent_skipped(self): + # domain_name not in have - nothing to delete + config = { + "host_name": None, + "domain_name": "other.com", + "name_server": None, + "domain_search": None, + } + have = {"host_name": "vyos150"} # no domain_name in have + cmds = build_commands(config, have, "absent") + self.assertEqual(cmds, []) + + +if __name__ == "__main__": + unittest.main() |
