From 4662d6d03742ecc2fd09c530cf4b70217975e5bb Mon Sep 17 00:00:00 2001 From: GomathiselviS Date: Thu, 17 Feb 2022 10:10:20 -0500 Subject: Add Vyos hostname resource module (#237) Add Vyos hostname resource module SUMMARY ISSUE TYPE New Module Pull Request COMPONENT NAME ADDITIONAL INFORMATION Reviewed-by: Nilashish Chakraborty Reviewed-by: None --- README.md | 1 + changelogs/fragments/vyos_hostname_rm.yaml | 3 + docs/vyos.vyos.vyos_hostname_module.rst | 386 +++++++++++++++++++++ meta/runtime.yml | 6 + .../network/vyos/argspec/hostname/__init__.py | 0 .../network/vyos/argspec/hostname/hostname.py | 49 +++ .../network/vyos/config/hostname/__init__.py | 0 .../network/vyos/config/hostname/hostname.py | 75 ++++ plugins/module_utils/network/vyos/facts/facts.py | 4 + .../network/vyos/facts/hostname/__init__.py | 0 .../network/vyos/facts/hostname/hostname.py | 76 ++++ .../network/vyos/rm_templates/hostname.py | 47 +++ plugins/modules/vyos_hostname.py | 284 +++++++++++++++ .../targets/vyos_hostname/defaults/main.yaml | 3 + .../targets/vyos_hostname/meta/main.yaml | 2 + .../targets/vyos_hostname/tasks/cli.yaml | 19 + .../targets/vyos_hostname/tasks/main.yaml | 4 + .../targets/vyos_hostname/tests/cli/_parsed.cfg | 1 + .../vyos_hostname/tests/cli/_populate_config.yaml | 8 + .../vyos_hostname/tests/cli/_remove_config.yaml | 8 + .../targets/vyos_hostname/tests/cli/deleted.yaml | 40 +++ .../vyos_hostname/tests/cli/empty_config.yaml | 60 ++++ .../targets/vyos_hostname/tests/cli/gathered.yaml | 28 ++ .../targets/vyos_hostname/tests/cli/merged.yaml | 45 +++ .../vyos_hostname/tests/cli/overridden.yaml | 42 +++ .../targets/vyos_hostname/tests/cli/parsed.yaml | 17 + .../targets/vyos_hostname/tests/cli/rendered.yaml | 21 ++ .../targets/vyos_hostname/tests/cli/replaced.yaml | 42 +++ .../targets/vyos_hostname/vars/main.yaml | 12 + .../network/vyos/fixtures/vyos_hostname_config.cfg | 1 + .../modules/network/vyos/test_vyos_hostname.py | 134 +++++++ 31 files changed, 1418 insertions(+) create mode 100644 changelogs/fragments/vyos_hostname_rm.yaml create mode 100644 docs/vyos.vyos.vyos_hostname_module.rst create mode 100644 plugins/module_utils/network/vyos/argspec/hostname/__init__.py create mode 100644 plugins/module_utils/network/vyos/argspec/hostname/hostname.py create mode 100644 plugins/module_utils/network/vyos/config/hostname/__init__.py create mode 100644 plugins/module_utils/network/vyos/config/hostname/hostname.py create mode 100644 plugins/module_utils/network/vyos/facts/hostname/__init__.py create mode 100644 plugins/module_utils/network/vyos/facts/hostname/hostname.py create mode 100644 plugins/module_utils/network/vyos/rm_templates/hostname.py create mode 100644 plugins/modules/vyos_hostname.py create mode 100644 tests/integration/targets/vyos_hostname/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_hostname/meta/main.yaml create mode 100644 tests/integration/targets/vyos_hostname/tasks/cli.yaml create mode 100644 tests/integration/targets/vyos_hostname/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/_parsed.cfg create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/deleted.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/empty_config.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/gathered.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/merged.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/overridden.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/parsed.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/rendered.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/cli/replaced.yaml create mode 100644 tests/integration/targets/vyos_hostname/vars/main.yaml create mode 100644 tests/unit/modules/network/vyos/fixtures/vyos_hostname_config.cfg create mode 100644 tests/unit/modules/network/vyos/test_vyos_hostname.py diff --git a/README.md b/README.md index f37e259f..cf2c9bad 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Name | Description [vyos.vyos.vyos_firewall_global](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_global_module.rst)|FIREWALL global resource module [vyos.vyos.vyos_firewall_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_interfaces_module.rst)|FIREWALL interfaces resource module [vyos.vyos.vyos_firewall_rules](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_rules_module.rst)|FIREWALL rules resource module +[vyos.vyos.vyos_hostname](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_hostname_module.rst)|Manages hostname resource module [vyos.vyos.vyos_interface](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_interface_module.rst)|(deprecated, removed after 2022-06-01) Manage Interface on VyOS network devices [vyos.vyos.vyos_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_interfaces_module.rst)|Interfaces resource module [vyos.vyos.vyos_l3_interface](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_l3_interface_module.rst)|(deprecated, removed after 2022-06-01) Manage L3 interfaces on VyOS network devices diff --git a/changelogs/fragments/vyos_hostname_rm.yaml b/changelogs/fragments/vyos_hostname_rm.yaml new file mode 100644 index 00000000..b9d9341d --- /dev/null +++ b/changelogs/fragments/vyos_hostname_rm.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Add vyos_hostname resource module. diff --git a/docs/vyos.vyos.vyos_hostname_module.rst b/docs/vyos.vyos.vyos_hostname_module.rst new file mode 100644 index 00000000..569017ab --- /dev/null +++ b/docs/vyos.vyos.vyos_hostname_module.rst @@ -0,0 +1,386 @@ +.. _vyos.vyos.vyos_hostname_module: + + +*********************** +vyos.vyos.vyos_hostname +*********************** + +**Manages hostname resource module** + + +Version added: 2.8.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This module manages the hostname attribute of Vyos network devices + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ config + +
+ dictionary +
+
+ +
Hostname configuration.
+
+
+ hostname + +
+ string +
+
+ +
set hostname for VYOS.
+
+
+ running_config + +
+ string +
+
+ +
This option is used only with state parsed.
+
The value of this option should be the output received from the vyos device by executing the command "show configuration commands | grep host-name".
+
The state parsed reads the configuration from running_config option and transforms it into Ansible structured data as per the resource module's argspec and the value is then returned in the parsed key within the result.
+
+
+ state + +
+ string +
+
+
    Choices: +
  • merged ←
  • +
  • replaced
  • +
  • overridden
  • +
  • deleted
  • +
  • gathered
  • +
  • parsed
  • +
  • rendered
  • +
+
+
The state the configuration should be left in
+
The states rendered, gathered and parsed does not perform any change on the device.
+
The state rendered will transform the configuration in config option to platform specific CLI commands which will be returned in the rendered key within the result. For state rendered active connection to remote host is not required.
+
The states merged, replaced and overridden have identical behaviour for this module.
+
The state gathered will fetch the running configuration from device and transform it into structured data in the format as per the resource module argspec and the value is returned in the gathered key within the result.
+
The state parsed reads the configuration from running_config option and transforms it into JSON format as per the resource module parameters and the value is returned in the parsed key within the result. The value of running_config option should be the same format as the output of command show configuration commands | grep host-name executed on device. For state parsed active connection to remote host is not required.
+
+
+ + +Notes +----- + +.. note:: + - Tested against vyos 1.1.8 + - This module works with connection ``network_cli``. + - The Configuration defaults of the Vyos network devices are supposed to hinder idempotent behavior of plays + + + +Examples +-------- + +.. code-block:: yaml + + # Using state: merged + # Before state: + # ------------- + # test#show configuration commands | grep host-name + # set system host-name 'vyostest' + # Merged play: + # ------------ + - name: Apply the provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyos + state: merged + # Commands Fired: + # --------------- + # "commands": [ + # "hostname vyos", + # ], + # After state: + # ------------ + # test#show configuration commands | grep host-name + # set system host-name 'vyos' + + # Using state: deleted + # Before state: + # ------------- + # test#show configuration commands | grep host-name + # set system host-name 'vyos' + # Deleted play: + # ------------- + - name: Remove all existing configuration + vyos.vyos.vyos_hostname: + state: deleted + # Commands Fired: + # --------------- + # "commands": [ + # "no hostname vyosTest", + # ], + # After state: + # ------------ + # test#show configuration commands | grep host-name + + # Using state: overridden + # Before state: + # ------------- + # test#show configuration commands | grep host-name + # set system host-name 'vyos' + # Overridden play: + # ---------------- + - name: Override commands with provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyosTest + state: overridden + # Commands Fired: + # --------------- + # "commands": [ + # "hostname vyosTest", + # ], + # After state: + # ------------ + # test#show configuration commands | grep host-name + # set system host-name 'vyosTest' + + # Using state: replaced + # Before state: + # ------------- + # test#show configuration commands | grep host-name + # set system host-name 'vyosTest' + # Replaced play: + # -------------- + - name: Replace commands with provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyos + state: replaced + # After state: + # ------------ + # test#show configuration commands | grep host-name + # set system host-name 'vyos' + + # Using state: gathered + # Before state: + # ------------- + #test#show configuration commands | grep host-name + # set system host-name 'vyos' + # Gathered play: + # -------------- + - name: Gather listed hostname config + vyos.vyos.vyos_hostname: + state: gathered + # Module Execution Result: + # ------------------------ + # "gathered": { + # "hostname": "vyos" + # }, + + # Using state: rendered + # Rendered play: + # -------------- + - name: Render the commands for provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyosTest + state: rendered + # Module Execution Result: + # ------------------------ + # "rendered": [ + # "set system host-name vyosTest", + # ] + + # Using state: parsed + # File: parsed.cfg + # ---------------- + # set system host-name 'vyos' + # Parsed play: + # ------------ + - name: Parse the provided configuration with the existing running configuration + vyos.vyos.vyos_hostname: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed + # Module Execution Result: + # ------------------------ + # "parsed": { + # "hostname": "vyos" + # } + + + +Return Values +------------- +Common return values are documented `here `_, the following are the fields unique to this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyReturnedDescription
+
+ after + +
+ dictionary +
+
when changed +
The resulting configuration after module execution.
+
+
Sample:
+
This output will always be in the same format as the module argspec.
+
+
+ before + +
+ dictionary +
+
when state is merged, replaced, overridden, deleted or purged +
The configuration prior to the module execution.
+
+
Sample:
+
This output will always be in the same format as the module argspec.
+
+
+ commands + +
+ list +
+
when state is merged, replaced, overridden, deleted or purged +
The set of commands pushed to the remote device.
+
+
Sample:
+
['sample command 1', 'sample command 2', 'sample command 3']
+
+
+ gathered + +
+ list +
+
when state is gathered +
Facts about the network resource gathered from the remote device as structured data.
+
+
Sample:
+
This output will always be in the same format as the module argspec.
+
+
+ parsed + +
+ list +
+
when state is parsed +
The device native config provided in running_config option parsed into structured data as per module argspec.
+
+
Sample:
+
This output will always be in the same format as the module argspec.
+
+
+ rendered + +
+ list +
+
when state is rendered +
The provided configuration in the task rendered in device-native format (offline).
+
+
Sample:
+
['sample command 1', 'sample command 2', 'sample command 3']
+
+

+ + +Status +------ + + +Authors +~~~~~~~ + +- Gomathi Selvi Srinivasan (@GomathiselviS) diff --git a/meta/runtime.yml b/meta/runtime.yml index 73ba6a2d..814c20bb 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -38,6 +38,10 @@ plugin_routing: redirect: vyos.vyos.vyos firewall_rules: redirect: vyos.vyos.vyos + vyos_hostname: + redirect: vyos.vyos.vyos + hostname: + redirect: vyos.vyos.vyos vyos_interface: redirect: vyos.vyos.vyos interface: @@ -153,6 +157,8 @@ plugin_routing: redirect: vyos.vyos.vyos_firewall_interfaces firewall_rules: redirect: vyos.vyos.vyos_firewall_rules + hostname: + redirect: vyos.vyos.vyos_hostname interface: redirect: vyos.vyos.vyos_interface deprecation: diff --git a/plugins/module_utils/network/vyos/argspec/hostname/__init__.py b/plugins/module_utils/network/vyos/argspec/hostname/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/module_utils/network/vyos/argspec/hostname/hostname.py b/plugins/module_utils/network/vyos/argspec/hostname/hostname.py new file mode 100644 index 00000000..1a3cf91f --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/hostname/hostname.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the +# cli_rm_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the module docstring and re-run +# cli_rm_builder. +# +############################################# + +""" +The arg spec for the vyos_hostname module +""" + + +class HostnameArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_hostname module""" + + argument_spec = { + "config": {"type": "dict", "options": {"hostname": {"type": "str"}}}, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "parsed", + "rendered", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/hostname/__init__.py b/plugins/module_utils/network/vyos/config/hostname/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/module_utils/network/vyos/config/hostname/hostname.py b/plugins/module_utils/network/vyos/config/hostname/hostname.py new file mode 100644 index 00000000..cf2c8c26 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/hostname/hostname.py @@ -0,0 +1,75 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +""" +The vyos_hostname config file. +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to its desired end-state is +created. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( + Facts, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.hostname import ( + HostnameTemplate, +) + + +class Hostname(ResourceModule): + """ + The vyos_hostname config class + """ + + def __init__(self, module): + super(Hostname, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="hostname", + tmplt=HostnameTemplate(), + ) + self.parsers = [] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = self.want + haved = self.have + + if self.state == "deleted": + wantd = {} + + self._compare(want=wantd, have=haved) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Hostname network resource. + """ + self.compare(parsers="hostname", want=want, have=have) diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 76cfd907..867c427f 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -70,6 +70,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ntp_g from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.snmp_server.snmp_server import ( Snmp_serverFacts, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.hostname.hostname import ( + HostnameFacts, +) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Default, Neighbors, @@ -98,6 +101,7 @@ FACT_RESOURCE_SUBSETS = dict( logging_global=Logging_globalFacts, ntp_global=Ntp_globalFacts, snmp_server=Snmp_serverFacts, + hostname=HostnameFacts, ) diff --git a/plugins/module_utils/network/vyos/facts/hostname/__init__.py b/plugins/module_utils/network/vyos/facts/hostname/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/plugins/module_utils/network/vyos/facts/hostname/hostname.py b/plugins/module_utils/network/vyos/facts/hostname/hostname.py new file mode 100644 index 00000000..acdddca0 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/hostname/hostname.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +""" +The vyos hostname fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.hostname import ( + HostnameTemplate, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.hostname.hostname import ( + HostnameArgs, +) + +import re + + +class HostnameFacts(object): + """The vyos hostname facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = HostnameArgs.argument_spec + + def get_config(self, connection): + return connection.get("show configuration commands | grep host-name") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Snmp_server network resource + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + + :rtype: dictionary + :returns: facts + """ + facts = {} + objs = [] + config_lines = [] + + if not data: + data = self.get_config(connection) + for resource in data.splitlines(): + config_lines.append(re.sub("'", "", resource)) + + # parse native config using the Hostname template + hostname_parser = HostnameTemplate( + lines=config_lines, module=self._module + ) + objs = hostname_parser.parse() + + ansible_facts["ansible_network_resources"].pop("hostname", None) + + params = utils.remove_empties( + hostname_parser.validate_config( + self.argument_spec, {"config": objs}, redact=True + ) + ) + + facts["hostname"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/plugins/module_utils/network/vyos/rm_templates/hostname.py b/plugins/module_utils/network/vyos/rm_templates/hostname.py new file mode 100644 index 00000000..79caee6d --- /dev/null +++ b/plugins/module_utils/network/vyos/rm_templates/hostname.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +""" +The Hostname parser templates file. This contains +a list of parser definitions and associated functions that +facilitates both facts gathering and native command generation for +the given network resource. +""" + +import re +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +class HostnameTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + prefix = {"set": "set", "remove": "delete"} + super(HostnameTemplate, self).__init__( + lines=lines, tmplt=self, prefix=prefix, module=module + ) + + # fmt: off + PARSERS = [ + # service snmp community <> + { + "name": "hostname", + "getval": re.compile( + r""" + ^set\ssystem\shost-name + \s+(?P\S+) + $""", + re.VERBOSE), + "setval": "system host-name {{ hostname }}", + "result": { + "hostname": "{{ name }}" + } + }, + ] + # fmt: on diff --git a/plugins/modules/vyos_hostname.py b/plugins/modules/vyos_hostname.py new file mode 100644 index 00000000..da22f414 --- /dev/null +++ b/plugins/modules/vyos_hostname.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2022 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +The module file for vyos_hostname +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = """ +module: vyos_hostname +version_added: 2.8.0 +short_description: Manages hostname resource module +description: This module manages the hostname attribute of Vyos network devices +author: Gomathi Selvi Srinivasan (@GomathiselviS) +notes: + - Tested against vyos 1.1.8 + - This module works with connection C(network_cli). + - The Configuration defaults of the Vyos network devices + are supposed to hinder idempotent behavior of plays +options: + config: + description: Hostname configuration. + type: dict + suboptions: + hostname: + description: set hostname for VYOS. + type: str + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the vyos device by + executing the command B("show configuration commands | grep host-name"). + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into Ansible structured data as per the resource module's argspec + and the value is then returned in the I(parsed) key within the result. + type: str + state: + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - parsed + - rendered + default: merged + description: + - The state the configuration should be left in + - The states I(rendered), I(gathered) and I(parsed) does not perform any change + on the device. + - The state I(rendered) will transform the configuration in C(config) option to + platform specific CLI commands which will be returned in the I(rendered) key + within the result. For state I(rendered) active connection to remote host is + not required. + - The states I(merged), I(replaced) and I(overridden) have identical + behaviour for this module. + - The state I(gathered) will fetch the running configuration from device and transform + it into structured data in the format as per the resource module argspec and + the value is returned in the I(gathered) key within the result. + - The state I(parsed) reads the configuration from C(running_config) option and + transforms it into JSON format as per the resource module parameters and the + value is returned in the I(parsed) key within the result. The value of C(running_config) + option should be the same format as the output of command + I(show configuration commands | grep host-name) executed on device. For state I(parsed) active + connection to remote host is not required. + type: str +""" + +EXAMPLES = """ +# Using state: merged +# Before state: +# ------------- +# test#show configuration commands | grep host-name +# set system host-name 'vyostest' +# Merged play: +# ------------ +- name: Apply the provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyos + state: merged +# Commands Fired: +# --------------- +# "commands": [ +# "hostname vyos", +# ], +# After state: +# ------------ +# test#show configuration commands | grep host-name +# set system host-name 'vyos' + +# Using state: deleted +# Before state: +# ------------- +# test#show configuration commands | grep host-name +# set system host-name 'vyos' +# Deleted play: +# ------------- +- name: Remove all existing configuration + vyos.vyos.vyos_hostname: + state: deleted +# Commands Fired: +# --------------- +# "commands": [ +# "no hostname vyosTest", +# ], +# After state: +# ------------ +# test#show configuration commands | grep host-name + +# Using state: overridden +# Before state: +# ------------- +# test#show configuration commands | grep host-name +# set system host-name 'vyos' +# Overridden play: +# ---------------- +- name: Override commands with provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyosTest + state: overridden +# Commands Fired: +# --------------- +# "commands": [ +# "hostname vyosTest", +# ], +# After state: +# ------------ +# test#show configuration commands | grep host-name +# set system host-name 'vyosTest' + +# Using state: replaced +# Before state: +# ------------- +# test#show configuration commands | grep host-name +# set system host-name 'vyosTest' +# Replaced play: +# -------------- +- name: Replace commands with provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyos + state: replaced +# After state: +# ------------ +# test#show configuration commands | grep host-name +# set system host-name 'vyos' + +# Using state: gathered +# Before state: +# ------------- +#test#show configuration commands | grep host-name +# set system host-name 'vyos' +# Gathered play: +# -------------- +- name: Gather listed hostname config + vyos.vyos.vyos_hostname: + state: gathered +# Module Execution Result: +# ------------------------ +# "gathered": { +# "hostname": "vyos" +# }, + +# Using state: rendered +# Rendered play: +# -------------- +- name: Render the commands for provided configuration + vyos.vyos.vyos_hostname: + config: + hostname: vyosTest + state: rendered +# Module Execution Result: +# ------------------------ +# "rendered": [ +# "set system host-name vyosTest", +# ] + +# Using state: parsed +# File: parsed.cfg +# ---------------- +# set system host-name 'vyos' +# Parsed play: +# ------------ +- name: Parse the provided configuration with the existing running configuration + vyos.vyos.vyos_hostname: + running_config: "{{ lookup('file', 'parsed.cfg') }}" + state: parsed +# Module Execution Result: +# ------------------------ +# "parsed": { +# "hostname": "vyos" +# } +""" + + +RETURN = """ +before: + description: The configuration prior to the module execution. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: dict + sample: > + This output will always be in the same format as the + module argspec. +after: + description: The resulting configuration after module execution. + returned: when changed + type: dict + sample: > + This output will always be in the same format as the + module argspec. +commands: + description: The set of commands pushed to the remote device. + returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged) + type: list + sample: + - sample command 1 + - sample command 2 + - sample command 3 +rendered: + description: The provided configuration in the task rendered in device-native format (offline). + returned: when I(state) is C(rendered) + type: list + sample: + - sample command 1 + - sample command 2 + - sample command 3 +gathered: + description: Facts about the network resource gathered from the remote device as structured data. + returned: when I(state) is C(gathered) + type: list + sample: > + This output will always be in the same format as the + module argspec. +parsed: + description: The device native config provided in I(running_config) option parsed into structured data as per module argspec. + returned: when I(state) is C(parsed) + type: list + sample: > + This output will always be in the same format as the + module argspec. +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.hostname.hostname import ( + HostnameArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.hostname.hostname import ( + Hostname, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + module = AnsibleModule( + argument_spec=HostnameArgs.argument_spec, + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ["state", "merged", ["config"]], + ["state", "replaced", ["config"]], + ["state", "overridden", ["config"]], + ["state", "rendered", ["config"]], + ["state", "parsed", ["running_config"]], + ], + supports_check_mode=True, + ) + + result = Hostname(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/vyos_hostname/defaults/main.yaml b/tests/integration/targets/vyos_hostname/defaults/main.yaml new file mode 100644 index 00000000..852a6bee --- /dev/null +++ b/tests/integration/targets/vyos_hostname/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: '[^_].*' +test_items: [] diff --git a/tests/integration/targets/vyos_hostname/meta/main.yaml b/tests/integration/targets/vyos_hostname/meta/main.yaml new file mode 100644 index 00000000..91da2a75 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/meta/main.yaml @@ -0,0 +1,2 @@ +--- +... diff --git a/tests/integration/targets/vyos_hostname/tasks/cli.yaml b/tests/integration/targets/vyos_hostname/tasks/cli.yaml new file mode 100644 index 00000000..93eb2fe4 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tasks/cli.yaml @@ -0,0 +1,19 @@ +--- +- name: Collect all cli test cases + find: + paths: '{{ role_path }}/tests/cli' + patterns: '{{ testcase }}.yaml' + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=ansible.netcommon.network_cli) + include: '{{ test_case_to_run }}' + vars: + ansible_connection: ansible.netcommon.network_cli + with_items: '{{ test_items }}' + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/vyos_hostname/tasks/main.yaml b/tests/integration/targets/vyos_hostname/tasks/main.yaml new file mode 100644 index 00000000..b957d2f4 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tasks/main.yaml @@ -0,0 +1,4 @@ +--- +- include: cli.yaml + tags: + - network_cli diff --git a/tests/integration/targets/vyos_hostname/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_hostname/tests/cli/_parsed.cfg new file mode 100644 index 00000000..3353c40d --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/_parsed.cfg @@ -0,0 +1 @@ +set system host-name 'vyosTest' diff --git a/tests/integration/targets/vyos_hostname/tests/cli/_populate_config.yaml b/tests/integration/targets/vyos_hostname/tests/cli/_populate_config.yaml new file mode 100644 index 00000000..4d332896 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/_populate_config.yaml @@ -0,0 +1,8 @@ +--- +- name: setup + vyos.vyos.vyos_config: + lines: + - set system host-name 'vyos' + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_hostname/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_hostname/tests/cli/_remove_config.yaml new file mode 100644 index 00000000..229c79c8 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/_remove_config.yaml @@ -0,0 +1,8 @@ +--- +- name: Remove pre-existing hostname config + vyos.vyos.vyos_hostname: + config: + state: deleted + ignore_errors: true + vars: + ansible_connection: ansible.netcommon.network_cli diff --git a/tests/integration/targets/vyos_hostname/tests/cli/deleted.yaml b/tests/integration/targets/vyos_hostname/tests/cli/deleted.yaml new file mode 100644 index 00000000..5f9df51d --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/deleted.yaml @@ -0,0 +1,40 @@ +--- +- debug: + msg: START vyos_hostname deleted integration tests on connection={{ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + + - name: Delete the provided configuration + register: result + vyos.vyos.vyos_hostname: &id001 + config: + state: deleted + + - name: Assert that before dicts were correctly generated + assert: + that: + - result.changed == true + - result.commands == deleted.commands + + - name: Assert that the after dicts were correctly generated + assert: + that: + - result.after == {} + + - name: Delete the existing configuration with the provided running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_hostname: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_hostname/tests/cli/empty_config.yaml new file mode 100644 index 00000000..8efead2d --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/empty_config.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: START vyos_hostname empty_config integration tests on connection={{ + ansible_connection }} + +- name: Merged with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_hostname: + config: + state: merged + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_hostname: + config: + state: replaced + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_hostname: + config: + state: overridden + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_hostname: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state + parsed' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_hostname: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_hostname/tests/cli/gathered.yaml b/tests/integration/targets/vyos_hostname/tests/cli/gathered.yaml new file mode 100644 index 00000000..0509fc56 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/gathered.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_hostname gathered integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + + - name: Gather config from the device in structured format. + register: result + vyos.vyos.vyos_hostname: + state: gathered + + - vyos.vyos.vyos_facts: + gather_network_resources: hostname + + - name: Assert that facts are correctly generated + assert: + that: + - result.changed == false + - result.gathered == ansible_facts['network_resources']['hostname'] + + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/tests/cli/merged.yaml b/tests/integration/targets/vyos_hostname/tests/cli/merged.yaml new file mode 100644 index 00000000..f9b28c70 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/merged.yaml @@ -0,0 +1,45 @@ +--- +- debug: + msg: START vyos_hostname merged integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_hostname: &id001 + config: + hostname: "vyosTest" + state: merged + + - vyos.vyos.vyos_facts: + gather_network_resources: hostname + + - assert: + that: + - result.commands|length == 1 + - result.changed == true + - result.commands|symmetric_difference(merged.commands) == [] + - result.after == ansible_facts['network_resources']['hostname'] + - result.after == merged.after + + + - name: Assert that before dicts were correctly generated + assert: + that: + - result.before == {} + + - name: + Merge the provided configuration with the existing running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_hostname: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/tests/cli/overridden.yaml b/tests/integration/targets/vyos_hostname/tests/cli/overridden.yaml new file mode 100644 index 00000000..d9fd76d1 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/overridden.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_hostname overridden integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: override the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_hostname: &id001 + config: + hostname: "vyosTest" + state: overridden + + - vyos.vyos.vyos_facts: + gather_network_resources: hostname + + - assert: + that: + - result.commands|length == 1 + - result.changed == true + - result.commands|symmetric_difference(merged.commands) == [] + - result.after == ansible_facts['network_resources']['hostname'] + - result.after == merged.after + + + - name: + override the provided configuration with the existing running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_hostname: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/tests/cli/parsed.yaml b/tests/integration/targets/vyos_hostname/tests/cli/parsed.yaml new file mode 100644 index 00000000..f6c00bbb --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/parsed.yaml @@ -0,0 +1,17 @@ +--- +- debug: + msg: START vyos_hostname parsed integration tests on connection={{ ansible_connection + }} + +- name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_hostname: + running_config: "{{ lookup('file', '_parsed.cfg') }}" + state: parsed + + +- name: Assert that config was correctly parsed + assert: + that: + - result.changed == false + - result.parsed == merged.after diff --git a/tests/integration/targets/vyos_hostname/tests/cli/rendered.yaml b/tests/integration/targets/vyos_hostname/tests/cli/rendered.yaml new file mode 100644 index 00000000..28ec797a --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/rendered.yaml @@ -0,0 +1,21 @@ +--- +- debug: + msg: START vyos_hostname rendered integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Render the given configuration in the form of native commands + register: result + vyos.vyos.vyos_hostname: + config: + hostname: 'vyosTest' + state: rendered + + - assert: + that: + - result.changed == false + - result.rendered|symmetric_difference(merged.commands) == [] + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/tests/cli/replaced.yaml b/tests/integration/targets/vyos_hostname/tests/cli/replaced.yaml new file mode 100644 index 00000000..6acb9937 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/cli/replaced.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_hostname replaced integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate_config.yaml + +- block: + - name: Replace the provided configuration with the existing running configuration + register: result + vyos.vyos.vyos_hostname: &id001 + config: + hostname: "vyosTest" + state: replaced + + - vyos.vyos.vyos_facts: + gather_network_resources: hostname + + - assert: + that: + - result.commands|length == 1 + - result.changed == true + - result.commands|symmetric_difference(merged.commands) == [] + - result.after == ansible_facts['network_resources']['hostname'] + - result.after == merged.after + + + - name: + Replace the provided configuration with the existing running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_hostname: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/vars/main.yaml b/tests/integration/targets/vyos_hostname/vars/main.yaml new file mode 100644 index 00000000..a6b49866 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/vars/main.yaml @@ -0,0 +1,12 @@ +--- +merged: + before: {} + commands: + - set system host-name vyosTest + after: + hostname: 'vyosTest' + +deleted: + commands: + - delete system host-name vyos + after: {} diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_hostname_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_hostname_config.cfg new file mode 100644 index 00000000..f94f7eb8 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_hostname_config.cfg @@ -0,0 +1 @@ +set system host-name 'vyos_test' diff --git a/tests/unit/modules/network/vyos/test_vyos_hostname.py b/tests/unit/modules/network/vyos/test_vyos_hostname.py new file mode 100644 index 00000000..8c2fdfb8 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_hostname.py @@ -0,0 +1,134 @@ +# (c) 2021 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 ansible_collections.vyos.vyos.tests.unit.compat.mock import patch +from ansible_collections.vyos.vyos.plugins.modules import vyos_hostname +from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( + set_module_args, +) +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosHostnameModule(TestVyosModule): + + module = vyos_hostname + + def setUp(self): + super(TestVyosHostnameModule, self).setUp() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_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() + ) + + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.hostname.hostname.HostnameFacts.get_config" + ) + + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestVyosHostnameModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None, transport="cli", filename=None): + if filename is None: + filename = "vyos_hostname_config.cfg" + + def load_from_file(*args, **kwargs): + output = load_fixture(filename) + return output + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_hostname_merged_idempotent(self): + set_module_args(dict(config=dict(hostname="vyos_test"))) + self.execute_module(changed=False, commands=[]) + + def test_vyos_hostname_replaced_idempotent(self): + set_module_args( + dict(config=dict(hostname="vyos_test"), state="replaced") + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_hostname_overridden_idempotent(self): + set_module_args( + dict(config=dict(hostname="vyos_test"), state="overridden") + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_hostname_merged(self): + set_module_args(dict(config=dict(hostname="vyos"))) + self.execute_module( + changed=True, commands=["set system host-name vyos"] + ) + + def test_vyos_hostname_replaced(self): + set_module_args(dict(config=dict(hostname="vyos"), state="replaced")) + self.execute_module( + changed=True, commands=["set system host-name vyos"] + ) + + def test_vyos_hostname_overridden(self): + set_module_args(dict(config=dict(hostname="vyos"), state="overridden")) + + def test_vyos_hostname_deleted(self): + set_module_args(dict(state="deleted")) + self.execute_module( + changed=True, commands=["delete system host-name vyos_test"] + ) + + def test_vyos_hostname_gathered(self): + set_module_args(dict(state="gathered")) + result = self.execute_module( + changed=False, filename="vyos_hostname_config.cfg" + ) + gathered_list = {"hostname": "vyos_test"} + self.assertEqual(sorted(gathered_list), sorted(result["gathered"])) + + def test_vyos_hostname_parsed(self): + parsed_str = "set system host-name vyos_test" + set_module_args(dict(running_config=parsed_str, state="parsed")) + result = self.execute_module(changed=False) + parsed_list = {"hostname": "vyos_test"} + self.assertEqual(sorted(parsed_list), sorted(result["parsed"])) + + def test_vyos_hostname_rendered(self): + set_module_args( + dict(state="rendered", config=dict(hostname="vyos_test")) + ) + commands = ["set system host-name vyos_test"] + result = self.execute_module(changed=False) + self.assertEqual( + sorted(result["rendered"]), sorted(commands), result["rendered"] + ) -- cgit v1.2.3