From 6ef98aed627ec6e3ae1baa217c4282284cd04f06 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Fri, 18 Jul 2025 12:08:34 +0530 Subject: [PATCH 1/3] Fix: Address all sanity issues including import errors and runtime metadata updates --- .gitignore | 3 ++ .../juniper/device/meta/runtime.yml | 13 ++++++-- .../juniper/device/plugins/action/hostname.py | 28 +++++++++++++++-- .../juniper/device/plugins/action/junos.py | 28 +++++++++++++++-- .../device/plugins/action/junos_facts.py | 5 ++-- .../juniper/device/plugins/action/netconf.py | 26 +++++++++++++++- .../device/plugins/doc_fragments/junos.py | 20 +++++++++++++ .../module_utils/netconf_tbr/exceptions.py | 4 +++ .../plugins/module_utils/netconf_tbr/junos.py | 23 +++++++++----- .../network/junos/argspec/facts/facts.py | 2 +- .../module_utils/network/junos/facts/facts.py | 2 +- .../module_utils/network/junos/junos.py | 2 +- .../plugins/modules/_tbrdevice_hostname.py | 3 +- .../device/plugins/modules/junos_banner.py | 2 +- .../device/plugins/modules/junos_command.py | 2 +- .../device/plugins/modules/junos_config.py | 2 +- .../device/plugins/modules/junos_netconf.py | 30 +++++++++++++++---- .../device/plugins/modules/junos_package.py | 2 +- .../device/plugins/modules/junos_ping.py | 2 +- .../device/plugins/modules/junos_rpc.py | 2 +- .../device/plugins/modules/junos_scp.py | 2 +- .../device/plugins/modules/junos_system.py | 2 +- .../device/plugins/modules/junos_user.py | 2 +- .../device/plugins/modules/junos_vrf.py | 2 +- .../device/plugins/modules/tbrdevice_facts.py | 4 +-- .../device/tests/unit/test_file_copy.py | 2 +- 26 files changed, 174 insertions(+), 41 deletions(-) create mode 100644 ansible_collections/juniper/device/plugins/doc_fragments/junos.py create mode 100644 ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/exceptions.py diff --git a/.gitignore b/.gitignore index ef97d417..991f4815 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ desktop.ini # VC Code .vscode +ansible_collections/ansible +ansible_collections/ansible.netcommon-8.0.1.info +ansible_collections/ansible.utils-6.0.0.info \ No newline at end of file diff --git a/ansible_collections/juniper/device/meta/runtime.yml b/ansible_collections/juniper/device/meta/runtime.yml index 5cf3ab46..3646b2f9 100644 --- a/ansible_collections/juniper/device/meta/runtime.yml +++ b/ansible_collections/juniper/device/meta/runtime.yml @@ -1,6 +1,6 @@ +--- requires_ansible: ">=2.17.0" plugin_routing: - plugins: modules: hostname: redirect: juniper.device.junos_hostname @@ -44,8 +44,15 @@ plugin_routing: redirect: juniper.device.junos_routing_instances routing_options: redirect: juniper.device.junos_routing_options - routing_options: - redirect: juniper.device.junos_routing_options + scp: + redirect: juniper.device.junos_scp + deprecation: + removal_date: "2025-01-01" + warning_text: See the plugin documentation for more details + junos_scp: + deprecation: + removal_date: "2025-01-01" + warning_text: See the plugin documentation for more details security_policies: redirect: juniper.device.junos_security_policies security_policies_global: diff --git a/ansible_collections/juniper/device/plugins/action/hostname.py b/ansible_collections/juniper/device/plugins/action/hostname.py index 3728e71b..abdca0cd 100644 --- a/ansible_collections/juniper/device/plugins/action/hostname.py +++ b/ansible_collections/juniper/device/plugins/action/hostname.py @@ -21,6 +21,30 @@ __metaclass__ = type +DOCUMENTATION = r""" +--- +module: hostname +short_description: Action plugin for hostname operations on Juniper devices +description: + - This action plugin handles hostname operations for Juniper devices. + - It provides the necessary connection setup and module routing. +version_added: "1.0.0" +author: + - Juniper Networks +notes: + - This is an action plugin that handles hostname operations. +""" + +EXAMPLES = r""" +# This action plugin is used internally by hostname modules +# No direct usage examples as this is an action plugin +""" + +RETURN = r""" +# This action plugin handles hostname operations +# Return values are handled by the calling module +""" + import copy import sys @@ -44,7 +68,7 @@ class ActionModule(ActionNetworkModule): def run(self, tmp=None, task_vars=None): - + del tmp # tmp no longer has any effect module_name = self._task.action.split(".")[-1] @@ -56,7 +80,7 @@ def run(self, tmp=None, task_vars=None): if self._play_context.connection == "local": provider = load_provider(junos_provider_spec, self._task.args) pc = copy.deepcopy(self._play_context) - #pc.network_os = "juniper.device.junos" + # pc.network_os = "juniper.device.junos" pc.network_os = "juniper.device.device" pc.remote_addr = provider["host"] or self._play_context.remote_addr diff --git a/ansible_collections/juniper/device/plugins/action/junos.py b/ansible_collections/juniper/device/plugins/action/junos.py index 3728e71b..a8b9ade7 100644 --- a/ansible_collections/juniper/device/plugins/action/junos.py +++ b/ansible_collections/juniper/device/plugins/action/junos.py @@ -21,6 +21,30 @@ __metaclass__ = type +DOCUMENTATION = r""" +--- +module: junos +short_description: Action plugin for Junos operations on Juniper devices +description: + - This action plugin handles Junos operations for Juniper devices. + - It provides the necessary connection setup and module routing. +version_added: "1.0.0" +author: + - Juniper Networks +notes: + - This is an action plugin that handles Junos operations. +""" + +EXAMPLES = r""" +# This action plugin is used internally by Junos modules +# No direct usage examples as this is an action plugin +""" + +RETURN = r""" +# This action plugin handles Junos operations +# Return values are handled by the calling module +""" + import copy import sys @@ -44,7 +68,7 @@ class ActionModule(ActionNetworkModule): def run(self, tmp=None, task_vars=None): - + del tmp # tmp no longer has any effect module_name = self._task.action.split(".")[-1] @@ -56,7 +80,7 @@ def run(self, tmp=None, task_vars=None): if self._play_context.connection == "local": provider = load_provider(junos_provider_spec, self._task.args) pc = copy.deepcopy(self._play_context) - #pc.network_os = "juniper.device.junos" + # pc.network_os = "juniper.device.junos" pc.network_os = "juniper.device.device" pc.remote_addr = provider["host"] or self._play_context.remote_addr diff --git a/ansible_collections/juniper/device/plugins/action/junos_facts.py b/ansible_collections/juniper/device/plugins/action/junos_facts.py index a1c2b13a..6e34bd34 100644 --- a/ansible_collections/juniper/device/plugins/action/junos_facts.py +++ b/ansible_collections/juniper/device/plugins/action/junos_facts.py @@ -44,12 +44,11 @@ class ActionModule(ActionNetworkModule): def run(self, tmp=None, task_vars=None): - del tmp # tmp no longer has any effect module_name = self._task.action.split(".")[-1] - #module_name = "device_facts" - #self._task.action = "device_facts" + # module_name = "device_facts" + # self._task.action = "device_facts" self._task.collections.append("juniper.device") self._config_module = True if module_name in ["junos_config", "config"] else False persistent_connection = self._play_context.connection.split(".")[-1] diff --git a/ansible_collections/juniper/device/plugins/action/netconf.py b/ansible_collections/juniper/device/plugins/action/netconf.py index 352cbd99..b67aabaf 100644 --- a/ansible_collections/juniper/device/plugins/action/netconf.py +++ b/ansible_collections/juniper/device/plugins/action/netconf.py @@ -21,6 +21,30 @@ __metaclass__ = type +DOCUMENTATION = r""" +--- +module: netconf +short_description: Action plugin for NETCONF operations on Juniper devices +description: + - This action plugin handles NETCONF operations for Juniper devices. + - It provides the necessary connection setup and module routing. +version_added: "1.0.0" +author: + - Juniper Networks +notes: + - This is an action plugin that handles NETCONF operations. +""" + +EXAMPLES = r""" +# This action plugin is used internally by NETCONF modules +# No direct usage examples as this is an action plugin +""" + +RETURN = r""" +# This action plugin handles NETCONF operations +# Return values are handled by the calling module +""" + import copy import sys @@ -44,7 +68,7 @@ class ActionModule(ActionNetworkModule): def run(self, tmp=None, task_vars=None): - + del tmp # tmp no longer has any effect module_name = self._task.action.split(".")[-1] diff --git a/ansible_collections/juniper/device/plugins/doc_fragments/junos.py b/ansible_collections/juniper/device/plugins/doc_fragments/junos.py new file mode 100644 index 00000000..bf4aad34 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/doc_fragments/junos.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +# Copyright: (c) 2015, Peter Sprygada +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + # Standard files documentation fragment + DOCUMENTATION = r"""options: {} +notes: +- For information on using CLI and netconf see the :ref:`Junos OS Platform Options + guide ` +- For more information on using Ansible to manage network devices see the :ref:`Ansible + Network Guide ` +- For more information on using Ansible to manage Juniper network devices see U(https://www.ansible.com/ansible-juniper). +""" diff --git a/ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/exceptions.py b/ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/exceptions.py new file mode 100644 index 00000000..0804f537 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/exceptions.py @@ -0,0 +1,4 @@ + +class AnsibleConnectionFailure(Exception): + """Raised when connection to the device fails.""" + pass diff --git a/ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/junos.py b/ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/junos.py index 58bb0165..f901ea29 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/junos.py +++ b/ansible_collections/juniper/device/plugins/module_utils/netconf_tbr/junos.py @@ -41,14 +41,23 @@ import json import re - -from ansible.errors import AnsibleConnectionFailure +from .exceptions import AnsibleConnectionFailure from ansible.module_utils._text import to_native, to_text from ansible.module_utils.six import string_types -from ansible_collections.ansible.netcommon.plugins.plugin_utils.netconf_base import ( - NetconfBase, - ensure_ncclient, -) + +try: + from ansible_collections.ansible.netcommon.plugins.plugin_utils.netconf_base import ( + NetconfBase, + ensure_ncclient, + ) + HAS_NETCONF_BASE = True +except ImportError: + # Handle case where netconf_base is not available during testing + HAS_NETCONF_BASE = False + NetconfBase = object + + def ensure_ncclient(func): + return func try: @@ -280,4 +289,4 @@ def commit( if timeout: subele = sub_ele(obj, "confirm-timeout") subele.text = str(timeout) - return self.rpc(obj) \ No newline at end of file + return self.rpc(obj) diff --git a/ansible_collections/juniper/device/plugins/module_utils/network/junos/argspec/facts/facts.py b/ansible_collections/juniper/device/plugins/module_utils/network/junos/argspec/facts/facts.py index c3e4d9dc..2ee80bd7 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/network/junos/argspec/facts/facts.py +++ b/ansible_collections/juniper/device/plugins/module_utils/network/junos/argspec/facts/facts.py @@ -26,4 +26,4 @@ def __init__(self, **kwargs): ), "gather_network_resources": dict(type="list", elements="str"), "available_network_resources": {"type": "bool", "default": False}, - } \ No newline at end of file + } diff --git a/ansible_collections/juniper/device/plugins/module_utils/network/junos/facts/facts.py b/ansible_collections/juniper/device/plugins/module_utils/network/junos/facts/facts.py index 2f6f5716..ca3a5381 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/network/junos/facts/facts.py +++ b/ansible_collections/juniper/device/plugins/module_utils/network/junos/facts/facts.py @@ -200,4 +200,4 @@ def get_facts( legacy_facts_type, ) - return self.ansible_facts, self._warnings \ No newline at end of file + return self.ansible_facts, self._warnings diff --git a/ansible_collections/juniper/device/plugins/module_utils/network/junos/junos.py b/ansible_collections/juniper/device/plugins/module_utils/network/junos/junos.py index 768bd73c..fc7e6342 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/network/junos/junos.py +++ b/ansible_collections/juniper/device/plugins/module_utils/network/junos/junos.py @@ -533,4 +533,4 @@ def to_param_list(module): else: return aggregate else: - return [module.params] \ No newline at end of file + return [module.params] diff --git a/ansible_collections/juniper/device/plugins/modules/_tbrdevice_hostname.py b/ansible_collections/juniper/device/plugins/modules/_tbrdevice_hostname.py index f61a1742..41e901ed 100644 --- a/ansible_collections/juniper/device/plugins/modules/_tbrdevice_hostname.py +++ b/ansible_collections/juniper/device/plugins/modules/_tbrdevice_hostname.py @@ -38,8 +38,7 @@ } DOCUMENTATION = """ ---- -module: junos_hostname +module: _tbrdevice_hostname version_added: 2.9.0 short_description: Manage Hostname server configuration on Junos devices. description: This module manages hostname configuration on devices running Junos. diff --git a/ansible_collections/juniper/device/plugins/modules/junos_banner.py b/ansible_collections/juniper/device/plugins/modules/junos_banner.py index c86cd48b..e0c885a4 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_banner.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_banner.py @@ -19,7 +19,7 @@ to add or remote banner text from the active running configuration. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: banner: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_command.py b/ansible_collections/juniper/device/plugins/modules/junos_command.py index f7746271..ab60f7ca 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_command.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_command.py @@ -21,7 +21,7 @@ not met. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: commands: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_config.py b/ansible_collections/juniper/device/plugins/modules/junos_config.py index 773489ea..1916499a 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_config.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_config.py @@ -20,7 +20,7 @@ performing rollback operations and zeroing the active configuration on the device. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: lines: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_netconf.py b/ansible_collections/juniper/device/plugins/modules/junos_netconf.py index d31e6eac..8f78702e 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_netconf.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_netconf.py @@ -1,9 +1,29 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# (c) 2017, Ansible by Red Hat, inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The module file for junos_netconf +""" from __future__ import absolute_import, division, print_function @@ -22,7 +42,7 @@ in the task by default netconf will be enabled on port 830 only. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: netconf_port: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_package.py b/ansible_collections/juniper/device/plugins/modules/junos_package.py index b7f978c9..3fa721da 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_package.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_package.py @@ -20,7 +20,7 @@ and install the specified version if there is a mismatch version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: src: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_ping.py b/ansible_collections/juniper/device/plugins/modules/junos_ping.py index 3045bf1d..14879a21 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_ping.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_ping.py @@ -21,7 +21,7 @@ - For targets running Python, use the M(ansible.builtin.ping) module instead. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos author: - Nilashish Chakraborty (@NilashishC) options: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_rpc.py b/ansible_collections/juniper/device/plugins/modules/junos_rpc.py index 23da5e95..7f23c49e 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_rpc.py @@ -21,7 +21,7 @@ to the requested output. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: rpc: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_scp.py b/ansible_collections/juniper/device/plugins/modules/junos_scp.py index 0fe11bfc..f9b2c26d 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_scp.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_scp.py @@ -18,7 +18,7 @@ - This module transfers files via SCP from or to remote devices running Junos. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos deprecated: why: Updated modules released with more functionality alternative: Use M(ansible.netcommon.net_get), M(ansible.netcommon.net_put) instead. diff --git a/ansible_collections/juniper/device/plugins/modules/junos_system.py b/ansible_collections/juniper/device/plugins/modules/junos_system.py index c8f3387b..b3167898 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_system.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_system.py @@ -20,7 +20,7 @@ those parameters from the device active configuration. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: hostname: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_user.py b/ansible_collections/juniper/device/plugins/modules/junos_user.py index 038e4af0..b94f0254 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_user.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_user.py @@ -20,7 +20,7 @@ and updating locally defined accounts version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: aggregate: description: diff --git a/ansible_collections/juniper/device/plugins/modules/junos_vrf.py b/ansible_collections/juniper/device/plugins/modules/junos_vrf.py index ed7278ff..cbc9383b 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_vrf.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_vrf.py @@ -19,7 +19,7 @@ devices. It allows playbooks to manage individual or the entire VRF collection. version_added: 1.0.0 extends_documentation_fragment: -- junipernetworks.junos.junos +- juniper.device.junos options: name: description: diff --git a/ansible_collections/juniper/device/plugins/modules/tbrdevice_facts.py b/ansible_collections/juniper/device/plugins/modules/tbrdevice_facts.py index 6b45889f..41a945c4 100644 --- a/ansible_collections/juniper/device/plugins/modules/tbrdevice_facts.py +++ b/ansible_collections/juniper/device/plugins/modules/tbrdevice_facts.py @@ -11,7 +11,7 @@ DOCUMENTATION = """ -module: junos_facts +module: tbrdevice_facts author: Nathaniel Case (@Qalthos) short_description: Collect facts from remote devices running Juniper Junos description: @@ -143,4 +143,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/ansible_collections/juniper/device/tests/unit/test_file_copy.py b/ansible_collections/juniper/device/tests/unit/test_file_copy.py index 0b84ba15..a82fb883 100644 --- a/ansible_collections/juniper/device/tests/unit/test_file_copy.py +++ b/ansible_collections/juniper/device/tests/unit/test_file_copy.py @@ -282,4 +282,4 @@ def test_invalid_action(mock_junos_module): main() # This will likely not raise, but will skip both branches - mock_instance.exit_json.assert_called_once_with(msg="", changed=False, failed=False) \ No newline at end of file + mock_instance.exit_json.assert_called_once_with(msg="", changed=False, failed=False) From c2fe7184aeaabe27bab6ce47c4e79b7cc101a972 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 21 Jul 2025 12:35:53 +0530 Subject: [PATCH 2/3] fix the validate-module errors --- .../device/plugins/modules/hostname.py | 361 +++++++++++++ .../juniper/device/plugins/modules/junos.py | 480 ++++++++++++++++++ .../juniper/device/plugins/modules/netconf.py | 214 ++++++++ 3 files changed, 1055 insertions(+) create mode 100644 ansible_collections/juniper/device/plugins/modules/hostname.py create mode 100644 ansible_collections/juniper/device/plugins/modules/junos.py create mode 100644 ansible_collections/juniper/device/plugins/modules/netconf.py diff --git a/ansible_collections/juniper/device/plugins/modules/hostname.py b/ansible_collections/juniper/device/plugins/modules/hostname.py new file mode 100644 index 00000000..167f9c9a --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/hostname.py @@ -0,0 +1,361 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for junos_hostname +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "network", +} + +DOCUMENTATION = """ +--- +module: hostname +version_added: 2.9.0 +short_description: Manage Hostname server configuration on Junos devices. +description: This module manages hostname configuration on devices running Junos. +author: Rohit Thakur (@rohitthakur2590) +requirements: + - ncclient (>=v0.6.4) + - xmltodict (>=0.12.0) +notes: + - This module requires the netconf system service be enabled on the device being managed. + - This module works with connection C(netconf). + - See L(the Junos OS Platform Options,https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html). + - Tested against JunOS v18.4R1 +options: + running_config: + description: + - This option is used only with state I(parsed). + - The value of this option should be the output received from the Junos device + by executing the command B(show system hostname). + - 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 + config: + description: A dictionary of system Hostname configuration. + type: dict + suboptions: + hostname: + description: Specify the hostname. + type: str + state: + 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 system hostname) executed on device. For state I(parsed) active + connection to remote host is not required. + type: str + choices: + - merged + - replaced + - deleted + - overridden + - parsed + - gathered + - rendered + default: merged +""" +EXAMPLES = """ +# Using merged +# +# Before state +# ------------ +# +# vagrant@vsrx# show system hostname +# +# [edit] +- name: Merge provided HOSTNAME configuration into running configuration. + junipernetworks.junos.junos_hostname: + config: + hostname: 'vsrx-18.4R1' + state: merged +# +# ------------------------- +# Module Execution Result +# ------------------------- +# "after": { +# "hostname": "vsrx-18.4R1" +# }, +# "before": {}, +# "changed": true, +# "commands": [ +# " +# "vsrx-18.4R1" +# ] +# After state +# ----------- +# +# vagrant@vsrx-18.4R1# show system host-name +# host-name vsrx-18.4R1; +# +# Using replaced +# +# Before state +# ------------ +# +# vagrant@vsrx-18.4R1# show system host-name +# host-name vsrx-18.4R1; +# +# [edit] +- name: Replaced target config with provided config. + junipernetworks.junos.junos_hostname: + config: + hostname: 'vsrx-12' + state: replaced +# +# ------------------------- +# Module Execution Result +# ------------------------- +# "after": { +# "hostname": "vsrx-12" +# }, +# "before": { +# "hostname": "vsrx-18.4R1" +# }, +# "changed": true, +# "commands": [ +# " +# "vsrx-12" +# ] +# After state +# ----------- +# +# vagrant@vsrx-18.4R1# show system host-name +# host-name vsrx-12; +# +# Using overridden +# +# Before state +# ------------ +# +# vagrant@vsrx-18.4R1# show system host-name +# host-name vsrx-18.4R1; +# +# [edit] +- name: Replaced target config with provided config. + junipernetworks.junos.junos_hostname: + config: + hostname: 'vsrx-12' + state: overridden +# +# ------------------------- +# Module Execution Result +# ------------------------- +# "after": { +# "hostname": "vsrx-12" +# }, +# "before": { +# "hostname": "vsrx-18.4R1" +# }, +# "changed": true, +# "commands": [ +# " +# "vsrx-12" +# ] +# After state +# ----------- +# +# vagrant@vsrx-18.4R1# show system host-name +# host-name vsrx-12; +# +# Using deleted +# +# Before state +# ------------ +# +# vagrant@vsrx-18.4R1# show system host-name +# host-name vsrx-12; +# +- name: Delete running HOSTNAME global configuration + junipernetworks.junos.junos_hostname: + config: + state: deleted +# +# ------------------------- +# Module Execution Result +# ------------------------- +# "after": {}, +# "before": { +# "hostname": "vsrx-12" +# }, +# "changed": true, +# "commands": [ +# " +# " +# ] +# After state +# ----------- +# +# vagrant@vsrx# show system ntp +# +# [edit] +# Using gathered +# +# Before state +# ------------ +# +# vagrant@vsrx-18.4R1# show system host-name +# host-name vsrx-12; +# +- name: Gather running HOSTNAME global configuration + junipernetworks.junos.junos_hostname: + state: gathered +# +# ------------------------- +# Module Execution Result +# ------------------------- +# "gathered": { +# "hostname": "vsrx-12", +# }, +# "changed": false, +# Using rendered +# +# Before state +# ------------ +# +- name: Render xml for provided facts. + junipernetworks.junos.junos_hostname: + config: + boot_server: '78.46.194.186' + state: rendered +# +# ------------------------- +# Module Execution Result +# ------------------------- +# "rendered": [ +# "" +# "78.46.194.186" +# ] +# +# Using parsed +# parsed.cfg +# ------------ +# +# +# +# 18.4R1-S2.4 +# +# vsrx-18.4R1 +# +# +# +# +- name: Parse HOSTNAME running config + junipernetworks.junos.junos_hostname: + running_config: "{{ lookup('file', './parsed.cfg') }}" + state: parsed +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "parsed": { +# "hostname": "vsrx-18.4R1" +# } +# +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: dict +after: + description: The resulting configuration model invocation. + returned: when changed + sample: > + The configuration returned will always be in the same format + of the parameters above. + type: dict +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: ['"78.46.194.186"'] +""" + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.juniper.device.plugins.module_utils.network.junos.argspec.hostname.hostname import ( + HostnameArgs, +) +from ansible_collections.juniper.device.plugins.module_utils.network.junos.config.hostname.hostname import ( + Hostname, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "rendered", ("config",)), + ("state", "parsed", ("running_config",)), + ] + module = AnsibleModule( + argument_spec=HostnameArgs.argument_spec, + required_if=required_if, + supports_check_mode=True, + ) + + result = Hostname(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/juniper/device/plugins/modules/junos.py b/ansible_collections/juniper/device/plugins/modules/junos.py new file mode 100644 index 00000000..5a95bef7 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/junos.py @@ -0,0 +1,480 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Ansible by Red Hat, inc +# 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 + + +DOCUMENTATION = """ +module: junos +author: Peter Sprygada (@privateip) +short_description: Run arbitrary commands on an Juniper JUNOS device +description: +- Sends an arbitrary set of commands to an JUNOS node and returns the results read + from the device. This module includes an argument that will cause the module to + wait for a specific condition before returning or timing out if the condition is + not met. +version_added: 1.0.0 +extends_documentation_fragment: +- juniper.device.junos +options: + commands: + description: + - The commands to send to the remote junos device. The + resulting output from the command is returned. If the I(wait_for) argument + is provided, the module is not returned until the condition is satisfied or + the number of I(retries) has been exceeded. + type: list + elements: str + rpcs: + description: + - The C(rpcs) argument accepts a list of RPCs to be executed over a netconf session + and the results from the RPC execution is return to the playbook via the modules + results dictionary. + type: list + elements: str + wait_for: + description: + - Specifies what to evaluate from the output of the command and what conditionals + to apply. This argument will cause the task to wait for a particular conditional + to be true before moving forward. If the conditional is not true by the configured + retries, the task fails. See examples. + type: list + elements: str + aliases: + - waitfor + match: + description: + - The I(match) argument is used in conjunction with the I(wait_for) argument to + specify the match policy. Valid values are C(all) or C(any). If the value + is set to C(all) then all conditionals in the I(wait_for) must be satisfied. If + the value is set to C(any) then only one of the values must be satisfied. + type: str + default: all + choices: + - any + - all + retries: + description: + - Specifies the number of retries a command should be tried before it is considered + failed. The command is run on the target device every retry and evaluated against + the I(wait_for) conditionals. + type: int + default: 10 + interval: + description: + - Configures the interval in seconds to wait between retries of the command. If + the command does not pass the specified conditional, the interval indicates + how to long to wait before trying the command again. + type: int + default: 1 + display: + description: + - Encoding scheme to use when serializing output from the device. This handles + how to properly understand the output and apply the conditionals path to the + result set. For I(rpcs) argument default display is C(xml) and for I(commands) + argument default display is C(text). Value C(set) is applicable only for fetching + configuration from device. + type: str + aliases: + - format + - output + choices: + - text + - json + - xml + - set +requirements: +- jxmlease +- ncclient (>=v0.5.2) +notes: +- This module requires the netconf system service be enabled on the remote device + being managed. +- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4. +- Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html). +- This module also works with C(network_cli) connections and with C(local) connections + for legacy playbooks. +""" + +EXAMPLES = """ +- name: run show version on remote devices + junipernetworks.junos.junos_command: + commands: show version + +- name: run show version and check to see if output contains Juniper + junipernetworks.junos.junos_command: + commands: show version + wait_for: result[0] contains Juniper + +- name: run multiple commands on remote nodes + junipernetworks.junos.junos_command: + commands: + - show version + - show interfaces + +- name: run multiple commands and evaluate the output + junipernetworks.junos.junos_command: + commands: + - show version + - show interfaces + wait_for: + - result[0] contains Juniper + - result[1] contains Loopback0 + +- name: run commands and specify the output format + junipernetworks.junos.junos_command: + commands: show version + display: json + +- name: run rpc on the remote device + junipernetworks.junos.junos_command: + commands: show configuration + display: set + +- name: run rpc on the remote device + junipernetworks.junos.junos_command: + rpcs: get-software-information +""" + +RETURN = """ +stdout: + description: The set of responses from the commands + returned: always apart from low level errors (such as action plugin) + type: list + sample: ['...', '...'] +stdout_lines: + description: The value of stdout split into a list + returned: always apart from low level errors (such as action plugin) + type: list + sample: [['...', '...'], ['...'], ['...']] +output: + description: The set of transformed xml to json format from the commands responses + returned: If the I(display) is in C(xml) format. + type: list + sample: ['...', '...'] +failed_conditions: + description: The list of conditionals that have failed + returned: failed + type: list + sample: ['...', '...'] +""" +import re +import shlex +import time + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.netconf import ( + exec_rpc, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import ( + Conditional, + FailedConditionalError, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_lines + +from ansible_collections.juniper.device.plugins.module_utils.network.junos.junos import ( + get_capabilities, + get_configuration, + get_connection, + tostring, +) + + +try: + from lxml.etree import Element, SubElement +except ImportError: + from xml.etree.ElementTree import Element, SubElement + +try: + import jxmlease + + HAS_JXMLEASE = True +except ImportError: + HAS_JXMLEASE = False + +USE_PERSISTENT_CONNECTION = True + + +def rpc(module, items): + responses = list() + for item in items: + name = item["name"] + xattrs = item["xattrs"] + fetch_config = False + + args = item.get("args") + text = item.get("text") + + name = str(name).replace("_", "-") + + if all((module.check_mode, not name.startswith("get"))): + module.fail_json(msg="invalid rpc for running in check_mode") + + if name == "command" and text == "show configuration" or name == "get-configuration": + fetch_config = True + + element = Element(name, xattrs) + + if text: + element.text = text + + elif args: + for key, value in iteritems(args): + key = str(key).replace("_", "-") + if isinstance(value, list): + for item in value: + child = SubElement(element, key) + if item is not True: + child.text = item + else: + child = SubElement(element, key) + if value is not True: + child.text = value + if fetch_config: + reply = get_configuration(module, format=xattrs["format"]) + else: + reply = exec_rpc(module, tostring(element), ignore_warning=False) + + if xattrs["format"] == "text": + if len(reply) >= 1: + if fetch_config: + data = reply.find(".//configuration-text") + else: + if text and text.startswith("show configuration"): + data = reply.find(".//configuration-output") + else: + data = reply.find(".//output") + + if data is None: + module.fail_json(msg=tostring(reply)) + + responses.append(data.text.strip()) + else: + responses.append(reply.text.strip()) + + elif xattrs["format"] == "json": + responses.append(module.from_json(reply.text.strip())) + + elif xattrs["format"] == "set": + data = reply.find(".//configuration-set") + if data is None: + module.fail_json( + msg="Display format 'set' is not supported by remote device.", + ) + responses.append(data.text.strip()) + + else: + responses.append(tostring(reply)) + + return responses + + +def split(value): + lex = shlex.shlex(value) + lex.quotes = '"' + lex.whitespace_split = True + lex.commenters = "" + return list(lex) + + +def parse_rpcs(module): + items = list() + + for rpc in module.params["rpcs"] or list(): + parts = shlex.split(rpc) + + name = parts.pop(0) + args = dict() + + for item in parts: + key, value = item.split("=") + if str(value).upper() in ["TRUE", "FALSE"]: + args[key] = bool(value) + elif re.match(r"^[0-9]+$", value): + args[key] = int(value) + else: + args[key] = str(value) + + display = module.params["display"] or "xml" + + if display == "set" and rpc != "get-configuration": + module.fail_json( + msg="Invalid display option '%s' given for rpc '%s'" % ("set", name), + ) + + xattrs = {"format": display} + items.append({"name": name, "args": args, "xattrs": xattrs}) + + return items + + +def parse_commands(module, warnings): + items = list() + + for command in module.params["commands"] or list(): + if module.check_mode and not command.startswith("show"): + warnings.append( + "Only show commands are supported when using check_mode, not " + "executing %s" % command, + ) + continue + + parts = command.split("|") + text = parts[0] + + display = module.params["display"] or "text" + + if "| display json" in command: + display = "json" + + elif "| display xml" in command: + display = "xml" + + if display == "set" or "| display set" in command: + if command.startswith("show configuration"): + display = "set" + else: + module.fail_json( + msg="Invalid display option '%s' given for command '%s'" % ("set", command), + ) + + xattrs = {"format": display} + items.append({"name": "command", "xattrs": xattrs, "text": text}) + + return items + + +def main(): + """entry point for module execution""" + argument_spec = dict( + commands=dict(type="list", elements="str"), + rpcs=dict(type="list", elements="str"), + display=dict( + choices=["text", "json", "xml", "set"], + aliases=["format", "output"], + ), + wait_for=dict(type="list", aliases=["waitfor"], elements="str"), + match=dict(default="all", choices=["all", "any"]), + retries=dict(default=10, type="int"), + interval=dict(default=1, type="int"), + ) + + required_one_of = [("commands", "rpcs")] + + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=required_one_of, + supports_check_mode=True, + ) + + warnings = list() + conn = get_connection(module) + capabilities = get_capabilities(module) + + if capabilities.get("network_api") == "cliconf": + if any( + ( + module.params["wait_for"], + module.params["match"], + module.params["rpcs"], + ), + ): + module.warn( + "arguments wait_for, match, rpcs are not supported when using transport=cli", + ) + commands = module.params["commands"] + + output = list() + display = module.params["display"] + for cmd in commands: + # if display format is not mentioned in command, add the display format + # from the modules params + if ("display json" not in cmd) and ("display xml" not in cmd): + if display and display != "text": + cmd += " | display {0}".format(display) + try: + output.append(conn.get(command=cmd)) + except ConnectionError as exc: + module.fail_json( + msg=to_text(exc, errors="surrogate_then_replace"), + ) + + lines = [out.split("\n") for out in output] + result = {"changed": False, "stdout": output, "stdout_lines": lines} + module.exit_json(**result) + + items = list() + items.extend(parse_commands(module, warnings)) + items.extend(parse_rpcs(module)) + + wait_for = module.params["wait_for"] or list() + conditionals = [Conditional(c) for c in wait_for] + + retries = module.params["retries"] + interval = module.params["interval"] + match = module.params["match"] + while retries > 0: + responses = rpc(module, items) + transformed = list() + output = list() + for item, resp in zip(items, responses): + if item["xattrs"]["format"] == "xml": + if not HAS_JXMLEASE: + module.fail_json( + msg="jxmlease is required but does not appear to be installed. " + "It can be installed using `pip install jxmlease`", + ) + + try: + json_resp = jxmlease.parse(resp) + transformed.append(json_resp) + output.append(json_resp) + except Exception: + raise ValueError(resp) + else: + transformed.append(resp) + + for item in list(conditionals): + try: + if item(transformed): + if match == "any": + conditionals = list() + break + conditionals.remove(item) + except FailedConditionalError: + pass + + if not conditionals: + break + + time.sleep(interval) + retries -= 1 + + if conditionals: + failed_conditions = [item.raw for item in conditionals] + msg = "One or more conditional statements have not been satisfied" + module.fail_json(msg=msg, failed_conditions=failed_conditions) + + result = { + "changed": False, + "warnings": warnings, + "stdout": responses, + "stdout_lines": list(to_lines(responses)), + } + + if output: + result["output"] = output + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/juniper/device/plugins/modules/netconf.py b/ansible_collections/juniper/device/plugins/modules/netconf.py new file mode 100644 index 00000000..dce8f515 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/netconf.py @@ -0,0 +1,214 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The module file for junos_netconf +""" +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +DOCUMENTATION = """ +--- +module: netconf +author: Peter Sprygada (@privateip) +short_description: Configures the Junos Netconf system service +description: +- This module provides an abstraction that enables and configures the netconf system + service running on Junos devices. This module can be used to easily enable the + Netconf API. Netconf provides a programmatic interface for working with configuration + and state resources as defined in RFC 6242. If the C(netconf_port) is not mentioned + in the task by default netconf will be enabled on port 830 only. +version_added: 1.0.0 +extends_documentation_fragment: +- juniper.device.junos +options: + netconf_port: + description: + - This argument specifies the port the netconf service should listen on for SSH + connections. The default port as defined in RFC 6242 is 830. + required: false + default: 830 + aliases: + - listens_on + type: int + state: + description: + - Specifies the state of the C(junos_netconf) resource on the remote device. If + the I(state) argument is set to I(present) the netconf service will be configured. If + the I(state) argument is set to I(absent) the netconf service will be removed + from the configuration. + type: str + required: false + default: present + choices: + - present + - absent +notes: +- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4. +- Recommended connection is C(network_cli). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html). +- This module also works with C(local) connections for legacy playbooks. +- If C(netconf_port) value is not mentioned in task by default it will be enabled + on port 830 only. Although C(netconf_port) value can be from 1 through 65535, avoid + configuring access on a port that is normally assigned for another service. This + practice avoids potential resource conflicts. +""" + +EXAMPLES = """ +- name: enable netconf service on port 830 + junipernetworks.junos.junos_netconf: + listens_on: 830 + state: present + +- name: disable netconf service + junipernetworks.junos.junos_netconf: + state: absent +""" + +RETURN = """ +commands: + description: Returns the command sent to the remote device + returned: when changed is True + type: str + sample: 'set system services netconf ssh port 830' +""" +import re + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import ConnectionError +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + +from ansible_collections.juniper.device.plugins.module_utils.network.junos.junos import ( + get_connection, +) + + +USE_PERSISTENT_CONNECTION = True + + +def map_obj_to_commands(updates, module): + want, have = updates + commands = list() + + if want["state"] == "absent": + if have["state"] == "present": + commands.append("delete system services netconf") + else: + if have["state"] == "absent" or want["netconf_port"] != have.get( + "netconf_port", + ): + commands.append( + "set system services netconf ssh port %s" % want["netconf_port"], + ) + + return commands + + +def parse_port(config): + match = re.search(r"port (\d+)", config) + if match: + return int(match.group(1)) + + +def map_config_to_obj(module): + conn = get_connection(module) + out = conn.get(command="show configuration system services netconf") + if out is None: + module.fail_json(msg="unable to retrieve current config") + config = str(out).strip() + + obj = {"state": "absent"} + if "ssh" in config: + obj.update({"state": "present", "netconf_port": parse_port(config)}) + return obj + + +def validate_netconf_port(value, module): + if not 1 <= value <= 65535: + module.fail_json(msg="netconf_port must be between 1 and 65535") + + +def map_params_to_obj(module): + obj = { + "netconf_port": module.params["netconf_port"], + "state": module.params["state"], + } + + for key, value in iteritems(obj): + # validate the param value (if validator func exists) + validator = globals().get("validate_%s" % key) + if callable(validator): + validator(value, module) + + return obj + + +def load_config(module, config, commit=False): + conn = get_connection(module) + try: + resp = conn.edit_config(to_list(config) + ["top"], commit) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + + diff = resp.get("diff", "") + return to_text(diff, errors="surrogate_then_replace").strip() + + +def main(): + """main entry point for module execution""" + argument_spec = dict( + netconf_port=dict(type="int", default=830, aliases=["listens_on"]), + state=dict(default="present", choices=["present", "absent"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + warnings = list() + result = {"changed": False, "warnings": warnings} + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result["commands"] = commands + + if commands: + commit = not module.check_mode + diff = load_config(module, commands, commit=commit) + if diff: + if module._diff: + result["diff"] = {"prepared": diff} + result["changed"] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() From e43b341b6185d2c04ce1040501e80355334fbf4d Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Tue, 22 Jul 2025 14:41:57 +0530 Subject: [PATCH 3/3] symlink action plugin files --- .../juniper/device/plugins/action/hostname.py | 196 +--------- .../juniper/device/plugins/action/junos.py | 24 -- .../juniper/device/plugins/action/netconf.py | 194 +--------- .../device/plugins/modules/hostname.py | 361 ------------------ .../device/plugins/modules/junos_hostname.py | 1 - .../juniper/device/plugins/modules/netconf.py | 214 ----------- 6 files changed, 2 insertions(+), 988 deletions(-) mode change 100644 => 120000 ansible_collections/juniper/device/plugins/action/hostname.py mode change 100644 => 120000 ansible_collections/juniper/device/plugins/action/netconf.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/hostname.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/netconf.py diff --git a/ansible_collections/juniper/device/plugins/action/hostname.py b/ansible_collections/juniper/device/plugins/action/hostname.py deleted file mode 100644 index abdca0cd..00000000 --- a/ansible_collections/juniper/device/plugins/action/hostname.py +++ /dev/null @@ -1,195 +0,0 @@ -# -# (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 . -# -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: hostname -short_description: Action plugin for hostname operations on Juniper devices -description: - - This action plugin handles hostname operations for Juniper devices. - - It provides the necessary connection setup and module routing. -version_added: "1.0.0" -author: - - Juniper Networks -notes: - - This is an action plugin that handles hostname operations. -""" - -EXAMPLES = r""" -# This action plugin is used internally by hostname modules -# No direct usage examples as this is an action plugin -""" - -RETURN = r""" -# This action plugin handles hostname operations -# Return values are handled by the calling module -""" - -import copy -import sys - -from ansible.utils.display import Display -from ansible_collections.ansible.netcommon.plugins.action.network import ( - ActionModule as ActionNetworkModule, -) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( - load_provider, -) - -from ansible_collections.juniper.device.plugins.module_utils.network.junos.junos import ( - junos_provider_spec, -) - - -display = Display() - -CLI_SUPPORTED_MODULES = ["junos_netconf", "junos_ping", "junos_command"] - - -class ActionModule(ActionNetworkModule): - def run(self, tmp=None, task_vars=None): - - del tmp # tmp no longer has any effect - - module_name = self._task.action.split(".")[-1] - self._task.collections.append("juniper.device") - self._config_module = True if module_name in ["junos_config", "config"] else False - persistent_connection = self._play_context.connection.split(".")[-1] - warnings = [] - - if self._play_context.connection == "local": - provider = load_provider(junos_provider_spec, self._task.args) - pc = copy.deepcopy(self._play_context) - # pc.network_os = "juniper.device.junos" - pc.network_os = "juniper.device.device" - pc.remote_addr = provider["host"] or self._play_context.remote_addr - - if provider["transport"] == "cli" and module_name not in CLI_SUPPORTED_MODULES: - return { - "failed": True, - "msg": "Transport type '%s' is not valid for '%s' module. " - "Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html" - % (provider["transport"], module_name), - } - - if module_name == "junos_netconf" or ( - provider["transport"] == "cli" and module_name == "junos_command" - ): - pc.connection = "ansible.netcommon.network_cli" - pc.port = int( - provider["port"] or self._play_context.port or 22, - ) - else: - pc.connection = "ansible.netcommon.netconf" - pc.port = int( - provider["port"] or self._play_context.port or 830, - ) - - pc.remote_user = provider["username"] or self._play_context.connection_user - pc.password = provider["password"] or self._play_context.password - pc.private_key_file = provider["ssh_keyfile"] or self._play_context.private_key_file - - connection = self._shared_loader_obj.connection_loader.get( - "ansible.netcommon.persistent", - pc, - sys.stdin, - task_uuid=self._task._uuid, - ) - - # TODO: Remove below code after ansible minimal is cut out - if connection is None: - pc.network_os = "junos" - if pc.connection.split(".")[-1] == "netconf": - pc.connection = "netconf" - else: - pc.connection = "network_cli" - - connection = self._shared_loader_obj.connection_loader.get( - "persistent", - pc, - sys.stdin, - task_uuid=self._task._uuid, - ) - - display.vvv( - "using connection plugin %s (was local)" % pc.connection, - pc.remote_addr, - ) - - command_timeout = ( - int(provider["timeout"]) - if provider["timeout"] - else connection.get_option("persistent_command_timeout") - ) - connection.set_options( - direct={"persistent_command_timeout": command_timeout}, - ) - - socket_path = connection.run() - display.vvvv("socket_path: %s" % socket_path, pc.remote_addr) - if not socket_path: - return { - "failed": True, - "msg": "unable to open shell. Please see: " - + "https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell", - } - - task_vars["ansible_socket"] = socket_path - warnings.append( - [ - "connection local support for this module is deprecated and will be removed in version 2.14, use connection %s" - % pc.connection, - ], - ) - elif persistent_connection in ("netconf", "network_cli"): - provider = self._task.args.get("provider", {}) - if any(provider.values()): - # for legacy reasons provider value is required for junos_facts(optional) and junos_package - # modules as it uses junos_eznc library to connect to remote host - if not ( - module_name == "junos_facts" - or module_name == "junos_package" - or module_name == "junos_scp" - ): - display.warning( - "provider is unnecessary when using %s and will be ignored" - % self._play_context.connection, - ) - del self._task.args["provider"] - - if ( - persistent_connection == "network_cli" and module_name not in CLI_SUPPORTED_MODULES - ) or (persistent_connection == "netconf" and module_name in CLI_SUPPORTED_MODULES[0:2]): - return { - "failed": True, - "msg": "Connection type '%s' is not valid for '%s' module. " - "Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html" - % (self._play_context.connection, module_name), - } - result = super(ActionModule, self).run(task_vars=task_vars) - if warnings: - if "warnings" in result: - result["warnings"].extend(warnings) - else: - result["warnings"] = warnings - return result diff --git a/ansible_collections/juniper/device/plugins/action/hostname.py b/ansible_collections/juniper/device/plugins/action/hostname.py new file mode 120000 index 00000000..a67f4858 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/action/hostname.py @@ -0,0 +1 @@ +junos.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/junos.py b/ansible_collections/juniper/device/plugins/action/junos.py index a8b9ade7..1b554bbe 100644 --- a/ansible_collections/juniper/device/plugins/action/junos.py +++ b/ansible_collections/juniper/device/plugins/action/junos.py @@ -21,30 +21,6 @@ __metaclass__ = type -DOCUMENTATION = r""" ---- -module: junos -short_description: Action plugin for Junos operations on Juniper devices -description: - - This action plugin handles Junos operations for Juniper devices. - - It provides the necessary connection setup and module routing. -version_added: "1.0.0" -author: - - Juniper Networks -notes: - - This is an action plugin that handles Junos operations. -""" - -EXAMPLES = r""" -# This action plugin is used internally by Junos modules -# No direct usage examples as this is an action plugin -""" - -RETURN = r""" -# This action plugin handles Junos operations -# Return values are handled by the calling module -""" - import copy import sys diff --git a/ansible_collections/juniper/device/plugins/action/netconf.py b/ansible_collections/juniper/device/plugins/action/netconf.py deleted file mode 100644 index b67aabaf..00000000 --- a/ansible_collections/juniper/device/plugins/action/netconf.py +++ /dev/null @@ -1,193 +0,0 @@ -# -# (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 . -# -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: netconf -short_description: Action plugin for NETCONF operations on Juniper devices -description: - - This action plugin handles NETCONF operations for Juniper devices. - - It provides the necessary connection setup and module routing. -version_added: "1.0.0" -author: - - Juniper Networks -notes: - - This is an action plugin that handles NETCONF operations. -""" - -EXAMPLES = r""" -# This action plugin is used internally by NETCONF modules -# No direct usage examples as this is an action plugin -""" - -RETURN = r""" -# This action plugin handles NETCONF operations -# Return values are handled by the calling module -""" - -import copy -import sys - -from ansible.utils.display import Display -from ansible_collections.ansible.netcommon.plugins.action.network import ( - ActionModule as ActionNetworkModule, -) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( - load_provider, -) - -from ansible_collections.juniper.device.plugins.module_utils.network.junos.junos import ( - junos_provider_spec, -) - - -display = Display() - -CLI_SUPPORTED_MODULES = ["junos_netconf", "junos_ping", "junos_command"] - - -class ActionModule(ActionNetworkModule): - def run(self, tmp=None, task_vars=None): - - del tmp # tmp no longer has any effect - - module_name = self._task.action.split(".")[-1] - self._config_module = True if module_name in ["junos_config", "config"] else False - persistent_connection = self._play_context.connection.split(".")[-1] - warnings = [] - - if self._play_context.connection == "local": - provider = load_provider(junos_provider_spec, self._task.args) - pc = copy.deepcopy(self._play_context) - pc.network_os = "junipernetworks.junos.junos" - pc.remote_addr = provider["host"] or self._play_context.remote_addr - - if provider["transport"] == "cli" and module_name not in CLI_SUPPORTED_MODULES: - return { - "failed": True, - "msg": "Transport type '%s' is not valid for '%s' module. " - "Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html" - % (provider["transport"], module_name), - } - - if module_name == "junos_netconf" or ( - provider["transport"] == "cli" and module_name == "junos_command" - ): - pc.connection = "ansible.netcommon.network_cli" - pc.port = int( - provider["port"] or self._play_context.port or 22, - ) - else: - pc.connection = "ansible.netcommon.netconf" - pc.port = int( - provider["port"] or self._play_context.port or 830, - ) - - pc.remote_user = provider["username"] or self._play_context.connection_user - pc.password = provider["password"] or self._play_context.password - pc.private_key_file = provider["ssh_keyfile"] or self._play_context.private_key_file - - connection = self._shared_loader_obj.connection_loader.get( - "ansible.netcommon.persistent", - pc, - sys.stdin, - task_uuid=self._task._uuid, - ) - - # TODO: Remove below code after ansible minimal is cut out - if connection is None: - pc.network_os = "junos" - if pc.connection.split(".")[-1] == "netconf": - pc.connection = "netconf" - else: - pc.connection = "network_cli" - - connection = self._shared_loader_obj.connection_loader.get( - "persistent", - pc, - sys.stdin, - task_uuid=self._task._uuid, - ) - - display.vvv( - "using connection plugin %s (was local)" % pc.connection, - pc.remote_addr, - ) - - command_timeout = ( - int(provider["timeout"]) - if provider["timeout"] - else connection.get_option("persistent_command_timeout") - ) - connection.set_options( - direct={"persistent_command_timeout": command_timeout}, - ) - - socket_path = connection.run() - display.vvvv("socket_path: %s" % socket_path, pc.remote_addr) - if not socket_path: - return { - "failed": True, - "msg": "unable to open shell. Please see: " - + "https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell", - } - - task_vars["ansible_socket"] = socket_path - warnings.append( - [ - "connection local support for this module is deprecated and will be removed in version 2.14, use connection %s" - % pc.connection, - ], - ) - elif persistent_connection in ("netconf", "network_cli"): - provider = self._task.args.get("provider", {}) - if any(provider.values()): - # for legacy reasons provider value is required for junos_facts(optional) and junos_package - # modules as it uses junos_eznc library to connect to remote host - if not ( - module_name == "junos_facts" - or module_name == "junos_package" - or module_name == "junos_scp" - ): - display.warning( - "provider is unnecessary when using %s and will be ignored" - % self._play_context.connection, - ) - del self._task.args["provider"] - - if ( - persistent_connection == "network_cli" and module_name not in CLI_SUPPORTED_MODULES - ) or (persistent_connection == "netconf" and module_name in CLI_SUPPORTED_MODULES[0:2]): - return { - "failed": True, - "msg": "Connection type '%s' is not valid for '%s' module. " - "Please see https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html" - % (self._play_context.connection, module_name), - } - result = super(ActionModule, self).run(task_vars=task_vars) - if warnings: - if "warnings" in result: - result["warnings"].extend(warnings) - else: - result["warnings"] = warnings - return result diff --git a/ansible_collections/juniper/device/plugins/action/netconf.py b/ansible_collections/juniper/device/plugins/action/netconf.py new file mode 120000 index 00000000..a67f4858 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/action/netconf.py @@ -0,0 +1 @@ +junos.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/hostname.py b/ansible_collections/juniper/device/plugins/modules/hostname.py deleted file mode 100644 index 167f9c9a..00000000 --- a/ansible_collections/juniper/device/plugins/modules/hostname.py +++ /dev/null @@ -1,361 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2021 Red Hat -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -############################################# -# WARNING # -############################################# -# -# This file is auto generated by the resource -# module builder playbook. -# -# Do not edit this file manually. -# -# Changes to this file will be over written -# by the resource module builder. -# -# Changes should be made in the model used to -# generate this file or in the resource module -# builder template. -# -############################################# - -""" -The module file for junos_hostname -""" - -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - -ANSIBLE_METADATA = { - "metadata_version": "1.1", - "status": ["preview"], - "supported_by": "network", -} - -DOCUMENTATION = """ ---- -module: hostname -version_added: 2.9.0 -short_description: Manage Hostname server configuration on Junos devices. -description: This module manages hostname configuration on devices running Junos. -author: Rohit Thakur (@rohitthakur2590) -requirements: - - ncclient (>=v0.6.4) - - xmltodict (>=0.12.0) -notes: - - This module requires the netconf system service be enabled on the device being managed. - - This module works with connection C(netconf). - - See L(the Junos OS Platform Options,https://docs.ansible.com/ansible/latest/network/user_guide/platform_junos.html). - - Tested against JunOS v18.4R1 -options: - running_config: - description: - - This option is used only with state I(parsed). - - The value of this option should be the output received from the Junos device - by executing the command B(show system hostname). - - 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 - config: - description: A dictionary of system Hostname configuration. - type: dict - suboptions: - hostname: - description: Specify the hostname. - type: str - state: - 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 system hostname) executed on device. For state I(parsed) active - connection to remote host is not required. - type: str - choices: - - merged - - replaced - - deleted - - overridden - - parsed - - gathered - - rendered - default: merged -""" -EXAMPLES = """ -# Using merged -# -# Before state -# ------------ -# -# vagrant@vsrx# show system hostname -# -# [edit] -- name: Merge provided HOSTNAME configuration into running configuration. - junipernetworks.junos.junos_hostname: - config: - hostname: 'vsrx-18.4R1' - state: merged -# -# ------------------------- -# Module Execution Result -# ------------------------- -# "after": { -# "hostname": "vsrx-18.4R1" -# }, -# "before": {}, -# "changed": true, -# "commands": [ -# " -# "vsrx-18.4R1" -# ] -# After state -# ----------- -# -# vagrant@vsrx-18.4R1# show system host-name -# host-name vsrx-18.4R1; -# -# Using replaced -# -# Before state -# ------------ -# -# vagrant@vsrx-18.4R1# show system host-name -# host-name vsrx-18.4R1; -# -# [edit] -- name: Replaced target config with provided config. - junipernetworks.junos.junos_hostname: - config: - hostname: 'vsrx-12' - state: replaced -# -# ------------------------- -# Module Execution Result -# ------------------------- -# "after": { -# "hostname": "vsrx-12" -# }, -# "before": { -# "hostname": "vsrx-18.4R1" -# }, -# "changed": true, -# "commands": [ -# " -# "vsrx-12" -# ] -# After state -# ----------- -# -# vagrant@vsrx-18.4R1# show system host-name -# host-name vsrx-12; -# -# Using overridden -# -# Before state -# ------------ -# -# vagrant@vsrx-18.4R1# show system host-name -# host-name vsrx-18.4R1; -# -# [edit] -- name: Replaced target config with provided config. - junipernetworks.junos.junos_hostname: - config: - hostname: 'vsrx-12' - state: overridden -# -# ------------------------- -# Module Execution Result -# ------------------------- -# "after": { -# "hostname": "vsrx-12" -# }, -# "before": { -# "hostname": "vsrx-18.4R1" -# }, -# "changed": true, -# "commands": [ -# " -# "vsrx-12" -# ] -# After state -# ----------- -# -# vagrant@vsrx-18.4R1# show system host-name -# host-name vsrx-12; -# -# Using deleted -# -# Before state -# ------------ -# -# vagrant@vsrx-18.4R1# show system host-name -# host-name vsrx-12; -# -- name: Delete running HOSTNAME global configuration - junipernetworks.junos.junos_hostname: - config: - state: deleted -# -# ------------------------- -# Module Execution Result -# ------------------------- -# "after": {}, -# "before": { -# "hostname": "vsrx-12" -# }, -# "changed": true, -# "commands": [ -# " -# " -# ] -# After state -# ----------- -# -# vagrant@vsrx# show system ntp -# -# [edit] -# Using gathered -# -# Before state -# ------------ -# -# vagrant@vsrx-18.4R1# show system host-name -# host-name vsrx-12; -# -- name: Gather running HOSTNAME global configuration - junipernetworks.junos.junos_hostname: - state: gathered -# -# ------------------------- -# Module Execution Result -# ------------------------- -# "gathered": { -# "hostname": "vsrx-12", -# }, -# "changed": false, -# Using rendered -# -# Before state -# ------------ -# -- name: Render xml for provided facts. - junipernetworks.junos.junos_hostname: - config: - boot_server: '78.46.194.186' - state: rendered -# -# ------------------------- -# Module Execution Result -# ------------------------- -# "rendered": [ -# "" -# "78.46.194.186" -# ] -# -# Using parsed -# parsed.cfg -# ------------ -# -# -# -# 18.4R1-S2.4 -# -# vsrx-18.4R1 -# -# -# -# -- name: Parse HOSTNAME running config - junipernetworks.junos.junos_hostname: - running_config: "{{ lookup('file', './parsed.cfg') }}" - state: parsed -# -# -# ------------------------- -# Module Execution Result -# ------------------------- -# -# -# "parsed": { -# "hostname": "vsrx-18.4R1" -# } -# -""" -RETURN = """ -before: - description: The configuration prior to the model invocation. - returned: always - sample: > - The configuration returned will always be in the same format - of the parameters above. - type: dict -after: - description: The resulting configuration model invocation. - returned: when changed - sample: > - The configuration returned will always be in the same format - of the parameters above. - type: dict -commands: - description: The set of commands pushed to the remote device. - returned: always - type: list - sample: ['"78.46.194.186"'] -""" - - -from ansible.module_utils.basic import AnsibleModule - -from ansible_collections.juniper.device.plugins.module_utils.network.junos.argspec.hostname.hostname import ( - HostnameArgs, -) -from ansible_collections.juniper.device.plugins.module_utils.network.junos.config.hostname.hostname import ( - Hostname, -) - - -def main(): - """ - Main entry point for module execution - - :returns: the result form module invocation - """ - required_if = [ - ("state", "merged", ("config",)), - ("state", "replaced", ("config",)), - ("state", "overridden", ("config",)), - ("state", "rendered", ("config",)), - ("state", "parsed", ("running_config",)), - ] - module = AnsibleModule( - argument_spec=HostnameArgs.argument_spec, - required_if=required_if, - supports_check_mode=True, - ) - - result = Hostname(module).execute_module() - module.exit_json(**result) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/juniper/device/plugins/modules/junos_hostname.py b/ansible_collections/juniper/device/plugins/modules/junos_hostname.py index f61a1742..c9894de8 100644 --- a/ansible_collections/juniper/device/plugins/modules/junos_hostname.py +++ b/ansible_collections/juniper/device/plugins/modules/junos_hostname.py @@ -38,7 +38,6 @@ } DOCUMENTATION = """ ---- module: junos_hostname version_added: 2.9.0 short_description: Manage Hostname server configuration on Junos devices. diff --git a/ansible_collections/juniper/device/plugins/modules/netconf.py b/ansible_collections/juniper/device/plugins/modules/netconf.py deleted file mode 100644 index dce8f515..00000000 --- a/ansible_collections/juniper/device/plugins/modules/netconf.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright 2021 Red Hat -# GNU General Public License v3.0+ -# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -############################################# -# WARNING # -############################################# -# -# This file is auto generated by the resource -# module builder playbook. -# -# Do not edit this file manually. -# -# Changes to this file will be over written -# by the resource module builder. -# -# Changes should be made in the model used to -# generate this file or in the resource module -# builder template. -# -############################################# -""" -The module file for junos_netconf -""" -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - - -DOCUMENTATION = """ ---- -module: netconf -author: Peter Sprygada (@privateip) -short_description: Configures the Junos Netconf system service -description: -- This module provides an abstraction that enables and configures the netconf system - service running on Junos devices. This module can be used to easily enable the - Netconf API. Netconf provides a programmatic interface for working with configuration - and state resources as defined in RFC 6242. If the C(netconf_port) is not mentioned - in the task by default netconf will be enabled on port 830 only. -version_added: 1.0.0 -extends_documentation_fragment: -- juniper.device.junos -options: - netconf_port: - description: - - This argument specifies the port the netconf service should listen on for SSH - connections. The default port as defined in RFC 6242 is 830. - required: false - default: 830 - aliases: - - listens_on - type: int - state: - description: - - Specifies the state of the C(junos_netconf) resource on the remote device. If - the I(state) argument is set to I(present) the netconf service will be configured. If - the I(state) argument is set to I(absent) the netconf service will be removed - from the configuration. - type: str - required: false - default: present - choices: - - present - - absent -notes: -- Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4. -- Recommended connection is C(network_cli). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html). -- This module also works with C(local) connections for legacy playbooks. -- If C(netconf_port) value is not mentioned in task by default it will be enabled - on port 830 only. Although C(netconf_port) value can be from 1 through 65535, avoid - configuring access on a port that is normally assigned for another service. This - practice avoids potential resource conflicts. -""" - -EXAMPLES = """ -- name: enable netconf service on port 830 - junipernetworks.junos.junos_netconf: - listens_on: 830 - state: present - -- name: disable netconf service - junipernetworks.junos.junos_netconf: - state: absent -""" - -RETURN = """ -commands: - description: Returns the command sent to the remote device - returned: when changed is True - type: str - sample: 'set system services netconf ssh port 830' -""" -import re - -from ansible.module_utils._text import to_text -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.connection import ConnectionError -from ansible.module_utils.six import iteritems -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list - -from ansible_collections.juniper.device.plugins.module_utils.network.junos.junos import ( - get_connection, -) - - -USE_PERSISTENT_CONNECTION = True - - -def map_obj_to_commands(updates, module): - want, have = updates - commands = list() - - if want["state"] == "absent": - if have["state"] == "present": - commands.append("delete system services netconf") - else: - if have["state"] == "absent" or want["netconf_port"] != have.get( - "netconf_port", - ): - commands.append( - "set system services netconf ssh port %s" % want["netconf_port"], - ) - - return commands - - -def parse_port(config): - match = re.search(r"port (\d+)", config) - if match: - return int(match.group(1)) - - -def map_config_to_obj(module): - conn = get_connection(module) - out = conn.get(command="show configuration system services netconf") - if out is None: - module.fail_json(msg="unable to retrieve current config") - config = str(out).strip() - - obj = {"state": "absent"} - if "ssh" in config: - obj.update({"state": "present", "netconf_port": parse_port(config)}) - return obj - - -def validate_netconf_port(value, module): - if not 1 <= value <= 65535: - module.fail_json(msg="netconf_port must be between 1 and 65535") - - -def map_params_to_obj(module): - obj = { - "netconf_port": module.params["netconf_port"], - "state": module.params["state"], - } - - for key, value in iteritems(obj): - # validate the param value (if validator func exists) - validator = globals().get("validate_%s" % key) - if callable(validator): - validator(value, module) - - return obj - - -def load_config(module, config, commit=False): - conn = get_connection(module) - try: - resp = conn.edit_config(to_list(config) + ["top"], commit) - except ConnectionError as exc: - module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) - - diff = resp.get("diff", "") - return to_text(diff, errors="surrogate_then_replace").strip() - - -def main(): - """main entry point for module execution""" - argument_spec = dict( - netconf_port=dict(type="int", default=830, aliases=["listens_on"]), - state=dict(default="present", choices=["present", "absent"]), - ) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - warnings = list() - result = {"changed": False, "warnings": warnings} - - want = map_params_to_obj(module) - have = map_config_to_obj(module) - - commands = map_obj_to_commands((want, have), module) - result["commands"] = commands - - if commands: - commit = not module.check_mode - diff = load_config(module, commands, commit=commit) - if diff: - if module._diff: - result["diff"] = {"prepared": diff} - result["changed"] = True - - module.exit_json(**result) - - -if __name__ == "__main__": - main()