diff --git a/.github/workflows/ah_token_refresh.yml b/.github/workflows/ah_token_refresh.yml
new file mode 100644
index 00000000..096685dd
--- /dev/null
+++ b/.github/workflows/ah_token_refresh.yml
@@ -0,0 +1,14 @@
+name: Refresh the automation hub token
+# the token expires every 30 days, so we need to refresh it
+on:
+ schedule:
+ - cron: '0 12 1,15 * *' # run 12pm on the 1st and 15th of the month
+ workflow_dispatch:
+
+jobs:
+ refresh:
+ uses: ansible/team-devtools/.github/workflows/ah_token_refresh.yml@v22.5.0
+ with:
+ environment: release
+ secrets:
+ ah_token: ${{ secrets.AH_TOKEN }}
diff --git a/changelogs/fragments/T7391_domain_search.yaml b/changelogs/fragments/T7391_domain_search.yaml
new file mode 100644
index 00000000..17f2c4be
--- /dev/null
+++ b/changelogs/fragments/T7391_domain_search.yaml
@@ -0,0 +1,4 @@
+---
+trivial:
+ - vyos_system - Added support for domain_search for 1.4+
+ - test_vyos_system - Added test for domain_search
diff --git a/changelogs/fragments/revert-409-paramiko-compatible.yaml b/changelogs/fragments/revert-409-paramiko-compatible.yaml
new file mode 100644
index 00000000..658c6166
--- /dev/null
+++ b/changelogs/fragments/revert-409-paramiko-compatible.yaml
@@ -0,0 +1,3 @@
+---
+trivial:
+ - changelog to enable the branch restore
diff --git a/plugins/module_utils/network/vyos/vyos.py b/plugins/module_utils/network/vyos/vyos.py
index 837ae905..4987d6b0 100644
--- a/plugins/module_utils/network/vyos/vyos.py
+++ b/plugins/module_utils/network/vyos/vyos.py
@@ -1,115 +1,116 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# (c) 2016 Red Hat Inc.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection, ConnectionError
_DEVICE_CONFIGS = {}
def get_connection(module):
if hasattr(module, "_vyos_connection"):
return module._vyos_connection
capabilities = get_capabilities(module)
network_api = capabilities.get("network_api")
if network_api == "cliconf":
module._vyos_connection = Connection(module._socket_path)
else:
module.fail_json(msg="Invalid connection type %s" % network_api)
return module._vyos_connection
def get_capabilities(module):
if hasattr(module, "_vyos_capabilities"):
return module._vyos_capabilities
try:
capabilities = Connection(module._socket_path).get_capabilities()
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
module._vyos_capabilities = json.loads(capabilities)
return module._vyos_capabilities
def get_config(module, flags=None, format=None):
flags = [] if flags is None else flags
global _DEVICE_CONFIGS
+
# If _DEVICE_CONFIGS is non-empty and module.params["match"] is "none",
# return the cached device configurations. This avoids redundant calls
# to the connection when no specific match criteria are provided.
if _DEVICE_CONFIGS != {} and (
module.params["match"] is not None and module.params["match"] == "none"
):
return to_text(_DEVICE_CONFIGS)
else:
connection = get_connection(module)
try:
out = connection.get_config(flags=flags, format=format)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
cfg = to_text(out, errors="surrogate_then_replace").strip()
_DEVICE_CONFIGS = cfg
return cfg
def run_commands(module, commands, check_rc=True):
connection = get_connection(module)
try:
response = connection.run_commands(commands=commands, check_rc=check_rc)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
return response
def load_config(module, commands, commit=False, comment=None, confirm=None):
connection = get_connection(module)
try:
response = connection.edit_config(
candidate=commands, commit=commit, comment=comment, confirm=confirm
)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
return response.get("diff")
def get_os_version(module):
connection = get_connection(module)
if connection.get_device_info():
os_version = connection.get_device_info()["network_os_major_version"]
return os_version
diff --git a/plugins/modules/vyos_system.py b/plugins/modules/vyos_system.py
index 96a0e9bc..d8d676e8 100644
--- a/plugins/modules/vyos_system.py
+++ b/plugins/modules/vyos_system.py
@@ -1,216 +1,224 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see .
#
+
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: vyos_system
author: Nathaniel Case (@Qalthos)
short_description: Run `set system` commands on VyOS devices
description:
- Runs one or more commands on remote devices running VyOS. This module can also be
introspected to validate key parameters before returning successfully.
version_added: 1.0.0
extends_documentation_fragment:
- vyos.vyos.vyos
notes:
- Tested against VyOS 1.1.8 (helium).
- This module works with connection C(ansible.netcommon.network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html).
options:
host_name:
description:
- Configure the device hostname parameter. This option takes an ASCII string value.
type: str
domain_name:
description:
- The new domain name to apply to the device.
type: str
name_server:
description:
- A list of name servers to use with the device. Mutually exclusive with I(domain_search)
type: list
elements: str
aliases:
- name_servers
domain_search:
description:
- A list of domain names to search. Mutually exclusive with I(name_server)
type: list
elements: str
state:
description:
- Whether to apply (C(present)) or remove (C(absent)) the settings.
default: present
type: str
choices:
- present
- absent
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- set system hostname vyos01
- set system domain-name foo.example.com
"""
EXAMPLES = """
- name: configure hostname and domain-name
vyos.vyos.vyos_system:
host_name: vyos01
domain_name: test.example.com
- name: remove all configuration
vyos.vyos.vyos_system:
state: absent
- name: configure name servers
vyos.vyos.vyos_system: name_servers - 8.8.8.8 - 8.8.4.4
- name: configure domain search suffixes
vyos.vyos.vyos_system:
domain_search:
- sub1.example.com
- sub2.example.com
"""
+from re import M, findall
from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
get_config,
+ get_os_version,
load_config,
)
-def spec_key_to_device_key(key):
+def spec_key_to_device_key(key, module):
device_key = key.replace("_", "-")
- # domain-search is longer than just it's key
+ # domain-search differs in 1.3- and 1.4+
if device_key == "domain-search":
- device_key += " domain"
+ if LooseVersion(get_os_version(module)) <= LooseVersion("1.3"):
+ device_key += " domain"
return device_key
def config_to_dict(module):
data = get_config(module)
config = {"domain_search": [], "name_server": []}
for line in data.split("\n"):
- if line.startswith("set system host-name"):
- config["host_name"] = line[22:-1]
- elif line.startswith("set system domain-name"):
- config["domain_name"] = line[24:-1]
- elif line.startswith("set system domain-search domain"):
- config["domain_search"].append(line[33:-1])
- elif line.startswith("set system name-server"):
- config["name_server"].append(line[24:-1])
-
+ config_line = findall(r"^set system\s+(\S+)(?:\s+domain)?\s+'([^']+)'", line, M)
+ if config_line:
+ if config_line[0][0] == "host-name":
+ config["host_name"] = config_line[0][1]
+ elif config_line[0][0] == "domain-name":
+ config["domain_name"] = config_line[0][1]
+ elif config_line[0][0] == "domain-search":
+ config["domain_search"].append(config_line[0][1])
+ elif config_line[0][0] == "name-server":
+ config["name_server"].append(config_line[0][1])
return config
-def spec_to_commands(want, have):
+def spec_to_commands(want, have, module):
commands = []
state = want.pop("state")
# state='absent' by itself has special meaning
if state == "absent" and all(v is None for v in want.values()):
# Clear everything
for key in have:
- commands.append("delete system %s" % spec_key_to_device_key(key))
+ commands.append("delete system %s" % spec_key_to_device_key(key, module))
for key in want:
if want[key] is None:
continue
current = have.get(key)
proposed = want[key]
- device_key = spec_key_to_device_key(key)
+ device_key = spec_key_to_device_key(key, module)
# These keys are lists which may need to be reconciled with the device
if key in ["domain_search", "name_server"]:
if not proposed:
# Empty list was passed, delete all values
commands.append("delete system %s" % device_key)
for config in proposed:
if state == "absent" and config in current:
commands.append("delete system %s '%s'" % (device_key, config))
elif state == "present" and config not in current:
commands.append("set system %s '%s'" % (device_key, config))
else:
if state == "absent" and current and proposed:
commands.append("delete system %s" % device_key)
elif state == "present" and proposed and proposed != current:
commands.append("set system %s '%s'" % (device_key, proposed))
return commands
def map_param_to_obj(module):
return {
"host_name": module.params["host_name"],
"domain_name": module.params["domain_name"],
"domain_search": module.params["domain_search"],
"name_server": module.params["name_server"],
"state": module.params["state"],
}
def main():
argument_spec = dict(
host_name=dict(type="str"),
domain_name=dict(type="str"),
domain_search=dict(type="list", elements="str"),
name_server=dict(type="list", aliases=["name_servers"], elements="str"),
state=dict(type="str", default="present", choices=["present", "absent"]),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
mutually_exclusive=[("domain_name", "domain_search")],
)
warnings = list()
result = {"changed": False, "warnings": warnings}
want = map_param_to_obj(module)
have = config_to_dict(module)
- commands = spec_to_commands(want, have)
+ commands = spec_to_commands(want, have, module)
result["commands"] = commands
if commands:
commit = not module.check_mode
load_config(module, commands, commit=commit)
result["changed"] = True
module.exit_json(**result)
if __name__ == "__main__":
main()
diff --git a/tests/integration/targets/vyos_system/tests/cli/domain_search.yaml b/tests/integration/targets/vyos_system/tests/cli/domain_search.yaml
new file mode 100644
index 00000000..2422d2c7
--- /dev/null
+++ b/tests/integration/targets/vyos_system/tests/cli/domain_search.yaml
@@ -0,0 +1,43 @@
+---
+- debug: msg="START cli/domain_search.yaml on connection={{ ansible_connection }}"
+
+- name: ensure facts
+ include_tasks: _get_version.yaml
+
+- name: setup
+ ignore_errors: true
+ vyos.vyos.vyos_system:
+ domain_search:
+ - nbg.bufanda.ke
+ state: absent
+
+- name: configure domain search setting
+ register: result
+ vyos.vyos.vyos_system:
+ domain_search:
+ - nbg.bufanda.ke
+
+- assert:
+ that:
+ - result.changed == true
+ - result.commands|length == 1
+ - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+- name: configure domain search setting
+ register: result
+ vyos.vyos.vyos_system:
+ domain_search:
+ - nbg.bufanda.ke
+
+- assert:
+ that:
+ - result.changed == false
+
+- name: teardown
+ ignore_errors: true
+ vyos.vyos.vyos_system:
+ domain_search:
+ - nbg.bufanda.ke
+ state: absent
+
+- debug: msg="END cli/basic.yaml on connection={{ ansible_connection }}"
diff --git a/tests/integration/targets/vyos_system/vars/pre-v1_4.yaml b/tests/integration/targets/vyos_system/vars/pre-v1_4.yaml
new file mode 100644
index 00000000..cb41c9c6
--- /dev/null
+++ b/tests/integration/targets/vyos_system/vars/pre-v1_4.yaml
@@ -0,0 +1,4 @@
+---
+merged:
+ commands:
+ - set system domain-search domain 'nbg.bufanda.ke'
diff --git a/tests/integration/targets/vyos_system/vars/v1_4.yaml b/tests/integration/targets/vyos_system/vars/v1_4.yaml
new file mode 100644
index 00000000..96f0b7c9
--- /dev/null
+++ b/tests/integration/targets/vyos_system/vars/v1_4.yaml
@@ -0,0 +1,4 @@
+---
+merged:
+ commands:
+ - set system domain-search 'nbg.bufanda.ke'
diff --git a/tests/unit/modules/network/vyos/test_vyos_system.py b/tests/unit/modules/network/vyos/test_vyos_system.py
index cf405cab..5edfa0df 100644
--- a/tests/unit/modules/network/vyos/test_vyos_system.py
+++ b/tests/unit/modules/network/vyos/test_vyos_system.py
@@ -1,114 +1,193 @@
# (c) 2016 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see .
# Make coding more python3-ish
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from unittest.mock import patch
from ansible_collections.vyos.vyos.plugins.modules import vyos_system
from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
from .vyos_module import TestVyosModule, load_fixture
class TestVyosSystemModule(TestVyosModule):
module = vyos_system
def setUp(self):
super(TestVyosSystemModule, self).setUp()
self.mock_get_config = patch(
"ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_config",
)
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch(
"ansible_collections.vyos.vyos.plugins.modules.vyos_system.load_config",
)
self.load_config = self.mock_load_config.start()
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version",
+ )
+ self.test_version = "1.2"
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = self.test_version
+ self.mock_facts_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version",
+ )
+ self.get_facts_os_version = self.mock_facts_get_os_version.start()
+ self.get_facts_os_version.return_value = self.test_version
+ self.maxDiff = None
+
def tearDown(self):
super(TestVyosSystemModule, self).tearDown()
self.mock_get_config.stop()
self.mock_load_config.stop()
+ self.mock_get_os_version.stop()
+ self.mock_facts_get_os_version.stop()
def load_fixtures(self, commands=None, filename=None):
self.get_config.return_value = load_fixture("vyos_config_config.cfg")
def test_vyos_system_hostname(self):
set_module_args(dict(host_name="foo"))
commands = ["set system host-name 'foo'"]
self.execute_module(changed=True, commands=commands)
def test_vyos_system_clear_hostname(self):
set_module_args(dict(host_name="foo", state="absent"))
commands = ["delete system host-name"]
self.execute_module(changed=True, commands=commands)
def test_vyos_remove_single_name_server(self):
set_module_args(dict(name_server=["8.8.4.4"], state="absent"))
commands = ["delete system name-server '8.8.4.4'"]
self.execute_module(changed=True, commands=commands)
def test_vyos_system_domain_name(self):
set_module_args(dict(domain_name="example2.com"))
commands = ["set system domain-name 'example2.com'"]
self.execute_module(changed=True, commands=commands)
def test_vyos_system_clear_domain_name(self):
set_module_args(dict(domain_name="example.com", state="absent"))
commands = ["delete system domain-name"]
self.execute_module(changed=True, commands=commands)
def test_vyos_system_domain_search(self):
set_module_args(dict(domain_search=["foo.example.com", "bar.example.com"]))
commands = [
"set system domain-search domain 'foo.example.com'",
"set system domain-search domain 'bar.example.com'",
]
self.execute_module(changed=True, commands=commands)
def test_vyos_system_clear_domain_search(self):
set_module_args(dict(domain_search=[]))
commands = ["delete system domain-search domain"]
self.execute_module(changed=True, commands=commands)
def test_vyos_system_no_change(self):
set_module_args(
dict(
host_name="router",
domain_name="example.com",
name_server=["8.8.8.8", "8.8.4.4"],
),
)
result = self.execute_module()
self.assertEqual([], result["commands"])
def test_vyos_system_clear_all(self):
set_module_args(dict(state="absent"))
commands = [
"delete system host-name",
"delete system domain-search domain",
"delete system domain-name",
"delete system name-server",
]
self.execute_module(changed=True, commands=commands)
+
+
+class TestVyosSystemModule14(TestVyosModule):
+ module = vyos_system
+
+ def setUp(self):
+ super(TestVyosSystemModule14, self).setUp()
+
+ self.mock_get_config = patch(
+ "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_config",
+ )
+ self.get_config = self.mock_get_config.start()
+
+ self.mock_load_config = patch(
+ "ansible_collections.vyos.vyos.plugins.modules.vyos_system.load_config",
+ )
+ self.load_config = self.mock_load_config.start()
+
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version",
+ )
+ self.test_version = "1.4"
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = self.test_version
+ self.mock_facts_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.modules.vyos_system.get_os_version",
+ )
+ self.get_facts_os_version = self.mock_facts_get_os_version.start()
+ self.get_facts_os_version.return_value = self.test_version
+ self.maxDiff = None
+
+ def tearDown(self):
+ super(TestVyosSystemModule14, self).tearDown()
+
+ self.mock_get_config.stop()
+ self.mock_load_config.stop()
+ self.mock_get_os_version.stop()
+ self.mock_facts_get_os_version.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ self.get_config.return_value = load_fixture("vyos_config_config.cfg")
+
+ def test_vyos_system_domain_search(self):
+ set_module_args(dict(domain_search=["foo.example.com", "bar.example.com"]))
+ commands = [
+ "set system domain-search 'foo.example.com'",
+ "set system domain-search 'bar.example.com'",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_system_clear_domain_search(self):
+ set_module_args(dict(domain_search=[]))
+ commands = ["delete system domain-search"]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_system_clear_all(self):
+ set_module_args(dict(state="absent"))
+ commands = [
+ "delete system host-name",
+ "delete system domain-search",
+ "delete system domain-name",
+ "delete system name-server",
+ ]
+ self.execute_module(changed=True, commands=commands)