From 0bb0c0e9ca6387deef35bda82b8ee1909258dc6d Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 23 Nov 2015 10:35:23 +0530 Subject: [PATCH 001/426] changed shebang path to work in virtualenv environment too --- library/junos_get_facts | 2 +- library/junos_install_config | 2 +- library/junos_install_os | 2 +- library/junos_shutdown | 2 +- library/junos_srx_cluster | 2 +- library/junos_zeroize | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/junos_get_facts b/library/junos_get_facts index 70d93050..a32c241f 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (c) 1999-2014, Juniper Networks Inc. # 2014, Jeremy Schulman diff --git a/library/junos_install_config b/library/junos_install_config index 29946dba..638bf4a8 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (c) 1999-2014, Juniper Networks Inc. # 2014, Jeremy Schulman diff --git a/library/junos_install_os b/library/junos_install_os index 406dafdb..c9814ef0 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (c) 1999-2014, Juniper Networks Inc. # 2014, Jeremy Schulman diff --git a/library/junos_shutdown b/library/junos_shutdown index 6150e88a..329ef945 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (c) 1999-2014, Juniper Networks Inc. # 2014, Jeremy Schulman diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 4a9f02ad..7faa4784 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (c) 1999-2014, Juniper Networks Inc. # 2014, Patrik Bok diff --git a/library/junos_zeroize b/library/junos_zeroize index 22f9e818..fa272734 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (c) 1999-2014, Juniper Networks Inc. # 2014, Jeremy Schulman From 3c6582eccf0f3e1e165caa067515cade7b3687de Mon Sep 17 00:00:00 2001 From: Stacy Smith Date: Tue, 8 Dec 2015 09:26:46 -0700 Subject: [PATCH 002/426] Add a timeout argument to the junos_commit module in order to support confirming a commit on a device which is slow and/or has a large config. --- library/junos_commit | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/junos_commit b/library/junos_commit index 2630a16d..6b62e39f 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -63,6 +63,13 @@ options: - TCP port number to use when connecting to the device required: false default: 830 + timeout: + description: + - Extend the NETCONF RPC timeout beyond the default value of + 30 seconds. Set this value to accommodate commits that + might take longer than the default timeout interval. + required: false + default: "0" logfile: description: - Path on the local server where the progress status is logged @@ -110,6 +117,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + timeout=dict(required=False, default=0), logfile=dict(required=False, default=None), comment=dict(required=False, default=None), confirm=dict(required=False, default=None) @@ -142,6 +150,10 @@ def main(): # --- UNREACHABLE --- try: + timeout = int(args['timeout']) + if timeout > 0: + dev.timeout = timeout + cu = Config(dev) logging.info("taking lock") From 5ea2bf59cd81cbf574e74f41d983aed17d4c4ea7 Mon Sep 17 00:00:00 2001 From: David Gethings Date: Tue, 12 Jan 2016 20:05:15 +0900 Subject: [PATCH 003/426] fixing log message and setting results variable when enabling cluster via the NETCONF API (but not via the console) --- library/junos_srx_cluster | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 7faa4784..0b2645eb 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -181,7 +181,8 @@ def main(): if cluster is True: if (args['cluster_id'] and args['node']) is not None: do_it = dev.rpc.set_chassis_cluster_enable(cluster_id=args['cluster_id'], node=args['node'], reboot=True) - logging.info('Device message: {0}'.format(do_it.findtext('.//message').strip())) + logging.info('Device message: {0}'.format(do_it.getparent().findtext('.//output').strip())) + results = {"changed": True, "reboot": True} else: msg = 'FAIL: Cluster and Node ID required with enable_cluster=YES' logging.error(msg) From 7aec58c9f8e7e2a2eb78d4a617fa0b65a2746c58 Mon Sep 17 00:00:00 2001 From: "Biancaniello, Michael" Date: Mon, 2 Nov 2015 17:12:37 -0500 Subject: [PATCH 004/426] Added support for get_config to properly report if dest file has (or would be) changed. --- library/junos_get_config | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/library/junos_get_config b/library/junos_get_config index a99b96e7..e7be6009 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -143,6 +143,7 @@ def main(): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') args = module.params + results = {} logfile = args['logfile'] if logfile is not None: @@ -182,11 +183,25 @@ def main(): config = dev.rpc.get_config(options=options, filter_xml=filter_xml) - with open(args['dest'], 'w') as confile: - if args['format'] == 'text': - confile.write(config.text.encode('ascii', 'replace')) - elif args['format'] == 'xml': - confile.write(etree.tostring(config)) + ## Compare existing file (if exists) with new output + if args['format'] == 'text': + newconf = config.text.encode('ascii', 'replace') + elif args['format'] == 'xml': + newconf = etree.tostring(config) + + oldconf = '' + if os.path.isfile(args['dest']): + with open(args['dest'], 'r') as confile: + oldconf = confile.read() + + ## Correctly report 'changed' attribute + if oldconf != newconf: + results['changed'] = True + + ## if check_mode, then don't actually change the dest file! + if not module.check_mode: + with open(args['dest'], 'w') as confile: + confile.write(newconf) except (ValueError, RpcError) as err: msg = 'Unable to get config: {0}'.format(str(err)) @@ -202,7 +217,7 @@ def main(): dev.close() - module.exit_json() + module.exit_json(**results) from ansible.module_utils.basic import * main() From b4e8f7b25d3e7033e72a1c5be34cd3bc7bbd64a6 Mon Sep 17 00:00:00 2001 From: "Biancaniello, Michael" Date: Mon, 2 Nov 2015 17:24:40 -0500 Subject: [PATCH 005/426] Fixed requirement status of 'dest' argument --- library/junos_get_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_get_config b/library/junos_get_config index e7be6009..c96e4c23 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -132,7 +132,7 @@ def main(): passwd=dict(required=False, default=None), port=dict(required=False, default=830), logfile=dict(required=False, default=None), - dest=dict(required=False, default=None), + dest=dict(required=True, default=None), format=dict(required=False, choices=['text', 'xml'], default='text'), options=dict(required=False, default=None), filter=dict(required=False, default=None) From 9bba141622894ac3fd47101663b424437e24a02e Mon Sep 17 00:00:00 2001 From: "Biancaniello, Michael" Date: Thu, 28 Jan 2016 12:26:42 -0500 Subject: [PATCH 006/426] Added support for returning diff. --- library/junos_get_config | 7 +++++++ library/junos_install_config | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/library/junos_get_config b/library/junos_get_config index c96e4c23..13bad407 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -197,6 +197,13 @@ def main(): ## Correctly report 'changed' attribute if oldconf != newconf: results['changed'] = True + if getattr(module,'_diff',False) is True: + results['diff'] = dict( + before = oldconf, + after = newconf, + before_header = args['dest'], + after_header = args['host'], + ) ## if check_mode, then don't actually change the dest file! if not module.check_mode: diff --git a/library/junos_install_config b/library/junos_install_config index 638bf4a8..a9dfed09 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -266,6 +266,10 @@ def _load_via_netconf(module): diff = cu.diff() if diff is not None: + if getattr(module,'_diff',False): + results['diff'] = dict( + prepared = diff + ) diffs_file = args['diffs_file'] if diffs_file is not None: try: From eb08fd0c94362fa5a4ce84a3849431336ef2cf6d Mon Sep 17 00:00:00 2001 From: "Biancaniello, Michael" Date: Thu, 28 Jan 2016 12:29:06 -0500 Subject: [PATCH 007/426] fixed IF statement. --- library/junos_install_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_config b/library/junos_install_config index a9dfed09..a502634d 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -266,7 +266,7 @@ def _load_via_netconf(module): diff = cu.diff() if diff is not None: - if getattr(module,'_diff',False): + if getattr(module,'_diff',False) is True: results['diff'] = dict( prepared = diff ) From 8383857d8e68f8c0c473d764d70d0410b52c9b71 Mon Sep 17 00:00:00 2001 From: "Biancaniello, Michael" Date: Fri, 29 Jan 2016 12:43:13 -0500 Subject: [PATCH 008/426] Moved assignment of 'changed' to ensure that check_mode reports a change if there would have been one. --- library/junos_install_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_config b/library/junos_install_config index a502634d..9e997b1d 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -266,6 +266,7 @@ def _load_via_netconf(module): diff = cu.diff() if diff is not None: + results['changed'] = True if getattr(module,'_diff',False) is True: results['diff'] = dict( prepared = diff @@ -302,7 +303,6 @@ def _load_via_netconf(module): opts['confirm'] = args['confirm'] cu.commit(**opts) - results['changed'] = True except CommitError as err: msg = "Unable to commit configuration: {0}".format(err) From 664b8c39258ad16cb25c15ec52f5dd44223b82d8 Mon Sep 17 00:00:00 2001 From: Carlos Vicente Date: Fri, 29 Jan 2016 16:03:03 -0500 Subject: [PATCH 009/426] Fix exception handling line --- library/junos_install_config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index 638bf4a8..9b7f4c39 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -277,9 +277,9 @@ def _load_via_netconf(module): msg += "I/O Error: ({0}): {1}".format(errno, strerror) logging.error(msg) module.fail_json(msg=msg) - except: + except Exception as err: msg = "Problem with diffs_file {0}: ".format(diffs_file) - msg += "Unexpected error:", sys.exc_info()[0] + msg += "Unexpected error: {0}".format(err) logging.error(msg) module.fail_json(msg=msg) From 019b0f699c416feac5b3b5f591012b5562e59b8e Mon Sep 17 00:00:00 2001 From: Carlos Vicente Date: Fri, 29 Jan 2016 16:37:01 -0500 Subject: [PATCH 010/426] Ignore non-utf-8 characters comming from diffs --- library/junos_install_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_config b/library/junos_install_config index 9b7f4c39..3d972fd2 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -270,7 +270,7 @@ def _load_via_netconf(module): if diffs_file is not None: try: f = open(diffs_file, 'w') - f.write(diff) + f.write(diff.encode('utf-8', 'ignore')) f.close() except IOError as (errno, strerror): msg = "Problem with diffs_file {0}: ".format(diffs_file) From e89a889600f373b4968297af54a2541a3c788f85 Mon Sep 17 00:00:00 2001 From: Stacy Smith Date: Tue, 9 Feb 2016 21:56:10 -0700 Subject: [PATCH 011/426] Addressing issue #102. Ansible 2 adds a feature to search the modules return value for any strings which should not be logged. This feature only handles built-in data types and does not handle custom objects. The junos_get_facts module was returning a junos.version_info object which caused this new feature to raise a TypeError. Since all Ansible return values are passed via JSON, the junos.version_info object was previously getting converted to a dictionary anyway, so this change should be backwards compatible with Ansible 1 as well. --- library/junos_get_facts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/junos_get_facts b/library/junos_get_facts index a32c241f..70ffffc3 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -152,6 +152,9 @@ def main(): dev.close() dev.facts['has_2RE'] = dev.facts['2RE'] del dev.facts['2RE'] # Ansible doesn't allow variables starting with numbers + # Ansible 2 doesn't like custom objects in the return value. + # Convert the version_info key from a junos.version_info object to a dict + dev.facts['version_info'] = dict(dev.facts['version_info']) m_results['facts'] = dev.facts if m_args['savedir'] is not None: fname = "{0}/{1}-facts.json".format(m_args['savedir'], dev.facts['hostname']) From 74c9238669b47ee552bec264e12f618e964464dd Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Tue, 9 Feb 2016 23:16:51 -0800 Subject: [PATCH 012/426] Add roles junos_cli --- library/junos_cli | 183 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 library/junos_cli diff --git a/library/junos_cli b/library/junos_cli new file mode 100644 index 00000000..02b27eed --- /dev/null +++ b/library/junos_cli @@ -0,0 +1,183 @@ +#!/usr/bin/env python + +# Copyright (c) 1999-2015, Juniper Networks Inc. +# 2014, Jeremy Schulman +# 2015, Rick Sherman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +DOCUMENTATION = ''' +--- +module: junos_cli +author: Damien Garros, Juniper Networks +version_added: "1.2.0" +short_description: Execute CLI/RPC on device and save the output locally +description: + - Execute CLI/RPC on device and save the output locally on a file +requirements: + - junos-eznc >= 1.2.2 +options: + host: + description: + - Set to {{ inventory_hostname }} + required: true + user: + description: + - Login username + required: false + default: $USER + cli: + description: + - CLI command to execute on the host + required: true + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + port: + description: + - TCP port number to use when connecting to the device + required: false + default: 830 + logfile: + description: + - Path on the local server where the progress status is logged + for debugging purposes + required: false + default: None + dest: + description: + - Path to the local server directory where cli output will + be saved. + required: true + default: None + format: + description: + - text - Cli output saved in text format + - xml - Cli output saved as XML + required: false + choices: ['text','xml'] + default: 'text' +''' + +EXAMPLES = ''' +- junos_cli: + host: "{{ inventory_hostname }}" + cli: "show chassis hardware" + logfile: cli.log + dest: "{{ inventory_hostname }}.xml" + format: xml +''' +from distutils.version import LooseVersion +import logging +from lxml import etree +from lxml.builder import E + +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + HAS_PYEZ = False + else: + HAS_PYEZ = True +except ImportError: + HAS_PYEZ = False + + +def main(): + + module = AnsibleModule( + argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr + cli=dict(required=True, default=None), + user=dict(required=False, default=os.getenv('USER')), + passwd=dict(required=False, default=None), + port=dict(required=False, default=830), + logfile=dict(required=False, default=None), + dest=dict(required=False, default=None), + format=dict(required=False, choices=['text', 'xml'], default='text') + ), + supports_check_mode=True) + + if not HAS_PYEZ: + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + + args = module.params + + logfile = args['logfile'] + if logfile is not None: + logging.basicConfig(filename=logfile, level=logging.INFO, + format='%(asctime)s:%(name)s:%(message)s') + logging.getLogger().name = 'CONFIG:' + args['host'] + + logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) + + try: + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], gather_facts=False).open() + except Exception as err: + msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) + logging.error(msg) + module.fail_json(msg=msg) + # --- UNREACHABLE --- + + try: + options = {} + options['format'] = args['format'] + + logging.info("Getting cli output") + + cli_output = dev.cli(command=args['cli'], format=args['format'] ) + + with open(args['dest'], 'w') as outputfile: + if args['format'] == 'text': + outputfile.write(cli_output) + elif args['format'] == 'xml': + outputfile.write(etree.tostring(cli_output)) + + except (ValueError, RpcError) as err: + msg = 'Unable to get cli output: {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + except Exception as err: + msg = 'Uncaught exception - please report: {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + dev.close() + + module.exit_json() + +from ansible.module_utils.basic import * +main() From d640907de5f3b851ea640f6ceb1fd2ba54f75781 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 12 Feb 2016 15:29:07 +0530 Subject: [PATCH 013/426] to make rpc calls via ansible --- library/junos_rpc | 194 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 library/junos_rpc diff --git a/library/junos_rpc b/library/junos_rpc new file mode 100644 index 00000000..4aff6cfa --- /dev/null +++ b/library/junos_rpc @@ -0,0 +1,194 @@ +#!/usr/bin/python + +# Copyright (c) 1999-2014, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +DOCUMENTATION = ''' +--- +module: junos_run_rpc +author: Nitin Kumar, Juniper Networks +version_added: "1.6" +short_description: run given rpc +description: + - run given rpc +requirements: + - junos-eznc >= 1.0.0 + - junos-netconify >= 0.1.1, when using the I(console) option +options: + host: + description: + - Set to {{ inventory_hostname }} + required: true + user: + description: + - Login username + required: false + default: $USER + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + logfile: + description: + - Path on the local server where the progress status is logged + for debugging purposes. This option is used only with the + I(console) option. + required: false + default: None + rpc: + description: + - rpc to be executed + kwargs: + description: + - params need to be passed to rpc, comma seperated if more params to be passed + format: + description: + - default return value would be lxml object, xml if format=xml + dest: + description: + - Path to the local server directory where configuration will + be saved. + required: false + default: None +''' + +EXAMPLES = ''' +# retrieve rpc response using NETCONF + +- junos_run_rpc: + host={{ inventory_hostname }} + rpc=get-config + kwargs=interface_name='st0' + format=xml + register=junos + +# access the output + +- name: version + debug: msg="{{ junos.rpc_reply }}" + +# save rpc output into a file + +- junos_run_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + dest="get_interface_information.xml" + format=xml +''' + +import os +import sys +import logging +from jnpr.junos import Device +from lxml import etree + +def junos_rpc(module, dev): + args = module.params + logfile = args['logfile'] + if logfile is not None: + logging.basicConfig(filename=logfile, level=logging.INFO, + format='%(asctime)s:%(name)s:%(message)s') + logging.getLogger().name = 'CONFIG:' + args['host'] + + results = {} + kwargs = module.params['kwargs'] + rpc = module.params['rpc'] + results['rpc'] = rpc + results['kwargs'] = kwargs + results['changed'] = False + results['check_mode'] = module.check_mode + logging.info("calling RPC: {0}".format(rpc)) + try: + if module.params['format']=="xml": + if kwargs == None: + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))() + else: + values = {k:v for k,v in [tuple(kwargs.split('='))]} + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(**values) + else: + if kwargs == None: + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))() + else: + values = {k:v for k,v in [tuple(kwargs.split('='))]} + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(**values) + + if module.params['dest'] is not None: + with open(module.params['dest'], 'w') as confile: + if isinstance(results['rpc_reply'], etree._Element): + confile.write(etree.tostring(results['rpc_reply'])) + else: + confile.write(results['rpc_reply']) + + results['changed'] = True + except Exception as err: + print sys.exc_info() + results['failed'] = True + logging.error("unable to get rpc due to:{0}".format(err.message)) + raise err + logging.info("rpc reponse recvd") + return results + +def main(): + module = AnsibleModule( + argument_spec=dict( + host=dict(required=True), + logfile=dict(required=False, default=None), + user=dict(required=False, default=os.getenv('USER')), + passwd=dict(required=False, default=None), + rpc=dict(required=True, default=None), + kwargs=dict(required=False, default=None), + format=dict(required=False, default=None), + dest=dict(required=False, default=None)), + supports_check_mode=False) + + m_args = module.params + # ----------- + # via NETCONF + # ----------- + dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd']) + try: + dev.open() + except Exception as err: + msg = 'unable to connect to {0}: {1}'.format(m_args['host'], str(err)) + module.fail_json(msg=msg) + return + + results = junos_rpc(module, dev) + dev.facts['version_info'] = dict(dev.facts['version_info']) + results['facts'] = dev.facts + + dev.close() + module.exit_json(**results) + +from ansible.module_utils.basic import * +main() From e6c8c314be486a87a09d8931cfc9a82ca6c543da Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 12 Feb 2016 16:36:18 +0530 Subject: [PATCH 014/426] to make rpc calls via ansible --- library/junos_rpc | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index 4aff6cfa..d7502eaa 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -34,7 +34,7 @@ DOCUMENTATION = ''' --- -module: junos_run_rpc +module: junos_rpc author: Nitin Kumar, Juniper Networks version_added: "1.6" short_description: run given rpc @@ -85,7 +85,7 @@ options: EXAMPLES = ''' # retrieve rpc response using NETCONF -- junos_run_rpc: +- junos_rpc: host={{ inventory_hostname }} rpc=get-config kwargs=interface_name='st0' @@ -99,7 +99,7 @@ EXAMPLES = ''' # save rpc output into a file -- junos_run_rpc: +- junos_rpc: host={{ inventory_hostname }} rpc=get-interface-information dest="get_interface_information.xml" @@ -129,25 +129,32 @@ def junos_rpc(module, dev): results['check_mode'] = module.check_mode logging.info("calling RPC: {0}".format(rpc)) try: - if module.params['format']=="xml": + if rpc in ['get-config', 'get_config']: if kwargs == None: - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))() + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(options={'format':args['format']}) else: values = {k:v for k,v in [tuple(kwargs.split('='))]} - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(**values) + values['format'] = args['format'] + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(options=values) else: if kwargs == None: - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))() + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}) else: values = {k:v for k,v in [tuple(kwargs.split('='))]} - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(**values) + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}, **values) if module.params['dest'] is not None: with open(module.params['dest'], 'w') as confile: if isinstance(results['rpc_reply'], etree._Element): - confile.write(etree.tostring(results['rpc_reply'])) + if args.get('format') == 'text': + confile.write((results['rpc_reply']).text) + else: + confile.write(etree.tostring(results['rpc_reply'])) else: - confile.write(results['rpc_reply']) + if args.get('format') == 'json': + confile.write(str(results['rpc_reply'])) + else: + confile.write(results['rpc_reply']) results['changed'] = True except Exception as err: @@ -167,7 +174,7 @@ def main(): passwd=dict(required=False, default=None), rpc=dict(required=True, default=None), kwargs=dict(required=False, default=None), - format=dict(required=False, default=None), + format=dict(required=False, default='xml'), dest=dict(required=False, default=None)), supports_check_mode=False) From 76e0214cb6ac2ab0c276232fe2a0ab855bad0602 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 12 Feb 2016 16:40:49 +0530 Subject: [PATCH 015/426] to make rpc calls via ansible --- library/junos_rpc | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index d7502eaa..360ce8be 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -73,7 +73,7 @@ options: - params need to be passed to rpc, comma seperated if more params to be passed format: description: - - default return value would be lxml object, xml if format=xml + - Which defines the output format dest: description: - Path to the local server directory where configuration will @@ -87,23 +87,16 @@ EXAMPLES = ''' - junos_rpc: host={{ inventory_hostname }} - rpc=get-config - kwargs=interface_name='st0' - format=xml + rpc=get-interface-information + kwargs="interface_name=em0" + format=xml/text/json + dest=get_interface_information.conf register=junos -# access the output +# print the config - name: version debug: msg="{{ junos.rpc_reply }}" - -# save rpc output into a file - -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - dest="get_interface_information.xml" - format=xml ''' import os From 6f5fbb44f6ee69ab9fb23e9b778720dbee20504d Mon Sep 17 00:00:00 2001 From: Dwarakanath Y Date: Mon, 22 Feb 2016 14:30:20 +0530 Subject: [PATCH 016/426] Updating contact details in README.md --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 289f90b9..8604b657 100644 --- a/README.md +++ b/README.md @@ -103,9 +103,12 @@ Apache 2.0 ## CONTRIBUTORS -- Jeremy Schulman (@nwkautomaniac), Core developer -- Rick Sherman (@shermdog01) -- Nitin Kumar (@vnitinv) -- Patrik Bok -- Ashley Burston +Juniper Networks is actively contributing to and maintaining this repo. Please contact jnpr-community-netdev@juniper.net for any queries. +*Contributors:* + +[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr) + +*Former Contributors:* + +[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog) From d295b475a0103c118b8761a109b717e806b95302 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Wed, 24 Feb 2016 15:35:24 +0530 Subject: [PATCH 017/426] support filter_xml to get-config rpc, kwargs to take boolean values too --- library/junos_rpc | 80 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index 360ce8be..6f8d1e00 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -1,7 +1,7 @@ #!/usr/bin/python -# Copyright (c) 1999-2014, Juniper Networks Inc. -# 2014, Jeremy Schulman +# Copyright (c) 1999-2016, Juniper Networks Inc. +# 2016, Nitin Kumar # # All rights reserved. # @@ -36,7 +36,7 @@ DOCUMENTATION = ''' --- module: junos_rpc author: Nitin Kumar, Juniper Networks -version_added: "1.6" +version_added: "1.9" short_description: run given rpc description: - run given rpc @@ -70,10 +70,21 @@ options: - rpc to be executed kwargs: description: - - params need to be passed to rpc, comma seperated if more params to be passed + - params need to be passed to rpc + ex: kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" + kwargs={interface_name:em0} + kwargs={interface_name:em0,media:True} + filter_xml: + description: + - This options can be used with get-config rpc only, + to retrieve specific config format: description: - - Which defines the output format + - text - configuration saved as text (curly-brace) format + - xml - configuration saved as XML + required: false + choices: ['text','xml'] + default: 'xml' dest: description: - Path to the local server directory where configuration will @@ -85,6 +96,12 @@ options: EXAMPLES = ''' # retrieve rpc response using NETCONF +- junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + dest=get_interface_information.conf + register=junos + - junos_rpc: host={{ inventory_hostname }} rpc=get-interface-information @@ -94,14 +111,30 @@ EXAMPLES = ''' register=junos # print the config - - name: version debug: msg="{{ junos.rpc_reply }}" + +# Example to fetch device configuration +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + dest=get_config.conf + +# Example to fetch device configuration +- name: Get Device Configuration for interface + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + filter_xml="" + dest=get_config.conf + register: junos ''' import os import sys import logging +import re from jnpr.junos import Device from lxml import etree @@ -122,20 +155,25 @@ def junos_rpc(module, dev): results['check_mode'] = module.check_mode logging.info("calling RPC: {0}".format(rpc)) try: + if kwargs is None: + values = {} + elif re.search(r'\{.*\}', kwargs): + values = {k:v for k,v in re.findall(r'(\w+)\s?:\s?(\w+)',kwargs)} + else: + values = {k:v for k,v in re.findall('(\w+)=(\w+)', kwargs)} + for k,v in values.items(): + if v in ['True', 'true']: + values[k]=True + elif v in ['False', 'false']: + values[k]=False if rpc in ['get-config', 'get_config']: - if kwargs == None: - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(options={'format':args['format']}) - else: - values = {k:v for k,v in [tuple(kwargs.split('='))]} - values['format'] = args['format'] - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(options=values) + filter_xml = module.params['filter_xml'] + if filter_xml is not None: + filter_xml = etree.XML(filter_xml) + values['format'] = args['format'] + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(filter_xml, options=values) else: - if kwargs == None: - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}) - else: - values = {k:v for k,v in [tuple(kwargs.split('='))]} - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}, **values) - + results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}, **values) if module.params['dest'] is not None: with open(module.params['dest'], 'w') as confile: if isinstance(results['rpc_reply'], etree._Element): @@ -151,7 +189,6 @@ def junos_rpc(module, dev): results['changed'] = True except Exception as err: - print sys.exc_info() results['failed'] = True logging.error("unable to get rpc due to:{0}".format(err.message)) raise err @@ -167,6 +204,7 @@ def main(): passwd=dict(required=False, default=None), rpc=dict(required=True, default=None), kwargs=dict(required=False, default=None), + filter_xml=dict(required=False, default=None), format=dict(required=False, default='xml'), dest=dict(required=False, default=None)), supports_check_mode=False) @@ -175,7 +213,7 @@ def main(): # ----------- # via NETCONF # ----------- - dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd']) + dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], gather_facts=False) try: dev.open() except Exception as err: @@ -184,8 +222,6 @@ def main(): return results = junos_rpc(module, dev) - dev.facts['version_info'] = dict(dev.facts['version_info']) - results['facts'] = dev.facts dev.close() module.exit_json(**results) From eb161c7dc4770f3f3f06c810e614fb5c16d95870 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 26 Feb 2016 16:08:24 +0530 Subject: [PATCH 018/426] changes as per readthedocs page need --- library/junos_cli | 5 ++--- library/junos_rpc | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index 02b27eed..6a045bfd 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -2,7 +2,6 @@ # Copyright (c) 1999-2015, Juniper Networks Inc. # 2014, Jeremy Schulman -# 2015, Rick Sherman # # All rights reserved. # @@ -38,9 +37,9 @@ DOCUMENTATION = ''' module: junos_cli author: Damien Garros, Juniper Networks version_added: "1.2.0" -short_description: Execute CLI/RPC on device and save the output locally +short_description: Execute CLI on device and save the output locally description: - - Execute CLI/RPC on device and save the output locally on a file + - Execute CLI on device and save the output locally on a file requirements: - junos-eznc >= 1.2.2 options: diff --git a/library/junos_rpc b/library/junos_rpc index 6f8d1e00..5032ce87 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -42,7 +42,6 @@ description: - run given rpc requirements: - junos-eznc >= 1.0.0 - - junos-netconify >= 0.1.1, when using the I(console) option options: host: description: @@ -71,9 +70,9 @@ options: kwargs: description: - params need to be passed to rpc - ex: kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" - kwargs={interface_name:em0} - kwargs={interface_name:em0,media:True} + kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" + kwargs={interface_name:em0} + kwargs={interface_name:em0,media:True} filter_xml: description: - This options can be used with get-config rpc only, From 17c5d2687b6514aec3d067f9f38c6ffffc153b49 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 26 Feb 2016 16:17:10 +0530 Subject: [PATCH 019/426] added new modules to README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8604b657..6287debf 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,11 @@ Juniper Networks provides support for using Ansible to deploy devices running th ## OVERVIEW OF MODULES +- junos_get_facts — Retrieve device-specific information from the host. +- junos_rpc — To execute RPC on device and save output locally +- junos_cli — To execute CLI on device and save output locally - junos_commit — Commit candidate configuration on device. - junos_get_config — Retrieve configuration of device. -- junos_get_facts — Retrieve device-specific information from the host. - junos_install_config — Modify the configuration of a device running Junos OS. - junos_install_os — Install a Junos OS software package. - junos_rollback — Rollback configuration of device. From 95fb351882bd58558992b61d36ef017715cce6e9 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 26 Feb 2016 16:22:50 +0530 Subject: [PATCH 020/426] version upgraded --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index d3360e46..c4c1ed2e 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.2.0" -DATE = "2015-July-31" +VERSION = "1.2.1" +DATE = "2016-Feb-26" From 9cf14d11b7f79d60fd5accf4a3f4405e8cae5453 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 26 Feb 2016 16:32:55 +0530 Subject: [PATCH 021/426] version upgraded --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index c4c1ed2e..520161d0 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.2.1" +VERSION = "1.2.3" DATE = "2016-Feb-26" From 22768ee46486a19113cf054cee08cffdb8f51bc6 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 26 Feb 2016 16:35:48 +0530 Subject: [PATCH 022/426] version upgraded to 1.3.0 --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 520161d0..335a4ea7 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.2.3" +VERSION = "1.3.0" DATE = "2016-Feb-26" From d258fab79a66cd8c0ca5ed2303bc3494ec52b76e Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 26 Feb 2016 19:45:00 +0530 Subject: [PATCH 023/426] with 2.0 ansible junos_rpc was not working --- library/junos_rpc | 46 ++++++++++++++++++++++++++++++++-------------- version.py | 2 +- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index 5032ce87..6b0fccf6 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -79,10 +79,12 @@ options: to retrieve specific config format: description: - - text - configuration saved as text (curly-brace) format - - xml - configuration saved as XML + - text - configuration saved as text (curly-brace) format. + xml - configuration saved as XML. + json - configuration saved as json, supported only for devices >=14.2 + Also with this format, rpc_reply attribute can be used with results required: false - choices: ['text','xml'] + choices: ['text','xml', 'json'] default: 'xml' dest: description: @@ -109,10 +111,6 @@ EXAMPLES = ''' dest=get_interface_information.conf register=junos -# print the config -- name: version - debug: msg="{{ junos.rpc_reply }}" - # Example to fetch device configuration - name: Get Device Configuration junos_rpc: @@ -128,6 +126,25 @@ EXAMPLES = ''' filter_xml="" dest=get_config.conf register: junos + +# Example to fetch configuration in json for >=14.2 +- name: Get Device Configuration + hosts: all + roles: + - Juniper.junos + connection: local + gather_facts: no + tasks: + - name: Get rpc run + junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + kwargs={interface_name:em0,media:True} + dest=get_interface_information.conf + register: junos + + - name: Print configuration + debug: msg="{{ junos.rpc_reply }}" ''' import os @@ -170,21 +187,22 @@ def junos_rpc(module, dev): if filter_xml is not None: filter_xml = etree.XML(filter_xml) values['format'] = args['format'] - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))(filter_xml, options=values) + rpc_reply = getattr(dev.rpc, rpc.replace('-','_'))(filter_xml, options=values) else: - results['rpc_reply'] = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}, **values) + rpc_reply = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}, **values) if module.params['dest'] is not None: with open(module.params['dest'], 'w') as confile: - if isinstance(results['rpc_reply'], etree._Element): + if isinstance(rpc_reply, etree._Element): if args.get('format') == 'text': - confile.write((results['rpc_reply']).text) + confile.write(rpc_reply.text) else: - confile.write(etree.tostring(results['rpc_reply'])) + confile.write(etree.tostring(rpc_reply)) else: if args.get('format') == 'json': - confile.write(str(results['rpc_reply'])) + results['rpc_reply'] = rpc_reply + confile.write(str(rpc_reply)) else: - confile.write(results['rpc_reply']) + confile.write(rpc_reply) results['changed'] = True except Exception as err: diff --git a/version.py b/version.py index 335a4ea7..83aac6f6 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.3.0" +VERSION = "1.3.1" DATE = "2016-Feb-26" From 3a19d5962f6eeaba2e38203b05523470d2b0c96d Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 26 Feb 2016 19:56:42 +0530 Subject: [PATCH 024/426] added example for json format for junos >=14.2 --- library/junos_rpc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/junos_rpc b/library/junos_rpc index 6b0fccf6..406faeba 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -128,6 +128,7 @@ EXAMPLES = ''' register: junos # Example to fetch configuration in json for >=14.2 +# and use it with rpc_reply - name: Get Device Configuration hosts: all roles: @@ -135,11 +136,12 @@ EXAMPLES = ''' connection: local gather_facts: no tasks: - - name: Get rpc run + - name: Get interface information junos_rpc: host={{ inventory_hostname }} rpc=get-interface-information kwargs={interface_name:em0,media:True} + format=json dest=get_interface_information.conf register: junos From 1a8ec66747f0b8829d48c8286da9c55e9e21cfbc Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Sun, 28 Feb 2016 23:18:04 -0800 Subject: [PATCH 025/426] Fix Shebang for junos_rpc --- library/junos_rpc | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index 406faeba..4c166833 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -1,7 +1,7 @@ -#!/usr/bin/python +#!/usr/bin/env python # Copyright (c) 1999-2016, Juniper Networks Inc. -# 2016, Nitin Kumar +# 2016, Nitin Kumar # # All rights reserved. # @@ -70,8 +70,8 @@ options: kwargs: description: - params need to be passed to rpc - kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" - kwargs={interface_name:em0} + kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" + kwargs={interface_name:em0} kwargs={interface_name:em0,media:True} filter_xml: description: @@ -79,8 +79,8 @@ options: to retrieve specific config format: description: - - text - configuration saved as text (curly-brace) format. - xml - configuration saved as XML. + - text - configuration saved as text (curly-brace) format. + xml - configuration saved as XML. json - configuration saved as json, supported only for devices >=14.2 Also with this format, rpc_reply attribute can be used with results required: false @@ -97,13 +97,13 @@ options: EXAMPLES = ''' # retrieve rpc response using NETCONF -- junos_rpc: +- junos_rpc: host={{ inventory_hostname }} rpc=get-interface-information dest=get_interface_information.conf register=junos -- junos_rpc: +- junos_rpc: host={{ inventory_hostname }} rpc=get-interface-information kwargs="interface_name=em0" @@ -119,7 +119,7 @@ EXAMPLES = ''' dest=get_config.conf # Example to fetch device configuration -- name: Get Device Configuration for interface +- name: Get Device Configuration for interface junos_rpc: host={{ inventory_hostname }} rpc=get-config @@ -136,7 +136,7 @@ EXAMPLES = ''' connection: local gather_facts: no tasks: - - name: Get interface information + - name: Get interface information junos_rpc: host={{ inventory_hostname }} rpc=get-interface-information @@ -167,8 +167,8 @@ def junos_rpc(module, dev): results = {} kwargs = module.params['kwargs'] rpc = module.params['rpc'] - results['rpc'] = rpc - results['kwargs'] = kwargs + results['rpc'] = rpc + results['kwargs'] = kwargs results['changed'] = False results['check_mode'] = module.check_mode logging.info("calling RPC: {0}".format(rpc)) @@ -205,7 +205,7 @@ def junos_rpc(module, dev): confile.write(str(rpc_reply)) else: confile.write(rpc_reply) - + results['changed'] = True except Exception as err: results['failed'] = True From 894fca531465c571e6aece17e7467a4390ca2396 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Sun, 28 Feb 2016 23:19:12 -0800 Subject: [PATCH 026/426] Add support for port in junos_rpc --- library/junos_rpc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/junos_rpc b/library/junos_rpc index 4c166833..3090b68f 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -57,6 +57,11 @@ options: - Login password required: false default: assumes ssh-key active + port: + description: + - TCP port number to use when connecting to the device + required: false + default: 830 logfile: description: - Path on the local server where the progress status is logged @@ -221,6 +226,7 @@ def main(): logfile=dict(required=False, default=None), user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), + port=dict(required=False, default=830), rpc=dict(required=True, default=None), kwargs=dict(required=False, default=None), filter_xml=dict(required=False, default=None), @@ -232,7 +238,8 @@ def main(): # ----------- # via NETCONF # ----------- - dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], gather_facts=False) + dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], + port=m_args['port'], gather_facts=False) try: dev.open() except Exception as err: From 053c2a88c9619c4bfcdceb4d685f2af5bcab1de4 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 29 Feb 2016 23:26:28 +0530 Subject: [PATCH 027/426] regular expression changes for handling lot more scenarios --- library/junos_rpc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index 406faeba..f271a2a6 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -176,9 +176,9 @@ def junos_rpc(module, dev): if kwargs is None: values = {} elif re.search(r'\{.*\}', kwargs): - values = {k:v for k,v in re.findall(r'(\w+)\s?:\s?(\w+)',kwargs)} + values = {k:v for k,v in re.findall(r'([\w-]+)\s?:\s?\'?\"?([\w\.-]+)\'?\"?',kwargs)} else: - values = {k:v for k,v in re.findall('(\w+)=(\w+)', kwargs)} + values = {k:v for k,v in re.findall('([\w-]+)=([\w\.-]+)', kwargs)} for k,v in values.items(): if v in ['True', 'true']: values[k]=True From 7d6b116e02e6237d65fa86aa7b2368d8e3bed168 Mon Sep 17 00:00:00 2001 From: Stacy Smith Date: Tue, 1 Mar 2016 11:08:42 -0700 Subject: [PATCH 028/426] Address #118 Explicitly set boolean arguments to be type='bool' so Ansible 2.x doesn't complain about the values not matching the choices. --- library/junos_install_config | 4 ++-- library/junos_install_os | 4 ++-- library/junos_shutdown | 2 +- library/junos_srx_cluster | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index e155c0f0..e66e367c 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -395,8 +395,8 @@ def main(): passwd=dict(required=False, default=None), console=dict(required=False, default=None), file=dict(required=True), - overwrite=dict(required=False, choices=BOOLEANS, default=False), - replace=dict(required=False, choices=BOOLEANS, default=False), + overwrite=dict(required=False, type='bool', choices=BOOLEANS, default=False), + replace=dict(required=False, type='bool', choices=BOOLEANS, default=False), logfile=dict(required=False, default=None), diffs_file=dict(required=False, default=None), savedir=dict(required=False), diff --git a/library/junos_install_os b/library/junos_install_os index c9814ef0..fbe994ea 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -229,8 +229,8 @@ def main(): passwd=dict(required=False, default=None), version=dict(required=False, default=None), logfile=dict(required=False, default=None), - no_copy=dict(required=False, choices=BOOLEANS, default=False), - reboot=dict(required=False, choices=BOOLEANS, default=True), + no_copy=dict(required=False, type='bool', choices=BOOLEANS, default=False), + reboot=dict(required=False, type='bool', choices=BOOLEANS, default=True), reboot_pause=dict(required=False, type='int', default=10), port=dict(required=False, default=830) ), diff --git a/library/junos_shutdown b/library/junos_shutdown index 329ef945..599b06e1 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -106,7 +106,7 @@ def main(): host=dict(required=True, default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), - reboot=dict(required=False, choices=BOOLEANS, default=False), + reboot=dict(required=False, type='bool', choices=BOOLEANS, default=False), port=dict(required=False, default=830) ), supports_check_mode=False) diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 0b2645eb..78c86eea 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -129,7 +129,7 @@ def main(): passwd=dict(required=False, default=None), port=dict(required=False, default=830), console=dict(required=False, default=None), # param to netconify - cluster_enable=dict(required=True, choices=BOOLEANS, default=None), + cluster_enable=dict(required=True, type='bool', choices=BOOLEANS, default=None), cluster_id=dict(required=False, default=None), node=dict(required=False, default=None), logfile=dict(required=False, default=None)), From 7bfabf0084d335326a93c967d78be199655397bb Mon Sep 17 00:00:00 2001 From: Stacy Smith Date: Mon, 7 Mar 2016 20:38:19 -0700 Subject: [PATCH 029/426] Addess Issue #120 The filter argument string was improperly converted into XML. Rather than building an XML hierarchy of elements, a list of elements was created. This fix creates an XML hierarchy. --- library/junos_get_config | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/junos_get_config b/library/junos_get_config index 13bad407..5e562550 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -110,7 +110,6 @@ EXAMPLES = ''' from distutils.version import LooseVersion import logging from lxml import etree -from lxml.builder import E try: from jnpr.junos import Device @@ -170,12 +169,13 @@ def main(): if args['filter']: flist = args['filter'].split('/') if flist[0] == 'configuration': - filter_xml = E(flist.pop(0)) + filter_xml = etree.Element(flist.pop(0)) else: - filter_xml = E('configuration') + filter_xml = etree.Element('configuration') + current_element = filter_xml for f in flist: - filter_xml.append(E(f)) + current_element = etree.SubElement(current_element,f) logging.info("Getting config with filter={0}".format(etree.tostring(filter_xml))) From 1d22aea2f9b8270d31a69abddc52f3c6c119616f Mon Sep 17 00:00:00 2001 From: jedelman8 Date: Wed, 9 Mar 2016 08:26:41 -0800 Subject: [PATCH 030/426] commit for junos_get_table --- library/junos_get_table.py | 240 +++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 library/junos_get_table.py diff --git a/library/junos_get_table.py b/library/junos_get_table.py new file mode 100644 index 00000000..fccf6873 --- /dev/null +++ b/library/junos_get_table.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python + +# Copyright 2016 Jason Edelman +# Network to Code, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DOCUMENTATION = ''' +--- +module: junos_get_table +author: Jason Edelman (@jedelman8) +version_added: "1.9.0" +short_description: Retrieve data from a Junos device using Tables/Views +description: + - Retrieve data from a Junos device using Tables/Views +requirements: + - junos-eznc >= 1.2.2 +options: + host: + description: + - Set to {{ inventory_hostname }} + required: true + user: + description: + - Login username + required: false + default: $USER + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + logfile: + description: + - Path on the local server where the progress status is logged + for debugging purposes + required: false + default: None + + port: + description: + - TCP port number to use when connecting to the device + required: false + default: 830 + table: + description: + - Name of PyEZ Table + required: false + default: None + file: + description: + - YAML file that has the table specified in table parameter + required: false + default: None + path: + description: + - Path of location of the YAML file + required: false + default: op directory in jnpr.junos.op + response_type: + description: + - Option to change how data is returned from the + module. Either list of dictionaries or the + Juniper PyEZ default (list of tuples, which becomes + lists by the time it gets to Ansible) + required: false + options: ['list_of_dicts', 'juniper_items'] + default: list_of_dicts + +''' + +EXAMPLES = ''' +# GET NEIGHBOR INFO USING STD LLDP TABLE +- junos_get_table: table=LLDPNeighborTable file=lldp.yml host={{ inventory_hostname }} user={{ un }} passwd={{ pwd }} + +# GET NEIGHBOR INFO USING CUSTOM LLDP TABLE IN CUSTOM PATH +- junos_get_table: table=NTCNeighborTable path=tables/ file=ntclldp.yaml host={{ inventory_hostname }} user={{ un }} passwd={{ pwd }} + +''' + +RETURN = ''' + +resource: + description: Dictionary of facts + returned: always + type: list of dictionaries or list of tuples (default items from PyEZ) + sample: + [ + { + "neighbor_interface": "fxp0", + "local_interface": "fxp0", + "neighbor": "vmx2" + }, + { + "neighbor_interface": "ge-0/0/2", + "local_interface": "ge-0/0/2", + "neighbor": "vmx2" + }, + { + "neighbor_interface": "fxp0", + "local_interface": "fxp0", + "neighbor": "vmx3" + } + ] + +''' + +from distutils.version import LooseVersion +import logging +import os +from lxml import etree +from lxml.builder import E +from jnpr.junos.factory.factory_loader import FactoryLoader +import yaml + +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + import jnpr.junos.op as tables_dir + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + HAS_PYEZ = False + else: + HAS_PYEZ = True +except ImportError: + HAS_PYEZ = False + +TABLE_PATH = os.path.dirname(os.path.abspath(tables_dir.__file__)) +CHOICES = ['list_of_dicts', 'juniper_items'] + + +def juniper_items_to_list_of_dicts(data): + """Convert Juniper PyEZ Table/View items to list of dictionaries + """ + + list_of_resources = [] + # data.items() is a list of tuples + for table_key, table_fields in data.items(): + # sample: + # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), + # ('neighbor', 'vmx2')] + # table_key - element 0 is the key from the Table - not using at all + # table_fields - element 1 is also a list of uples + temp = {} + for normalized_key, normalized_value in table_fields: + # calling it normalized value because + # YOU/WE created the keys + temp[normalized_key] = normalized_value + list_of_resources.append(temp) + return list_of_resources + + +def main(): + + module = AnsibleModule( + argument_spec=dict(host=dict(required=True, + default=None), # host or ipaddr + user=dict(required=False, + default=os.getenv('USER')), + passwd=dict(required=False, default=None), + port=dict(required=False, default=830), + logfile=dict(required=False, default=None), + file=dict(required=True, default=None), + path=dict(required=False, default=TABLE_PATH), + table=dict(required=True, default=None), + response_type=dict(choices=CHOICES, + default='list_of_dicts') + ), + supports_check_mode=False) + + if not HAS_PYEZ: + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + + args = module.params + + logfile = args['logfile'] + response_type = args['response_type'] + + if not args['file'].endswith('.yml'): + module.fail_json(msg='file must end with .yml extension') + + if logfile is not None: + logging.basicConfig(filename=logfile, level=logging.INFO, + format='%(asctime)s:%(name)s:%(message)s') + logging.getLogger().name = 'CONFIG:' + args['host'] + + logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], + args['host'], + args['port'])) + try: + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], gather_facts=False).open() + except Exception as err: + msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) + logging.error(msg) + module.fail_json(msg=msg) + # --- UNREACHABLE --- + + resource = [] + try: + file_name = os.path.join(args['path'], args['file']) + try: + globals().update(FactoryLoader().load( + yaml.load(open(file_name).read()))) + except IOError as err: + module.fail_json(msg='Unable to find file: {0}'.format(file_name)) + logging.info("Getting data from device") + try: + data = globals()[args['table']](dev) + except KeyError: + module.fail_json(msg='Unable to find Table in provided yaml file', + table=args['table'], file=file_name) + data.get() + if response_type == 'list_of_dicts': + resource = juniper_items_to_list_of_dicts(data) + else: + resource = data.items() + except Exception as err: + msg = 'Uncaught exception - please report: {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + dev.close() + + module.exit_json(resource=resource) + +from ansible.module_utils.basic import * + +if __name__ == "__main__": + main() From 9638b0e5f959b9f3b1f753b245968908611338b0 Mon Sep 17 00:00:00 2001 From: Kevin Kirsche Date: Thu, 31 Mar 2016 10:15:34 -0400 Subject: [PATCH 031/426] Update README.md Update the installation README documentation to represent what occurs when it's currently installed via ansible galaxy (reflect updated version number for example) --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6287debf..1309f478 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,10 @@ To download the junos role to the Ansible server, execute the ansible-galaxy ins ``` [root@ansible-cm]# ansible-galaxy install Juniper.junos -downloading role 'junos', owned by Juniper -no version specified, installing 1.0.0 -- downloading role from -https://github.com/Juniper/ansible-junos-stdlib/archive/1.0.0.tar.gz -- extracting Juniper.junos to /etc/ansible/roles/Juniper.junos -Juniper.junos was installed successfully +- downloading role 'junos', owned by Juniper +- downloading role from https://github.com/Juniper/ansible-junos-stdlib/archive/1.3.1.tar.gz +- extracting Juniper.junos to /usr/local/etc/ansible/roles/Juniper.junos +- Juniper.junos was installed successfully ``` ### Git clone From 6e3d21006f695df9794187054f032074398229a0 Mon Sep 17 00:00:00 2001 From: jedelman8 Date: Thu, 7 Apr 2016 09:08:46 -0700 Subject: [PATCH 032/426] fix for nested tables --- library/junos_get_table.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/junos_get_table.py b/library/junos_get_table.py index fccf6873..ed5a69ba 100644 --- a/library/junos_get_table.py +++ b/library/junos_get_table.py @@ -152,8 +152,9 @@ def juniper_items_to_list_of_dicts(data): # table_fields - element 1 is also a list of uples temp = {} for normalized_key, normalized_value in table_fields: - # calling it normalized value because - # YOU/WE created the keys + # calling it normalized value because YOU/WE created the keys + if not isinstance(normalized_value, (str, list, int, dict)): + normalized_value = juniper_items_to_list_of_dicts(normalized_value) temp[normalized_key] = normalized_value list_of_resources.append(temp) return list_of_resources From b9ad3649c61cd348328a3103af5bd812fea69c1d Mon Sep 17 00:00:00 2001 From: jedelman8 Date: Thu, 7 Apr 2016 09:27:36 -0700 Subject: [PATCH 033/426] fix for None values in Tables --- library/junos_get_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_get_table.py b/library/junos_get_table.py index ed5a69ba..07e7ee98 100644 --- a/library/junos_get_table.py +++ b/library/junos_get_table.py @@ -153,7 +153,7 @@ def juniper_items_to_list_of_dicts(data): temp = {} for normalized_key, normalized_value in table_fields: # calling it normalized value because YOU/WE created the keys - if not isinstance(normalized_value, (str, list, int, dict)): + if normalized_value and not isinstance(normalized_value, (str, list, int, dict)): normalized_value = juniper_items_to_list_of_dicts(normalized_value) temp[normalized_key] = normalized_value list_of_resources.append(temp) From e545178a5345d357f94cc23eb162ff2464fa143a Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 8 Apr 2016 15:31:46 -0700 Subject: [PATCH 034/426] Add initial version of ping module --- library/junos_ping | 239 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 library/junos_ping diff --git a/library/junos_ping b/library/junos_ping new file mode 100644 index 00000000..a2c076ed --- /dev/null +++ b/library/junos_ping @@ -0,0 +1,239 @@ +#!/usr/bin/env python + +# Copyright (c) 1999-2016, Juniper Networks Inc. +# 2016, Damien Garros +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +DOCUMENTATION = ''' +--- +module: junos_ping +author: Damien Garros, Juniper Networks +version_added: "1.3.1" +short_description: execute ping on junos devices +description: + - execute ping on junos devices +requirements: + - junos-eznc >= 1.2.2 +options: + host: + description: + - Set to {{ inventory_hostname }} + required: true + user: + description: + - Login username + required: false + default: $USER + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + port: + description: + - TCP port number to use when connecting to the device + required: false + default: 830 + dest_ip: + description: + - Destination ip + required: true + source_ip: + description: + - Source IP used to send the ping + required: false + ttl: + description: + - Number of device allowed between source and destination + required: false + routing_instance: + description: + - Name of the routing instance to use to send the ping + required: false + source_ip: + description: + - source IP + required: false + count: + description: + - Number of packet to send + required: false + default: 5 +''' + +EXAMPLES = ''' +# Simple example + tasks: + - name: "Execute ping peer" + junos_ping: + host={{ junos_host }} + port={{ netconf_port }} + user={{ ansible_ssh_user }} + passwd={{ ansible_ssh_pass }} + dest_ip=8.8.8.8 + +# Using loop and more parameters + tasks: + - name: "Execute ping peer" + junos_ping: + host={{ junos_host }} + port={{ netconf_port }} + user={{ ansible_ssh_user }} + passwd={{ ansible_ssh_pass }} + dest_ip={{ item.peer_ip }} + source_ip={{ item.local_ip }} + ttl=1 + with_items: "{{underlay.neighbors}}" +''' + +import os +import sys +import logging +import re +from lxml import etree +from distutils.version import LooseVersion +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + HAS_PYEZ = False + else: + HAS_PYEZ = True +except ImportError: + HAS_PYEZ = False + + +def main(): + module = AnsibleModule( + argument_spec=dict( + host=dict(required=True), + logfile=dict(required=False, default=None), + user=dict(required=False, default=os.getenv('USER')), + passwd=dict(required=False, default=None), + port=dict(required=False, default=830), + dest_ip=dict(required=True, default=None), + source_ip=dict(required=False, default=None), + rapid=dict(required=False, default=True), + routing_instance=dict(required=False, default=None), + ttl=dict(required=False, default=None), + count=dict(required=False, default='5') + ), + supports_check_mode=False + ) + + if not HAS_PYEZ: + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + + results = {} + m_args = module.params + + # Open connection to device + dev = Device( + m_args['host'], + user=m_args['user'], + passwd=m_args['passwd'], + port=m_args['port'], + gather_facts=False) + + try: + dev.open() + except Exception as err: + msg = 'Unable to connect to {0}: {1}'.format(m_args['host'], str(err)) + module.fail_json(msg=msg) + return + + results['dest_ip'] = m_args['dest_ip'] + results['count'] = m_args['count'] + results['rapid'] = m_args['rapid'] + + results['changed'] = False + + # Prepare parameters + ping_params = dict( + host=m_args['dest_ip'], + count=m_args['count'], + rapid=m_args['rapid']) + + if m_args['source_ip'] is not None: + ping_params['source'] = m_args['source_ip'] + results['source_ip'] = m_args['source_ip'] + + if m_args['routing_instance'] is not None: + ping_params['routing_instance'] = m_args['routing_instance'] + results['routing_instance'] = m_args['routing_instance'] + + if m_args['ttl'] is not None: + ping_params['ttl'] = m_args['ttl'] + results['ttl'] = m_args['ttl'] + + try: + + # Execute Ping + rpc_reply = dev.rpc.ping(**ping_params) + + # results['xml_reply'] = etree.tostring(rpc_reply) + + # Try to Find probe summary + probe_summary = rpc_reply.find('probe-results-summary') + if probe_summary is None: + results['failed'] = True + + error_message = rpc_reply.find('rpc-error/error-message').text + if error_message is not None: + msg = error_message.strip() + + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + # Extract packet loss + packet_loss = rpc_reply.xpath("//ping-results/probe-results-summary/packet-loss")[0].text.strip() + packet_sent = rpc_reply.xpath("//ping-results/probe-results-summary/probes-sent")[0].text.strip() + responses_received = rpc_reply.xpath("//ping-results/probe-results-summary/responses-received")[0].text.strip() + + if int(packet_loss) != 0: + results['failed'] = True + msg = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packet_sent), str(responses_received)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + except Exception as err: + results['failed'] = True + logging.error("unable to execute ping due to:{0}".format(err.message)) + raise err + + dev.close() + module.exit_json(**results) + +from ansible.module_utils.basic import * + +main() From 92a8f9009235d2cf625531948642c2059cb997c1 Mon Sep 17 00:00:00 2001 From: Damien Date: Sun, 10 Apr 2016 23:12:57 -0700 Subject: [PATCH 035/426] Initial commit --- library/junos_jsnapy | 169 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 library/junos_jsnapy diff --git a/library/junos_jsnapy b/library/junos_jsnapy new file mode 100644 index 00000000..65715496 --- /dev/null +++ b/library/junos_jsnapy @@ -0,0 +1,169 @@ +#!/usr/bin/env python + +# Copyright (c) 1999-2015, Juniper Networks Inc. +# 2016, Damien Garros +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +DOCUMENTATION = ''' +--- +module: junos_cli +author: Damien Garros, Juniper Networks +version_added: "1.2.0" +short_description: Execute tests with Jsnapy +description: + - Execute tests with Jsnapy +requirements: + - junos-eznc >= 1.2.2 +options: + host: + description: + - Set to {{ inventory_hostname }} + required: true + user: + description: + - Login username + required: false + default: $USER + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + port: + description: + - TCP port number to use when connecting to the device + required: false + default: 830 +''' + +EXAMPLES = ''' + +''' +from distutils.version import LooseVersion +import logging +import os +from lxml import etree +from lxml.builder import E +from jnpr.jsnapy import SnapAdmin + +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + HAS_PYEZ = False + else: + HAS_PYEZ = True +except ImportError: + HAS_PYEZ = False + + +def main(): + + module = AnsibleModule( + argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr + user=dict(required=False, default=os.getenv('USER')), + passwd=dict(required=False, default=None), + port=dict(required=False, default=830), + test=dict(required=True, default=None), + ), + supports_check_mode=True) + + if not HAS_PYEZ: + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + + m_args = module.params + + results = {} + results['test'] = m_args['test'] + + try: + dev = Device(m_args['host'], + user=m_args['user'], + password=m_args['passwd'], + port=m_args['port'], + gather_facts=False).open() + + except Exception as err: + msg = 'Unable to connect to {0}: {1}'.format(args['host'], str(err)) + logging.error(msg) + module.fail_json(msg=msg) + # --- UNREACHABLE --- + + try: + js = SnapAdmin() + # config_file = os.path.abspath(m_args['test']) + config_file = '/Users/dgarros/projects/lab-evpn-vxlan/jsnap_tests/mytest.yml' + + try: + js.snap(config_file, "mytestforjsnap", dev=dev) + except Exception as err: + msg = 'Unable to get snap from device - {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + try: + snapvalue = js.check(config_file, "mytestforjsnap") + except Exception as err: + msg = 'Exception during check - {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + for snapcheck in snapvalue: + results['snapcheck.result'] = snapcheck.result + + # results['snapresult'] = snapcheck.result + + # if snapvalue[].no_failed != 0: + # msg = 'Tests failed' + # logging.error(msg) + # dev.close() + # module.fail_json(msg=msg) + + except (ValueError, RpcError) as err: + msg = 'Unable execute Test' + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + except Exception as err: + msg = 'Uncaught exception - please report: {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + dev.close() + module.exit_json(**results) + +from ansible.module_utils.basic import * +main() From 8d4783f1f87c312ffa6ba15f9c925c4835e700aa Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 12 Apr 2016 00:39:37 -0700 Subject: [PATCH 036/426] Add support for interface parameter --- library/junos_ping | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/junos_ping b/library/junos_ping index a2c076ed..f01faf83 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -82,6 +82,10 @@ options: description: - source IP required: false + interface: + description: + - Interface used to send traffic out + required: false count: description: - Number of packet to send @@ -141,6 +145,7 @@ def main(): port=dict(required=False, default=830), dest_ip=dict(required=True, default=None), source_ip=dict(required=False, default=None), + interface=dict(required=False, default=None), rapid=dict(required=False, default=True), routing_instance=dict(required=False, default=None), ttl=dict(required=False, default=None), @@ -190,6 +195,10 @@ def main(): ping_params['routing_instance'] = m_args['routing_instance'] results['routing_instance'] = m_args['routing_instance'] + if m_args['interface'] is not None: + ping_params['interface'] = m_args['interface'] + results['interface'] = m_args['interface'] + if m_args['ttl'] is not None: ping_params['ttl'] = m_args['ttl'] results['ttl'] = m_args['ttl'] From e8af0f2a5fa86dd201b984a60d1e4e19b9e064d1 Mon Sep 17 00:00:00 2001 From: Damien Date: Thu, 14 Apr 2016 17:12:42 -0700 Subject: [PATCH 037/426] Replace jsnap module with Roslan's --- library/junos_jsnapy | 169 ----------------------------- library/junos_jsnapy.py | 232 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 169 deletions(-) delete mode 100644 library/junos_jsnapy create mode 100644 library/junos_jsnapy.py diff --git a/library/junos_jsnapy b/library/junos_jsnapy deleted file mode 100644 index 65715496..00000000 --- a/library/junos_jsnapy +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2015, Juniper Networks Inc. -# 2016, Damien Garros -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_cli -author: Damien Garros, Juniper Networks -version_added: "1.2.0" -short_description: Execute tests with Jsnapy -description: - - Execute tests with Jsnapy -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - TCP port number to use when connecting to the device - required: false - default: 830 -''' - -EXAMPLES = ''' - -''' -from distutils.version import LooseVersion -import logging -import os -from lxml import etree -from lxml.builder import E -from jnpr.jsnapy import SnapAdmin - -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), - port=dict(required=False, default=830), - test=dict(required=True, default=None), - ), - supports_check_mode=True) - - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - - m_args = module.params - - results = {} - results['test'] = m_args['test'] - - try: - dev = Device(m_args['host'], - user=m_args['user'], - password=m_args['passwd'], - port=m_args['port'], - gather_facts=False).open() - - except Exception as err: - msg = 'Unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - try: - js = SnapAdmin() - # config_file = os.path.abspath(m_args['test']) - config_file = '/Users/dgarros/projects/lab-evpn-vxlan/jsnap_tests/mytest.yml' - - try: - js.snap(config_file, "mytestforjsnap", dev=dev) - except Exception as err: - msg = 'Unable to get snap from device - {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - try: - snapvalue = js.check(config_file, "mytestforjsnap") - except Exception as err: - msg = 'Exception during check - {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - for snapcheck in snapvalue: - results['snapcheck.result'] = snapcheck.result - - # results['snapresult'] = snapcheck.result - - # if snapvalue[].no_failed != 0: - # msg = 'Tests failed' - # logging.error(msg) - # dev.close() - # module.fail_json(msg=msg) - - except (ValueError, RpcError) as err: - msg = 'Unable execute Test' - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - module.exit_json(**results) - -from ansible.module_utils.basic import * -main() diff --git a/library/junos_jsnapy.py b/library/junos_jsnapy.py new file mode 100644 index 00000000..ef4f0de7 --- /dev/null +++ b/library/junos_jsnapy.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +#Copyright (c) 1999-2015, Juniper Networks Inc. +# 2016, Roslan Zaki +# +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +DOCUMENTATION = ''' +--- +module: junos_jsnapy +author: Roslan Zaki, Juniper Networks +version_added: "1.0.0" +version_control: + 13-Apr-2016 v1.0.0 - Basic working model + +Short_description: Integrate JSnapy to ansible +description: + - Execute JSnapy module from Ansible +requirements: + - junos-eznc >= 1.2.2 +options: + host: + description: + - Set to {{ inventory_hostname }} + required: true + user: + description: + - Login username + required: false + default: $USER + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + port: + description: + - TCP port number to use when connecting to the device + required: false + default: 830 + logfile: + description: + - Path on the local server where the progress status is logged + for debugging purposes + required: false + default: None + dest: + description: + - Path to the local server directory where configuration will + be saved. + required: true + default: None + dir: + description: + - Path for the JSNAPy yaml configuration file + required: false + default: '/etc/jsnapy/testfiles' + action: + description: + Possible actions available + - jsnapcheck + - check + - snap_pre + - snap_post + required: True + default: None + config_file: + description: + - The YAML configuration file for the JSNAPy tests + required: true + default: None +''' + +EXAMPLES = ''' + - name: JUNOS Post Checklist + junos_jsnapy: + host: "{{ inventory_hostname}}" + passwd: "{{ tm1_password }}" + action: "snap_post" + config_file: "first_test.yml" + logfile: "migration_post.log" + register: jsnapy + + - name: Debug jsnapy + debug: msg="{{jsnapy}}" + +''' +from distutils.version import LooseVersion +import logging +from lxml import etree +from lxml.builder import E + +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + from jnpr.jsnapy import SnapAdmin + import os + import time + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + HAS_PYEZ = False + else: + HAS_PYEZ = True +except ImportError: + HAS_PYEZ = False + + +def jsnap_selection(dev, module): + + args = module.params + action = args['action'] + config_file = args['config_file'] + dir = args['dir'] + + config_data = os.path.join(dir, config_file) + + results = dict() + js = SnapAdmin() + + if action == 'snapcheck': + snapValue = js.snapcheck(data=config_data, dev=dev) + elif action == 'snap_pre': + snapValue = js.snap(data=config_data, dev=dev, file_name='PRE') + elif action == 'snap_post': + snapValue = js.snap(data=config_data, dev=dev, file_name='POST') + elif action == 'check': + snapValue = js.check(data=config_data, dev=dev, pre_file='PRE', post_file='POST') + + if isinstance(snapValue, (list)): + for snapCheck in snapValue: + router = snapCheck.device + results['router'] = router + results['final_result'] = snapCheck.result + results['total_passed'] = snapCheck.no_passed + results['total_failed'] = snapCheck.no_failed + total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) + results['total_tests'] = total_test + percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) + results['passPercentage'] = percentagePassed + + return results + +def main(): + + module = AnsibleModule( + argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr + user=dict(required=False, default=os.getenv('USER')), + passwd=dict(required=False, default=None), + port=dict(required=False, default=830), + logfile=dict(required=False, default=None), + config_file=dict(required=False, default=None), + dir=dict(required=False, default='/etc/jsnapy/testfiles'), + action=dict(required=False, choices=['check', 'snapcheck', 'snap_pre', 'snap_post'], default=None) + ), + supports_check_mode=False) + + if not HAS_PYEZ: + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + + args = module.params + results = {} + + logfile = args['logfile'] + if logfile is not None: + logging.basicConfig(filename=logfile, level=logging.INFO, + format='%(asctime)s:%(name)s:%(message)s') + logging.getLogger().name = 'JSnapy:' + args['host'] + + logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) + + try: + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], gather_facts=False).open() + except Exception as err: + msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) + logging.error(msg) + module.fail_json(msg=msg) + # --- UNREACHABLE --- + + try: + logging.info("Main program: ") + results = jsnap_selection(dev, module) + + if 'total_failed' in results and int(results['total_failed']) != 0: + msg = 'Test Failed: Executed: {0}, Passed {1}, Failed {2}'.format( + str(results['total_tests']), + str(results['total_passed']), + str(results['total_failed']) + ) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + except Exception as err: + msg = 'Uncaught exception - please report: {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + dev.close() + + module.exit_json(**results) + +from ansible.module_utils.basic import * +main() From c5cfd4541050e58de15880701bd4c8ae130cfd01 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 18 Apr 2016 15:06:10 +0530 Subject: [PATCH 038/426] changing file name --- library/{junos_jsnapy.py => junos_jsnapy} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename library/{junos_jsnapy.py => junos_jsnapy} (100%) diff --git a/library/junos_jsnapy.py b/library/junos_jsnapy similarity index 100% rename from library/junos_jsnapy.py rename to library/junos_jsnapy From 234a2ec54854538903f18ef42e2e28d324607c5d Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 18 Apr 2016 15:28:25 +0530 Subject: [PATCH 039/426] added test_files option --- library/junos_jsnapy | 477 ++++++++++++++++++++++--------------------- 1 file changed, 245 insertions(+), 232 deletions(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index ef4f0de7..bf7ce313 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -1,232 +1,245 @@ -#!/usr/bin/env python -#Copyright (c) 1999-2015, Juniper Networks Inc. -# 2016, Roslan Zaki -# -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_jsnapy -author: Roslan Zaki, Juniper Networks -version_added: "1.0.0" -version_control: - 13-Apr-2016 v1.0.0 - Basic working model - -Short_description: Integrate JSnapy to ansible -description: - - Execute JSnapy module from Ansible -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - TCP port number to use when connecting to the device - required: false - default: 830 - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - dest: - description: - - Path to the local server directory where configuration will - be saved. - required: true - default: None - dir: - description: - - Path for the JSNAPy yaml configuration file - required: false - default: '/etc/jsnapy/testfiles' - action: - description: - Possible actions available - - jsnapcheck - - check - - snap_pre - - snap_post - required: True - default: None - config_file: - description: - - The YAML configuration file for the JSNAPy tests - required: true - default: None -''' - -EXAMPLES = ''' - - name: JUNOS Post Checklist - junos_jsnapy: - host: "{{ inventory_hostname}}" - passwd: "{{ tm1_password }}" - action: "snap_post" - config_file: "first_test.yml" - logfile: "migration_post.log" - register: jsnapy - - - name: Debug jsnapy - debug: msg="{{jsnapy}}" - -''' -from distutils.version import LooseVersion -import logging -from lxml import etree -from lxml.builder import E - -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - from jnpr.jsnapy import SnapAdmin - import os - import time - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - -def jsnap_selection(dev, module): - - args = module.params - action = args['action'] - config_file = args['config_file'] - dir = args['dir'] - - config_data = os.path.join(dir, config_file) - - results = dict() - js = SnapAdmin() - - if action == 'snapcheck': - snapValue = js.snapcheck(data=config_data, dev=dev) - elif action == 'snap_pre': - snapValue = js.snap(data=config_data, dev=dev, file_name='PRE') - elif action == 'snap_post': - snapValue = js.snap(data=config_data, dev=dev, file_name='POST') - elif action == 'check': - snapValue = js.check(data=config_data, dev=dev, pre_file='PRE', post_file='POST') - - if isinstance(snapValue, (list)): - for snapCheck in snapValue: - router = snapCheck.device - results['router'] = router - results['final_result'] = snapCheck.result - results['total_passed'] = snapCheck.no_passed - results['total_failed'] = snapCheck.no_failed - total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) - results['total_tests'] = total_test - percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) - results['passPercentage'] = percentagePassed - - return results - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), - port=dict(required=False, default=830), - logfile=dict(required=False, default=None), - config_file=dict(required=False, default=None), - dir=dict(required=False, default='/etc/jsnapy/testfiles'), - action=dict(required=False, choices=['check', 'snapcheck', 'snap_pre', 'snap_post'], default=None) - ), - supports_check_mode=False) - - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - - args = module.params - results = {} - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'JSnapy:' + args['host'] - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - try: - logging.info("Main program: ") - results = jsnap_selection(dev, module) - - if 'total_failed' in results and int(results['total_failed']) != 0: - msg = 'Test Failed: Executed: {0}, Passed {1}, Failed {2}'.format( - str(results['total_tests']), - str(results['total_passed']), - str(results['total_failed']) - ) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - - module.exit_json(**results) - -from ansible.module_utils.basic import * -main() +#!/usr/bin/env python + +#Copyright (c) 1999-2015, Juniper Networks Inc. +# 2016, Roslan Zaki +# +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +DOCUMENTATION = ''' +--- +module: junos_jsnapy +author: Roslan Zaki, Juniper Networks +version_added: "1.0.0" +version_control: + 13-Apr-2016 v1.0.0 - Basic working model + +Short_description: Integrate JSnapy to ansible +description: + - Execute JSnapy module from Ansible +requirements: + - junos-eznc >= 1.2.2 +options: + host: + description: + - Set to {{ inventory_hostname }} + required: true + user: + description: + - Login username + required: false + default: $USER + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + port: + description: + - TCP port number to use when connecting to the device + required: false + default: 830 + logfile: + description: + - Path on the local server where the progress status is logged + for debugging purposes + required: false + default: None + dest: + description: + - Path to the local server directory where configuration will + be saved. + required: true + default: None + dir: + description: + - Path for the JSNAPy yaml configuration file + required: false + default: '/etc/jsnapy/testfiles' + action: + description: + Possible actions available + - jsnapcheck + - check + - snap_pre + - snap_post + required: True + default: None + test_files: + description: + - Test files which need to executed + required: False + type: list + default: None + config_file: + description: + - The YAML configuration file for the JSNAPy tests + required: true + default: None +''' + +EXAMPLES = ''' + - name: JUNOS Post Checklist + junos_jsnapy: + host: "{{ inventory_hostname}}" + passwd: "{{ tm1_password }}" + action: "snap_post" + config_file: "first_test.yml" + logfile: "migration_post.log" + register: jsnapy + + - name: Debug jsnapy + debug: msg="{{jsnapy}}" + +''' +from distutils.version import LooseVersion +import logging +from lxml import etree +from lxml.builder import E + +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + from jnpr.jsnapy import SnapAdmin + import os + import time + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + HAS_PYEZ = False + else: + HAS_PYEZ = True +except ImportError: + HAS_PYEZ = False + + +def jsnap_selection(dev, module): + + args = module.params + action = args['action'] + config_file = args.get('config_file') + if config_file: + config_dir = args['dir'] + config_data = os.path.join(config_dir, config_file) + else: + test_files = args.get('test_files') + config_data = {'tests': test_files} + + results = dict() + js = SnapAdmin() + + if action == 'snapcheck': + snapValue = js.snapcheck(data=config_data, dev=dev) + elif action == 'snap_pre': + snapValue = js.snap(data=config_data, dev=dev, file_name='PRE') + elif action == 'snap_post': + snapValue = js.snap(data=config_data, dev=dev, file_name='POST') + elif action == 'check': + snapValue = js.check(data=config_data, dev=dev, pre_file='PRE', post_file='POST') + + if isinstance(snapValue, (list)): + for snapCheck in snapValue: + router = snapCheck.device + results['router'] = router + results['final_result'] = snapCheck.result + results['total_passed'] = snapCheck.no_passed + results['total_failed'] = snapCheck.no_failed + total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) + results['total_tests'] = total_test + percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) + results['passPercentage'] = percentagePassed + + return results + +def main(): + + module = AnsibleModule( + argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr + user=dict(required=False, default=os.getenv('USER')), + passwd=dict(required=False, default=None), + port=dict(required=False, default=830), + logfile=dict(required=False, default=None), + test_files=dict(required=False, type='list', default=None), + config_file=dict(required=False, default=None), + dir=dict(required=False, default='/etc/jsnapy/testfiles'), + action=dict(required=False, choices=['check', 'snapcheck', 'snap_pre', 'snap_post'], default=None) + ), + mutually_exclusive=[['test_files', 'config_file']], + required_one_of=[['test_files', 'config_file']], + supports_check_mode=False) + + if not HAS_PYEZ: + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + + args = module.params + results = {} + + logfile = args['logfile'] + if logfile is not None: + logging.basicConfig(filename=logfile, level=logging.INFO, + format='%(asctime)s:%(name)s:%(message)s') + logging.getLogger().name = 'JSnapy:' + args['host'] + + logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) + + try: + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], gather_facts=False).open() + except Exception as err: + msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) + logging.error(msg) + module.fail_json(msg=msg) + # --- UNREACHABLE --- + + try: + logging.info("Main program: ") + results = jsnap_selection(dev, module) + + if 'total_failed' in results and int(results['total_failed']) != 0: + msg = 'Test Failed: Executed: {0}, Passed {1}, Failed {2}'.format( + str(results['total_tests']), + str(results['total_passed']), + str(results['total_failed']) + ) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + except Exception as err: + msg = 'Uncaught exception - please report: {0}'.format(str(err)) + logging.error(msg) + dev.close() + module.fail_json(msg=msg) + + dev.close() + + module.exit_json(**results) + +from ansible.module_utils.basic import * +main() From 26d4d2d8ea1285e656a0286479a8917a0e8f2f1e Mon Sep 17 00:00:00 2001 From: Carlos Vicente Date: Tue, 19 Apr 2016 16:05:38 -0400 Subject: [PATCH 040/426] Add timeout parameter to junos_rollback --- library/junos_rollback | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/junos_rollback b/library/junos_rollback index 881e0cb0..90615f3f 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -74,6 +74,13 @@ options: - The rollback id value [0-49] required: true default: None + timeout: + description: + - Extend the NETCONF RPC timeout beyond the default value of + 30 seconds. Set this value to accommodate rollbacks + that might take longer than the default timeout interval. + required: false + default: "0" comment: description: - Provide a comment to the commit of the configuration @@ -125,6 +132,7 @@ def main(): port=dict(required=False, default=830), logfile=dict(required=False, default=None), rollback=dict(required=True, default=None), + timeout=dict(required=False, default=0), comment=dict(required=False, default=None), confirm=dict(required=False, default=None), diffs_file=dict(required=False, default=None) @@ -162,6 +170,10 @@ def main(): module.fail_json(msg=msg) # --- UNREACHABLE --- + timeout = int(args['timeout']) + if timeout > 0: + dev.timeout = timeout + try: cu = Config(dev) From 199d0a52231721945dfa8335452c08672d70948d Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 19 Apr 2016 15:50:26 -0700 Subject: [PATCH 041/426] Add description for Rapid and remove duplicated source_ip --- library/junos_ping | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index f01faf83..3767c91a 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -70,6 +70,11 @@ options: description: - Source IP used to send the ping required: false + rapid: + description: + - Execute ping at 100pps instead of 1pps + required: false + default: true ttl: description: - Number of device allowed between source and destination @@ -78,10 +83,6 @@ options: description: - Name of the routing instance to use to send the ping required: false - source_ip: - description: - - source IP - required: false interface: description: - Interface used to send traffic out From 21b6f3cd6154110caf0aca26a0ceb417b424675b Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 19 Apr 2016 15:53:05 -0700 Subject: [PATCH 042/426] Add timeout option --- library/junos_ping | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/library/junos_ping b/library/junos_ping index 3767c91a..f791b038 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -83,6 +83,13 @@ options: description: - Name of the routing instance to use to send the ping required: false + timeout: + description: + - Extend the NETCONF RPC timeout beyond the default value of + 30 seconds. Set this value to accommodate rollbacks + that might take longer than the default timeout interval. + required: false + default: "0" interface: description: - Interface used to send traffic out @@ -144,6 +151,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + timeout=dict(required=False, default=0), dest_ip=dict(required=True, default=None), source_ip=dict(required=False, default=None), interface=dict(required=False, default=None), @@ -204,6 +212,11 @@ def main(): ping_params['ttl'] = m_args['ttl'] results['ttl'] = m_args['ttl'] + ## Change default Timeout + timeout = int(args['timeout']) + if timeout > 0: + dev.timeout = timeout + try: # Execute Ping From 41ff26fc90432c03df6a9bf14b6f24d5034dd94b Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 19 Apr 2016 15:55:18 -0700 Subject: [PATCH 043/426] Update description for TTL --- library/junos_ping | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index f791b038..424b2fd9 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -77,7 +77,7 @@ options: default: true ttl: description: - - Number of device allowed between source and destination + - Maximum number of IP routers (IP hops) allowed between source and destination required: false routing_instance: description: @@ -212,7 +212,7 @@ def main(): ping_params['ttl'] = m_args['ttl'] results['ttl'] = m_args['ttl'] - ## Change default Timeout + ## Change default Timeout timeout = int(args['timeout']) if timeout > 0: dev.timeout = timeout From 99be823fa64107922216730e5bb512e38b92bc72 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 19 Apr 2016 15:59:33 -0700 Subject: [PATCH 044/426] Remove logging system --- library/junos_ping | 6 ------ 1 file changed, 6 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 424b2fd9..2e7678a1 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -128,7 +128,6 @@ EXAMPLES = ''' import os import sys -import logging import re from lxml import etree from distutils.version import LooseVersion @@ -142,12 +141,10 @@ try: except ImportError: HAS_PYEZ = False - def main(): module = AnsibleModule( argument_spec=dict( host=dict(required=True), - logfile=dict(required=False, default=None), user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), @@ -233,7 +230,6 @@ def main(): if error_message is not None: msg = error_message.strip() - logging.error(msg) dev.close() module.fail_json(msg=msg) @@ -245,13 +241,11 @@ def main(): if int(packet_loss) != 0: results['failed'] = True msg = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packet_sent), str(responses_received)) - logging.error(msg) dev.close() module.fail_json(msg=msg) except Exception as err: results['failed'] = True - logging.error("unable to execute ping due to:{0}".format(err.message)) raise err dev.close() From b7d9b1997eccc6255fa0db9816d5b3a2c1567b84 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 19 Apr 2016 16:08:30 -0700 Subject: [PATCH 045/426] Incorporate feedback from review Change find() to findtext() Change argument to exit_json Add check for packet loss --- library/junos_ping | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 2e7678a1..6a4a1d08 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -219,33 +219,35 @@ def main(): # Execute Ping rpc_reply = dev.rpc.ping(**ping_params) - # results['xml_reply'] = etree.tostring(rpc_reply) - # Try to Find probe summary probe_summary = rpc_reply.find('probe-results-summary') if probe_summary is None: results['failed'] = True + results['msg'] = rpc_reply.findtext('rpc-error/error-message').strip() + dev.close() + module.exit_json(**results) - error_message = rpc_reply.find('rpc-error/error-message').text - if error_message is not None: - msg = error_message.strip() + # Extract packet loss + packet_loss = rpc_reply.findtext("//ping-results/probe-results-summary/packet-loss").strip() + packets_sent = rpc_reply.findtext("//ping-results/probe-results-summary/probes-sent").strip() + responses_received = rpc_reply.findtext("//ping-results/probe-results-summary/responses-received").strip() + if packet_loss == "": + results['msg'] = 'Unable to collect results from ping, response was not found or was empty' dev.close() - module.fail_json(msg=msg) + module.exit_json(**results) - # Extract packet loss - packet_loss = rpc_reply.xpath("//ping-results/probe-results-summary/packet-loss")[0].text.strip() - packet_sent = rpc_reply.xpath("//ping-results/probe-results-summary/probes-sent")[0].text.strip() - responses_received = rpc_reply.xpath("//ping-results/probe-results-summary/responses-received")[0].text.strip() + results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packets_sent), str(responses_received)) if int(packet_loss) != 0: results['failed'] = True - msg = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packet_sent), str(responses_received)) dev.close() - module.fail_json(msg=msg) + module.exit_json(**results) except Exception as err: results['failed'] = True + results['msg'] = "unable to execute ping due to:{0}".format(err.message) + dev.close() raise err dev.close() From 84e2024dd2c1f7921ce146363ce9365975f9d1c8 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 19 Apr 2016 16:14:15 -0700 Subject: [PATCH 046/426] Add option to define acceptable_packet_loss --- library/junos_ping | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 6a4a1d08..9e010268 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -94,6 +94,11 @@ options: description: - Interface used to send traffic out required: false + acceptable_packet_loss: + description: + - Number of packets lost that is acceptable to consider the test PASS + required: false + default: "0" count: description: - Number of packet to send @@ -155,6 +160,7 @@ def main(): rapid=dict(required=False, default=True), routing_instance=dict(required=False, default=None), ttl=dict(required=False, default=None), + acceptable_packet_loss=dict(required=False, default=0), count=dict(required=False, default='5') ), supports_check_mode=False @@ -210,7 +216,7 @@ def main(): results['ttl'] = m_args['ttl'] ## Change default Timeout - timeout = int(args['timeout']) + timeout = int(m_args['timeout']) if timeout > 0: dev.timeout = timeout @@ -239,7 +245,7 @@ def main(): results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packets_sent), str(responses_received)) - if int(packet_loss) != 0: + if int(packet_loss) > int(m_args['acceptable_packet_loss']): results['failed'] = True dev.close() module.exit_json(**results) From 30eb3c4af1e1b30949326b37ca6f257d57b5b5e4 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 19 Apr 2016 17:39:05 -0700 Subject: [PATCH 047/426] Convert tll to string, remove strip from findtext in case response is empty --- library/junos_ping | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 9e010268..6474fd84 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -212,8 +212,8 @@ def main(): results['interface'] = m_args['interface'] if m_args['ttl'] is not None: - ping_params['ttl'] = m_args['ttl'] - results['ttl'] = m_args['ttl'] + ping_params['ttl'] = str(m_args['ttl']) + results['ttl'] = str(m_args['ttl']) ## Change default Timeout timeout = int(m_args['timeout']) @@ -225,6 +225,13 @@ def main(): # Execute Ping rpc_reply = dev.rpc.ping(**ping_params) + except Exception as err: + results['failed'] = True + results['msg'] = "unable to execute ping due to:{0}".format(err.message) + dev.close() + raise err + + try: # Try to Find probe summary probe_summary = rpc_reply.find('probe-results-summary') if probe_summary is None: @@ -234,16 +241,16 @@ def main(): module.exit_json(**results) # Extract packet loss - packet_loss = rpc_reply.findtext("//ping-results/probe-results-summary/packet-loss").strip() - packets_sent = rpc_reply.findtext("//ping-results/probe-results-summary/probes-sent").strip() - responses_received = rpc_reply.findtext("//ping-results/probe-results-summary/responses-received").strip() + packet_loss = rpc_reply.findtext("ping-results/probe-results-summary/packet-loss") + packets_sent = rpc_reply.findtext("ping-results/probe-results-summary/probes-sent") + responses_received = rpc_reply.findtext("ping-results/probe-results-summary/responses-received") - if packet_loss == "": + if packet_loss is None: results['msg'] = 'Unable to collect results from ping, response was not found or was empty' dev.close() module.exit_json(**results) - results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packets_sent), str(responses_received)) + results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss.strip()), str(packets_sent.strip()), str(responses_received.strip())) if int(packet_loss) > int(m_args['acceptable_packet_loss']): results['failed'] = True @@ -252,7 +259,7 @@ def main(): except Exception as err: results['failed'] = True - results['msg'] = "unable to execute ping due to:{0}".format(err.message) + results['msg'] = "unable to analyze ping results due to:{0}".format(err.message) dev.close() raise err From c9305b5b473ee7bed221216fa84332683408f14a Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 20 Apr 2016 18:01:48 -0700 Subject: [PATCH 048/426] Fix timeout description and add more data to results dict --- library/junos_ping | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/junos_ping b/library/junos_ping index 6474fd84..8abac97e 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -86,7 +86,7 @@ options: timeout: description: - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate rollbacks + 30 seconds. Set this value to accommodate pings that might take longer than the default timeout interval. required: false default: "0" @@ -251,6 +251,9 @@ def main(): module.exit_json(**results) results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss.strip()), str(packets_sent.strip()), str(responses_received.strip())) + results['packet_loss'] = packet_loss + results['packets_sent'] = packets_sent + results['packets_received'] = packets_received if int(packet_loss) > int(m_args['acceptable_packet_loss']): results['failed'] = True From 19a7b25171a1b45cea98da25f44f899ad2096d2e Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 20 Apr 2016 18:09:14 -0700 Subject: [PATCH 049/426] Add timeout option for junos_rpc --- library/junos_rpc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/library/junos_rpc b/library/junos_rpc index 2ec60e9b..9722c04b 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -91,6 +91,13 @@ options: required: false choices: ['text','xml', 'json'] default: 'xml' + timeout: + description: + - Extend the NETCONF RPC timeout beyond the default value of + 30 seconds. Set this value to accommodate RPCs + that might take longer than the default timeout interval. + required: false + default: "0" dest: description: - Path to the local server directory where configuration will @@ -227,6 +234,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + timeout=dict(required=False, default=0), rpc=dict(required=True, default=None), kwargs=dict(required=False, default=None), filter_xml=dict(required=False, default=None), @@ -247,6 +255,11 @@ def main(): module.fail_json(msg=msg) return + ## Change default Timeout + timeout = int(m_args['timeout']) + if timeout > 0: + dev.timeout = timeout + results = junos_rpc(module, dev) dev.close() From aef1307f19f79d2caa7e6a7c8128a1947dd9b14a Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 20 Apr 2016 18:13:24 -0700 Subject: [PATCH 050/426] Change rapid option management --- library/junos_ping | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 8abac97e..93c15269 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -196,9 +196,11 @@ def main(): # Prepare parameters ping_params = dict( host=m_args['dest_ip'], - count=m_args['count'], - rapid=m_args['rapid']) + count=m_args['count']) + if m_args['rapid'] is True: + ping_params['rapid'] = 'True' + if m_args['source_ip'] is not None: ping_params['source'] = m_args['source_ip'] results['source_ip'] = m_args['source_ip'] From c59bbda153f37cc18377081e9fdee79d3e779a9d Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 20 Apr 2016 18:16:33 -0700 Subject: [PATCH 051/426] Convert count to str --- library/junos_ping | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 93c15269..7f8db7de 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -196,11 +196,11 @@ def main(): # Prepare parameters ping_params = dict( host=m_args['dest_ip'], - count=m_args['count']) + count=str(m_args['count'])) if m_args['rapid'] is True: - ping_params['rapid'] = 'True' - + ping_params['rapid'] = m_args['rapid'] + if m_args['source_ip'] is not None: ping_params['source'] = m_args['source_ip'] results['source_ip'] = m_args['source_ip'] From c23b3bf089b7d2b6e1ea21dcfdc4d52ea8d1f6d6 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 20 Apr 2016 18:19:55 -0700 Subject: [PATCH 052/426] Add timeout option for Junos_cli --- library/junos_cli | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/library/junos_cli b/library/junos_cli index 6a045bfd..22b46e84 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -66,6 +66,13 @@ options: - TCP port number to use when connecting to the device required: false default: 830 + timeout: + description: + - Extend the NETCONF RPC timeout beyond the default value of + 30 seconds. Set this value to accommodate Cli commands + that might take longer than the default timeout interval. + required: false + default: "0" logfile: description: - Path on the local server where the progress status is logged @@ -120,6 +127,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + timeout=dict(required=False, default=0), logfile=dict(required=False, default=None), dest=dict(required=False, default=None), format=dict(required=False, choices=['text', 'xml'], default='text') @@ -148,6 +156,11 @@ def main(): module.fail_json(msg=msg) # --- UNREACHABLE --- + ## Change default Timeout + timeout = int(m_args['timeout']) + if timeout > 0: + dev.timeout = timeout + try: options = {} options['format'] = args['format'] From b8c1db8341ea2fdcb28c3608c99a82cad53c2db1 Mon Sep 17 00:00:00 2001 From: Jeremy Pollard Date: Thu, 28 Apr 2016 15:05:16 -0700 Subject: [PATCH 053/426] Fix typo in timeout in junos_cli, simplified default timeout handling --- library/junos_cli | 12 +++++------- library/junos_rpc | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index 22b46e84..cf334b65 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -68,11 +68,11 @@ options: default: 830 timeout: description: - - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate Cli commands + - Set the NETCONF RPC timeout (default value of + 30 seconds). Set this value to accommodate Cli commands that might take longer than the default timeout interval. required: false - default: "0" + default: "30" logfile: description: - Path on the local server where the progress status is logged @@ -127,7 +127,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), - timeout=dict(required=False, default=0), + timeout=dict(required=False, type='int', default=30), logfile=dict(required=False, default=None), dest=dict(required=False, default=None), format=dict(required=False, choices=['text', 'xml'], default='text') @@ -157,9 +157,7 @@ def main(): # --- UNREACHABLE --- ## Change default Timeout - timeout = int(m_args['timeout']) - if timeout > 0: - dev.timeout = timeout + dev.timeout = args['timeout'] try: options = {} diff --git a/library/junos_rpc b/library/junos_rpc index 9722c04b..8ae211f7 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -93,11 +93,11 @@ options: default: 'xml' timeout: description: - - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate RPCs + - Set the NETCONF RPC timeout (default value of + 30 seconds). Set this value to accommodate RPCs that might take longer than the default timeout interval. required: false - default: "0" + default: "30" dest: description: - Path to the local server directory where configuration will @@ -234,7 +234,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), - timeout=dict(required=False, default=0), + timeout=dict(required=False, type='int', default=30), rpc=dict(required=True, default=None), kwargs=dict(required=False, default=None), filter_xml=dict(required=False, default=None), @@ -256,9 +256,7 @@ def main(): return ## Change default Timeout - timeout = int(m_args['timeout']) - if timeout > 0: - dev.timeout = timeout + dev.timeout = m_args['timeout'] results = junos_rpc(module, dev) From d309f3e1d22d234feec0b065028582f65c718178 Mon Sep 17 00:00:00 2001 From: Jeremy Pollard Date: Fri, 29 Apr 2016 14:39:43 -0700 Subject: [PATCH 054/426] Reverting to use PyEZ timeout as default --- library/junos_cli | 13 +++++++------ library/junos_rpc | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index cf334b65..df3d90f9 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -68,11 +68,11 @@ options: default: 830 timeout: description: - - Set the NETCONF RPC timeout (default value of - 30 seconds). Set this value to accommodate Cli commands - that might take longer than the default timeout interval. + - Set the NETCONF RPC timeout. Set this value to accommodate Cli + commands that might take longer than the default timeout interval. + Setting to 0 will use the PyEZ default (30 seconds). required: false - default: "30" + default: "0" logfile: description: - Path on the local server where the progress status is logged @@ -127,7 +127,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), - timeout=dict(required=False, type='int', default=30), + timeout=dict(required=False, type='int', default=0), logfile=dict(required=False, default=None), dest=dict(required=False, default=None), format=dict(required=False, choices=['text', 'xml'], default='text') @@ -157,7 +157,8 @@ def main(): # --- UNREACHABLE --- ## Change default Timeout - dev.timeout = args['timeout'] + if args['timeout'] > 0: + dev.timeout = args['timeout'] try: options = {} diff --git a/library/junos_rpc b/library/junos_rpc index 8ae211f7..0bcec7ae 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -93,11 +93,11 @@ options: default: 'xml' timeout: description: - - Set the NETCONF RPC timeout (default value of - 30 seconds). Set this value to accommodate RPCs - that might take longer than the default timeout interval. + - Set the NETCONF RPC timeout. Set this value to accommodate Cli + commands that might take longer than the default timeout interval. + Setting to 0 will use the PyEZ default (30 seconds). required: false - default: "30" + default: "0" dest: description: - Path to the local server directory where configuration will @@ -234,7 +234,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), - timeout=dict(required=False, type='int', default=30), + timeout=dict(required=False, type='int', default=0), rpc=dict(required=True, default=None), kwargs=dict(required=False, default=None), filter_xml=dict(required=False, default=None), @@ -256,7 +256,8 @@ def main(): return ## Change default Timeout - dev.timeout = m_args['timeout'] + if m_args['timeout'] > 0: + dev.timeout = m_args['timeout'] results = junos_rpc(module, dev) From 00e0980d6b564083216bc4d604ac1dbaafef0a82 Mon Sep 17 00:00:00 2001 From: Damien Date: Mon, 16 May 2016 18:46:34 -0700 Subject: [PATCH 055/426] Fix bug with ping module --- library/junos_ping | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 7f8db7de..37a122ba 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -243,19 +243,20 @@ def main(): module.exit_json(**results) # Extract packet loss - packet_loss = rpc_reply.findtext("ping-results/probe-results-summary/packet-loss") - packets_sent = rpc_reply.findtext("ping-results/probe-results-summary/probes-sent") - responses_received = rpc_reply.findtext("ping-results/probe-results-summary/responses-received") + packet_loss = probe_summary.findtext("packet-loss") + packets_sent = probe_summary.findtext("probes-sent") + responses_received = probe_summary.findtext("responses-received") if packet_loss is None: results['msg'] = 'Unable to collect results from ping, response was not found or was empty' + results['failed'] = True dev.close() module.exit_json(**results) results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss.strip()), str(packets_sent.strip()), str(responses_received.strip())) results['packet_loss'] = packet_loss results['packets_sent'] = packets_sent - results['packets_received'] = packets_received + results['packets_received'] = responses_received if int(packet_loss) > int(m_args['acceptable_packet_loss']): results['failed'] = True From c558eaa89e84c59eb13f55f4a2271747d61ca109 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 18:31:02 -0700 Subject: [PATCH 056/426] Add travis test file and requirements --- .travis.yml | 20 ++++++++++++++++++++ requirements.txt | 2 ++ 2 files changed, 22 insertions(+) create mode 100644 .travis.yml create mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..ea248684 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: python +python: + - 2.7 + +sudo: required + +env: + - ANSIBLE_VERSION=1.9.6 + - ANSIBLE_VERSION=2.0.2.0 +# - ANSIBLE_VERSION=2.0.0.2 + +install: + - pip install -r requirements.txt + - pip install -q ansible==$ANSIBLE_VERSION + - cd .. + - tar -czf Juniper.junos $TRAVIS_REPO_SLUG + - ansible-galaxy install Juniper.junos + +script: + - python -m pytest -v --durations=10 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..2dfdfb29 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +junos-eznc +jxmlease From e93db65d3ddeb45709c43e062f31923ef672ee5a Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 18:32:35 -0700 Subject: [PATCH 057/426] Initial travis build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ea248684..daa70373 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ sudo: required env: - ANSIBLE_VERSION=1.9.6 - - ANSIBLE_VERSION=2.0.2.0 +# - ANSIBLE_VERSION=2.0.2.0 # - ANSIBLE_VERSION=2.0.0.2 install: From 80860af09a1bc1e63279bdd19427156708b1a84a Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 18:54:55 -0700 Subject: [PATCH 058/426] Fix dir name --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index daa70373..937e43fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,9 @@ install: - pip install -r requirements.txt - pip install -q ansible==$ANSIBLE_VERSION - cd .. - - tar -czf Juniper.junos $TRAVIS_REPO_SLUG + - tar -czf Juniper.junos ansible-junos-stdlib - ansible-galaxy install Juniper.junos + - cd ansible-junos-stdlib script: - python -m pytest -v --durations=10 From b0850c0d2675938259a40131a3ae6f66b1fbeeda Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 19:44:50 -0700 Subject: [PATCH 059/426] Add Vagrant installation --- .travis.yml | 15 ++++++++-- tests/Vagrantfile | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/Vagrantfile diff --git a/.travis.yml b/.travis.yml index 937e43fa..d5b2a191 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,17 @@ install: - pip install -q ansible==$ANSIBLE_VERSION - cd .. - tar -czf Juniper.junos ansible-junos-stdlib - - ansible-galaxy install Juniper.junos - - cd ansible-junos-stdlib + - sudo ansible-galaxy install Juniper.junos + - cd ansible-junos-stdlib/tests +## Install Vagrant + - sudo apt-get update -q + - sudo apt-get install -q virtualbox --fix-missing + - sudo wget -nv https://releases.hashicorp.com/vagrant/1.8.1/vagrant_1.8.1_x86_64.deb + - sudo dpkg -i vagrant_1.8.1_x86_64.deb + - bundle install + - vagrant plugin install vagrant-junos + - vagrant plugin install vagrant-host-shell + - vagrant up script: - - python -m pytest -v --durations=10 + - vagrant status diff --git a/tests/Vagrantfile b/tests/Vagrantfile new file mode 100644 index 00000000..a76a4438 --- /dev/null +++ b/tests/Vagrantfile @@ -0,0 +1,71 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure(2) do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://atlas.hashicorp.com/search. + config.vm.box = "juniper/ffp-12.1X47-D15.4-packetmode" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider "virtualbox" do |vb| + # # Display the VirtualBox GUI when booting the machine + # vb.gui = true + # + # # Customize the amount of memory on the VM: + # vb.memory = "1024" + # end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies + # such as FTP and Heroku are also available. See the documentation at + # https://docs.vagrantup.com/v2/push/atlas.html for more information. + # config.push.define "atlas" do |push| + # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" + # end + + # Enable provisioning with a shell script. Additional provisioners such as + # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the + # documentation for more information about their specific syntax and use. + # config.vm.provision "shell", inline: <<-SHELL + # sudo apt-get update + # sudo apt-get install -y apache2 + # SHELL +end From 06b1dbc08ff13cfd91e0541f5cc4de7192ed7e3c Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 23:40:24 -0700 Subject: [PATCH 060/426] Ignore vagrant files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5a1cee33..21c084e8 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ nosetests.xml docs/build docs/*.rst docs/_build/ + +tests/.vagrant/* From ac75254d13283b6beee5ad4e41106aca314f6587 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 23:40:58 -0700 Subject: [PATCH 061/426] strip variables from \n\r --- library/junos_ping | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 37a122ba..b52b766a 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -254,9 +254,9 @@ def main(): module.exit_json(**results) results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss.strip()), str(packets_sent.strip()), str(responses_received.strip())) - results['packet_loss'] = packet_loss - results['packets_sent'] = packets_sent - results['packets_received'] = responses_received + results['packet_loss'] = packet_loss.strip() + results['packets_sent'] = packets_sent.strip() + results['packets_received'] = responses_received.strip() if int(packet_loss) > int(m_args['acceptable_packet_loss']): results['failed'] = True From c4b05eefcd6b2c44d655a25dfa547c224617fe72 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 23:41:19 -0700 Subject: [PATCH 062/426] Add Ansible config and playbook for Vagrant --- tests/Vagrantfile | 69 +++---------------- tests/ansible.cfg | 5 ++ tests/junos_get_facts/pb.junos_get_facts.yaml | 26 +++++++ tests/junos_ping/pb.junos_ping.yaml | 61 ++++++++++++++++ 4 files changed, 103 insertions(+), 58 deletions(-) create mode 100644 tests/ansible.cfg create mode 100644 tests/junos_get_facts/pb.junos_get_facts.yaml create mode 100644 tests/junos_ping/pb.junos_ping.yaml diff --git a/tests/Vagrantfile b/tests/Vagrantfile index a76a4438..1cea031f 100644 --- a/tests/Vagrantfile +++ b/tests/Vagrantfile @@ -10,62 +10,15 @@ Vagrant.configure(2) do |config| # For a complete reference, please see the online documentation at # https://docs.vagrantup.com. - # Every Vagrant development environment requires a box. You can search for - # boxes at https://atlas.hashicorp.com/search. - config.vm.box = "juniper/ffp-12.1X47-D15.4-packetmode" - - # Disable automatic box update checking. If you disable this, then - # boxes will only be checked for updates when the user runs - # `vagrant box outdated`. This is not recommended. - # config.vm.box_check_update = false - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. - # config.vm.network "forwarded_port", guest: 80, host: 8080 - - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network "private_network", ip: "192.168.33.10" - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network "public_network" - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - # config.vm.synced_folder "../data", "/vagrant_data" - - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - # Example for VirtualBox: - # - # config.vm.provider "virtualbox" do |vb| - # # Display the VirtualBox GUI when booting the machine - # vb.gui = true - # - # # Customize the amount of memory on the VM: - # vb.memory = "1024" - # end - # - # View the documentation for the provider you are using for more - # information on available options. - - # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies - # such as FTP and Heroku are also available. See the documentation at - # https://docs.vagrantup.com/v2/push/atlas.html for more information. - # config.push.define "atlas" do |push| - # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" - # end - - # Enable provisioning with a shell script. Additional provisioners such as - # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the - # documentation for more information about their specific syntax and use. - # config.vm.provision "shell", inline: <<-SHELL - # sudo apt-get update - # sudo apt-get install -y apache2 - # SHELL + config.vm.define "vsrx" do |vsrx| + vsrx.vm.hostname = "vsrx" + vsrx.vm.box = "juniper/ffp-12.1X47-D15.4-packetmode" + end + + config.vm.provision "ansible" do |ansible| + ansible.groups = { + "all" => ["vsrx"] + } + ansible.playbook = "junos_get_facts/pb.junos_get_facts.yaml" + end end diff --git a/tests/ansible.cfg b/tests/ansible.cfg new file mode 100644 index 00000000..2127be75 --- /dev/null +++ b/tests/ansible.cfg @@ -0,0 +1,5 @@ + +[defaults] +hash_behaviour=merge +inventory = .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory +roles_path = ../../ diff --git a/tests/junos_get_facts/pb.junos_get_facts.yaml b/tests/junos_get_facts/pb.junos_get_facts.yaml new file mode 100644 index 00000000..c5cdc194 --- /dev/null +++ b/tests/junos_get_facts/pb.junos_get_facts.yaml @@ -0,0 +1,26 @@ +--- +- name: Test junos_get_facts module + hosts: all + connection: local + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Gather Facts" + junos_get_facts: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: root + passwd: Juniper + ignore_errors: True + register: test1 + +# - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1.facts.hostname == 'vsrx' + - test1.facts.virtual == true diff --git a/tests/junos_ping/pb.junos_ping.yaml b/tests/junos_ping/pb.junos_ping.yaml new file mode 100644 index 00000000..ecf32b5f --- /dev/null +++ b/tests/junos_ping/pb.junos_ping.yaml @@ -0,0 +1,61 @@ +--- +- name: Test junos_ping module + hosts: all + connection: local + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Ping Google DNS" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: root + passwd: Juniper + dest_ip: 8.8.8.8 + register: test1 + ignore_errors: True +# - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1.packet_loss == '0' + +############ + + - name: "TEST 2 - Ping Wrong IP" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: root + passwd: Juniper + dest_ip: 8.8.1.1 + register: test2 + ignore_errors: True +# - debug: var=test2 + + - name: Check TEST 2 + assert: + that: + - test2.packet_loss == '100' +################# + + - name: "TEST 3 - Change nbr packets" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: root + passwd: Juniper + dest_ip: 8.8.8.8 + count: 3 + register: test3 + ignore_errors: True +# - debug: var=test3 + + - name: Check TEST 3 + assert: + that: + - test3.packets_sent == '3' From 878d4ab2b7b3852cffd6edefc53a66e3fc5fb998 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 17 May 2016 23:48:14 -0700 Subject: [PATCH 063/426] Update ansible module installation in travis --- .travis.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d5b2a191..35a2424b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,14 @@ env: install: - pip install -r requirements.txt - pip install -q ansible==$ANSIBLE_VERSION +## Create module package and install it at the same level as the project - cd .. - tar -czf Juniper.junos ansible-junos-stdlib - - sudo ansible-galaxy install Juniper.junos - - cd ansible-junos-stdlib/tests + - mkdir tmp + - mv Juniper.junos tmp/ + - cd tmp + - ansible-galaxy install -p ../ Juniper.junos + - cd ../ansible-junos-stdlib/tests ## Install Vagrant - sudo apt-get update -q - sudo apt-get install -q virtualbox --fix-missing @@ -27,4 +31,5 @@ install: - vagrant up script: - - vagrant status + - ansible-playbook junos_ping/pb.junos_ping.yaml + - ansible-playbook junos_get_facts/pb.junos_get_facts.yaml From c46e17a819536dc95b7c6d030955fdefcbcdbd17 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 18 May 2016 00:03:48 -0700 Subject: [PATCH 064/426] Remove bundle --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 35a2424b..11085ee3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,6 @@ install: - sudo apt-get install -q virtualbox --fix-missing - sudo wget -nv https://releases.hashicorp.com/vagrant/1.8.1/vagrant_1.8.1_x86_64.deb - sudo dpkg -i vagrant_1.8.1_x86_64.deb - - bundle install - vagrant plugin install vagrant-junos - vagrant plugin install vagrant-host-shell - vagrant up From a589ad260ab6bf393a8d2518559ee7eaee24d655 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 18 May 2016 00:08:51 -0700 Subject: [PATCH 065/426] Change travis distro --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 11085ee3..2170e6be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - 2.7 sudo: required +dist: trusty env: - ANSIBLE_VERSION=1.9.6 From c04c29ab84df1917288e5e518f4a8081d1cfb2c1 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 18 May 2016 00:11:32 -0700 Subject: [PATCH 066/426] Install kernel header --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2170e6be..68ec355f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,13 @@ install: - cd tmp - ansible-galaxy install -p ../ Juniper.junos - cd ../ansible-junos-stdlib/tests -## Install Vagrant +## Install Virtualbox - sudo apt-get update -q + - sudo apt-get install linux-headers-$(uname -r) - sudo apt-get install -q virtualbox --fix-missing + - VBoxManage --version + +## Install Vagrant - sudo wget -nv https://releases.hashicorp.com/vagrant/1.8.1/vagrant_1.8.1_x86_64.deb - sudo dpkg -i vagrant_1.8.1_x86_64.deb - vagrant plugin install vagrant-junos From 5178e54a06dd1deb3fb630749c4e0ca73752ac3f Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 18 May 2016 00:15:39 -0700 Subject: [PATCH 067/426] Add confirmation for linux header install --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68ec355f..7d1d1a9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,11 +23,11 @@ install: - cd ../ansible-junos-stdlib/tests ## Install Virtualbox - sudo apt-get update -q - - sudo apt-get install linux-headers-$(uname -r) + - sudo apt-get install -y linux-headers-$(uname -r) - sudo apt-get install -q virtualbox --fix-missing - VBoxManage --version -## Install Vagrant +## Install Vagrant - sudo wget -nv https://releases.hashicorp.com/vagrant/1.8.1/vagrant_1.8.1_x86_64.deb - sudo dpkg -i vagrant_1.8.1_x86_64.deb - vagrant plugin install vagrant-junos From 04d264b2449e888de4fa7398f65a17845fda6d72 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 18 May 2016 00:23:02 -0700 Subject: [PATCH 068/426] Change virtual box install --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d1d1a9e..5c8b5456 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,13 @@ install: - mv Juniper.junos tmp/ - cd tmp - ansible-galaxy install -p ../ Juniper.junos - - cd ../ansible-junos-stdlib/tests + ## Install Virtualbox - sudo apt-get update -q + - sudo apt-get install -y dkms - sudo apt-get install -y linux-headers-$(uname -r) - - sudo apt-get install -q virtualbox --fix-missing + - sudo wget -nv http://download.virtualbox.org/virtualbox/5.0.20/virtualbox-5.0_5.0.20-106931~Ubuntu~trusty_amd64.deb + - sudo dpkg -i virtualbox-5.0_5.0.20-106931~Ubuntu~trusty_amd64.deb - VBoxManage --version ## Install Vagrant @@ -32,6 +34,9 @@ install: - sudo dpkg -i vagrant_1.8.1_x86_64.deb - vagrant plugin install vagrant-junos - vagrant plugin install vagrant-host-shell + +## start Junos VM + - cd ../ansible-junos-stdlib/tests - vagrant up script: From c15ca0f86335932bd434548231ca758eace617f4 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 18 May 2016 07:23:02 -0700 Subject: [PATCH 069/426] Turn off VT-x and limit to 1 CPU --- tests/Vagrantfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Vagrantfile b/tests/Vagrantfile index 1cea031f..4ccccbaf 100644 --- a/tests/Vagrantfile +++ b/tests/Vagrantfile @@ -13,8 +13,15 @@ Vagrant.configure(2) do |config| config.vm.define "vsrx" do |vsrx| vsrx.vm.hostname = "vsrx" vsrx.vm.box = "juniper/ffp-12.1X47-D15.4-packetmode" + + vsrx.vm.provider :virtualbox do |vb| + vb.customize ["modifyvm", :id, "--hwvirtex", "off"] + vb.cpus = 1 + end end + + config.vm.provision "ansible" do |ansible| ansible.groups = { "all" => ["vsrx"] From 0eee56f6095f33f02f0209272eedc5b07eba7b79 Mon Sep 17 00:00:00 2001 From: Jeroen van Renen Date: Tue, 24 May 2016 15:02:06 +0200 Subject: [PATCH 070/426] Add sleep 10 before cu.commit --- library/junos_install_config | 1 + 1 file changed, 1 insertion(+) diff --git a/library/junos_install_config b/library/junos_install_config index e66e367c..d0307969 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -302,6 +302,7 @@ def _load_via_netconf(module): if args['confirm'] is not None: opts['confirm'] = args['confirm'] + time.sleep(10) cu.commit(**opts) except CommitError as err: From 2af06c6ad6dc6beb5fc9e29dd16c68fccf934b1c Mon Sep 17 00:00:00 2001 From: Jeroen van Renen Date: Thu, 26 May 2016 10:01:24 +0200 Subject: [PATCH 071/426] Added option dual_re Set to yes in a Dual RE scenario when 'commit sync' is enabled in the config. (set system commit synchronize) --- library/junos_install_config | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index d0307969..9aaca87f 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -138,6 +138,12 @@ options: - Provide a confirm in minutes to the commit of the configuration required: false default: None + dual_re: + description: + - Set to yes in a Dual RE scenario when 'commit sync' is enabled in the config + required: false + default: no + choices: ['true','false','yes','no'] ''' EXAMPLES = ''' @@ -302,8 +308,11 @@ def _load_via_netconf(module): if args['confirm'] is not None: opts['confirm'] = args['confirm'] - time.sleep(10) - cu.commit(**opts) + if args['dual_re']: + time.sleep(10) + cu.commit(**opts) + else: + cu.commit(**opts) except CommitError as err: msg = "Unable to commit configuration: {0}".format(err) @@ -404,7 +413,8 @@ def main(): timeout=dict(required=False, default=0), comment=dict(required=False, default=None), port=dict(required=False, default=830), - confirm=dict(required=False, default=None) + confirm=dict(required=False, default=None), + dual_re=dict(required=False, type='bool', choices=BOOLEANS, default=False) ), supports_check_mode=True) From 9886bb7abaa9f36be506cd159c3c2c7087d609d8 Mon Sep 17 00:00:00 2001 From: Jeroen van Renen Date: Thu, 26 May 2016 10:43:46 +0200 Subject: [PATCH 072/426] Shorter --- library/junos_install_config | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index 9aaca87f..2853a76a 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -310,9 +310,8 @@ def _load_via_netconf(module): if args['dual_re']: time.sleep(10) - cu.commit(**opts) - else: - cu.commit(**opts) + + cu.commit(**opts) except CommitError as err: msg = "Unable to commit configuration: {0}".format(err) From 90884cb45cc6d6b0d4235b9b331b1cc5ef1f7bf8 Mon Sep 17 00:00:00 2001 From: Jeroen van Renen Date: Tue, 14 Jun 2016 15:53:51 +0200 Subject: [PATCH 073/426] Sleep 1 instead of 10 new insight, commit check is completed on both route engines when commit is given (1 ok is given for both RE), apparently a short amount of time is needed for the lock to clear on the second RE. --- library/junos_install_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_config b/library/junos_install_config index 2853a76a..d91cbeb3 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -309,7 +309,7 @@ def _load_via_netconf(module): opts['confirm'] = args['confirm'] if args['dual_re']: - time.sleep(10) + time.sleep(1) cu.commit(**opts) From 398576080dca193fea50e5be1f8ce418cd87e5c0 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 20 Jun 2016 13:33:23 +0530 Subject: [PATCH 074/426] changed as per latest JSNAPy --- library/junos_jsnapy | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index bf7ce313..9297a2f7 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -135,7 +135,7 @@ try: HAS_PYEZ = False else: HAS_PYEZ = True -except ImportError: +except ImportError as ex: HAS_PYEZ = False @@ -151,7 +151,7 @@ def jsnap_selection(dev, module): test_files = args.get('test_files') config_data = {'tests': test_files} - results = dict() + results = {} js = SnapAdmin() if action == 'snapcheck': @@ -170,6 +170,7 @@ def jsnap_selection(dev, module): results['final_result'] = snapCheck.result results['total_passed'] = snapCheck.no_passed results['total_failed'] = snapCheck.no_failed + results['test_results'] = snapCheck.test_results total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) results['total_tests'] = total_test percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) @@ -219,17 +220,15 @@ def main(): try: logging.info("Main program: ") - results = jsnap_selection(dev, module) + data = jsnap_selection(dev, module) - if 'total_failed' in results and int(results['total_failed']) != 0: - msg = 'Test Failed: Executed: {0}, Passed {1}, Failed {2}'.format( - str(results['total_tests']), - str(results['total_passed']), - str(results['total_failed']) + if data['final_result']=='Failed': + msg = 'Test Failed: Passed {0}, Failed {1}'.format( + data.no_passed, data.no_failed ) logging.error(msg) dev.close() - module.fail_json(msg=msg) + module.fail_json(msg=msg, **data) except Exception as err: msg = 'Uncaught exception - please report: {0}'.format(str(err)) @@ -238,8 +237,7 @@ def main(): module.fail_json(msg=msg) dev.close() - - module.exit_json(**results) + module.exit_json(**data) from ansible.module_utils.basic import * main() From a2ab1830859faccb88a89c6f296b6c0518453b1a Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 20 Jun 2016 14:53:36 +0530 Subject: [PATCH 075/426] in case of failure module was breaking --- library/junos_jsnapy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 9297a2f7..4b93cd95 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -224,7 +224,7 @@ def main(): if data['final_result']=='Failed': msg = 'Test Failed: Passed {0}, Failed {1}'.format( - data.no_passed, data.no_failed + data['total_passed'], data['total_failed'] ) logging.error(msg) dev.close() From 98c8e422dc6b42ec17a4d360aac7a1468d12f9b2 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Wed, 20 Jul 2016 12:20:10 +0530 Subject: [PATCH 076/426] renamed junos_get_table.py to be in sync with other modules --- library/{junos_get_table.py => junos_get_table} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename library/{junos_get_table.py => junos_get_table} (100%) diff --git a/library/junos_get_table.py b/library/junos_get_table similarity index 100% rename from library/junos_get_table.py rename to library/junos_get_table From ba3994a8b53f2fed11c590edd81f913829525fc0 Mon Sep 17 00:00:00 2001 From: Sean Sawtell Date: Fri, 29 Jul 2016 16:08:10 -0400 Subject: [PATCH 077/426] update junos_shutdown to support delayed reboot and shutdown (in_min and at) options --- library/junos_shutdown | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/library/junos_shutdown b/library/junos_shutdown index 599b06e1..768c1bb0 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -66,6 +66,17 @@ options: required: false default: no choices: ['yes','no'] + at: + description: + - Specify a time for the reboot (yyyymmddhhmm). Can be used only with reboot=yes. + required: false + default: None + in_min: + description: + - Specify a delay, in minutes, before the shutdown or reboot. + If both "at" and "in_min" are specified, "in_min" will be ignored. + required: false + default: 0 shutdown: description: - Safety mechanism. You B(MUST) set this to 'shutdown'. @@ -107,7 +118,9 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), reboot=dict(required=False, type='bool', choices=BOOLEANS, default=False), - port=dict(required=False, default=830) + port=dict(required=False, default=830), + in_min=dict(required=False, default=0), + at=dict(required=False, type='str', default=None) ), supports_check_mode=False) @@ -119,6 +132,12 @@ def main(): module.fail_json(msg='Say "shutdown" to proceed') restart_mode = module.boolean(args['reboot']) + delay = args['in_min'] + + # because PyEZ does not support 'at' option on shutdown, only with reboot + if args['at'] and not restart_mode: + module.fail_json(msg="Argument *at* must be used with argument *reboot='yes'*") + # --- UNREACHABLE --- try: dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port']).open() @@ -127,16 +146,24 @@ def main(): module.fail_json(msg=msg) # --- UNREACHABLE --- - results = {'changed': True, 'reboot': restart_mode} - sw = SW(dev) do_it = sw.reboot if restart_mode is True else sw.poweroff - do_it(0) - # dev.close isn't performed since the device will - # be in the process of shutting down and we'll - # lose connectivity anyways ... + try: + if args['at']: + msg = do_it(at=args['at']) + dev.close() + else: + msg = do_it(in_min=delay) + # no need to close session with immediate shutdown/reboot because + # the device will be shutting and we will lose connectivity anyway + if delay > 0: + dev.close() + except Exception as err: + msg = 'error during device reboot or shutdown {0}: {1}'.format(args['host'], str(err)) + module.fail_json(msg=msg) + results = {'changed': True, 'reboot': restart_mode, 'msg':msg} module.exit_json(**results) from ansible.module_utils.basic import * From 7b9d668c0f72d7bcfca24f5be80845da7afa007a Mon Sep 17 00:00:00 2001 From: Damien Date: Sat, 30 Jul 2016 20:01:00 -0700 Subject: [PATCH 078/426] Check if nbr_test is different to zero before using it --- library/junos_jsnapy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 4b93cd95..0523718a 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -163,6 +163,7 @@ def jsnap_selection(dev, module): elif action == 'check': snapValue = js.check(data=config_data, dev=dev, pre_file='PRE', post_file='POST') + percentagePassed = 0 if isinstance(snapValue, (list)): for snapCheck in snapValue: router = snapCheck.device @@ -173,7 +174,10 @@ def jsnap_selection(dev, module): results['test_results'] = snapCheck.test_results total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) results['total_tests'] = total_test - percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) + + if total_test != 0: + percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) + results['passPercentage'] = percentagePassed return results From cbbbf2a0e12b947190b0431905a1d20be69f37cb Mon Sep 17 00:00:00 2001 From: Damien Date: Sat, 30 Jul 2016 20:06:50 -0700 Subject: [PATCH 079/426] Add test to check if test files are present --- library/junos_jsnapy | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 0523718a..d9e35dfb 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -123,6 +123,7 @@ from distutils.version import LooseVersion import logging from lxml import etree from lxml.builder import E +import os.path try: from jnpr.junos import Device @@ -149,6 +150,13 @@ def jsnap_selection(dev, module): config_data = os.path.join(config_dir, config_file) else: test_files = args.get('test_files') + + for test_file in test_files: + if not os.path.isfile(test_file): + msg = 'unable to find the test file {0}'.format(test_file) + logging.error(msg) + module.fail_json(msg=msg) + config_data = {'tests': test_files} results = {} From 9adb432b477635ab3020d48a6c8fbfa1aa2dc0ac Mon Sep 17 00:00:00 2001 From: Stian Vikan Date: Mon, 8 Aug 2016 09:02:51 +0200 Subject: [PATCH 080/426] fix regex to match xe/ge/et interfaces --- library/junos_rpc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_rpc b/library/junos_rpc index 0bcec7ae..1e6e9d78 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -190,7 +190,7 @@ def junos_rpc(module, dev): elif re.search(r'\{.*\}', kwargs): values = {k:v for k,v in re.findall(r'([\w-]+)\s?:\s?\'?\"?([\w\.-]+)\'?\"?',kwargs)} else: - values = {k:v for k,v in re.findall('([\w-]+)=([\w\.-]+)', kwargs)} + values = {k:v for k,v in re.findall('([\w-]+)=([\w\.\-\/]+)', kwargs)} for k,v in values.items(): if v in ['True', 'true']: values[k]=True From 7748ebdb939c37dd627830dff2445e759ae55dd3 Mon Sep 17 00:00:00 2001 From: Jeroen van Renen Date: Wed, 17 Aug 2016 17:13:38 +0200 Subject: [PATCH 081/426] Changed 'dual_re' to 'check_commit_wait' Instead of adding a default value of 1s the 'check_commit_wait' parameter indicates a timeout is needed between check and commit and can be set between 1 and 4s. --- library/junos_install_config | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index d91cbeb3..4d242548 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -138,12 +138,12 @@ options: - Provide a confirm in minutes to the commit of the configuration required: false default: None - dual_re: + check_commit_wait: description: - - Set to yes in a Dual RE scenario when 'commit sync' is enabled in the config + - Set to number of seconds to wait between check and commit. required: false - default: no - choices: ['true','false','yes','no'] + default: "0" + choices: [1 - 4] ''' EXAMPLES = ''' @@ -308,8 +308,10 @@ def _load_via_netconf(module): if args['confirm'] is not None: opts['confirm'] = args['confirm'] - if args['dual_re']: - time.sleep(1) + if int(args['check_commit_wait']) > 0: + check_commit_wait = int(args['check_commit_wait']) + if 1 <= check_commit_wait <= 4: + time.sleep(check_commit_wait) cu.commit(**opts) @@ -413,7 +415,6 @@ def main(): comment=dict(required=False, default=None), port=dict(required=False, default=830), confirm=dict(required=False, default=None), - dual_re=dict(required=False, type='bool', choices=BOOLEANS, default=False) ), supports_check_mode=True) From 91121d9c88573f0b686a51d07591cc3d7dd4f021 Mon Sep 17 00:00:00 2001 From: Jeroen van Renen Date: Wed, 17 Aug 2016 17:16:31 +0200 Subject: [PATCH 082/426] Only check if parameter is used. Only check if parameter is used. --- library/junos_install_config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/junos_install_config b/library/junos_install_config index 4d242548..61892bb7 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -308,7 +308,7 @@ def _load_via_netconf(module): if args['confirm'] is not None: opts['confirm'] = args['confirm'] - if int(args['check_commit_wait']) > 0: + if args['check_commit_wait']: check_commit_wait = int(args['check_commit_wait']) if 1 <= check_commit_wait <= 4: time.sleep(check_commit_wait) @@ -415,6 +415,7 @@ def main(): comment=dict(required=False, default=None), port=dict(required=False, default=830), confirm=dict(required=False, default=None), + check_commit_wait=dict(required=False, default=None) ), supports_check_mode=True) From 9bffa717ef43dee018cb203119934e294910a1fb Mon Sep 17 00:00:00 2001 From: Jeroen van Renen Date: Thu, 18 Aug 2016 13:23:04 +0200 Subject: [PATCH 083/426] No default value No default value --- library/junos_install_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_config b/library/junos_install_config index 61892bb7..91713158 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -142,7 +142,7 @@ options: description: - Set to number of seconds to wait between check and commit. required: false - default: "0" + default: None choices: [1 - 4] ''' From 98783de784008be2c17613d2383e18712848369e Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 23 Aug 2016 18:39:37 -0700 Subject: [PATCH 084/426] Change behavior in case of test failure --- library/junos_jsnapy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index d9e35dfb..8b7f82a4 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -240,7 +240,7 @@ def main(): ) logging.error(msg) dev.close() - module.fail_json(msg=msg, **data) + module.exit_json(msg=msg, **data) except Exception as err: msg = 'Uncaught exception - please report: {0}'.format(str(err)) From 7ec79a330b98559d4725277e9c450e482f90822e Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 23 Aug 2016 18:39:59 -0700 Subject: [PATCH 085/426] Add Callback for JSNAPy --- callback_plugins/jsnapy.py | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 callback_plugins/jsnapy.py diff --git a/callback_plugins/jsnapy.py b/callback_plugins/jsnapy.py new file mode 100644 index 00000000..a248f830 --- /dev/null +++ b/callback_plugins/jsnapy.py @@ -0,0 +1,69 @@ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import collections +import os +import time +import pprint +import json + +from ansible.plugins.callback import CallbackBase +from ansible import constants as C + +class CallbackModule(CallbackBase): + """ + This callback add extra logging for the module junos_jsnapy . + """ + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'aggregate' + CALLBACK_NAME = 'jsnapy' + + def __init__(self): + self._pp = pprint.PrettyPrinter(indent=4) + self._results = {} + + super(CallbackModule, self).__init__() + + def v2_runner_on_ok(self, result): + """ + Collect test results for all tests executed if module is junos_jsnapy + """ + module_name = '' + ## Extract module name + if 'invocation' in result._result: + module_name = result._result['invocation']['module_name'] + + if module_name == 'junos_jsnapy': + ## Check if dict entry already exist for this host + host = result._host.name + if not host in self._results.keys(): + self._results[host] = [] + + self._results[host].append(result) + + def v2_playbook_on_stats(self, stats): + + ## Go over all results for all hosts + for host, results in self._results.iteritems(): + has_printed_banner = False + for result in results: + # self._pp.pprint(result.__dict__) + res = result._result + if res['final_result'] == "Failed": + for test_name, test_results in res['test_results'].iteritems(): + for testlet in test_results: + if testlet['count']['fail'] != 0: + + if not has_printed_banner: + self._display.banner("JSNAPy Results for: " + str(host)) + has_printed_banner = True + + for test in testlet['failed']: + self._display.display( + "Value of '{0}' not '{1}' at '{2}' with {3}".format( + str(testlet['node_name']), + str(testlet['testoperation']), + str(testlet['xpath']), + json.dumps(test['post'])), + color=C.COLOR_ERROR) From 54cc16e73438c333d61d56b9f8ac035545d7cb5f Mon Sep 17 00:00:00 2001 From: James Nickerson Date: Thu, 1 Sep 2016 14:03:01 -0700 Subject: [PATCH 086/426] fix mixed tabs and whitespaces --- library/junos_install_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_config b/library/junos_install_config index 91713158..52551e13 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -309,7 +309,7 @@ def _load_via_netconf(module): opts['confirm'] = args['confirm'] if args['check_commit_wait']: - check_commit_wait = int(args['check_commit_wait']) + check_commit_wait = int(args['check_commit_wait']) if 1 <= check_commit_wait <= 4: time.sleep(check_commit_wait) From dacaabbaa6045c3d3a0e4796f20bfb87a8c7ab18 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Wed, 14 Sep 2016 12:56:59 +0530 Subject: [PATCH 087/426] ImportError to show proper message --- library/junos_cli | 24 ++++++--------- library/junos_commit | 29 ++++++++---------- library/junos_get_config | 25 +++++++--------- library/junos_get_facts | 7 ++--- .../{junos_get_table.py => junos_get_table} | 24 +++++++-------- library/junos_install_config | 24 +++++++-------- library/junos_install_os | 22 +++++--------- library/junos_jsnapy | 30 ++++++++----------- library/junos_ping | 20 +++++-------- library/junos_rollback | 26 +++++++--------- library/junos_rpc | 10 ++++++- library/junos_shutdown | 22 +++++--------- library/junos_srx_cluster | 4 +-- library/junos_zeroize | 4 +-- 14 files changed, 113 insertions(+), 158 deletions(-) rename library/{junos_get_table.py => junos_get_table} (94%) diff --git a/library/junos_cli b/library/junos_cli index df3d90f9..b5192554 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -107,18 +107,6 @@ import logging from lxml import etree from lxml.builder import E -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -134,11 +122,17 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_commit b/library/junos_commit index 6b62e39f..0e40c2b9 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -94,22 +94,10 @@ EXAMPLES = ''' logfile=changes.log comment="Non load commit" ''' + from distutils.version import LooseVersion import logging -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.utils.config import Config - from jnpr.junos.exception import CommitError, LockError, UnlockError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -124,14 +112,21 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params in_check_mode = module.check_mode - results = dict(changed=False) + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.utils.config import Config + from jnpr.junos.exception import CommitError, LockError, UnlockError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_get_config b/library/junos_get_config index 5e562550..4e09e90b 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -111,18 +111,6 @@ from distutils.version import LooseVersion import logging from lxml import etree -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -138,10 +126,17 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + results = {} logfile = args['logfile'] diff --git a/library/junos_get_facts b/library/junos_get_facts index 70ffffc3..7fee664c 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -112,10 +112,9 @@ EXAMPLES = ''' import os import json - +from distutils.version import LooseVersion def main(): - from distutils.version import LooseVersion module = AnsibleModule( argument_spec=dict( host=dict(required=True), @@ -136,8 +135,8 @@ def main(): from jnpr.junos.version import VERSION if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) # ----------- # via NETCONF # ----------- diff --git a/library/junos_get_table.py b/library/junos_get_table similarity index 94% rename from library/junos_get_table.py rename to library/junos_get_table index 07e7ee98..7c0f79ba 100644 --- a/library/junos_get_table.py +++ b/library/junos_get_table @@ -120,19 +120,8 @@ import os from lxml import etree from lxml.builder import E -from jnpr.junos.factory.factory_loader import FactoryLoader import yaml -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - import jnpr.junos.op as tables_dir - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False TABLE_PATH = os.path.dirname(os.path.abspath(tables_dir.__file__)) CHOICES = ['list_of_dicts', 'juniper_items'] @@ -178,11 +167,18 @@ def main(): ), supports_check_mode=False) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + import jnpr.junos.op as tables_dir + from jnpr.junos.factory.factory_loader import FactoryLoader + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + logfile = args['logfile'] response_type = args['response_type'] diff --git a/library/junos_install_config b/library/junos_install_config index e66e367c..d199378f 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -173,17 +173,6 @@ from os.path import isfile import os from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.exception import * - from jnpr.junos.utils.config import Config - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False def _load_via_netconf(module): @@ -407,11 +396,18 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.exception import * + from jnpr.junos.utils.config import Config + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + # ------------------------------ # make sure file actually exists # ------------------------------ diff --git a/library/junos_install_os b/library/junos_install_os index fbe994ea..06105bca 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -128,16 +128,6 @@ import logging import re import os from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - def junos_install_os(module, dev): args = module.params @@ -220,7 +210,6 @@ def junos_install_os(module, dev): def main(): - # module = AnsibleModule( argument_spec=dict( host=dict(required=True), @@ -236,11 +225,16 @@ def main(): ), supports_check_mode=True ) + args = module.params - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) - args = module.params # @@@ need to verify that the package file actually exists # @@@ before proceeding. diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 4b93cd95..22e7c7c9 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -123,21 +123,8 @@ from distutils.version import LooseVersion import logging from lxml import etree from lxml.builder import E - -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - from jnpr.jsnapy import SnapAdmin - import os - import time - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError as ex: - HAS_PYEZ = False - +import os +import time def jsnap_selection(dev, module): @@ -195,12 +182,19 @@ def main(): required_one_of=[['test_files', 'config_file']], supports_check_mode=False) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params results = {} + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + from jnpr.jsnapy import SnapAdmin + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_ping b/library/junos_ping index 37a122ba..e696cd11 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -136,15 +136,6 @@ import sys import re from lxml import etree from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False def main(): module = AnsibleModule( @@ -166,12 +157,17 @@ def main(): supports_check_mode=False ) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - results = {} m_args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + # Open connection to device dev = Device( m_args['host'], diff --git a/library/junos_rollback b/library/junos_rollback index 90615f3f..9eb521ab 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -110,19 +110,6 @@ EXAMPLES = ''' from distutils.version import LooseVersion import logging -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.utils.config import Config - from jnpr.junos.exception import CommitError, LockError, UnlockError, RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -139,12 +126,19 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params in_check_mode = module.check_mode + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.utils.config import Config + from jnpr.junos.exception import CommitError, LockError, UnlockError, RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + results = dict(changed=False) logfile = args['logfile'] diff --git a/library/junos_rpc b/library/junos_rpc index 0bcec7ae..e9fc4bdc 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -165,7 +165,6 @@ import os import sys import logging import re -from jnpr.junos import Device from lxml import etree def junos_rpc(module, dev): @@ -243,6 +242,15 @@ def main(): supports_check_mode=False) m_args = module.params + + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + # ----------- # via NETCONF # ----------- diff --git a/library/junos_shutdown b/library/junos_shutdown index 599b06e1..c6fa09da 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -86,18 +86,6 @@ EXAMPLES = ''' ''' from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.utils.sw import SW - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -111,8 +99,14 @@ def main(): ), supports_check_mode=False) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + try: + from jnpr.junos import Device + from jnpr.junos.utils.sw import SW + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) args = module.params if args['shutdown'] != 'shutdown': diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 78c86eea..2c20ee64 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -162,8 +162,8 @@ def main(): from jnpr.junos.version import VERSION if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], gather_facts=('false')) try: diff --git a/library/junos_zeroize b/library/junos_zeroize index fa272734..663dd8f8 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -154,8 +154,8 @@ def main(): from jnpr.junos.version import VERSION if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port']) try: From b5d8877813344567424480d4052eefa4764108cf Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Wed, 14 Sep 2016 20:30:01 +0530 Subject: [PATCH 088/426] ImportError to show proper message (#159) --- library/junos_cli | 24 +++++++++--------------- library/junos_commit | 29 ++++++++++++----------------- library/junos_get_config | 25 ++++++++++--------------- library/junos_get_facts | 7 +++---- library/junos_get_table | 24 ++++++++++-------------- library/junos_install_config | 24 ++++++++++-------------- library/junos_install_os | 22 ++++++++-------------- library/junos_jsnapy | 30 ++++++++++++------------------ library/junos_ping | 20 ++++++++------------ library/junos_rollback | 26 ++++++++++---------------- library/junos_rpc | 10 +++++++++- library/junos_shutdown | 22 ++++++++-------------- library/junos_srx_cluster | 4 ++-- library/junos_zeroize | 4 ++-- 14 files changed, 113 insertions(+), 158 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index df3d90f9..b5192554 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -107,18 +107,6 @@ import logging from lxml import etree from lxml.builder import E -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -134,11 +122,17 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_commit b/library/junos_commit index 6b62e39f..0e40c2b9 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -94,22 +94,10 @@ EXAMPLES = ''' logfile=changes.log comment="Non load commit" ''' + from distutils.version import LooseVersion import logging -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.utils.config import Config - from jnpr.junos.exception import CommitError, LockError, UnlockError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -124,14 +112,21 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params in_check_mode = module.check_mode - results = dict(changed=False) + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.utils.config import Config + from jnpr.junos.exception import CommitError, LockError, UnlockError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_get_config b/library/junos_get_config index 5e562550..4e09e90b 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -111,18 +111,6 @@ from distutils.version import LooseVersion import logging from lxml import etree -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -138,10 +126,17 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + results = {} logfile = args['logfile'] diff --git a/library/junos_get_facts b/library/junos_get_facts index 70ffffc3..7fee664c 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -112,10 +112,9 @@ EXAMPLES = ''' import os import json - +from distutils.version import LooseVersion def main(): - from distutils.version import LooseVersion module = AnsibleModule( argument_spec=dict( host=dict(required=True), @@ -136,8 +135,8 @@ def main(): from jnpr.junos.version import VERSION if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) # ----------- # via NETCONF # ----------- diff --git a/library/junos_get_table b/library/junos_get_table index 07e7ee98..7c0f79ba 100644 --- a/library/junos_get_table +++ b/library/junos_get_table @@ -120,19 +120,8 @@ import logging import os from lxml import etree from lxml.builder import E -from jnpr.junos.factory.factory_loader import FactoryLoader import yaml -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - import jnpr.junos.op as tables_dir - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False TABLE_PATH = os.path.dirname(os.path.abspath(tables_dir.__file__)) CHOICES = ['list_of_dicts', 'juniper_items'] @@ -178,11 +167,18 @@ def main(): ), supports_check_mode=False) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + import jnpr.junos.op as tables_dir + from jnpr.junos.factory.factory_loader import FactoryLoader + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + logfile = args['logfile'] response_type = args['response_type'] diff --git a/library/junos_install_config b/library/junos_install_config index 52551e13..b2756dfb 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -179,17 +179,6 @@ from os.path import isfile import os from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.exception import * - from jnpr.junos.utils.config import Config - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False def _load_via_netconf(module): @@ -419,11 +408,18 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.exception import * + from jnpr.junos.utils.config import Config + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + # ------------------------------ # make sure file actually exists # ------------------------------ diff --git a/library/junos_install_os b/library/junos_install_os index fbe994ea..06105bca 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -128,16 +128,6 @@ import logging import re import os from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - def junos_install_os(module, dev): args = module.params @@ -220,7 +210,6 @@ def junos_install_os(module, dev): def main(): - # module = AnsibleModule( argument_spec=dict( host=dict(required=True), @@ -236,11 +225,16 @@ def main(): ), supports_check_mode=True ) + args = module.params - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) - args = module.params # @@@ need to verify that the package file actually exists # @@@ before proceeding. diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 4b93cd95..22e7c7c9 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -123,21 +123,8 @@ from distutils.version import LooseVersion import logging from lxml import etree from lxml.builder import E - -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - from jnpr.jsnapy import SnapAdmin - import os - import time - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError as ex: - HAS_PYEZ = False - +import os +import time def jsnap_selection(dev, module): @@ -195,12 +182,19 @@ def main(): required_one_of=[['test_files', 'config_file']], supports_check_mode=False) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params results = {} + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + from jnpr.jsnapy import SnapAdmin + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_ping b/library/junos_ping index 37a122ba..e696cd11 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -136,15 +136,6 @@ import sys import re from lxml import etree from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False def main(): module = AnsibleModule( @@ -166,12 +157,17 @@ def main(): supports_check_mode=False ) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - results = {} m_args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + # Open connection to device dev = Device( m_args['host'], diff --git a/library/junos_rollback b/library/junos_rollback index 90615f3f..9eb521ab 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -110,19 +110,6 @@ EXAMPLES = ''' from distutils.version import LooseVersion import logging -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.utils.config import Config - from jnpr.junos.exception import CommitError, LockError, UnlockError, RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -139,12 +126,19 @@ def main(): ), supports_check_mode=True) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - args = module.params in_check_mode = module.check_mode + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.utils.config import Config + from jnpr.junos.exception import CommitError, LockError, UnlockError, RpcError + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + results = dict(changed=False) logfile = args['logfile'] diff --git a/library/junos_rpc b/library/junos_rpc index 1e6e9d78..36305753 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -165,7 +165,6 @@ import os import sys import logging import re -from jnpr.junos import Device from lxml import etree def junos_rpc(module, dev): @@ -243,6 +242,15 @@ def main(): supports_check_mode=False) m_args = module.params + + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + # ----------- # via NETCONF # ----------- diff --git a/library/junos_shutdown b/library/junos_shutdown index 768c1bb0..242b7c64 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -97,18 +97,6 @@ EXAMPLES = ''' ''' from distutils.version import LooseVersion -try: - from jnpr.junos import Device - from jnpr.junos.utils.sw import SW - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - HAS_PYEZ = False - else: - HAS_PYEZ = True -except ImportError: - HAS_PYEZ = False - - def main(): module = AnsibleModule( @@ -124,8 +112,14 @@ def main(): ), supports_check_mode=False) - if not HAS_PYEZ: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + try: + from jnpr.junos import Device + from jnpr.junos.utils.sw import SW + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) args = module.params if args['shutdown'] != 'shutdown': diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 78c86eea..2c20ee64 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -162,8 +162,8 @@ def main(): from jnpr.junos.version import VERSION if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], gather_facts=('false')) try: diff --git a/library/junos_zeroize b/library/junos_zeroize index fa272734..663dd8f8 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -154,8 +154,8 @@ def main(): from jnpr.junos.version import VERSION if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError: - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port']) try: From 670e9c240d7fa9e33b30af8cc4a4b93f88c59a78 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Thu, 15 Sep 2016 12:13:28 +0530 Subject: [PATCH 089/426] moved import to proper location in junos_install_config --- library/junos_install_config | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index b2756dfb..8040a014 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -184,6 +184,16 @@ from distutils.version import LooseVersion def _load_via_netconf(module): args = module.params + try: + from jnpr.junos import Device + from jnpr.junos.exception import * + from jnpr.junos.utils.config import Config + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, @@ -410,16 +420,6 @@ def main(): args = module.params - try: - from jnpr.junos import Device - from jnpr.junos.exception import * - from jnpr.junos.utils.config import Config - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - # ------------------------------ # make sure file actually exists # ------------------------------ From c18a15554cfbc448448c6918eb2d5e78d31f4be8 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Thu, 15 Sep 2016 16:58:12 +0530 Subject: [PATCH 090/426] Adding console connection support from pyez 2.0 --- library/junos_cli | 21 ++++++++++++++++++--- library/junos_get_facts | 32 ++++++++++++++++++++++++++------ library/junos_install_config | 27 +++++++++++++++++++++------ library/junos_rpc | 24 ++++++++++++++++++++---- 4 files changed, 85 insertions(+), 19 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index b5192554..7109ad2a 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -66,6 +66,11 @@ options: - TCP port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None timeout: description: - Set the NETCONF RPC timeout. Set this value to accommodate Cli @@ -101,6 +106,15 @@ EXAMPLES = ''' logfile: cli.log dest: "{{ inventory_hostname }}.xml" format: xml + +# Run cli over console connection via PyEZ +- junos_cli: + cli="show chassis hardware" + host={{ inventory_hostname }} + port=7001 + mode='telnet' + dest="{{ inventory_hostname }}.xml" + format='xml' ''' from distutils.version import LooseVersion import logging @@ -115,6 +129,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), timeout=dict(required=False, type='int', default=0), logfile=dict(required=False, default=None), dest=dict(required=False, default=None), @@ -128,8 +143,8 @@ def main(): from jnpr.junos import Device from jnpr.junos.version import VERSION from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -143,7 +158,7 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], gather_facts=False).open() + port=args['port'], mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_get_facts b/library/junos_get_facts index 7fee664c..3f2d31f1 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -45,7 +45,7 @@ description: and returns the information as a JSON dictionary. The information is similar to facts gathered by other IT frameworks. requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -83,9 +83,14 @@ options: default: None port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None ''' EXAMPLES = ''' @@ -108,6 +113,19 @@ EXAMPLES = ''' - name: version debug: msg="{{ junos.facts.version }}" + +# retrieve facts using console connection via PyEZ + +- junos_get_facts: + host={{ inventory_hostname }} + mode="telnet" + port=7016 + register: junos + +# access the facts + +- name: version + debug: msg="{{ junos.facts }}" ''' import os @@ -123,7 +141,8 @@ def main(): savedir=dict(required=False, default=None), user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), - port=dict(required=False, default=830)), + port=dict(required=False, default=830), + mode=dict(required=False, default=None)), supports_check_mode=True) m_args = module.params @@ -133,14 +152,15 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) # ----------- # via NETCONF # ----------- - dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], port=m_args['port']) + dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], port=m_args['port'], + mode=m_args['mode'], gather_facts=True) try: dev.open() except Exception as err: diff --git a/library/junos_install_config b/library/junos_install_config index 8040a014..89c9b612 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -51,7 +51,7 @@ description: NETCONF include ASCII text, Junos XML elements, and Junos OS B(set) commands. Configurations performed through the console must only use ASCII text formatting. requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -130,9 +130,14 @@ options: default: None port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None confirm: description: - Provide a confirm in minutes to the commit of the configuration @@ -172,6 +177,14 @@ EXAMPLES = ''' host={{ inventory_hostname }} file=snmp.conf replace=yes + +# Install/load config using Console connection via PyEZ + +- junos_install_config: + host={{ inventory_hostname }} + port=7016 + mode='telnet' + file=default_new_switch.conf ''' import logging @@ -189,8 +202,8 @@ def _load_via_netconf(module): from jnpr.junos.exception import * from jnpr.junos.utils.config import Config from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -212,8 +225,9 @@ def _load_via_netconf(module): logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port']) - dev.open(gather_facts=False) + dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], + mode=args['mode'], gather_facts=False) + dev.open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) @@ -413,6 +427,7 @@ def main(): timeout=dict(required=False, default=0), comment=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), confirm=dict(required=False, default=None), check_commit_wait=dict(required=False, default=None) ), diff --git a/library/junos_rpc b/library/junos_rpc index 36305753..1ac82fda 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -41,7 +41,7 @@ short_description: run given rpc description: - run given rpc requirements: - - junos-eznc >= 1.0.0 + - junos-eznc >= 2.0.0 options: host: description: @@ -62,6 +62,11 @@ options: - TCP port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None logfile: description: - Path on the local server where the progress status is logged @@ -130,6 +135,15 @@ EXAMPLES = ''' rpc=get-config dest=get_config.conf +# Example to fetch device configuration over console connection +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + port=7005 + mode='telnet' + rpc=get-config + dest=get_config.conf + # Example to fetch device configuration - name: Get Device Configuration for interface junos_rpc: @@ -165,6 +179,7 @@ import os import sys import logging import re +from distutils.version import LooseVersion from lxml import etree def junos_rpc(module, dev): @@ -233,6 +248,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), timeout=dict(required=False, type='int', default=0), rpc=dict(required=True, default=None), kwargs=dict(required=False, default=None), @@ -246,8 +262,8 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -255,7 +271,7 @@ def main(): # via NETCONF # ----------- dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], - port=m_args['port'], gather_facts=False) + port=m_args['port'], mode=m_args['mode'], gather_facts=False) try: dev.open() except Exception as err: From b943b19f02eee2597e9d8a3b4c073a866645242f Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Thu, 15 Sep 2016 16:59:20 +0530 Subject: [PATCH 091/426] added missing import --- library/junos_rpc | 1 + 1 file changed, 1 insertion(+) diff --git a/library/junos_rpc b/library/junos_rpc index 36305753..80d4efe4 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -165,6 +165,7 @@ import os import sys import logging import re +from distutils.version import LooseVersion from lxml import etree def junos_rpc(module, dev): From 6be4ee0cd8100162158e010871df94395d740d10 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Thu, 15 Sep 2016 22:51:11 +0530 Subject: [PATCH 092/426] console connection option using mode param --- library/junos_cli | 4 ++-- library/junos_ping | 31 ++++++++++++++++++++++++++----- library/junos_rpc | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index 7109ad2a..a72e0fea 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -41,7 +41,7 @@ short_description: Execute CLI on device and save the output locally description: - Execute CLI on device and save the output locally on a file requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -63,7 +63,7 @@ options: default: assumes ssh-key active port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 mode: diff --git a/library/junos_ping b/library/junos_ping index e696cd11..62e91b23 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -41,7 +41,7 @@ short_description: execute ping on junos devices description: - execute ping on junos devices requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -59,9 +59,14 @@ options: default: assumes ssh-key active port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None dest_ip: description: - Destination ip @@ -117,6 +122,17 @@ EXAMPLES = ''' passwd={{ ansible_ssh_pass }} dest_ip=8.8.8.8 +# ping over console connection + tasks: + - name: "Execute ping peer" + junos_ping: + host={{ inventory_hostname }} + port=2011 + mode='telnet' + user={{ ansible_ssh_user }} + passwd={{ ansible_ssh_pass }} + dest_ip=8.8.8.8 + # Using loop and more parameters tasks: - name: "Execute ping peer" @@ -144,6 +160,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), timeout=dict(required=False, default=0), dest_ip=dict(required=True, default=None), source_ip=dict(required=False, default=None), @@ -163,8 +180,8 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -174,6 +191,7 @@ def main(): user=m_args['user'], passwd=m_args['passwd'], port=m_args['port'], + mode=m_args['mode'], gather_facts=False) try: @@ -249,7 +267,10 @@ def main(): dev.close() module.exit_json(**results) - results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss.strip()), str(packets_sent.strip()), str(responses_received.strip())) + packet_loss = packet_loss.strip() + packets_sent = packets_sent.strip() + responses_received = responses_received.strip() + results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packets_sent), str(responses_received)) results['packet_loss'] = packet_loss results['packets_sent'] = packets_sent results['packets_received'] = responses_received diff --git a/library/junos_rpc b/library/junos_rpc index 1ac82fda..774adc0e 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -59,7 +59,7 @@ options: default: assumes ssh-key active port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 mode: From c9466d5b379c91378334f030bc2a61a04907c35f Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 16 Sep 2016 16:00:52 +0530 Subject: [PATCH 093/426] to summport functionality over console connection too with PyEZ 2.0 support --- library/junos_commit | 24 +++++++++++++++++----- library/junos_get_config | 25 ++++++++++++++++++----- library/junos_get_table | 43 +++++++++++++++++++++++++++------------ library/junos_jsnapy | 36 ++++++++++++++++++++------------ library/junos_rollback | 28 ++++++++++++++++++++----- library/junos_shutdown | 25 ++++++++++++++++++----- library/junos_srx_cluster | 28 ++++++++++++++++++++----- library/junos_zeroize | 26 +++++++++++++++++------ 8 files changed, 178 insertions(+), 57 deletions(-) diff --git a/library/junos_commit b/library/junos_commit index 0e40c2b9..e280a41c 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -42,7 +42,7 @@ short_description: Execute commit on device description: - Execute a Commit on a device running Junos independently of loading a configuration requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -60,9 +60,14 @@ options: default: assumes ssh-key active port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None timeout: description: - Extend the NETCONF RPC timeout beyond the default value of @@ -93,6 +98,14 @@ EXAMPLES = ''' host: "{{ inventory_hostname }}" logfile=changes.log comment="Non load commit" + +# over console connection via PyEZ +- name: junos commit + junos_commit: + host={{ inventory_hostname }} + port=7016 + mode='telnet' + comment="commit with console connection via PyEZ" ''' from distutils.version import LooseVersion @@ -105,6 +118,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), timeout=dict(required=False, default=0), logfile=dict(required=False, default=None), comment=dict(required=False, default=None), @@ -121,8 +135,8 @@ def main(): from jnpr.junos.version import VERSION from jnpr.junos.utils.config import Config from jnpr.junos.exception import CommitError, LockError, UnlockError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -137,7 +151,7 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], gather_facts=False).open() + port=args['port'], mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_get_config b/library/junos_get_config index 4e09e90b..a6bfed85 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -43,7 +43,7 @@ description: - Retrieve the configuration of a device running Junos and save it to a file. B(Note) unicode chars will be converted to '??' as also done in PyEZ requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -61,9 +61,14 @@ options: default: assumes ssh-key active port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None logfile: description: - Path on the local server where the progress status is logged @@ -106,6 +111,15 @@ EXAMPLES = ''' format: xml filter: "interfaces" options: {inherit: inherit, groups: groups} + +# over console connection via PyEZ +- junos_get_config: + host: "{{ inventory_hostname }}" + logfile: get_config.log + dest: "{{ inventory_hostname }}.xml" + port: 7016 + mode: 'telnet' + format: xml ''' from distutils.version import LooseVersion import logging @@ -118,6 +132,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), logfile=dict(required=False, default=None), dest=dict(required=True, default=None), format=dict(required=False, choices=['text', 'xml'], default='text'), @@ -132,8 +147,8 @@ def main(): from jnpr.junos import Device from jnpr.junos.version import VERSION from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -149,7 +164,7 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], gather_facts=False).open() + port=args['port'], mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_get_table b/library/junos_get_table index 7c0f79ba..999dc9fc 100644 --- a/library/junos_get_table +++ b/library/junos_get_table @@ -24,7 +24,7 @@ short_description: Retrieve data from a Junos device using Tables/Views description: - Retrieve data from a Junos device using Tables/Views requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -46,12 +46,16 @@ options: for debugging purposes required: false default: None - port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None table: description: - Name of PyEZ Table @@ -86,6 +90,14 @@ EXAMPLES = ''' # GET NEIGHBOR INFO USING CUSTOM LLDP TABLE IN CUSTOM PATH - junos_get_table: table=NTCNeighborTable path=tables/ file=ntclldp.yaml host={{ inventory_hostname }} user={{ un }} passwd={{ pwd }} +# +- name: Table/View example via console connection + junos_get_table: + table=RouteTable + file=routes.yml + host={{ inventory_hostname }} + port=7016 + mode='telnet' ''' RETURN = ''' @@ -122,6 +134,17 @@ from lxml import etree from lxml.builder import E import yaml +import_err_message = None +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + import jnpr.junos.op as tables_dir + from jnpr.junos.factory.factory_loader import FactoryLoader + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + import_err_message = 'junos-eznc >= 2.0.0 is required for this module' +except ImportError as ex: + import_err_message = 'ImportError: %s' % ex.message + TABLE_PATH = os.path.dirname(os.path.abspath(tables_dir.__file__)) CHOICES = ['list_of_dicts', 'juniper_items'] @@ -158,6 +181,7 @@ def main(): default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), logfile=dict(required=False, default=None), file=dict(required=True, default=None), path=dict(required=False, default=TABLE_PATH), @@ -169,15 +193,8 @@ def main(): args = module.params - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - import jnpr.junos.op as tables_dir - from jnpr.junos.factory.factory_loader import FactoryLoader - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) + if import_err_message is not None: + module.fail_json(msg=import_err_message) logfile = args['logfile'] response_type = args['response_type'] @@ -195,7 +212,7 @@ def main(): args['port'])) try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], gather_facts=False).open() + port=args['port'], mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 22e7c7c9..4ed77edd 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -45,7 +45,7 @@ Short_description: Integrate JSnapy to ansible description: - Execute JSnapy module from Ansible requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -63,9 +63,14 @@ options: default: assumes ssh-key active port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None logfile: description: - Path on the local server where the progress status is logged @@ -126,6 +131,17 @@ from lxml.builder import E import os import time +import_err_message = None +try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + from jnpr.junos.exception import RpcError + from jnpr.jsnapy import SnapAdmin + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + import_err_message = 'junos-eznc >= 2.0.0 is required for this module' +except ImportError as ex: + import_err_message = 'ImportError: %s' % ex.message + def jsnap_selection(dev, module): args = module.params @@ -172,6 +188,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), logfile=dict(required=False, default=None), test_files=dict(required=False, type='list', default=None), config_file=dict(required=False, default=None), @@ -185,27 +202,20 @@ def main(): args = module.params results = {} - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - from jnpr.jsnapy import SnapAdmin - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) + if import_err_message is not None: + module.fail_json(msg=import_err_message) logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'JSnapy:' + args['host'] + logging.getLogger().name = 'JSNAPy:' + args['host'] logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], gather_facts=False).open() + port=args['port'], mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_rollback b/library/junos_rollback index 9eb521ab..5090365f 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -42,7 +42,7 @@ short_description: Rollback configuration of device description: - Rollback the configuration of a device running Junos requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -60,9 +60,14 @@ options: default: assumes ssh-key active port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None logfile: description: - Path on the local server where the progress status is logged @@ -106,7 +111,19 @@ EXAMPLES = ''' rollback=1 comment="Rolled back by Ansible" confirm=5 + +# over console connection via PyEZ +- junos_rollback: + host: "{{ inventory_hostname }}" + logfile=rollback.log + diffs_file=rollback.diff + rollback=1 + comment="Rolled back by Ansible" + confirm=5 + mode='telnet' + port=7015 ''' + from distutils.version import LooseVersion import logging @@ -117,6 +134,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), logfile=dict(required=False, default=None), rollback=dict(required=True, default=None), timeout=dict(required=False, default=0), @@ -134,8 +152,8 @@ def main(): from jnpr.junos.version import VERSION from jnpr.junos.utils.config import Config from jnpr.junos.exception import CommitError, LockError, UnlockError, RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -157,7 +175,7 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], gather_facts=False).open() + port=args['port'], mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_shutdown b/library/junos_shutdown index 242b7c64..aaf11931 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -44,7 +44,7 @@ description: This is equivalent to executing either the Junos OS B(request system power-off) or B(request system reboot) operational command. requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -84,9 +84,14 @@ options: default: None port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None ''' EXAMPLES = ''' @@ -94,6 +99,14 @@ EXAMPLES = ''' host={{ inventory_hostname }} shutdown="shutdown" reboot=yes + +# over console connection via PyEZ +- junos_shutdown: + host={{ inventory_hostname }} + shutdown="shutdown" + reboot=yes + mode='telnet' + port=7016 ''' from distutils.version import LooseVersion @@ -107,6 +120,7 @@ def main(): passwd=dict(required=False, default=None), reboot=dict(required=False, type='bool', choices=BOOLEANS, default=False), port=dict(required=False, default=830), + mode=dict(required=False, default=None), in_min=dict(required=False, default=0), at=dict(required=False, type='str', default=None) ), @@ -116,8 +130,8 @@ def main(): from jnpr.junos import Device from jnpr.junos.utils.sw import SW from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) @@ -134,7 +148,8 @@ def main(): # --- UNREACHABLE --- try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port']).open() + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) module.fail_json(msg=msg) diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 2c20ee64..3c3ceb03 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -44,7 +44,7 @@ description: The device must be capable of forming an srx cluster and have the correct cables installed. requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 options: host: description: @@ -52,9 +52,14 @@ options: required: true port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None console: description: - SERIAL or TERMINAL-SERVER port setting, per use with @@ -114,7 +119,18 @@ EXAMPLES = ''' passwd=password123 cluster_enable=false logfile=cluster.log + +# over console connection via PyEZ +-junos_srx_cluster: + host={{ inventory_hostname }} + user=rick + passwd=password123 + mode="telnet" + port=7032 + cluster_enable=true + logfile=cluster.log ''' + import os import logging from distutils.version import LooseVersion @@ -128,6 +144,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), port=dict(required=False, default=830), + mode=dict(required=False, default=None), console=dict(required=False, default=None), # param to netconify cluster_enable=dict(required=True, type='bool', choices=BOOLEANS, default=None), cluster_id=dict(required=False, default=None), @@ -160,12 +177,13 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], gather_facts=('false')) + dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], + mode=args['mode'], gather_facts=False) try: logging.info('LOGIN: host={0} port={1}'.format(args['host'], args['port'])) dev.open() diff --git a/library/junos_zeroize b/library/junos_zeroize index 663dd8f8..854a6402 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -52,7 +52,7 @@ description: configuration. After the reboot, you must log in through the console as root in order to access the device. requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.0.0 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -90,9 +90,14 @@ options: default: None port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 + mode: + description: + - mode of console connection (telnet/serial) + required: false + default: None notes: - You B(MUST) either use the I(host) option or the I(console) option to designate how the device is accessed. @@ -102,6 +107,13 @@ EXAMPLES = ''' - junos_zeroize: host={{ inventory_hostname }} zeroize="zeroize" + +# over console connection via PyEZ +- junos_zeroize: + host={{ inventory_hostname }} + zeroize="zeroize" + port=7011 + mode="telnet" ''' import os @@ -118,7 +130,8 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None), logfile=dict(required=False, default=None), - port=dict(required=False, default=830) + port=dict(required=False, default=830), + mode=dict(required=False, default=None) ), supports_check_mode=False) @@ -152,12 +165,13 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port']) + dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], + mode=args['mode'], gather_facts=False) try: use_notifier(None, 'LOGIN', 'host={0}'.format(args['host'])) dev.open() From f4f7936fc5797b571408072c386b0d27c2fd54e2 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 19 Sep 2016 21:30:20 +0530 Subject: [PATCH 094/426] PyEZ prereq version reverted back to 1.2.2 --- library/junos_cli | 6 +++--- library/junos_commit | 6 +++--- library/junos_get_config | 6 +++--- library/junos_get_facts | 6 +++--- library/junos_get_table | 6 +++--- library/junos_install_config | 6 +++--- library/junos_jsnapy | 6 +++--- library/junos_ping | 6 +++--- library/junos_rollback | 6 +++--- library/junos_rpc | 6 +++--- library/junos_shutdown | 6 +++--- library/junos_srx_cluster | 6 +++--- library/junos_zeroize | 6 +++--- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index a72e0fea..444da70c 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -41,7 +41,7 @@ short_description: Execute CLI on device and save the output locally description: - Execute CLI on device and save the output locally on a file requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -143,8 +143,8 @@ def main(): from jnpr.junos import Device from jnpr.junos.version import VERSION from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_commit b/library/junos_commit index e280a41c..f695104d 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -42,7 +42,7 @@ short_description: Execute commit on device description: - Execute a Commit on a device running Junos independently of loading a configuration requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -135,8 +135,8 @@ def main(): from jnpr.junos.version import VERSION from jnpr.junos.utils.config import Config from jnpr.junos.exception import CommitError, LockError, UnlockError - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_get_config b/library/junos_get_config index a6bfed85..4c245fa0 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -43,7 +43,7 @@ description: - Retrieve the configuration of a device running Junos and save it to a file. B(Note) unicode chars will be converted to '??' as also done in PyEZ requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -147,8 +147,8 @@ def main(): from jnpr.junos import Device from jnpr.junos.version import VERSION from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_get_facts b/library/junos_get_facts index 3f2d31f1..112685a0 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -45,7 +45,7 @@ description: and returns the information as a JSON dictionary. The information is similar to facts gathered by other IT frameworks. requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -152,8 +152,8 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) # ----------- diff --git a/library/junos_get_table b/library/junos_get_table index 999dc9fc..d147a9b1 100644 --- a/library/junos_get_table +++ b/library/junos_get_table @@ -24,7 +24,7 @@ short_description: Retrieve data from a Junos device using Tables/Views description: - Retrieve data from a Junos device using Tables/Views requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -140,8 +140,8 @@ try: from jnpr.junos.version import VERSION import jnpr.junos.op as tables_dir from jnpr.junos.factory.factory_loader import FactoryLoader - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - import_err_message = 'junos-eznc >= 2.0.0 is required for this module' + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + import_err_message = 'junos-eznc >= 1.2.2 is required for this module' except ImportError as ex: import_err_message = 'ImportError: %s' % ex.message diff --git a/library/junos_install_config b/library/junos_install_config index 89c9b612..ff97a679 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -51,7 +51,7 @@ description: NETCONF include ASCII text, Junos XML elements, and Junos OS B(set) commands. Configurations performed through the console must only use ASCII text formatting. requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -202,8 +202,8 @@ def _load_via_netconf(module): from jnpr.junos.exception import * from jnpr.junos.utils.config import Config from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 4ed77edd..2ca5f9ca 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -45,7 +45,7 @@ Short_description: Integrate JSnapy to ansible description: - Execute JSnapy module from Ansible requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -137,8 +137,8 @@ try: from jnpr.junos.version import VERSION from jnpr.junos.exception import RpcError from jnpr.jsnapy import SnapAdmin - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - import_err_message = 'junos-eznc >= 2.0.0 is required for this module' + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + import_err_message = 'junos-eznc >= 1.2.2 is required for this module' except ImportError as ex: import_err_message = 'ImportError: %s' % ex.message diff --git a/library/junos_ping b/library/junos_ping index 62e91b23..ecb4f0a9 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -41,7 +41,7 @@ short_description: execute ping on junos devices description: - execute ping on junos devices requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -180,8 +180,8 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_rollback b/library/junos_rollback index 5090365f..0eba52ad 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -42,7 +42,7 @@ short_description: Rollback configuration of device description: - Rollback the configuration of a device running Junos requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -152,8 +152,8 @@ def main(): from jnpr.junos.version import VERSION from jnpr.junos.utils.config import Config from jnpr.junos.exception import CommitError, LockError, UnlockError, RpcError - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_rpc b/library/junos_rpc index 774adc0e..578e98e6 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -41,7 +41,7 @@ short_description: run given rpc description: - run given rpc requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -262,8 +262,8 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_shutdown b/library/junos_shutdown index aaf11931..92b616bc 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -44,7 +44,7 @@ description: This is equivalent to executing either the Junos OS B(request system power-off) or B(request system reboot) operational command. requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -130,8 +130,8 @@ def main(): from jnpr.junos import Device from jnpr.junos.utils.sw import SW from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 3c3ceb03..f2b9dbed 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -44,7 +44,7 @@ description: The device must be capable of forming an srx cluster and have the correct cables installed. requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 options: host: description: @@ -177,8 +177,8 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) diff --git a/library/junos_zeroize b/library/junos_zeroize index 854a6402..1d3c4065 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -52,7 +52,7 @@ description: configuration. After the reboot, you must log in through the console as root in order to access the device. requirements: - - junos-eznc >= 2.0.0 + - junos-eznc >= 1.2.2 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -165,8 +165,8 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) From 9b20a43cfb752c2dcded9ec9b77acadead12d7b4 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 19 Sep 2016 23:05:29 +0530 Subject: [PATCH 095/426] added error handling in case pyez version<2.0 and console connection is being tried --- library/junos_cli | 3 +++ library/junos_commit | 2 ++ library/junos_get_config | 3 +++ library/junos_get_facts | 3 +++ library/junos_get_table | 3 +++ library/junos_install_config | 3 +++ library/junos_install_os | 5 ++++- library/junos_jsnapy | 5 ++++- library/junos_ping | 3 +++ library/junos_rollback | 3 +++ library/junos_rpc | 2 ++ library/junos_shutdown | 6 +++++- library/junos_srx_cluster | 3 +++ library/junos_zeroize | 3 +++ 14 files changed, 44 insertions(+), 3 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index 444da70c..d180d5ea 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -148,6 +148,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_commit b/library/junos_commit index f695104d..e67aeff6 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -140,6 +140,8 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') logfile = args['logfile'] if logfile is not None: diff --git a/library/junos_get_config b/library/junos_get_config index 4c245fa0..de958949 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -152,6 +152,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') + results = {} logfile = args['logfile'] diff --git a/library/junos_get_facts b/library/junos_get_facts index 112685a0..d601f7c6 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -156,6 +156,9 @@ def main(): module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + + if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') # ----------- # via NETCONF # ----------- diff --git a/library/junos_get_table b/library/junos_get_table index d147a9b1..d61dff10 100644 --- a/library/junos_get_table +++ b/library/junos_get_table @@ -196,6 +196,9 @@ def main(): if import_err_message is not None: module.fail_json(msg=import_err_message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') + logfile = args['logfile'] response_type = args['response_type'] diff --git a/library/junos_install_config b/library/junos_install_config index ff97a679..3d233447 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -207,6 +207,9 @@ def _load_via_netconf(module): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_install_os b/library/junos_install_os index 06105bca..876b8482 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -111,7 +111,7 @@ options: default: None port: description: - - TCP port number to use when connecting to the device + - port number to use when connecting to the device required: false default: 830 ''' @@ -235,6 +235,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if int(args['port']) == 23 and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for telnet connection.') + # @@@ need to verify that the package file actually exists # @@@ before proceeding. diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 2ca5f9ca..1d797315 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -126,12 +126,12 @@ EXAMPLES = ''' ''' from distutils.version import LooseVersion import logging -from lxml import etree from lxml.builder import E import os import time import_err_message = None + try: from jnpr.junos import Device from jnpr.junos.version import VERSION @@ -205,6 +205,9 @@ def main(): if import_err_message is not None: module.fail_json(msg=import_err_message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') + logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, diff --git a/library/junos_ping b/library/junos_ping index ecb4f0a9..36c24a65 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -185,6 +185,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') + # Open connection to device dev = Device( m_args['host'], diff --git a/library/junos_rollback b/library/junos_rollback index 0eba52ad..9be4e122 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -157,6 +157,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') + results = dict(changed=False) logfile = args['logfile'] diff --git a/library/junos_rpc b/library/junos_rpc index 578e98e6..975eec43 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -267,6 +267,8 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') # ----------- # via NETCONF # ----------- diff --git a/library/junos_shutdown b/library/junos_shutdown index 92b616bc..44eeb1a0 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -126,6 +126,8 @@ def main(): ), supports_check_mode=False) + args = module.params + try: from jnpr.junos import Device from jnpr.junos.utils.sw import SW @@ -135,7 +137,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) - args = module.params + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') + if args['shutdown'] != 'shutdown': module.fail_json(msg='Say "shutdown" to proceed') diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index f2b9dbed..b5ca98a3 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -182,6 +182,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') + dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], mode=args['mode'], gather_facts=False) try: diff --git a/library/junos_zeroize b/library/junos_zeroize index 1d3c4065..7d5a293d 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -170,6 +170,9 @@ def main(): except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) + if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') + dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], mode=args['mode'], gather_facts=False) try: From 6e2fced905fb2d662e8992516c1695ab564cb805 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Tue, 20 Sep 2016 00:01:24 +0530 Subject: [PATCH 096/426] changes as per ydwaraka feedback --- library/junos_cli | 5 +++-- library/junos_commit | 5 +++-- library/junos_get_config | 5 +++-- library/junos_get_facts | 5 +++-- library/junos_get_table | 5 +++-- library/junos_install_config | 5 +++-- library/junos_jsnapy | 3 ++- library/junos_ping | 5 +++-- library/junos_rollback | 5 +++-- library/junos_rpc | 5 +++-- library/junos_shutdown | 5 +++-- library/junos_srx_cluster | 5 +++-- library/junos_zeroize | 5 +++-- 13 files changed, 38 insertions(+), 25 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index d180d5ea..d05cb0a7 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -68,7 +68,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None timeout: @@ -107,7 +108,7 @@ EXAMPLES = ''' dest: "{{ inventory_hostname }}.xml" format: xml -# Run cli over console connection via PyEZ +# Run cli over console server connection using PyEZ >= 2.0 - junos_cli: cli="show chassis hardware" host={{ inventory_hostname }} diff --git a/library/junos_commit b/library/junos_commit index e67aeff6..4800d8fb 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -65,7 +65,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None timeout: @@ -99,7 +100,7 @@ EXAMPLES = ''' logfile=changes.log comment="Non load commit" -# over console connection via PyEZ +# over console server connection using PyEZ >= 2.0 - name: junos commit junos_commit: host={{ inventory_hostname }} diff --git a/library/junos_get_config b/library/junos_get_config index de958949..ee7a0541 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -66,7 +66,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None logfile: @@ -112,7 +113,7 @@ EXAMPLES = ''' filter: "interfaces" options: {inherit: inherit, groups: groups} -# over console connection via PyEZ +# over console server connection using PyEZ >= 2.0 - junos_get_config: host: "{{ inventory_hostname }}" logfile: get_config.log diff --git a/library/junos_get_facts b/library/junos_get_facts index d601f7c6..a8703a94 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -88,7 +88,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None ''' @@ -114,7 +115,7 @@ EXAMPLES = ''' - name: version debug: msg="{{ junos.facts.version }}" -# retrieve facts using console connection via PyEZ +# retrieve facts using console server connection using PyEZ >= 2.0 - junos_get_facts: host={{ inventory_hostname }} diff --git a/library/junos_get_table b/library/junos_get_table index d61dff10..3ded17fe 100644 --- a/library/junos_get_table +++ b/library/junos_get_table @@ -53,7 +53,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None table: @@ -91,7 +92,7 @@ EXAMPLES = ''' - junos_get_table: table=NTCNeighborTable path=tables/ file=ntclldp.yaml host={{ inventory_hostname }} user={{ un }} passwd={{ pwd }} # -- name: Table/View example via console connection +- name: Table/View example via console server connection using PyEZ >= 2.0 junos_get_table: table=RouteTable file=routes.yml diff --git a/library/junos_install_config b/library/junos_install_config index 3d233447..bd3192c0 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -135,7 +135,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None confirm: @@ -178,7 +179,7 @@ EXAMPLES = ''' file=snmp.conf replace=yes -# Install/load config using Console connection via PyEZ +# Install/load config using console server connection using PyEZ >= 2.0 - junos_install_config: host={{ inventory_hostname }} diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 1d797315..0a642d12 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -68,7 +68,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None logfile: diff --git a/library/junos_ping b/library/junos_ping index 36c24a65..b9ae9a1e 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -64,7 +64,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None dest_ip: @@ -122,7 +123,7 @@ EXAMPLES = ''' passwd={{ ansible_ssh_pass }} dest_ip=8.8.8.8 -# ping over console connection +# ping over console server connection using PyEZ >= 2.0 tasks: - name: "Execute ping peer" junos_ping: diff --git a/library/junos_rollback b/library/junos_rollback index 9be4e122..1f9bad0a 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -65,7 +65,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None logfile: @@ -112,7 +113,7 @@ EXAMPLES = ''' comment="Rolled back by Ansible" confirm=5 -# over console connection via PyEZ +# over console server connection using PyEZ >= 2.0 - junos_rollback: host: "{{ inventory_hostname }}" logfile=rollback.log diff --git a/library/junos_rpc b/library/junos_rpc index 975eec43..2008fe9e 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -64,7 +64,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None logfile: @@ -135,7 +136,7 @@ EXAMPLES = ''' rpc=get-config dest=get_config.conf -# Example to fetch device configuration over console connection +# Fetch configuration over console server connection using PyEZ >= 2.0 - name: Get Device Configuration junos_rpc: host={{ inventory_hostname }} diff --git a/library/junos_shutdown b/library/junos_shutdown index 44eeb1a0..12bf793e 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -89,7 +89,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None ''' @@ -100,7 +101,7 @@ EXAMPLES = ''' shutdown="shutdown" reboot=yes -# over console connection via PyEZ +# over console server connection using PyEZ >= 2.0 - junos_shutdown: host={{ inventory_hostname }} shutdown="shutdown" diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index b5ca98a3..ba16191e 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -57,7 +57,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None console: @@ -120,7 +121,7 @@ EXAMPLES = ''' cluster_enable=false logfile=cluster.log -# over console connection via PyEZ +# over console server connection using PyEZ >= 2.0 -junos_srx_cluster: host={{ inventory_hostname }} user=rick diff --git a/library/junos_zeroize b/library/junos_zeroize index 7d5a293d..d6e9bd7f 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -95,7 +95,8 @@ options: default: 830 mode: description: - - mode of console connection (telnet/serial) + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. required: false default: None notes: @@ -108,7 +109,7 @@ EXAMPLES = ''' host={{ inventory_hostname }} zeroize="zeroize" -# over console connection via PyEZ +# over console server connection using PyEZ >= 2.0 - junos_zeroize: host={{ inventory_hostname }} zeroize="zeroize" From 313803729fac324ae276a657e6a5d7c7987fe6b4 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Tue, 20 Sep 2016 22:17:57 +0530 Subject: [PATCH 097/426] getting ready for next release --- README.md | 2 ++ setup.py | 1 - version.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1309f478..23ba346e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ Juniper Networks provides support for using Ansible to deploy devices running th - junos_shutdown — Shut down or reboot a device running Junos OS. - junos_srx_cluster — Enable/Disable cluster mode for SRX devices - junos_zeroize — Remove all configuration information on the Routing Engines and reset all key values on a device. +- junos_get_table - Retrieve data from a Junos device using Tables/Views +- junos_ping - execute ping on junos devices ## DOCUMENTATION diff --git a/setup.py b/setup.py index 5a14e204..b7f41a84 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,6 @@ keywords="Ansible Junos NETCONF networking automation", url="http://www.github.com/Juniper/ansible-junos-stdlib", packages=['library'], - #scripts = ['library/junos_get_facts', 'library/junos_install_config'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', diff --git a/version.py b/version.py index 83aac6f6..0a1c4786 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.3.1" -DATE = "2016-Feb-26" +VERSION = "1.4.0" +DATE = "2016-Sept-21" From 169341f221738832dc41bec3a42c175c858f1b06 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 20 Sep 2016 22:15:29 +0400 Subject: [PATCH 098/426] add more examples to junos_jsnapy and add disclaimer regarding behavior --- library/junos_jsnapy | 46 +++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index b0550d9f..7e54893d 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -36,14 +36,18 @@ DOCUMENTATION = ''' --- module: junos_jsnapy -author: Roslan Zaki, Juniper Networks -version_added: "1.0.0" +author: Roslan Zaki & Damien Garros, Juniper Networks +version_added: "1.4.0" version_control: 13-Apr-2016 v1.0.0 - Basic working model -Short_description: Integrate JSnapy to ansible +Short_description: Integrate JSnapy to ansible. description: - - Execute JSnapy module from Ansible + - Execute JSnapy test from Ansible. + Attention, to not break Ansible behavior, this module only report "failed" + if the module itself fails, not if a test fails. + To check the test results you need to subscribe to the result and assert + the returned value requirements: - junos-eznc >= 1.2.2 options: @@ -114,16 +118,36 @@ options: EXAMPLES = ''' - name: JUNOS Post Checklist junos_jsnapy: - host: "{{ inventory_hostname}}" - passwd: "{{ tm1_password }}" - action: "snap_post" - config_file: "first_test.yml" - logfile: "migration_post.log" - register: jsnapy + host: "{{ inventory_hostname}}" + passwd: "{{ tm1_password }}" + action: "snap_post" + config_file: "first_test.yml" + logfile: "migration_post.log" + register: test1 + + - name: Check JSNAPy tests results + assert: + that: + - "test1.passPercentage == 100" - name: Debug jsnapy - debug: msg="{{jsnapy}}" + debug: msg=test1 + +--------- + - name: Test based on a test_file directly + junos_jsnapy: + host: "{{ junos_host }}" + port: "{{ netconf_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: tests/test_junos_interface.yaml + action: snapcheck + register: test1 + - name: Check JSNAPy tests results + assert: + that: + - "test1.passPercentage == 100" ''' from distutils.version import LooseVersion import logging From d0f87d80b1c506d063bf113f243df5a2896b285d Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 20 Sep 2016 22:19:10 +0400 Subject: [PATCH 099/426] Add tests to check if key is present when identifying module_name --- callback_plugins/jsnapy.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/callback_plugins/jsnapy.py b/callback_plugins/jsnapy.py index a248f830..1ccf23ac 100644 --- a/callback_plugins/jsnapy.py +++ b/callback_plugins/jsnapy.py @@ -19,6 +19,9 @@ class CallbackModule(CallbackBase): CALLBACK_TYPE = 'aggregate' CALLBACK_NAME = 'jsnapy' +## useful links regarding Callback +## https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py + def __init__(self): self._pp = pprint.PrettyPrinter(indent=4) self._results = {} @@ -29,10 +32,12 @@ def v2_runner_on_ok(self, result): """ Collect test results for all tests executed if module is junos_jsnapy """ - module_name = '' + ## Extract module name + module_name = '' if 'invocation' in result._result: - module_name = result._result['invocation']['module_name'] + if 'module_name' in result._result['invocation']: + module_name = result._result['invocation']['module_name'] if module_name == 'junos_jsnapy': ## Check if dict entry already exist for this host From 9e7a4684056e165d22f69629d69fb879d4fd7834 Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 20 Sep 2016 22:26:10 +0400 Subject: [PATCH 100/426] Add indications for the callback_plugin --- library/junos_jsnapy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 7e54893d..1545c755 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -47,7 +47,11 @@ description: Attention, to not break Ansible behavior, this module only report "failed" if the module itself fails, not if a test fails. To check the test results you need to subscribe to the result and assert - the returned value + the returned value. + An experimental Callback_Plugin for junos_jsnapy is available to provide + additional information about tests that failed. + To enable it, you need to add "callback_whitelist = jsnapy" in your ansible + configuration file. requirements: - junos-eznc >= 1.2.2 options: From e221e7af8585615bc6e964bae64a258ec44d6d9c Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 20 Sep 2016 22:36:24 +0400 Subject: [PATCH 101/426] Add information regarding callback plugin in README file --- README.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1309f478..0a74e620 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,37 @@ Juniper Networks provides support for using Ansible to deploy devices running th - junos_srx_cluster — Enable/Disable cluster mode for SRX devices - junos_zeroize — Remove all configuration information on the Routing Engines and reset all key values on a device. +### OVERVIEW OF PLUGINS + +In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `junos_jsnapy`. + +The callback_plugin `jsnapy` helps to print on the screen additional information regarding + jsnapy failed tests. +For each failed test, a log will be printed after the RECAP of the playbook. + +> The plugin `jsnapy` is currently in **Experimental** stage, please provide feedback. + +``` +PLAY RECAP ********************************************************************* +qfx10002-01 : ok=3 changed=0 unreachable=0 failed=1 +qfx10002-02 : ok=3 changed=0 unreachable=0 failed=1 +qfx5100-01 : ok=1 changed=0 unreachable=0 failed=1 + +JSNAPy Results for: qfx10002-01 ************************************************ +Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} +Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "60021", "peer-state": "Idle", "peer-address": "192.168.0.1"} +Value of 'oper-status' not 'is-equal' at '//interface-information/physical-interface[normalize-space(admin-status)='up' and logical-interface/address-family/address-family-name ]' with {"oper-status": "down", "name": "et-0/0/18"} + +JSNAPy Results for: qfx10002-02 ************************************************ +Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} +``` + +Callback plugins are not activated by default and needs to be manually added to the +configuration file under `[defaults]` with the variable `callback_whitelist` +``` +[defaults] +callback_whitelist = jsnapy +``` ## DOCUMENTATION [Official documentation](http://www.juniper.net/techpubs/en_US/release-independent/junos-ansible/information-products/pathway-pages/index.html) (detailed information, includes examples) @@ -42,7 +73,7 @@ To download the junos role to the Ansible server, execute the ansible-galaxy ins For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: user@ansible-junos-stdlib> source env-setup - + This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: ```` @@ -100,7 +131,7 @@ Thes modules require the following to be installed on the Ansible server: ## LICENSE Apache 2.0 - + ## CONTRIBUTORS Juniper Networks is actively contributing to and maintaining this repo. Please contact jnpr-community-netdev@juniper.net for any queries. From 6aea9fb4bdbf5710d253bb88d4745d762d93a5ad Mon Sep 17 00:00:00 2001 From: Damien Date: Tue, 20 Sep 2016 23:35:34 +0400 Subject: [PATCH 102/426] Add Dockerfile --- Dockerfile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..8450e36b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM juniper/pyez:2.0.1 +MAINTAINER ntwrkguru@gmail.com + +WORKDIR /tmp +RUN mkdir /tmp/ansible-junos-stdlib &&\ + mkdir /tmp/ansible-junos-stdlib/library &&\ + mkdir /tmp/ansible-junos-stdlib/meta &&\ + mkdir /project + +ADD library /tmp/ansible-junos-stdlib/library +ADD meta /tmp/ansible-junos-stdlib/meta + +RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ + apk update && apk add ca-certificates &&\ + apk add build-base gcc g++ make python-dev &&\ + pip install junos-netconify &&\ + pip install jxmlease &&\ + pip install ansible &&\ + ansible-galaxy install Juniper.junos &&\ + apk del -r --purge gcc make g++ &&\ + rm -rf /source/* &&\ + rm -rf /var/cache/apk/* &&\ + rm -rf /tmp/* + +WORKDIR /project From 2c99eff5f33a7363d131a358098dcdbb37c77bd4 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Wed, 21 Sep 2016 11:10:25 +0530 Subject: [PATCH 103/426] adding junos_jsnapy to README --- README.md | 1 + library/junos_jsnapy | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e83fdf00..2dc43325 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Juniper Networks provides support for using Ansible to deploy devices running th - junos_zeroize — Remove all configuration information on the Routing Engines and reset all key values on a device. - junos_get_table - Retrieve data from a Junos device using Tables/Views - junos_ping - execute ping on junos devices +- junos_jsnapy - Integrate JSNAPy to ansible which helps audit network devices ### OVERVIEW OF PLUGINS diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 1545c755..f5f0779f 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -41,9 +41,9 @@ version_added: "1.4.0" version_control: 13-Apr-2016 v1.0.0 - Basic working model -Short_description: Integrate JSnapy to ansible. +Short_description: Integrate JSNAPy to ansible. description: - - Execute JSnapy test from Ansible. + - Execute JSNAPy test from Ansible. Attention, to not break Ansible behavior, this module only report "failed" if the module itself fails, not if a test fails. To check the test results you need to subscribe to the result and assert From 0a0c1f9ad648b5e916792e2769a4d212cb4728f8 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Wed, 21 Sep 2016 17:40:42 +0530 Subject: [PATCH 104/426] Added details needed by galaxy --- meta/main.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/meta/main.yml b/meta/main.yml index 015cc58b..d8d3360e 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -17,7 +17,10 @@ galaxy_info: # the ones that apply to your role. If you don't see your # platform on this list, let us know and we'll get it added! # - #platforms: + platforms: + - name: junos + versions: + - all #- name: EL # versions: # - all @@ -102,6 +105,8 @@ galaxy_info: # the platforms above, uncomment those that apply to your role. # categories: + - networking + galaxy_tags: #- cloud #- cloud:ec2 #- cloud:gce @@ -116,6 +121,8 @@ galaxy_info: #- packaging #- system #- web + - junos + - juniper dependencies: [] # List your role dependencies here, one per line. Only # dependencies available via galaxy should be listed here. From 2f3b4de1967d6276f94f4cd779f2b95486cb9771 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Mon, 24 Oct 2016 17:38:40 +0530 Subject: [PATCH 105/426] snap_pre was broken & snapcheck was expecting testfiles full path --- library/junos_jsnapy | 46 +++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index f5f0779f..6551ddd7 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -94,7 +94,7 @@ options: default: None dir: description: - - Path for the JSNAPy yaml configuration file + - Path for the JSNAPy yaml testfiles/configuration file required: false default: '/etc/jsnapy/testfiles' action: @@ -179,10 +179,16 @@ def jsnap_selection(dev, module): config_file = args.get('config_file') if config_file: config_dir = args['dir'] - config_data = os.path.join(config_dir, config_file) + config_data = config_file + # in case config file given is not full path + if not os.path.isfile(config_file): + config_data = os.path.join(config_dir, config_file) else: test_files = args.get('test_files') - + test_file_dir = args['dir'] + fn = lambda test_file: os.path.join(test_file_dir, test_file) \ + if not os.path.isfile(test_file) else test_file + test_files = map(fn, test_files) for test_file in test_files: if not os.path.isfile(test_file): msg = 'unable to find the test file {0}'.format(test_file) @@ -191,7 +197,7 @@ def jsnap_selection(dev, module): config_data = {'tests': test_files} - results = {} + results = {'action': action} js = SnapAdmin() if action == 'snapcheck': @@ -205,21 +211,21 @@ def jsnap_selection(dev, module): percentagePassed = 0 if isinstance(snapValue, (list)): - for snapCheck in snapValue: - router = snapCheck.device - results['router'] = router - results['final_result'] = snapCheck.result - results['total_passed'] = snapCheck.no_passed - results['total_failed'] = snapCheck.no_failed - results['test_results'] = snapCheck.test_results - total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) - results['total_tests'] = total_test - - if total_test != 0: - percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) - - results['passPercentage'] = percentagePassed - + if action in ['snapcheck', 'check']: + for snapCheck in snapValue: + router = snapCheck.device + results['router'] = router + results['final_result'] = snapCheck.result + results['total_passed'] = snapCheck.no_passed + results['total_failed'] = snapCheck.no_failed + results['test_results'] = snapCheck.test_results + total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) + results['total_tests'] = total_test + if total_test != 0: + percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) + results['passPercentage'] = percentagePassed + elif action in ['snap_pre', 'snap_post']: + results['changed'] = True return results def main(): @@ -270,7 +276,7 @@ def main(): logging.info("Main program: ") data = jsnap_selection(dev, module) - if data['final_result']=='Failed': + if data['action'] in ['check', 'snapcheck'] and data['final_result']=='Failed': msg = 'Test Failed: Passed {0}, Failed {1}'.format( data['total_passed'], data['total_failed'] ) From b542f842e8e8d7ff89c2f513f9da7e0df0fd483d Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 9 Nov 2016 20:26:34 -0500 Subject: [PATCH 106/426] Use Ravello to create Test topology --- .travis.yml | 37 ++++-------- tests/.travis_ansible_python.yaml | 1 + tests/ansible.cfg | 3 +- tests/group_vars/all/donotdelete | 0 tests/pb.junos_ping.yaml | 59 +++++++++++++++++++ tests/pb.rav.token.app_stop.yaml | 34 +++++++++++ tests/pb.rav.token.create-deploy.yaml | 85 +++++++++++++++++++++++++++ tests/pb.rav.token.fqdn_get.yaml | 54 +++++++++++++++++ tests/ravello.ini | 18 ++++++ 9 files changed, 262 insertions(+), 29 deletions(-) create mode 100644 tests/.travis_ansible_python.yaml create mode 100644 tests/group_vars/all/donotdelete create mode 100644 tests/pb.junos_ping.yaml create mode 100644 tests/pb.rav.token.app_stop.yaml create mode 100644 tests/pb.rav.token.create-deploy.yaml create mode 100644 tests/pb.rav.token.fqdn_get.yaml create mode 100644 tests/ravello.ini diff --git a/.travis.yml b/.travis.yml index 5c8b5456..ebbae935 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,38 +6,21 @@ sudo: required dist: trusty env: - - ANSIBLE_VERSION=1.9.6 -# - ANSIBLE_VERSION=2.0.2.0 -# - ANSIBLE_VERSION=2.0.0.2 + - ANSIBLE_VERSION=2.2.0.0 install: + - cd tests - pip install -r requirements.txt - pip install -q ansible==$ANSIBLE_VERSION -## Create module package and install it at the same level as the project - - cd .. - - tar -czf Juniper.junos ansible-junos-stdlib - - mkdir tmp - - mv Juniper.junos tmp/ - - cd tmp - - ansible-galaxy install -p ../ Juniper.junos - -## Install Virtualbox - - sudo apt-get update -q - - sudo apt-get install -y dkms - - sudo apt-get install -y linux-headers-$(uname -r) - - sudo wget -nv http://download.virtualbox.org/virtualbox/5.0.20/virtualbox-5.0_5.0.20-106931~Ubuntu~trusty_amd64.deb - - sudo dpkg -i virtualbox-5.0_5.0.20-106931~Ubuntu~trusty_amd64.deb - - VBoxManage --version - -## Install Vagrant - - sudo wget -nv https://releases.hashicorp.com/vagrant/1.8.1/vagrant_1.8.1_x86_64.deb - - sudo dpkg -i vagrant_1.8.1_x86_64.deb - - vagrant plugin install vagrant-junos - - vagrant plugin install vagrant-host-shell - + - ansible-galaxy install Juniper.junos -p ./roles + - pip install ansible junos-eznc lxml jxmlease markupsafe requests httplib2 + - mkdir library + - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible + - mv .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml ## start Junos VM - - cd ../ansible-junos-stdlib/tests - - vagrant up + - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml + - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml + - ansible-playbook -i ravello.ini pb.junos_ping.yaml script: - ansible-playbook junos_ping/pb.junos_ping.yaml diff --git a/tests/.travis_ansible_python.yaml b/tests/.travis_ansible_python.yaml new file mode 100644 index 00000000..e316fb70 --- /dev/null +++ b/tests/.travis_ansible_python.yaml @@ -0,0 +1 @@ +ansible_python_interpreter: "/home/travis/virtualenv/python2.7.10/bin/python" diff --git a/tests/ansible.cfg b/tests/ansible.cfg index 2127be75..d09f592f 100644 --- a/tests/ansible.cfg +++ b/tests/ansible.cfg @@ -1,5 +1,4 @@ [defaults] hash_behaviour=merge -inventory = .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory -roles_path = ../../ +roles_path = /etc/ansible/roles:library/ravello-ansible/roles diff --git a/tests/group_vars/all/donotdelete b/tests/group_vars/all/donotdelete new file mode 100644 index 00000000..e69de29b diff --git a/tests/pb.junos_ping.yaml b/tests/pb.junos_ping.yaml new file mode 100644 index 00000000..3bdd07a7 --- /dev/null +++ b/tests/pb.junos_ping.yaml @@ -0,0 +1,59 @@ +--- +- name: Test junos_ping module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Ping Google DNS" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + register: test1 + ignore_errors: True +# - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1.packet_loss == '0' + +############ + + - name: "TEST 2 - Ping Wrong IP" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.1.1 + register: test2 + ignore_errors: True +# - debug: var=test2 + + - name: Check TEST 2 + assert: + that: + - test2.packet_loss == '100' +################# + + - name: "TEST 3 - Change nbr packets" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + count: 3 + register: test3 + ignore_errors: True +# - debug: var=test3 + + - name: Check TEST 3 + assert: + that: + - test3.packets_sent == '3' diff --git a/tests/pb.rav.token.app_stop.yaml b/tests/pb.rav.token.app_stop.yaml new file mode 100644 index 00000000..e1d8fadc --- /dev/null +++ b/tests/pb.rav.token.app_stop.yaml @@ -0,0 +1,34 @@ +--- +- name: Get FQDN for all VM on Ravello + connection: local + hosts: all + gather_facts: no + roles: + - ravello.lib + + tasks: +############################### +## Get VM ID ### +############################### + - name: Get App ID from Ravello + ravello_get_id: + resource_name: "{{ ravello_ci_app_name }}" + resource_type: applications + token: "{{ ravello_ci_token }}" + register: app + run_once: true + + +############################### +## Delete the application ## +############################### + - name: Stop Application on Ravello + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/stop" + method: POST + status_code: 202 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + run_once: true + changed_when: true diff --git a/tests/pb.rav.token.create-deploy.yaml b/tests/pb.rav.token.create-deploy.yaml new file mode 100644 index 00000000..25bb21f3 --- /dev/null +++ b/tests/pb.rav.token.create-deploy.yaml @@ -0,0 +1,85 @@ +--- +- name: Create Application on Ravello for CI + connection: local + hosts: all + gather_facts: no + roles: + - ravello.lib + vars: + ravello_deploy_topology_cloud: AMAZON + ravello_deploy_topology_region: Oregon + ravello_deploy_topology_optimization: PERFORMANCE_OPTIMIZED + ravello_deploy_topology_start_all: true + + tasks: + - name: Create Application from Blueprint for CI + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/" + method: POST + status_code: 201 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + body: + name: "{{ ravello_ci_app_name }}" + description: "App created by Travis CI" + baseBlueprintId: "{{ ravello_ci_blueprint }}" + body_format: json + run_once: true + changed_when: true + + - name: Get App ID from Ravello + ravello_get_id: + resource_name: "{{ ravello_ci_app_name }}" + resource_type: applications + token: "{{ ravello_ci_token }}" + register: app + run_once: true + + - debug: var=app + run_once: true + +####################################################### +## Deploy Application(s) ## +####################################################### + - name: Deploy Application On Ravello + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/publish" + method: POST + status_code: 202 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + body: > + { + "preferredCloud": "{{ ravello_deploy_topology_cloud }}", + "preferredRegion": "{{ ravello_deploy_topology_region }}", + "optimizationLevel": "{{ ravello_deploy_topology_optimization }}", + "startAllVms": "{{ ravello_deploy_topology_start_all }}" + } + body_format: json + run_once: true + +######################################## +## Set application Expiration time ## +######################################## + - name: Set Application Expiration time + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/setExpiration" + method: POST + status_code: 200 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + body: '{ "expirationFromNowSeconds": {{ ravello_ci_expiration_time_min * 60 }} }' + body_format: json + run_once: true + + - name: Wait for devices to come up + pause: minutes=5 + + - name: Wait for devices to come up + pause: minutes=5 + + - name: Wait for devices to come up + pause: minutes=5 diff --git a/tests/pb.rav.token.fqdn_get.yaml b/tests/pb.rav.token.fqdn_get.yaml new file mode 100644 index 00000000..6304c2cf --- /dev/null +++ b/tests/pb.rav.token.fqdn_get.yaml @@ -0,0 +1,54 @@ +--- +- name: Get FQDN for all VM on Ravello + connection: local + hosts: junos + gather_facts: no + roles: + - ravello.lib + + tasks: + +############################### +## Get VM ID ### +############################### + - name: Get App ID from Ravello + ravello_get_id: + resource_name: "{{ ravello_ci_app_name }}" + resource_type: applications + token: "{{ ravello_ci_token }}" + register: app + run_once: true + + # - debug: var=app + + - name: Get VM ID from Ravello + ravello_get_id: + application_id: "{{ app.json.id }}" + resource_type: vms + resource_name: "{{ inventory_hostname }}" + token: "{{ ravello_ci_token }}" + failed_if_not_found: true + register: vm + + # - debug: var=vm + + - name: Get VM public FQDN + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/vms/{{ vm.json.id }}/fqdn;deployment" + method: GET + status_code: 200 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + register: ravello_public_ip + + - name: Delete previous file + file: + path: "host_vars/{{ inventory_hostname}}/fqdn.yaml" + state: absent + + - name: Populate ansible_ssh_host Variable based on FQDN + lineinfile: + create: yes + dest: "host_vars/{{ inventory_hostname}}/fqdn.yaml" + line: "ansible_ssh_host: {{ ravello_public_ip.json.value }}" diff --git a/tests/ravello.ini b/tests/ravello.ini new file mode 100644 index 00000000..f991dfb0 --- /dev/null +++ b/tests/ravello.ini @@ -0,0 +1,18 @@ +[all:children] +junos + +[junos] +vqfx-01 +vqfx-02 + +################################### +### Define variables per groups ### +################################### +[all:vars] +ansible_ssh_user=root +ansible_ssh_pass=Juniper +ansible_ssh_port=22 +ravello_ci_app_name="Ansible-junos-stdlib {{ lookup('env','TRAVIS_COMMIT') }}" +ravello_ci_blueprint="75695295" +ravello_ci_token="GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf" +ravello_ci_expiration_time_min=50 From 8059f7fc01fb9a88cd08d3e7510fa72e328eae01 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 9 Nov 2016 20:30:49 -0500 Subject: [PATCH 107/426] Move cd tests later in the test --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ebbae935..11edb046 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,11 @@ env: - ANSIBLE_VERSION=2.2.0.0 install: - - cd tests - pip install -r requirements.txt - pip install -q ansible==$ANSIBLE_VERSION - ansible-galaxy install Juniper.junos -p ./roles - pip install ansible junos-eznc lxml jxmlease markupsafe requests httplib2 + - cd tests - mkdir library - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible - mv .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml From c9367daec4279cded75559fd2aa03d1c16c5569f Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 9 Nov 2016 20:49:32 -0500 Subject: [PATCH 108/426] Move cd test again --- .travis.yml | 2 +- tests/pb.rav.token.create-deploy.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 11edb046..f2147ded 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,9 @@ env: install: - pip install -r requirements.txt - pip install -q ansible==$ANSIBLE_VERSION + - cd tests - ansible-galaxy install Juniper.junos -p ./roles - pip install ansible junos-eznc lxml jxmlease markupsafe requests httplib2 - - cd tests - mkdir library - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible - mv .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml diff --git a/tests/pb.rav.token.create-deploy.yaml b/tests/pb.rav.token.create-deploy.yaml index 25bb21f3..4f789e35 100644 --- a/tests/pb.rav.token.create-deploy.yaml +++ b/tests/pb.rav.token.create-deploy.yaml @@ -82,4 +82,4 @@ pause: minutes=5 - name: Wait for devices to come up - pause: minutes=5 + pause: minutes=2 From 5dca1f5e539530e290954358160eb3316c657097 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 9 Nov 2016 21:22:12 -0500 Subject: [PATCH 109/426] Clean up tests --- .travis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2147ded..ddf04dff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,11 +17,8 @@ install: - mkdir library - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible - mv .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml -## start Junos VM + +script: - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml - ansible-playbook -i ravello.ini pb.junos_ping.yaml - -script: - - ansible-playbook junos_ping/pb.junos_ping.yaml - - ansible-playbook junos_get_facts/pb.junos_get_facts.yaml From b91c6f9171831cf6bc2ead72ee55c45f4b2758aa Mon Sep 17 00:00:00 2001 From: Med Amine Ben Asker Date: Tue, 15 Nov 2016 22:26:41 +0100 Subject: [PATCH 110/426] Fix missing interpreter: (on Rhel7) (#176) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b7f41a84..92d71829 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python from setuptools import setup from version import VERSION From d660832b0da945946757c145f47e7cfde87f4478 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 30 Nov 2016 09:28:27 -0800 Subject: [PATCH 111/426] Add Jsnapy library for junos_jsnapy (#184) --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 8450e36b..c41e6eed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ pip install junos-netconify &&\ pip install jxmlease &&\ pip install ansible &&\ + pip install jsnapy &&\ ansible-galaxy install Juniper.junos &&\ apk del -r --purge gcc make g++ &&\ rm -rf /source/* &&\ From 77747f543d16e1b56339ec93cd0f3b49c0ac0ae0 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 30 Nov 2016 10:23:23 -0800 Subject: [PATCH 112/426] small changes --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ddf04dff..16142a76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ dist: trusty env: - ANSIBLE_VERSION=2.2.0.0 + install: - pip install -r requirements.txt From 590c0bf1e3ff729b15fc08208c7f46ba67aa7503 Mon Sep 17 00:00:00 2001 From: Damien Date: Wed, 30 Nov 2016 10:44:19 -0800 Subject: [PATCH 113/426] Redesign how test files list is constructed, previous solution was not working, added more checks (#185) --- library/junos_jsnapy | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 6551ddd7..254353d7 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -183,19 +183,35 @@ def jsnap_selection(dev, module): # in case config file given is not full path if not os.path.isfile(config_file): config_data = os.path.join(config_dir, config_file) + if not os.path.isfile(config_data): + msg = 'unable to find the config file {0} (test directory:{1})'.format(config_file,args['dir']) + logging.error(msg) + module.fail_json(msg=msg) + else: test_files = args.get('test_files') - test_file_dir = args['dir'] - fn = lambda test_file: os.path.join(test_file_dir, test_file) \ - if not os.path.isfile(test_file) else test_file - test_files = map(fn, test_files) + + test_files_validated = [] + ## Check if file is present without and with dir for test_file in test_files: if not os.path.isfile(test_file): - msg = 'unable to find the test file {0}'.format(test_file) - logging.error(msg) - module.fail_json(msg=msg) + test_file_full = os.path.join(args['dir'], test_file) + if not os.path.isfile(test_file_full): + msg = 'unable to find the test file {0} (test directory:{1})'.format(test_file,args['dir']) + logging.error(msg) + module.fail_json(msg=msg) + else: + test_files_validated.append(test_file_full) + else: + test_files_validated.append(test_file) + + ## Check that we have at least one test file + if len(test_files_validated) == 0: + msg = 'The list of test file is empty, please check your input' + logging.error(msg) + module.fail_json(msg=msg) - config_data = {'tests': test_files} + config_data = {'tests': test_files_validated} results = {'action': action} js = SnapAdmin() From 1700ecbef948e283b8313c3663859f8406d10350 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 11:10:31 -0800 Subject: [PATCH 114/426] Cleanup ping after conflict --- library/junos_ping | 7 ------- 1 file changed, 7 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index 6627e12f..b9ae9a1e 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -271,12 +271,6 @@ def main(): dev.close() module.exit_json(**results) -<<<<<<< HEAD - results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss.strip()), str(packets_sent.strip()), str(responses_received.strip())) - results['packet_loss'] = packet_loss.strip() - results['packets_sent'] = packets_sent.strip() - results['packets_received'] = responses_received.strip() -======= packet_loss = packet_loss.strip() packets_sent = packets_sent.strip() responses_received = responses_received.strip() @@ -284,7 +278,6 @@ def main(): results['packet_loss'] = packet_loss results['packets_sent'] = packets_sent results['packets_received'] = responses_received ->>>>>>> b91c6f9171831cf6bc2ead72ee55c45f4b2758aa if int(packet_loss) > int(m_args['acceptable_packet_loss']): results['failed'] = True From dfac66a80ebaccb05cac8e34d53721cbbef984bf Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 13:35:40 -0800 Subject: [PATCH 115/426] Move tests for junos_get_facts --- tests/pb.junos_get_facts.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/pb.junos_get_facts.yaml diff --git a/tests/pb.junos_get_facts.yaml b/tests/pb.junos_get_facts.yaml new file mode 100644 index 00000000..a94f6d9b --- /dev/null +++ b/tests/pb.junos_get_facts.yaml @@ -0,0 +1,26 @@ +--- +- name: Test junos_get_facts module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Gather Facts" + junos_get_facts: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + ignore_errors: True + register: test1 + + # - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1.facts.hostname + - test1.facts.serialnumber + - test1.facts.model + - test1.facts.fqdn From 07eefaabb3e231fc1b584dfb2c68c98aae246d7d Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 13:36:15 -0800 Subject: [PATCH 116/426] Cleanup vagrant tests --- tests/Vagrantfile | 31 ---------- tests/junos_get_facts/pb.junos_get_facts.yaml | 26 -------- tests/junos_ping/pb.junos_ping.yaml | 61 ------------------- 3 files changed, 118 deletions(-) delete mode 100644 tests/Vagrantfile delete mode 100644 tests/junos_get_facts/pb.junos_get_facts.yaml delete mode 100644 tests/junos_ping/pb.junos_ping.yaml diff --git a/tests/Vagrantfile b/tests/Vagrantfile deleted file mode 100644 index 4ccccbaf..00000000 --- a/tests/Vagrantfile +++ /dev/null @@ -1,31 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -# All Vagrant configuration is done below. The "2" in Vagrant.configure -# configures the configuration version (we support older styles for -# backwards compatibility). Please don't change it unless you know what -# you're doing. -Vagrant.configure(2) do |config| - # The most common configuration options are documented and commented below. - # For a complete reference, please see the online documentation at - # https://docs.vagrantup.com. - - config.vm.define "vsrx" do |vsrx| - vsrx.vm.hostname = "vsrx" - vsrx.vm.box = "juniper/ffp-12.1X47-D15.4-packetmode" - - vsrx.vm.provider :virtualbox do |vb| - vb.customize ["modifyvm", :id, "--hwvirtex", "off"] - vb.cpus = 1 - end - end - - - - config.vm.provision "ansible" do |ansible| - ansible.groups = { - "all" => ["vsrx"] - } - ansible.playbook = "junos_get_facts/pb.junos_get_facts.yaml" - end -end diff --git a/tests/junos_get_facts/pb.junos_get_facts.yaml b/tests/junos_get_facts/pb.junos_get_facts.yaml deleted file mode 100644 index c5cdc194..00000000 --- a/tests/junos_get_facts/pb.junos_get_facts.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -- name: Test junos_get_facts module - hosts: all - connection: local - gather_facts: no - vars: - ansible_python_interpreter: /usr/local/bin/python - roles: - - Juniper.junos - tasks: - - name: "TEST 1 - Gather Facts" - junos_get_facts: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: root - passwd: Juniper - ignore_errors: True - register: test1 - -# - debug: var=test1 - - - name: Check TEST 1 - assert: - that: - - test1.facts.hostname == 'vsrx' - - test1.facts.virtual == true diff --git a/tests/junos_ping/pb.junos_ping.yaml b/tests/junos_ping/pb.junos_ping.yaml deleted file mode 100644 index ecf32b5f..00000000 --- a/tests/junos_ping/pb.junos_ping.yaml +++ /dev/null @@ -1,61 +0,0 @@ ---- -- name: Test junos_ping module - hosts: all - connection: local - gather_facts: no - vars: - ansible_python_interpreter: /usr/local/bin/python - roles: - - Juniper.junos - tasks: - - name: "TEST 1 - Ping Google DNS" - junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: root - passwd: Juniper - dest_ip: 8.8.8.8 - register: test1 - ignore_errors: True -# - debug: var=test1 - - - name: Check TEST 1 - assert: - that: - - test1.packet_loss == '0' - -############ - - - name: "TEST 2 - Ping Wrong IP" - junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: root - passwd: Juniper - dest_ip: 8.8.1.1 - register: test2 - ignore_errors: True -# - debug: var=test2 - - - name: Check TEST 2 - assert: - that: - - test2.packet_loss == '100' -################# - - - name: "TEST 3 - Change nbr packets" - junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: root - passwd: Juniper - dest_ip: 8.8.8.8 - count: 3 - register: test3 - ignore_errors: True -# - debug: var=test3 - - - name: Check TEST 3 - assert: - that: - - test3.packets_sent == '3' From 895d585fd565502b47e9c25dc9f90452d950c1df Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 13:36:52 -0800 Subject: [PATCH 117/426] Add tests for junos_jsnapy --- .travis.yml | 4 +- tests/junos_jsnapy/add_loopback.set | 3 + tests/junos_jsnapy/delete_loopback.set | 2 + tests/junos_jsnapy/test_junos_storage.yml | 11 ++ tests/junos_jsnapy/test_loopback.yml | 16 ++ tests/junos_jsnapy/test_version.yml | 12 ++ tests/pb.junos_jsnapy.yaml | 217 ++++++++++++++++++++++ 7 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 tests/junos_jsnapy/add_loopback.set create mode 100644 tests/junos_jsnapy/delete_loopback.set create mode 100644 tests/junos_jsnapy/test_junos_storage.yml create mode 100644 tests/junos_jsnapy/test_loopback.yml create mode 100644 tests/junos_jsnapy/test_version.yml create mode 100644 tests/pb.junos_jsnapy.yaml diff --git a/.travis.yml b/.travis.yml index 16142a76..1369d770 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ dist: trusty env: - ANSIBLE_VERSION=2.2.0.0 - + install: - pip install -r requirements.txt @@ -22,4 +22,6 @@ install: script: - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml +## Execute Tests - ansible-playbook -i ravello.ini pb.junos_ping.yaml + - ansible-playbook -i ravello.ini pb.junos_jsnapy.yaml diff --git a/tests/junos_jsnapy/add_loopback.set b/tests/junos_jsnapy/add_loopback.set new file mode 100644 index 00000000..6019602d --- /dev/null +++ b/tests/junos_jsnapy/add_loopback.set @@ -0,0 +1,3 @@ +set routing-instances LO10 interface lo0.10 +set routing-instances LO10 instance-type virtual-router +set interfaces lo0 unit 10 family inet diff --git a/tests/junos_jsnapy/delete_loopback.set b/tests/junos_jsnapy/delete_loopback.set new file mode 100644 index 00000000..818ff206 --- /dev/null +++ b/tests/junos_jsnapy/delete_loopback.set @@ -0,0 +1,2 @@ +delete routing-instances LO10 +delete interfaces lo0 unit 10 diff --git a/tests/junos_jsnapy/test_junos_storage.yml b/tests/junos_jsnapy/test_junos_storage.yml new file mode 100644 index 00000000..3473c27e --- /dev/null +++ b/tests/junos_jsnapy/test_junos_storage.yml @@ -0,0 +1,11 @@ +tests_include: + - check_storage + +check_storage: + - command: show system storage + - iterate: + xpath: //system-storage-information/filesystem[normalize-space(mounted-on)='/'] + tests: + - is-lt: used-percent, 95 + info: "File system {{post['mounted-on']}} use less than 95%" + err: "File system {{post['mounted-on']}} use {{post['used-percent']}} %" diff --git a/tests/junos_jsnapy/test_loopback.yml b/tests/junos_jsnapy/test_loopback.yml new file mode 100644 index 00000000..cd27467d --- /dev/null +++ b/tests/junos_jsnapy/test_loopback.yml @@ -0,0 +1,16 @@ +tests_include: + - test_command_version + +test_command_version: + - command: show interfaces terse lo* + - iterate: + xpath: //logical-interface + id: './name' + tests: + - list-not-more: admin-status + err: "Test Failed!! name list changed, <{{pre['admin-status']}}> with name <{{id_0}}> is not found in post snapshot" + info: "Test successful!! name list is same, with name <{{id_0}}>" + + - list-not-less: admin-status + err: "Test Failed!! name list changed, <{{pre['admin-status']}}> with name <{{id_0}}> was not present in PRE snapshot" + info: "Test successful!! name list is same, with name <{{id_0}}>" diff --git a/tests/junos_jsnapy/test_version.yml b/tests/junos_jsnapy/test_version.yml new file mode 100644 index 00000000..ed2571c8 --- /dev/null +++ b/tests/junos_jsnapy/test_version.yml @@ -0,0 +1,12 @@ +tests_include: + - test_version_check + +test_version_check: + - command: show version + - iterate: + id: host-name + xpath: //software-information + tests: + - exists: //package-information/name + info: "Test Succeeded!! node //package-information/name exists with name <{{pre['//package-information/name']}}> and hostname: <{{id_0}} > " + err: "Test Failed!!! node //package-information/name does not exists in hostname: <{{id_0}}> !! " diff --git a/tests/pb.junos_jsnapy.yaml b/tests/pb.junos_jsnapy.yaml new file mode 100644 index 00000000..874ccd8a --- /dev/null +++ b/tests/pb.junos_jsnapy.yaml @@ -0,0 +1,217 @@ +--- +- name: Test junos_ping module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: +################################################## +#### TEST 1 ## +################################################## + - name: "TEST 1 - Execute SNAPCHECK with 1 test file / no dir" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_junos_storage.yml + action: snapcheck + register: test1 + ignore_errors: True + tags: [ test1 ] + # - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1|succeeded + - test1.passPercentage == 100 + - test1.total_tests == 1 + tags: [ test1 ] + +################################################## +#### TEST 2 ## +################################################## + - name: "TEST 2 - Execute SNAPCHECK with 2 test file & dir" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: + - test_junos_storage.yml + - test_version.yml + dir: junos_jsnapy + action: snapcheck + register: test2 + ignore_errors: True + tags: [ test2 ] + # - debug: var=test2 + + - name: Check TEST 2 + assert: + that: + - test2|succeeded + - test2.passPercentage == 100 + - test2.total_tests == 2 + tags: [ test2 ] + +################################################## +#### TEST 3 ## +################################################## + - name: "TEST 3 - Wrong test file" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: file_that_doesnt_exist.yml + action: snapcheck + register: test3 + ignore_errors: True + tags: [ test3 ] + # - debug: var=test3 + + - name: Check TEST 3 + assert: + that: + - test3|failed + tags: [ test3 ] + +################################################## +#### TEST 4 ## +################################################## + - name: "TEST 4 - SNAP_PRE" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: snap_pre + register: test4 + ignore_errors: True + tags: [ test4 ] + + # - debug: var=test4 + + - name: Check TEST 4 + assert: + that: + - test4|succeeded + tags: [ test4 ] + +################################################## +#### TEST 5 ## +################################################## + - name: "TEST 5 - SNAP_POST" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: snap_post + register: test5 + ignore_errors: True + tags: [ test5 ] + + # - debug: var=test5 + + - name: Check TEST 5 + assert: + that: + - test5|succeeded + tags: [ test5 ] + +################################################## +#### TEST 6 ## +################################################## + - name: "TEST 6 - CHECK" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: check + register: test6 + ignore_errors: True + tags: [ test6 ] + + - debug: var=test6 + + - name: Check TEST 6 + assert: + that: + - test6|succeeded + - test6.passPercentage == 100 + tags: [ test6 ] + +################################################## +#### TEST 7 ## +################################################## + - name: "PRE-TEST 7 - Add loopback address" + junos_install_config: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + file: junos_jsnapy/add_loopback.set + overwrite: no + register: test7_1 + ignore_errors: True + tags: [ test7 ] + + - name: Wait for loopback to come up + pause: seconds=15 + + - name: "TEST 7 - SNAP_POST with additional loopback" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: snap_post + register: test7_2 + ignore_errors: True + tags: [ test7 ] + # - debug: var=pretest7 + + - name: "TEST 7 - CHECK" + junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: check + register: test7 + ignore_errors: True + tags: [ test7 ] + - debug: var=test7 + + - name: "TEST 7 - Cleanup" + junos_install_config: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + file: junos_jsnapy/delete_loopback.set + overwrite: no + register: test7_3 + ignore_errors: True + tags: [ test7 ] + + - name: Check TEST 7 + assert: + that: + - test7_1|succeeded + - test7_2|succeeded + - test7_3|succeeded + - test7|succeeded + - test7.passPercentage == 50 + - test7.total_tests == 2 + tags: [ test7 ] From 9f377751ba780380a2616ca6f12a301224bf7a78 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 13:39:43 -0800 Subject: [PATCH 118/426] Add more checks to make sure info returned by modules is valid --- callback_plugins/jsnapy.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/callback_plugins/jsnapy.py b/callback_plugins/jsnapy.py index 1ccf23ac..3525f6b5 100644 --- a/callback_plugins/jsnapy.py +++ b/callback_plugins/jsnapy.py @@ -35,11 +35,21 @@ def v2_runner_on_ok(self, result): ## Extract module name module_name = '' + module_args = {} if 'invocation' in result._result: if 'module_name' in result._result['invocation']: module_name = result._result['invocation']['module_name'] + module_args = result._result['invocation']['module_args'] + + ## Check if dic return has all valid information + if module_name == '' or module_args == {}: + return None + elif not module_args.has_key('action'): + return None + + if module_name == 'junos_jsnapy' and \ + ( module_args['action'] == 'snapcheck' or module_args['action'] == 'check' ): - if module_name == 'junos_jsnapy': ## Check if dict entry already exist for this host host = result._host.name if not host in self._results.keys(): @@ -65,10 +75,18 @@ def v2_playbook_on_stats(self, stats): has_printed_banner = True for test in testlet['failed']: + + # Check if POST exist in the response + data = '' + if test.has_key('post'): + data = test['post'] + else: + data = test + self._display.display( "Value of '{0}' not '{1}' at '{2}' with {3}".format( str(testlet['node_name']), str(testlet['testoperation']), str(testlet['xpath']), - json.dumps(test['post'])), + json.dumps(data)), color=C.COLOR_ERROR) From 0c7d650060aeeca7b5f9ca334311bd368d269a19 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 17:29:04 -0800 Subject: [PATCH 119/426] Add ARG for Ansible and Jsnapy version to change that at Build time --- Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c41e6eed..a3454401 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ FROM juniper/pyez:2.0.1 MAINTAINER ntwrkguru@gmail.com +ARG ver_ansible=2.2.0.0 +ARG ver_jsnapy=1.0.0 + WORKDIR /tmp RUN mkdir /tmp/ansible-junos-stdlib &&\ mkdir /tmp/ansible-junos-stdlib/library &&\ @@ -15,8 +18,8 @@ RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ apk add build-base gcc g++ make python-dev &&\ pip install junos-netconify &&\ pip install jxmlease &&\ - pip install ansible &&\ - pip install jsnapy &&\ + pip install -q ansible==$ver_ansible &&\ + pip install -q jsnapy==$ver_jsnapy &&\ ansible-galaxy install Juniper.junos &&\ apk del -r --purge gcc make g++ &&\ rm -rf /source/* &&\ From 3d77ab24f8ca1f25b9b7eb54e80a09cc50dbf9da Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 17:29:30 -0800 Subject: [PATCH 120/426] Reorganize travis-ci file to use Docker container for tests --- .travis.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1369d770..2ab2c61c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,21 +7,24 @@ dist: trusty env: - ANSIBLE_VERSION=2.2.0.0 - + - ANSIBLE_VERSION=2.1.3.0 install: - - pip install -r requirements.txt +## Create Docker with Ansible modules and all dependancies + - docker build --build-arg ver_ansible=$ANSIBLE_VERSION -t juniper/pyez-ansible:travis . +## Install Ansible locally for Ravello and install Roles - pip install -q ansible==$ANSIBLE_VERSION - cd tests - - ansible-galaxy install Juniper.junos -p ./roles - - pip install ansible junos-eznc lxml jxmlease markupsafe requests httplib2 - mkdir library - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible + - pip install -r library/ravello-ansible/requirements.txt - mv .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml script: +## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml -## Execute Tests - - ansible-playbook -i ravello.ini pb.junos_ping.yaml - - ansible-playbook -i ravello.ini pb.junos_jsnapy.yaml + +## Execute Tests with Docker + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i local.ini pb.junos_ping.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i local.ini pb.junos_jsnapy.yaml From db40091f85a43ad4eee2cc04751a6773c18e0e35 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 17:55:25 -0800 Subject: [PATCH 121/426] Fix issue with inventory name --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ab2c61c..f1330ab4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,5 +26,5 @@ script: - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml ## Execute Tests with Docker - - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i local.ini pb.junos_ping.yaml - - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i local.ini pb.junos_jsnapy.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_jsnapy.yaml From af2147d2d90206fc4bdc590c6407c1f33653c2e4 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 17:56:07 -0800 Subject: [PATCH 122/426] Change name of the application on Ravello to include more information --- tests/ravello.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ravello.ini b/tests/ravello.ini index f991dfb0..051bbefb 100644 --- a/tests/ravello.ini +++ b/tests/ravello.ini @@ -12,7 +12,7 @@ vqfx-02 ansible_ssh_user=root ansible_ssh_pass=Juniper ansible_ssh_port=22 -ravello_ci_app_name="Ansible-junos-stdlib {{ lookup('env','TRAVIS_COMMIT') }}" +ravello_ci_app_name="Ansible-junos-stdlib Ansible_{{ lookup('env','ANSIBLE_VERSION') }} Travis_{{ lookup('env','TRAVIS_JOB_ID') }} {{ lookup('env','TRAVIS_COMMIT') }}" ravello_ci_blueprint="75695295" ravello_ci_token="GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf" ravello_ci_expiration_time_min=50 From 90588be09aa73183a8c2a684ff870b362b2d66b2 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 18:03:52 -0800 Subject: [PATCH 123/426] Add URL to see the Applications on Ravello --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f1330ab4..c3aa98cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ install: script: ## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses +## Anyone can connect here to see the list of applications running and see the VMs +## https://cloud.ravellosystems.com/#/GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf/apps - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml From 62c3ece6c96bdb00404d93c50b40bee632e144db Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 30 Nov 2016 18:14:14 -0800 Subject: [PATCH 124/426] Delete travis_ansible_python file once ravello topology is up --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c3aa98cf..c79f93ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - mkdir library - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible - pip install -r library/ravello-ansible/requirements.txt - - mv .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml + - cp .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml script: ## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses @@ -26,6 +26,7 @@ script: ## https://cloud.ravellosystems.com/#/GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf/apps - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml + - rm group_vars/all/travis_ansible_python.yaml ## Execute Tests with Docker - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml From 27083817d876b8033fc3dfccbcff7e578d0bc829 Mon Sep 17 00:00:00 2001 From: Damien Date: Mon, 5 Dec 2016 20:37:24 +0100 Subject: [PATCH 125/426] Update README.md (#188) Add a mention of the core modules at the beginning. Made some cosmetics changes (coloration etc ..) Add my name as contributor --- README.md | 57 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 2dc43325..47442373 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,24 @@ Juniper Networks provides support for using Ansible to deploy devices running the Junos operating system (Junos OS). The Juniper Networks Ansible library, which is hosted on the Ansible Galaxy website under the role [junos](https://galaxy.ansible.com/list#/roles/1116), enables you to use Ansible to perform specific operational and configuration tasks on devices running Junos OS, including installing and upgrading Junos OS, deploying specific devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to [INSTALLATION](#installation) section for setup. +In addition to these modules, Since 2.1, Ansible natively include some [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). Both Core and Galaxy modules can cohexist on the same platform. + ## OVERVIEW OF MODULES -- junos_get_facts — Retrieve device-specific information from the host. -- junos_rpc — To execute RPC on device and save output locally -- junos_cli — To execute CLI on device and save output locally -- junos_commit — Commit candidate configuration on device. -- junos_get_config — Retrieve configuration of device. -- junos_install_config — Modify the configuration of a device running Junos OS. -- junos_install_os — Install a Junos OS software package. -- junos_rollback — Rollback configuration of device. -- junos_shutdown — Shut down or reboot a device running Junos OS. -- junos_srx_cluster — Enable/Disable cluster mode for SRX devices -- junos_zeroize — Remove all configuration information on the Routing Engines and reset all key values on a device. -- junos_get_table - Retrieve data from a Junos device using Tables/Views -- junos_ping - execute ping on junos devices -- junos_jsnapy - Integrate JSNAPy to ansible which helps audit network devices +- **junos_get_facts** — Retrieve device-specific information from the host. +- **junos_commit** — Commit candidate configuration on device. +- **junos_get_config** — Retrieve configuration of device. +- **junos_install_config** — Modify the configuration of a device running Junos OS. +- **junos_install_os** — Install a Junos OS software package. +- **junos_rollback** — Rollback configuration of device. +- **junos_shutdown** — Shut down or reboot a device running Junos OS. +- **junos_srx_cluster** — Enable/Disable cluster mode for SRX devices +- **junos_zeroize** — Remove all configuration information on the Routing Engines and reset all key values on a device. +- **junos_get_table** - Retrieve data from a Junos device using Tables/Views +- **junos_ping** - execute ping on junos devices +- **junos_jsnapy** - Integrate JSNAPy to ansible which helps audit network devices +- **junos_rpc** — To execute RPC on device and save output locally +- **junos_cli** — To execute CLI on device and save output locally ### OVERVIEW OF PLUGINS @@ -74,20 +76,20 @@ To download the junos role to the Ansible server, execute the ansible-galaxy ins ### Git clone For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: - - user@ansible-junos-stdlib> source env-setup - +``` +user@ansible-junos-stdlib> source env-setup +``` This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: -```` +``` [jeremy@ansible-junos-stdlib]$ echo $ANSIBLE_LIBRARY /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible -```` +``` ## Example Playbook This example outlines how to use Ansible to install or upgrade the software image on a device running Junos OS. -``` +```yaml --- - name: Install Junos OS hosts: dc1 @@ -107,11 +109,11 @@ This example outlines how to use Ansible to install or upgrade the software imag wait_for: host={{ inventory_hostname }} port=830 timeout=5 - name: Install Junos OS package junos_install_os: - host={{ inventory_hostname }} - reboot=yes - version={{ OS_version }} - package={{ pkg_dir }}/{{ OS_package }} - logfile={{ log_dir }}/software.log + host: "{{ inventory_hostname }}" + reboot: yes + version: "{{ OS_version }}" + package: "{{ pkg_dir }}/{{ OS_package }}" + logfile: "{{ log_dir }}/software.log" register: sw notify: - wait_reboot @@ -128,8 +130,7 @@ Thes modules require the following to be installed on the Ansible server: * Python 2.6 or 2.7 * [Ansible](http://www.ansible.com) 1.5 or later -* Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 1.2.2 or later -* Junos [netconify](https://github.com/jeremyschulman/py-junos-netconify) 1.0.1 or later (if using console) +* Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 1.2.2 or later (2.0.0 if you want to use the mode: telnet) ## LICENSE @@ -141,7 +142,7 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c *Contributors:* -[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr) +[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr), [Damien Garros](https://github.com/dgarros) *Former Contributors:* From e50945df08dfb60a4e241375ab87eff1b683522a Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Thu, 8 Dec 2016 07:05:52 -0800 Subject: [PATCH 126/426] Don't log password (#183) --- library/junos_cli | 2 +- library/junos_commit | 2 +- library/junos_get_config | 2 +- library/junos_get_facts | 2 +- library/junos_get_table | 2 +- library/junos_install_config | 2 +- library/junos_install_os | 2 +- library/junos_jsnapy | 2 +- library/junos_ping | 2 +- library/junos_rollback | 2 +- library/junos_rpc | 2 +- library/junos_shutdown | 2 +- library/junos_srx_cluster | 4 ++-- library/junos_zeroize | 4 ++-- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index d05cb0a7..79132cdd 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -128,7 +128,7 @@ def main(): argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr cli=dict(required=True, default=None), user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), timeout=dict(required=False, type='int', default=0), diff --git a/library/junos_commit b/library/junos_commit index 4800d8fb..65f0f919 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -117,7 +117,7 @@ def main(): module = AnsibleModule( argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), timeout=dict(required=False, default=0), diff --git a/library/junos_get_config b/library/junos_get_config index ee7a0541..af75dc70 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -131,7 +131,7 @@ def main(): module = AnsibleModule( argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), diff --git a/library/junos_get_facts b/library/junos_get_facts index a8703a94..06564a1a 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -141,7 +141,7 @@ def main(): logfile=dict(required=False, default=None), savedir=dict(required=False, default=None), user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None)), supports_check_mode=True) diff --git a/library/junos_get_table b/library/junos_get_table index 3ded17fe..3e644028 100644 --- a/library/junos_get_table +++ b/library/junos_get_table @@ -180,7 +180,7 @@ def main(): default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), diff --git a/library/junos_install_config b/library/junos_install_config index bd3192c0..24a8e19f 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -420,7 +420,7 @@ def main(): argument_spec=dict( host=dict(required=True), user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), console=dict(required=False, default=None), file=dict(required=True), overwrite=dict(required=False, type='bool', choices=BOOLEANS, default=False), diff --git a/library/junos_install_os b/library/junos_install_os index 876b8482..a9376584 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -215,7 +215,7 @@ def main(): host=dict(required=True), package=dict(required=True), user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), version=dict(required=False, default=None), logfile=dict(required=False, default=None), no_copy=dict(required=False, type='bool', choices=BOOLEANS, default=False), diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 254353d7..43502c1e 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -249,7 +249,7 @@ def main(): module = AnsibleModule( argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), diff --git a/library/junos_ping b/library/junos_ping index b9ae9a1e..3491df9c 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -159,7 +159,7 @@ def main(): argument_spec=dict( host=dict(required=True), user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), timeout=dict(required=False, default=0), diff --git a/library/junos_rollback b/library/junos_rollback index 1f9bad0a..f00c3577 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -133,7 +133,7 @@ def main(): module = AnsibleModule( argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), diff --git a/library/junos_rpc b/library/junos_rpc index 2008fe9e..9af10524 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -247,7 +247,7 @@ def main(): host=dict(required=True), logfile=dict(required=False, default=None), user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), timeout=dict(required=False, type='int', default=0), diff --git a/library/junos_shutdown b/library/junos_shutdown index 12bf793e..8b32bc7f 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -118,7 +118,7 @@ def main(): shutdown=dict(required=True, default=None), # must be set to 'shutdown' host=dict(required=True, default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), reboot=dict(required=False, type='bool', choices=BOOLEANS, default=False), port=dict(required=False, default=830), mode=dict(required=False, default=None), diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index ba16191e..36917e4e 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -143,7 +143,7 @@ def main(): argument_spec=dict( host=dict(required=True, default=None), # host or ipaddr user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), mode=dict(required=False, default=None), console=dict(required=False, default=None), # param to netconify @@ -186,7 +186,7 @@ def main(): if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], + dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], mode=args['mode'], gather_facts=False) try: logging.info('LOGIN: host={0} port={1}'.format(args['host'], args['port'])) diff --git a/library/junos_zeroize b/library/junos_zeroize index d6e9bd7f..b283e6b0 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -52,7 +52,7 @@ description: configuration. After the reboot, you must log in through the console as root in order to access the device. requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 1.2.2 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -129,7 +129,7 @@ def main(): host=dict(required=False, default=None), # host or ipaddr console=dict(required=False, default=None), # param to netconify user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None), + passwd=dict(required=False, default=None, no_log=True), logfile=dict(required=False, default=None), port=dict(required=False, default=830), mode=dict(required=False, default=None) From 22b18bf7d2e64f903a2141ee102a9b378d18919a Mon Sep 17 00:00:00 2001 From: Damien Date: Mon, 12 Dec 2016 17:22:15 -0800 Subject: [PATCH 127/426] Add test before writing output to dest (#193) --- library/junos_cli | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index 79132cdd..56daeb64 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -89,7 +89,7 @@ options: description: - Path to the local server directory where cli output will be saved. - required: true + required: false default: None format: description: @@ -181,11 +181,12 @@ def main(): cli_output = dev.cli(command=args['cli'], format=args['format'] ) - with open(args['dest'], 'w') as outputfile: - if args['format'] == 'text': - outputfile.write(cli_output) - elif args['format'] == 'xml': - outputfile.write(etree.tostring(cli_output)) + if dest is not None: + with open(args['dest'], 'w') as outputfile: + if args['format'] == 'text': + outputfile.write(cli_output) + elif args['format'] == 'xml': + outputfile.write(etree.tostring(cli_output)) except (ValueError, RpcError) as err: msg = 'Unable to get cli output: {0}'.format(str(err)) From 1896c35fb09092e2083e927f724a4b7579d208f3 Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 16 Dec 2016 16:18:47 +0530 Subject: [PATCH 128/426] To support rpc kwargs as dict --- library/junos_rpc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/junos_rpc b/library/junos_rpc index 9af10524..9d1c7fd2 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -84,6 +84,9 @@ options: kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" kwargs={interface_name:em0} kwargs={interface_name:em0,media:True} + kwargs: + interface_name: em0 + media: True filter_xml: description: - This options can be used with get-config rpc only, @@ -202,6 +205,8 @@ def junos_rpc(module, dev): try: if kwargs is None: values = {} + elif isinstance(kwargs, dict): + values = kwargs elif re.search(r'\{.*\}', kwargs): values = {k:v for k,v in re.findall(r'([\w-]+)\s?:\s?\'?\"?([\w\.-]+)\'?\"?',kwargs)} else: From b8f18440aa154b165de4ec5225a7dfb456a7d3dc Mon Sep 17 00:00:00 2001 From: nitin kumar Date: Fri, 16 Dec 2016 16:21:36 +0530 Subject: [PATCH 129/426] To support rpc kwargs as dict --- library/junos_rpc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index 9d1c7fd2..2311d313 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -168,11 +168,13 @@ EXAMPLES = ''' tasks: - name: Get interface information junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - kwargs={interface_name:em0,media:True} - format=json - dest=get_interface_information.conf + host: "{{ inventory_hostname }}" + rpc: get-interface-information + kwargs: + interface_name: em0 + media: True + format: json + dest: get_interface_information.conf register: junos - name: Print configuration From f324f7bb786ad9c5efa33a333a53e1ddb0714df0 Mon Sep 17 00:00:00 2001 From: nitinkr Date: Tue, 20 Dec 2016 17:12:17 +0530 Subject: [PATCH 130/426] Fix: rpc args as dictionary was broken with ansible 2.2 --- library/junos_rpc | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/library/junos_rpc b/library/junos_rpc index 2311d313..28ec8175 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -194,13 +194,12 @@ def junos_rpc(module, dev): if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'CONFIG:' + args['host'] + logging.getLogger().name = 'JUNOS_RPC:' + args['host'] results = {} kwargs = module.params['kwargs'] rpc = module.params['rpc'] results['rpc'] = rpc - results['kwargs'] = kwargs results['changed'] = False results['check_mode'] = module.check_mode logging.info("calling RPC: {0}".format(rpc)) @@ -210,14 +209,25 @@ def junos_rpc(module, dev): elif isinstance(kwargs, dict): values = kwargs elif re.search(r'\{.*\}', kwargs): - values = {k:v for k,v in re.findall(r'([\w-]+)\s?:\s?\'?\"?([\w\.-]+)\'?\"?',kwargs)} + try: + # When data is provided like + # kwargs: + # interface_name: em0 + # media: True + values = eval(kwargs) + except: + # In case when data is provided as below + # kwargs={interface-name:em0,media:True} + values = {k:v for k,v in re.findall(r'([\w-]+)\s?:\s?\'?\"?([\w\.-]+)\'?\"?',kwargs)} else: + # kwargs="interface_name=em0,media=True" values = {k:v for k,v in re.findall('([\w-]+)=([\w\.\-\/]+)', kwargs)} for k,v in values.items(): if v in ['True', 'true']: values[k]=True elif v in ['False', 'false']: values[k]=False + results['kwargs'] = values if rpc in ['get-config', 'get_config']: filter_xml = module.params['filter_xml'] if filter_xml is not None: @@ -266,7 +276,6 @@ def main(): supports_check_mode=False) m_args = module.params - try: from jnpr.junos import Device from jnpr.junos.version import VERSION From 6a2e68927a49554f1f268cc27da8c1ba9cf34272 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 21 Dec 2016 16:54:37 -0800 Subject: [PATCH 131/426] Remove dest option from doc, indicate action as mandatory in doc, add examples for snap_pre & snap_post --- library/junos_jsnapy | 51 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/library/junos_jsnapy b/library/junos_jsnapy index 43502c1e..cf3ee4f0 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -86,12 +86,6 @@ options: for debugging purposes required: false default: None - dest: - description: - - Path to the local server directory where configuration will - be saved. - required: true - default: None dir: description: - Path for the JSNAPy yaml testfiles/configuration file @@ -104,7 +98,7 @@ options: - check - snap_pre - snap_post - required: True + required: false default: None test_files: description: @@ -115,7 +109,7 @@ options: config_file: description: - The YAML configuration file for the JSNAPy tests - required: true + required: false default: None ''' @@ -152,6 +146,45 @@ EXAMPLES = ''' assert: that: - "test1.passPercentage == 100" +--------- + - name: "Collect Pre Snapshot" + junos_jsnapy: + host: "{{ junos_host }}" + port: "{{ netconf_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: tests/test_loopback.yml + action: snap_pre + register: test_pre + +--------- + - name: "Collect Post Snapshot" + junos_jsnapy: + host: "{{ junos_host }}" + port: "{{ netconf_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: tests/test_loopback.yml + action: snap_post + register: test_post + +--------- + - name: "Check after PRE - POST check" + junos_jsnapy: + host: "{{ junos_host }}" + port: "{{ netconf_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: tests/test_loopback.yml + action: check + register: test_check + + - name: Check Results + assert: + that: + - test_check|succeeded + - test_check.passPercentage == 100 + ''' from distutils.version import LooseVersion import logging @@ -256,7 +289,7 @@ def main(): test_files=dict(required=False, type='list', default=None), config_file=dict(required=False, default=None), dir=dict(required=False, default='/etc/jsnapy/testfiles'), - action=dict(required=False, choices=['check', 'snapcheck', 'snap_pre', 'snap_post'], default=None) + action=dict(required=True, choices=['check', 'snapcheck', 'snap_pre', 'snap_post'], default=None) ), mutually_exclusive=[['test_files', 'config_file']], required_one_of=[['test_files', 'config_file']], From 7ddc12d4cb4429f2994eb0396b4187a35c0d3c56 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Thu, 22 Dec 2016 12:31:45 -0800 Subject: [PATCH 132/426] =?UTF-8?q?Fix=20issue=20with=20variable=20name=20?= =?UTF-8?q?args[=E2=80=98dest=E2=80=99]=20instead=20of=20dest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- library/junos_cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_cli b/library/junos_cli index 56daeb64..47d5990a 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -181,7 +181,7 @@ def main(): cli_output = dev.cli(command=args['cli'], format=args['format'] ) - if dest is not None: + if args['dest'] is not None: with open(args['dest'], 'w') as outputfile: if args['format'] == 'text': outputfile.write(cli_output) From 94d4eb11286da51b65f88d6f1255ec06741d0244 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Thu, 22 Dec 2016 12:39:41 -0800 Subject: [PATCH 133/426] Use Docker for everything ansible on travis --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c79f93ab..e39f4433 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,15 +18,13 @@ install: - mkdir library - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible - pip install -r library/ravello-ansible/requirements.txt - - cp .travis_ansible_python.yaml group_vars/all/travis_ansible_python.yaml script: ## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses ## Anyone can connect here to see the list of applications running and see the VMs ## https://cloud.ravellosystems.com/#/GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf/apps - - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml - - rm group_vars/all/travis_ansible_python.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml ## Execute Tests with Docker - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml From 76ab4f0e77aa07d6395dd3e6cd2a13a387d80795 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Thu, 22 Dec 2016 14:27:28 -0800 Subject: [PATCH 134/426] Cleanup tests and travis to use a docker container for ravello-ansible instead of local version --- .travis.yml | 22 ++++++++++++++++------ tests/.travis_ansible_python.yaml | 1 - tests/ansible.cfg | 2 +- tests/group_vars/all/donotdelete | 0 4 files changed, 17 insertions(+), 8 deletions(-) delete mode 100644 tests/.travis_ansible_python.yaml delete mode 100644 tests/group_vars/all/donotdelete diff --git a/.travis.yml b/.travis.yml index e39f4433..19ed7c0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,19 +12,29 @@ env: install: ## Create Docker with Ansible modules and all dependancies - docker build --build-arg ver_ansible=$ANSIBLE_VERSION -t juniper/pyez-ansible:travis . + - docker pull juniper/ravello-ansible:v0.1 ## Install Ansible locally for Ravello and install Roles - - pip install -q ansible==$ANSIBLE_VERSION - cd tests - - mkdir library - - git clone https://github.com/Juniper/ravello-ansible.git library/ravello-ansible - - pip install -r library/ravello-ansible/requirements.txt script: ## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses ## Anyone can connect here to see the list of applications running and see the VMs ## https://cloud.ravellosystems.com/#/GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf/apps - - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml + - > + docker run -t -i -v $(pwd):/project \ + -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" \ + -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" \ + -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" \ + juniper/ravello-ansible:v0.1 \ + ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml + + - > + docker run -t -i -v $(pwd):/project \ + -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" \ + -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" \ + -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" \ + juniper/ravello-ansible:v0.1 \ + ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml ## Execute Tests with Docker - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml diff --git a/tests/.travis_ansible_python.yaml b/tests/.travis_ansible_python.yaml deleted file mode 100644 index e316fb70..00000000 --- a/tests/.travis_ansible_python.yaml +++ /dev/null @@ -1 +0,0 @@ -ansible_python_interpreter: "/home/travis/virtualenv/python2.7.10/bin/python" diff --git a/tests/ansible.cfg b/tests/ansible.cfg index d09f592f..7435948f 100644 --- a/tests/ansible.cfg +++ b/tests/ansible.cfg @@ -1,4 +1,4 @@ [defaults] hash_behaviour=merge -roles_path = /etc/ansible/roles:library/ravello-ansible/roles +roles_path = /etc/ansible/roles diff --git a/tests/group_vars/all/donotdelete b/tests/group_vars/all/donotdelete deleted file mode 100644 index e69de29b..00000000 From 419b6ce1c261f3d8363a5e146681181eb2edf4f7 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Thu, 22 Dec 2016 14:32:32 -0800 Subject: [PATCH 135/426] Remove multi-line shell commands in travis --- .travis.yml | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19ed7c0d..9ea6616e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,21 +20,8 @@ script: ## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses ## Anyone can connect here to see the list of applications running and see the VMs ## https://cloud.ravellosystems.com/#/GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf/apps - - > - docker run -t -i -v $(pwd):/project \ - -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" \ - -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" \ - -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" \ - juniper/ravello-ansible:v0.1 \ - ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - - - > - docker run -t -i -v $(pwd):/project \ - -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" \ - -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" \ - -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" \ - juniper/ravello-ansible:v0.1 \ - ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml + - docker run -t -i -v $(pwd):/project -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" juniper/ravello-ansible:v0.1 ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml + - docker run -t -i -v $(pwd):/project -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" juniper/ravello-ansible:v0.1 ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml ## Execute Tests with Docker - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml From 0cce6ff5911cc0ffacc3e09e7f930e1098944a36 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Wed, 25 Jan 2017 23:49:30 +0530 Subject: [PATCH 136/426] Priv ssh key (#205) * added ssh_private_key_file paramter to all modules * replaced args to m_args as being used by other params --- library/junos_cli | 12 +++++++++++- library/junos_commit | 12 +++++++++++- library/junos_get_config | 12 +++++++++++- library/junos_get_facts | 12 +++++++++++- library/junos_get_table | 12 +++++++++++- library/junos_install_config | 12 +++++++++++- library/junos_install_os | 14 ++++++++++++-- library/junos_jsnapy | 12 +++++++++++- library/junos_ping | 10 ++++++++++ library/junos_rollback | 12 +++++++++++- library/junos_rpc | 12 +++++++++++- library/junos_shutdown | 12 +++++++++++- library/junos_srx_cluster | 12 +++++++++++- library/junos_zeroize | 12 +++++++++++- 14 files changed, 154 insertions(+), 14 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index 47d5990a..1d7718e6 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -66,6 +66,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -130,6 +138,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), timeout=dict(required=False, type='int', default=0), logfile=dict(required=False, default=None), @@ -162,7 +171,8 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], mode=args['mode'], gather_facts=False).open() + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_commit b/library/junos_commit index 65f0f919..a20dff72 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -63,6 +63,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -119,6 +127,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), timeout=dict(required=False, default=0), logfile=dict(required=False, default=None), @@ -154,7 +163,8 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], mode=args['mode'], gather_facts=False).open() + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_get_config b/library/junos_get_config index af75dc70..ced145b4 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -64,6 +64,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -133,6 +141,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), dest=dict(required=True, default=None), @@ -168,7 +177,8 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], mode=args['mode'], gather_facts=False).open() + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_get_facts b/library/junos_get_facts index 06564a1a..96d717f7 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -86,6 +86,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -143,6 +151,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None)), supports_check_mode=True) @@ -164,7 +173,8 @@ def main(): # via NETCONF # ----------- dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], port=m_args['port'], - mode=m_args['mode'], gather_facts=True) + ssh_private_key_file=m_args['ssh_private_key_file'], mode=m_args['mode'], + gather_facts=True) try: dev.open() except Exception as err: diff --git a/library/junos_get_table b/library/junos_get_table index 3e644028..45141c86 100644 --- a/library/junos_get_table +++ b/library/junos_get_table @@ -51,6 +51,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -182,6 +190,7 @@ def main(): default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), file=dict(required=True, default=None), @@ -216,7 +225,8 @@ def main(): args['port'])) try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], mode=args['mode'], gather_facts=False).open() + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_install_config b/library/junos_install_config index 24a8e19f..21e4b581 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -133,6 +133,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -230,7 +238,8 @@ def _load_via_netconf(module): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], - mode=args['mode'], gather_facts=False) + ssh_private_key_file=args['ssh_private_key_file'], mode=args['mode'], + gather_facts=False) dev.open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) @@ -431,6 +440,7 @@ def main(): timeout=dict(required=False, default=0), comment=dict(required=False, default=None), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), confirm=dict(required=False, default=None), check_commit_wait=dict(required=False, default=None) diff --git a/library/junos_install_os b/library/junos_install_os index a9376584..38cd63eb 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -114,6 +114,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None ''' EXAMPLES = ''' @@ -221,7 +229,8 @@ def main(): no_copy=dict(required=False, type='bool', choices=BOOLEANS, default=False), reboot=dict(required=False, type='bool', choices=BOOLEANS, default=True), reboot_pause=dict(required=False, type='int', default=10), - port=dict(required=False, default=830) + port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None) ), supports_check_mode=True ) @@ -242,7 +251,8 @@ def main(): # @@@ need to verify that the package file actually exists # @@@ before proceeding. - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port']) + dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], + ssh_private_key_file=args['ssh_private_key_file']) try: dev.open() except Exception as err: diff --git a/library/junos_jsnapy b/library/junos_jsnapy index cf3ee4f0..b9d0babb 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -74,6 +74,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -284,6 +292,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), test_files=dict(required=False, type='list', default=None), @@ -314,7 +323,8 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], mode=args['mode'], gather_facts=False).open() + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_ping b/library/junos_ping index 3491df9c..9aeee94b 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -62,6 +62,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -161,6 +169,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), timeout=dict(required=False, default=0), dest_ip=dict(required=True, default=None), @@ -195,6 +204,7 @@ def main(): user=m_args['user'], passwd=m_args['passwd'], port=m_args['port'], + ssh_private_key_file=m_args['ssh_private_key_file'], mode=m_args['mode'], gather_facts=False) diff --git a/library/junos_rollback b/library/junos_rollback index f00c3577..cb1c225b 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -63,6 +63,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -135,6 +143,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), logfile=dict(required=False, default=None), rollback=dict(required=True, default=None), @@ -179,7 +188,8 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], mode=args['mode'], gather_facts=False).open() + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) logging.error(msg) diff --git a/library/junos_rpc b/library/junos_rpc index 28ec8175..1c03857c 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -62,6 +62,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -266,6 +274,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), timeout=dict(required=False, type='int', default=0), rpc=dict(required=True, default=None), @@ -290,7 +299,8 @@ def main(): # via NETCONF # ----------- dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], - port=m_args['port'], mode=m_args['mode'], gather_facts=False) + port=m_args['port'], ssh_private_key_file=m_args['ssh_private_key_file'], + mode=m_args['mode'], gather_facts=False) try: dev.open() except Exception as err: diff --git a/library/junos_shutdown b/library/junos_shutdown index 8b32bc7f..96c9956b 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -87,6 +87,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -121,6 +129,7 @@ def main(): passwd=dict(required=False, default=None, no_log=True), reboot=dict(required=False, type='bool', choices=BOOLEANS, default=False), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), in_min=dict(required=False, default=0), at=dict(required=False, type='str', default=None) @@ -154,7 +163,8 @@ def main(): try: dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], mode=args['mode'], gather_facts=False).open() + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False).open() except Exception as err: msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) module.fail_json(msg=msg) diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 36917e4e..6f043211 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -55,6 +55,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -145,6 +153,7 @@ def main(): user=dict(required=False, default=os.getenv('USER')), passwd=dict(required=False, default=None, no_log=True), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), console=dict(required=False, default=None), # param to netconify cluster_enable=dict(required=True, type='bool', choices=BOOLEANS, default=None), @@ -186,7 +195,8 @@ def main(): if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], mode=args['mode'], gather_facts=False) try: logging.info('LOGIN: host={0} port={1}'.format(args['host'], args['port'])) diff --git a/library/junos_zeroize b/library/junos_zeroize index b283e6b0..f9909f1e 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -93,6 +93,14 @@ options: - port number to use when connecting to the device required: false default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None mode: description: - mode of console connection (telnet/serial). If mode is not @@ -132,6 +140,7 @@ def main(): passwd=dict(required=False, default=None, no_log=True), logfile=dict(required=False, default=None), port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None) ), supports_check_mode=False) @@ -174,7 +183,8 @@ def main(): if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], mode=args['mode'], gather_facts=False) try: use_notifier(None, 'LOGIN', 'host={0}'.format(args['host'])) From 1c993cdadbf91ea8606894fd4f40fbfe847f11b6 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Fri, 3 Mar 2017 07:34:57 -0700 Subject: [PATCH 137/426] Modify junos_get_facts to be compatible with py-junos-eznc#638. (#210) Ansible 2 doesn't like custom objects in a module's return value, and after py-junos-eznc#638 PyEZ facts are now a custom object rather than a true dict. Since facts are read-only, we must begin by copying facts into a dict. Also, since PyEZ facts are now "on-demand", this must be done before closing the connection. These changes remain backwards compatible with older PyEZ versions that have older fact gathering code. --- library/junos_get_facts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/library/junos_get_facts b/library/junos_get_facts index 96d717f7..bbbbcb7c 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -182,13 +182,31 @@ def main(): module.fail_json(msg=msg) return else: + # Ansible 2 doesn't like custom objects in the return value, and + # PyEZ facts are now a custom object rather than a true dict. + # Since facts are read-only, we must begin by copying facts into a + # dict. Also, since PyEZ facts are now "on-demand", this must be + # done before closing the connection. These changes remain backwards + # compatible with older PyEZ versions that have older fact gathering + # code. + m_results['facts'] = dict(dev.facts) dev.close() - dev.facts['has_2RE'] = dev.facts['2RE'] - del dev.facts['2RE'] # Ansible doesn't allow variables starting with numbers - # Ansible 2 doesn't like custom objects in the return value. - # Convert the version_info key from a junos.version_info object to a dict - dev.facts['version_info'] = dict(dev.facts['version_info']) - m_results['facts'] = dev.facts + # Ansible doesn't allow keys starting with numbers. + # Replace the '2RE' key with the 'has_2RE' key. + if '2RE' in m_results['facts']: + m_results['facts']['has_2RE'] = m_results['facts']['2RE'] + del m_results['facts']['2RE'] + # The value of the 'version_info' key is a custom junos.version_info + # object. Convert this value to a dict. + if 'version_info' in m_results['facts']: + m_results['facts']['version_info'] = dict( + m_results['facts']['version_info']) + # The values of the ['junos_info'][re_name]['object'] keys are + # custom junos.version_info objects. Convert all of these to dicts. + if 'junos_info' in m_results['facts']: + for key in m_results['facts']['junos_info']: + m_results['facts']['junos_info'][key]['object'] = dict( + m_results['facts']['junos_info'][key]['object']) if m_args['savedir'] is not None: fname = "{0}/{1}-facts.json".format(m_args['savedir'], dev.facts['hostname']) with open(fname, 'w') as factfile: From b4e9fe17bc6c3f664b543a31a3812fec0118d124 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 3 Mar 2017 20:05:42 +0530 Subject: [PATCH 138/426] junos_get_config options type defined for Ansible >=2.1 (#211) With Ansible version >=2.1, if the type is not defined, the value is assigned as string only. Hence change to take explicitly define type of options param --- library/junos_get_config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_get_config b/library/junos_get_config index ced145b4..428cbb32 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -146,7 +146,7 @@ def main(): logfile=dict(required=False, default=None), dest=dict(required=True, default=None), format=dict(required=False, choices=['text', 'xml'], default='text'), - options=dict(required=False, default=None), + options=dict(required=False, default=None, type='dict'), filter=dict(required=False, default=None) ), supports_check_mode=True) From f395f2b7e72cf17d9bb2acca390947f46a9a42ff Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 8 Mar 2017 10:36:48 -0500 Subject: [PATCH 139/426] Updated Dockerfile and README --- Dockerfile | 6 +++--- README.md | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index a3454401..b180436e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM juniper/pyez:2.0.1 -MAINTAINER ntwrkguru@gmail.com +FROM juniper/pyez:latest +MAINTAINER Stephen Steiner -ARG ver_ansible=2.2.0.0 +ARG ver_ansible=2.3.0.0 ARG ver_jsnapy=1.0.0 WORKDIR /tmp diff --git a/README.md b/README.md index 47442373..f56db68f 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,33 @@ This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the inst [jeremy@ansible-junos-stdlib]$ echo $ANSIBLE_LIBRARY /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible ``` - +### Docker +To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. +``` +$ docker run -it --rm juniper/pyez-ansible ash +``` +Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. +``` +$ docker run -it --rm -v $PWD:/playbooks ash +``` +You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: +``` +example +|playbook.yml +|hosts +|-vars +|-templates +|-scripts +``` +We can move to the example directory and run the playbook with the following command: +``` +$ docker run -it --rm -v $PWD:/playbooks ansible-playbook -i hosts playbook.yml +``` +You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. +``` +$ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks ansible-playbook" +$ pb-ansible -i hosts playbook.yml +``` ## Example Playbook This example outlines how to use Ansible to install or upgrade the software image on a device running Junos OS. From b580ba3691a43673abeac68fc1fe59c25d00ecf1 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 8 Mar 2017 11:33:36 -0500 Subject: [PATCH 140/426] Typo in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f56db68f..bcfa1234 100644 --- a/README.md +++ b/README.md @@ -105,11 +105,11 @@ example ``` We can move to the example directory and run the playbook with the following command: ``` -$ docker run -it --rm -v $PWD:/playbooks ansible-playbook -i hosts playbook.yml +$ docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml ``` You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. ``` -$ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks ansible-playbook" +$ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook" $ pb-ansible -i hosts playbook.yml ``` ## Example Playbook From cdcbcccb68099497416ba3cd5362dd887bf9ad0b Mon Sep 17 00:00:00 2001 From: ntwrkguru Date: Wed, 8 Mar 2017 11:56:00 -0500 Subject: [PATCH 141/426] Typo in README (#213) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f56db68f..bcfa1234 100644 --- a/README.md +++ b/README.md @@ -105,11 +105,11 @@ example ``` We can move to the example directory and run the playbook with the following command: ``` -$ docker run -it --rm -v $PWD:/playbooks ansible-playbook -i hosts playbook.yml +$ docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml ``` You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. ``` -$ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks ansible-playbook" +$ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook" $ pb-ansible -i hosts playbook.yml ``` ## Example Playbook From 70e6b952695d4730c2739325202d6f76603ca611 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 8 Mar 2017 11:56:20 -0500 Subject: [PATCH 142/426] Revert to Ansible 2.2 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b180436e..7534e750 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM juniper/pyez:latest MAINTAINER Stephen Steiner -ARG ver_ansible=2.3.0.0 +ARG ver_ansible=2.2.0.0 ARG ver_jsnapy=1.0.0 WORKDIR /tmp From 97df1958ccb709969a6718850208485a13a6adb2 Mon Sep 17 00:00:00 2001 From: ntwrkguru Date: Wed, 8 Mar 2017 12:00:14 -0500 Subject: [PATCH 143/426] Reverting to Ansible 2.2 (#214) * Typo in README * Revert to Ansible 2.2 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b180436e..7534e750 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM juniper/pyez:latest MAINTAINER Stephen Steiner -ARG ver_ansible=2.3.0.0 +ARG ver_ansible=2.2.0.0 ARG ver_jsnapy=1.0.0 WORKDIR /tmp From 374a12eaaf93d454b7722abb49d0fc8aec6e3675 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 8 Mar 2017 12:38:24 -0500 Subject: [PATCH 144/426] Cleaned up markdown and changed contributors --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bcfa1234..14646db6 100644 --- a/README.md +++ b/README.md @@ -75,26 +75,36 @@ To download the junos role to the Ansible server, execute the ansible-galaxy ins ``` ### Git clone + For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: + ``` user@ansible-junos-stdlib> source env-setup ``` + This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: ``` [jeremy@ansible-junos-stdlib]$ echo $ANSIBLE_LIBRARY /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible ``` + ### Docker + To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. + ``` $ docker run -it --rm juniper/pyez-ansible ash ``` + Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. + ``` $ docker run -it --rm -v $PWD:/playbooks ash ``` + You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: + ``` example |playbook.yml @@ -103,15 +113,21 @@ example |-templates |-scripts ``` + We can move to the example directory and run the playbook with the following command: + ``` +$ cd example/ $ docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml ``` + You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. + ``` $ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook" $ pb-ansible -i hosts playbook.yml ``` + ## Example Playbook This example outlines how to use Ansible to install or upgrade the software image on a device running Junos OS. @@ -168,8 +184,8 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c *Contributors:* -[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr), [Damien Garros](https://github.com/dgarros) +[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr), [Stephen Steiner](https://github.com/ntwrkguru) *Former Contributors:* -[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog) +[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros) From f3b282f13d2aa8e183bf9020f375ab1b2038c718 Mon Sep 17 00:00:00 2001 From: Stacy Smith Date: Mon, 13 Mar 2017 19:05:19 -0600 Subject: [PATCH 145/426] Update modules so they aren't executed upon import. This makes them compatible with Ansible 2.3 which tries to import them to see if `USE_PERSISTENT_CONNECTION` is set. --- library/junos_cli | 4 +++- library/junos_commit | 4 +++- library/junos_get_config | 4 +++- library/junos_get_facts | 4 +++- library/junos_install_config | 4 +++- library/junos_install_os | 4 +++- library/junos_jsnapy | 4 +++- library/junos_ping | 3 ++- library/junos_rollback | 4 +++- library/junos_rpc | 4 +++- library/junos_shutdown | 4 +++- library/junos_srx_cluster | 4 +++- library/junos_zeroize | 4 +++- 13 files changed, 38 insertions(+), 13 deletions(-) diff --git a/library/junos_cli b/library/junos_cli index 1d7718e6..72ce84bd 100644 --- a/library/junos_cli +++ b/library/junos_cli @@ -215,4 +215,6 @@ def main(): module.exit_json() from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_commit b/library/junos_commit index a20dff72..e380d1ed 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -228,4 +228,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_get_config b/library/junos_get_config index ced145b4..cfd64cb6 100755 --- a/library/junos_get_config +++ b/library/junos_get_config @@ -251,4 +251,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_get_facts b/library/junos_get_facts index 96d717f7..9b22c560 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -242,4 +242,6 @@ def main(): module.exit_json(**m_results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_install_config b/library/junos_install_config index 21e4b581..5c69a8fe 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -461,4 +461,6 @@ def main(): _ldr(module) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_install_os b/library/junos_install_os index 38cd63eb..06cef9f7 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -273,4 +273,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_jsnapy b/library/junos_jsnapy index b9d0babb..4fe6b794 100644 --- a/library/junos_jsnapy +++ b/library/junos_jsnapy @@ -353,4 +353,6 @@ def main(): module.exit_json(**data) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_ping b/library/junos_ping index 9aeee94b..f120c1a0 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -305,4 +305,5 @@ def main(): from ansible.module_utils.basic import * -main() +if __name__ == '__main__': + main() diff --git a/library/junos_rollback b/library/junos_rollback index cb1c225b..581ad292 100755 --- a/library/junos_rollback +++ b/library/junos_rollback @@ -279,4 +279,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_rpc b/library/junos_rpc index 1c03857c..63758ec5 100644 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -318,4 +318,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_shutdown b/library/junos_shutdown index 96c9956b..febddc30 100644 --- a/library/junos_shutdown +++ b/library/junos_shutdown @@ -191,4 +191,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster index 6f043211..653f771f 100644 --- a/library/junos_srx_cluster +++ b/library/junos_srx_cluster @@ -266,4 +266,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() diff --git a/library/junos_zeroize b/library/junos_zeroize index f9909f1e..ce7305b3 100644 --- a/library/junos_zeroize +++ b/library/junos_zeroize @@ -227,4 +227,6 @@ def main(): module.exit_json(**results) from ansible.module_utils.basic import * -main() + +if __name__ == '__main__': + main() From 974008175617f8f678778825e3e140e81f49334b Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Thu, 16 Mar 2017 20:26:44 +0530 Subject: [PATCH 146/426] "update" action for config load (#218) * junos_get_config options type defined for Ansible >=2.1 * Config.load can take "update" as action * Python 3.x compatible changes --- library/junos_install_config | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index 5c69a8fe..a212ebc0 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -84,6 +84,19 @@ options: required: false default: no choices: ['true','false','yes','no'] + update: + description: + - If set to ``True`` Compare a complete loaded configuration against + the candidate configuration. For each hierarchy level or + configuration object that is different in the two configurations, + the version in the loaded configuration replaces the version in the + candidate configuration. When the configuration is later committed, + only system processes that are affected by the changed configuration + elements parse the new configuration. This action is supported from + PyEZ 2.1 + required: false + default: no + choices: ['true','false','yes','no'] replace: description: - Specify whether the configuration I(file) uses "replace:" statements. @@ -202,13 +215,13 @@ import os from distutils.version import LooseVersion - def _load_via_netconf(module): args = module.params try: from jnpr.junos import Device - from jnpr.junos.exception import * + from jnpr.junos.exception import LockError, UnlockError, \ + ConfigLoadError, CommitError from jnpr.junos.utils.config import Config from jnpr.junos.version import VERSION if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): @@ -228,9 +241,12 @@ def _load_via_netconf(module): in_check_mode = module.check_mode overwrite = module.boolean(module.params['overwrite']) replace = module.boolean(module.params['replace']) + update = module.boolean(module.params['update']) - if all([overwrite, replace]): - msg = "Overwrite and Replace cannot both be True!" + actions = filter(lambda item: module.params.get(item, False), + ('overwrite', 'replace', 'update')) + if len(list(actions)) >= 2: + msg = 'action can be only one among %s' % ', '.join(actions) logging.error(msg) module.fail_json(msg=msg) @@ -281,6 +297,8 @@ def _load_via_netconf(module): load_args['merge'] = False elif overwrite is True: load_args['overwrite'] = True + elif update is True: + load_args['update'] = True elif overwrite is False: load_args['merge'] = True cu.load(**load_args) @@ -433,6 +451,7 @@ def main(): console=dict(required=False, default=None), file=dict(required=True), overwrite=dict(required=False, type='bool', choices=BOOLEANS, default=False), + update=dict(required=False, type='bool', choices=BOOLEANS, default=False), replace=dict(required=False, type='bool', choices=BOOLEANS, default=False), logfile=dict(required=False, default=None), diffs_file=dict(required=False, default=None), From b14dc80a8e68cb094cd33413a09b18ef8388b71c Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Thu, 23 Mar 2017 20:00:04 +0530 Subject: [PATCH 147/426] ISSU/NSSU support (#219) --- library/junos_install_os | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/library/junos_install_os b/library/junos_install_os index 06cef9f7..33f76edf 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -88,7 +88,26 @@ options: after the installation completes. required: false default: yes - choices: ['yes','no'] + choices: ['true','false', 'yes','no'] + issu: + description: + - If set to B(true), allows unified in-service software upgrade + (ISSU) feature enables you to upgrade between two different + Junos OS releases with no disruption on the control plane and + with minimal disruption of traffic. + required: false + default: false + choices: ['true','false', 'yes','no'] + nssu: + description: + - If set to B(true), allows nonstop software upgrade (NSSU) + enables you to upgrade the software running on a Juniper + Networks EX Series Virtual Chassis or a Juniper Networks EX + Series Ethernet Switch with redundant Routing Engines with a + single command and minimal disruption to network traffic. + required: false + default: false + choices: ['true','false', 'yes','no'] reboot_pause: description: - Amount of time in seconds to wait after the reboot is issued @@ -137,6 +156,7 @@ import re import os from distutils.version import LooseVersion + def junos_install_os(module, dev): args = module.params @@ -194,6 +214,10 @@ def junos_install_os(module, dev): sw_args = dict(progress=update_my_progress) sw_args['no_copy'] = module.boolean(args['no_copy']) + if module.boolean(args['issu']): + sw_args['issu'] = module.boolean(args['issu']) + if module.boolean(args['nssu']): + sw_args['nssu'] = module.boolean(args['nssu']) ok = sw.install(package, **sw_args) if ok is not True: @@ -228,6 +252,8 @@ def main(): logfile=dict(required=False, default=None), no_copy=dict(required=False, type='bool', choices=BOOLEANS, default=False), reboot=dict(required=False, type='bool', choices=BOOLEANS, default=True), + issu=dict(required=False, type='bool', choices=BOOLEANS, default=False), + nssu=dict(required=False, type='bool', choices=BOOLEANS, default=False), reboot_pause=dict(required=False, type='int', default=10), port=dict(required=False, default=830), ssh_private_key_file=dict(required=False, default=None) @@ -247,6 +273,9 @@ def main(): if int(args['port']) == 23 and LooseVersion(VERSION) < LooseVersion('2.0.0'): module.fail_json(msg='junos-eznc >= 2.0.0 is required for telnet connection.') + if (module.boolean(args['issu']) or module.boolean(args['nssu'])) and \ + LooseVersion(VERSION) < LooseVersion('2.1.0'): + module.fail_json(msg='junos-eznc >= 2.1.0 is required for ISSU/NSSU support.') # @@@ need to verify that the package file actually exists # @@@ before proceeding. From 8ce62ae5520b0f57b53ba22f09e876daccffafad Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 6 Apr 2017 16:58:17 -0600 Subject: [PATCH 148/426] JSON dump of Junos facts requires native Python data types. Fixes #223 (#224) After Juniper/py-junos-eznc#638, PyEZ facts are now a custom object rather than a true dict. In addition, facts are now read-only. This was partially addressed in #210, but I missed the case where the `savedir` option is specified to dump the facts into JSON files. The same fix is also required in this case. --- library/junos_get_facts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_get_facts b/library/junos_get_facts index 351691f4..e2400ef6 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -210,7 +210,7 @@ def main(): if m_args['savedir'] is not None: fname = "{0}/{1}-facts.json".format(m_args['savedir'], dev.facts['hostname']) with open(fname, 'w') as factfile: - json.dump(dev.facts, factfile) + json.dump(m_results['facts'], factfile) else: # ----------- # via CONSOLE From 43b0f4885cea248ed3602e95ee69fab3b26c9f42 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Fri, 21 Apr 2017 14:24:58 -0600 Subject: [PATCH 149/426] Preparing for v.1.4.1 release. (#228) * Preparing for v.1.4.1 release. * Removing Ansible 1.9.6 from testing until the tests can be updated. --- .gitignore | 9 +++++++++ .travis.yml | 1 + COPYRIGHT | 2 +- Dockerfile | 4 ++-- README.md | 11 +++++++++-- docs/conf.py | 2 +- meta/main.yml | 2 +- setup.py | 2 +- version.py | 4 ++-- 9 files changed, 27 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 21c084e8..ca8ba219 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,12 @@ docs/*.rst docs/_build/ tests/.vagrant/* + +# Window File Explorer +desktop.ini + +# Mac OS X Finder +.DS_Store + +# PyCharm +.idea diff --git a/.travis.yml b/.travis.yml index 9ea6616e..96bd3d8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ sudo: required dist: trusty env: + - ANSIBLE_VERSION=2.3.0.0 - ANSIBLE_VERSION=2.2.0.0 - ANSIBLE_VERSION=2.1.3.0 diff --git a/COPYRIGHT b/COPYRIGHT index 0a5bbb37..a56915bb 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,5 +1,5 @@ - Copyright (c) 1999-2014, Juniper Networks Inc. + Copyright (c) 1999-2017, Juniper Networks Inc. 2014, Jeremy Schulman All rights reserved. diff --git a/Dockerfile b/Dockerfile index 7534e750..066dc1a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM juniper/pyez:latest MAINTAINER Stephen Steiner -ARG ver_ansible=2.2.0.0 -ARG ver_jsnapy=1.0.0 +ARG ver_ansible=2.3.0.0 +ARG ver_jsnapy=1.1.0 WORKDIR /tmp RUN mkdir /tmp/ansible-junos-stdlib &&\ diff --git a/README.md b/README.md index bcfa1234..582a1e24 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,8 @@ callback_whitelist = jsnapy This repo assumes you have the [DEPENDENCIES](#dependencies) installed on your system. ### Ansible Galaxy Role -To download the junos role to the Ansible server, execute the ansible-galaxy install command, and specify **Juniper.junos**. +To download the latest released version of the junos role to the Ansible +server, execute the ansible-galaxy install command, and specify **Juniper.junos**. ``` [root@ansible-cm]# ansible-galaxy install Juniper.junos @@ -73,6 +74,11 @@ To download the junos role to the Ansible server, execute the ansible-galaxy ins - extracting Juniper.junos to /usr/local/etc/ansible/roles/Juniper.junos - Juniper.junos was installed successfully ``` +You can also use the ansible-galaxy install command to install the latest +development version of the junos role directly from GitHub. +``` +sudo ansible-galaxy install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos +``` ### Git clone For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: @@ -156,7 +162,8 @@ Thes modules require the following to be installed on the Ansible server: * Python 2.6 or 2.7 * [Ansible](http://www.ansible.com) 1.5 or later -* Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 1.2.2 or later (2.0.0 if you want to use the mode: telnet) +* Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.0.1 or +later (2.1.1 is recommended and required for some features) ## LICENSE diff --git a/docs/conf.py b/docs/conf.py index 397f09e0..49d0d69c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # General information about the project. project = u'Junos Ansible Modules' -copyright = u'2014, Juniper Networks, Inc' +copyright = u'2014-2017, Juniper Networks, Inc' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/meta/main.yml b/meta/main.yml index d8d3360e..46ce539e 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -11,7 +11,7 @@ galaxy_info: # - Apache # - CC-BY license: Apache 2.0 - min_ansible_version: 1.5 + min_ansible_version: 1.9 # # Below are all platforms currently available. Just uncomment # the ones that apply to your role. If you don't see your diff --git a/setup.py b/setup.py index 92d71829..863902aa 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="ansible-junos-stdlib", version=VERSION, - author="Jeremy Schulman", + author="Jeremy Schulman, Nitin Kumar, Rick Sherman, Stacy Smith", author_email="jnpr-community-netdev@juniper.net", description=("Ansible Network build automation of Junos devices."), license="Apache 2.0", diff --git a/version.py b/version.py index 0a1c4786..dcce9c6d 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.4.0" -DATE = "2016-Sept-21" +VERSION = "1.4.1" +DATE = "2017-April-21" From ad9f49da1adb3096a42ba1f6886f44fb5ec59277 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Fri, 21 Apr 2017 21:28:08 -0600 Subject: [PATCH 150/426] Fix documentation for v 1.4.1 (#229) --- library/junos_rpc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) mode change 100644 => 100755 library/junos_rpc diff --git a/library/junos_rpc b/library/junos_rpc old mode 100644 new mode 100755 index 63758ec5..772b6352 --- a/library/junos_rpc +++ b/library/junos_rpc @@ -92,9 +92,7 @@ options: kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" kwargs={interface_name:em0} kwargs={interface_name:em0,media:True} - kwargs: - interface_name: em0 - media: True + kwargs={interface_name:em0,media:True} filter_xml: description: - This options can be used with get-config rpc only, From c7614f343175b89ac5c27d42655688e79632fbee Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 24 Apr 2017 10:47:09 -0600 Subject: [PATCH 151/426] Fix docs for Ansible 2.3 (#230) * Fix documentation build for Ansible >= 2.3 Ansible >= 2.3 returns an additional parameter from ansible.utils.module_docs.get_docstring(). This was preventing the readthedocs documenation from building correctly. Updated the ansible2rst.process_module() to handle both the old and new return values of ansible.utils.module_docs.get_docstring(). --- docs/ansible2rst.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/ansible2rst.py b/docs/ansible2rst.py index 6330dc67..c4538ad4 100755 --- a/docs/ansible2rst.py +++ b/docs/ansible2rst.py @@ -26,10 +26,11 @@ import sys import datetime import cgi +from distutils.version import LooseVersion from jinja2 import Environment, FileSystemLoader -import ansible.utils from ansible.utils import module_docs +from ansible import __version__ as ansible_version ##################################################################################### # constants and paths @@ -131,7 +132,10 @@ def jinja2_environment(template_dir, typ): def process_module(fname, template, outputname): print MODULEDIR + fname - doc, examples, returndocs = module_docs.get_docstring(MODULEDIR + fname) + if LooseVersion(ansible_version) >= LooseVersion('2.3.0'): + doc, examples, returndocs, _metadata = module_docs.get_docstring(MODULEDIR + fname) + else: + doc, examples, returndocs = module_docs.get_docstring(MODULEDIR + fname) all_keys = [] From 3068e3fd79783135ef0f5279514d6dcc7bf737ba Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 24 Apr 2017 11:24:07 -0600 Subject: [PATCH 152/426] Prepare v. 1.4.2 (#231) * Prepare for the version 1.4.2 release. --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index dcce9c6d..72c15288 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.4.1" -DATE = "2017-April-21" +VERSION = "1.4.2" +DATE = "2017-April-24" From 35e0e05ddd7cca6910d1280711e93431177d4969 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 24 Apr 2017 14:12:15 -0600 Subject: [PATCH 153/426] Prepare v 2 0 0 dev0 (#232) --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 72c15288..6b78ccab 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.4.2" +VERSION = "2.0.0+dev0" DATE = "2017-April-24" From dad93fffdbcd5c4537657e21d413ac31e1f25ba1 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 23 May 2017 14:44:19 -0600 Subject: [PATCH 154/426] Add the force_host argument to junos_install_os. Addresses #233. (#234) * Preparing for v.1.4.1 release. * Add the `force_host` argument to `junos_install_os`. Addresses #233. By default, the Host Software package on QFX-series platforms is not upgraded by the RPC. Adding the argument to the RPC forces the Host Software package to also be upgraded. This PR adds the ability to pass the `force_host` argument to the `junos_install_os` module. This optional boolean argument defaults to False. When set to True, it passes the argument to the underlying RPC. * Removing Ansible 1.9.6 from testing. --- library/junos_install_os | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/junos_install_os b/library/junos_install_os index 33f76edf..424dd84f 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -108,6 +108,13 @@ options: required: false default: false choices: ['true','false', 'yes','no'] + force_host: + description: + - If set to B(true), forces the upgrade of the Host Software + package on QFX-series devices. + required: false + default: false + choices: ['true','false', 'yes','no'] reboot_pause: description: - Amount of time in seconds to wait after the reboot is issued @@ -218,6 +225,8 @@ def junos_install_os(module, dev): sw_args['issu'] = module.boolean(args['issu']) if module.boolean(args['nssu']): sw_args['nssu'] = module.boolean(args['nssu']) + if module.boolean(args['force_host']): + sw_args['force_host'] = module.boolean(args['force_host']) ok = sw.install(package, **sw_args) if ok is not True: @@ -254,6 +263,7 @@ def main(): reboot=dict(required=False, type='bool', choices=BOOLEANS, default=True), issu=dict(required=False, type='bool', choices=BOOLEANS, default=False), nssu=dict(required=False, type='bool', choices=BOOLEANS, default=False), + force_host=dict(required=False, type='bool', choices=BOOLEANS, default=False), reboot_pause=dict(required=False, type='int', default=10), port=dict(required=False, default=830), ssh_private_key_file=dict(required=False, default=None) From 8f184ca3b90e91fe05fe34344d105ef7245c7382 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 23 May 2017 14:47:03 -0600 Subject: [PATCH 155/426] Add new re_name and master_state facts to junos_get_facts (#239) * Preparing for v.1.4.1 release. * Add new re_name and master_state facts to junos_get_facts These new facts are based on the dev.re_name and dev.master PyEZ device properties. * Removing Ansible 1.9 from .travis.yml --- library/junos_get_facts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/junos_get_facts b/library/junos_get_facts index e2400ef6..704b98cd 100644 --- a/library/junos_get_facts +++ b/library/junos_get_facts @@ -162,13 +162,12 @@ def main(): try: from jnpr.junos import Device from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.1.2'): + module.fail_json(msg='junos-eznc >= 2.1.2 is required for this ' + 'module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) - if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') # ----------- # via NETCONF # ----------- @@ -190,6 +189,8 @@ def main(): # compatible with older PyEZ versions that have older fact gathering # code. m_results['facts'] = dict(dev.facts) + m_results['facts']['re_name'] = dev.re_name + m_results['facts']['master_state'] = dev.master dev.close() # Ansible doesn't allow keys starting with numbers. # Replace the '2RE' key with the 'has_2RE' key. From e9234ac875d042bf2c2f8f041d75b4e539c34efa Mon Sep 17 00:00:00 2001 From: vnitinv Date: Mon, 19 Jun 2017 13:55:41 +0530 Subject: [PATCH 156/426] junos_install_os used to fail without 'logfile' parameter --- library/junos_install_os | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_os b/library/junos_install_os index 424dd84f..633e52ce 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -180,7 +180,7 @@ def junos_install_os(module, dev): def do_log(msg, level='info'): getattr(logging, level)(msg) else: - def do_log(msg): + def do_log(msg, level=None): pass # ------------------------------------------------------------------------- From c65358184504fc25df0f8926a7f9c261187dacb5 Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Wed, 21 Jun 2017 04:35:09 +0530 Subject: [PATCH 157/426] version finding regex in case version is not provided (#249) --- library/junos_install_os | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/junos_install_os b/library/junos_install_os index 424dd84f..6576c7b2 100644 --- a/library/junos_install_os +++ b/library/junos_install_os @@ -189,7 +189,7 @@ def junos_install_os(module, dev): if args['version'] is None: # extract version string from package file - m = re.search('-([^\\-]*)-domestic.*', args['package']) + m = re.search('-(\d{2}\.\d.*)\..*$', args['package']) args['version'] = m.group(1) has_ver = dev.facts['version'] From 1cd7f6a5d0f33c2d058183df5d177b044a1890ac Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Mon, 17 Jul 2017 20:03:23 +0530 Subject: [PATCH 158/426] ignore_warning support for junos_install_config (#248) * ignore_warning support for junos_install_config * PyEZ required version changed, list conversion as per review comment * changes as per review comment --- library/junos_install_config | 51 +++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index a212ebc0..dd5a91f9 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -51,7 +51,7 @@ description: NETCONF include ASCII text, Junos XML elements, and Junos OS B(set) commands. Configurations performed through the console must only use ASCII text formatting. requirements: - - junos-eznc >= 1.2.2 + - junos-eznc >= 2.1.1 - junos-netconify >= 1.0.1, when using the I(console) option options: host: @@ -165,6 +165,16 @@ options: - Provide a confirm in minutes to the commit of the configuration required: false default: None + ignore_warning: + description: + - A boolean, string or list of string. + If the value is True, it will ignore all warnings regardless of the + warning message. If the value is a string, it will ignore + warning(s) if the message of each warning matches the string. If + the value is a list of strings, ignore warning(s) if the message of + each warning matches at least one of the strings in the list. + required: false + default: None check_commit_wait: description: - Set to number of seconds to wait between check and commit. @@ -213,6 +223,10 @@ import logging from os.path import isfile import os from distutils.version import LooseVersion +import re +import ast + +from ansible.module_utils.basic import * def _load_via_netconf(module): @@ -224,14 +238,11 @@ def _load_via_netconf(module): ConfigLoadError, CommitError from jnpr.junos.utils.config import Config from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + if not LooseVersion(VERSION) >= LooseVersion('2.1.1'): + module.fail_json(msg='junos-eznc >= 2.1.1 is required for this module') except ImportError as ex: module.fail_json(msg='ImportError: %s' % ex.message) - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - logfile = args['logfile'] if logfile is not None: logging.basicConfig(filename=logfile, level=logging.INFO, @@ -250,11 +261,21 @@ def _load_via_netconf(module): logging.error(msg) module.fail_json(msg=msg) - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) + if str(args['ignore_warning']).lower() in BOOLEANS: + args['ignore_warning'] = module.boolean(module.params['ignore_warning']) + elif isinstance(args['ignore_warning'], str) and \ + re.search('\[.*\]', args['ignore_warning']): + args['ignore_warning'] = ast.literal_eval(args['ignore_warning']) + + logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], + args['host'], + args['port'])) try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], - ssh_private_key_file=args['ssh_private_key_file'], mode=args['mode'], + dev = Device(args['host'], user=args['user'], password=args['passwd'], + port=args['port'], + ssh_private_key_file=args['ssh_private_key_file'], + mode=args['mode'], gather_facts=False) dev.open() except Exception as err: @@ -287,12 +308,15 @@ def _load_via_netconf(module): logging.error(msg) module.fail_json(msg=msg) + logging.info("loading config") + load_args = {} + if args['ignore_warning'] is not None: + load_args = {'ignore_warning': args['ignore_warning']} try: # load the config. the cu.load will raise # an exception if there is even a warning. # so we want to avoid that condition. - logging.info("loading config") - load_args = {'path': file_path} + load_args.update({'path': file_path}) if replace is True: load_args['merge'] = False elif overwrite is True: @@ -347,6 +371,8 @@ def _load_via_netconf(module): else: logging.info("committing change, please be patient") opts = {} + if args['ignore_warning'] is not None: + opts = {'ignore_warning': args['ignore_warning']} if args['comment'] is not None: opts['comment'] = args['comment'] if args['confirm'] is not None: @@ -462,6 +488,7 @@ def main(): ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), confirm=dict(required=False, default=None), + ignore_warning=dict(required=False, default=None), check_commit_wait=dict(required=False, default=None) ), supports_check_mode=True) @@ -479,7 +506,5 @@ def main(): _ldr = _load_via_netconf if args['console'] is None else _load_via_console _ldr(module) -from ansible.module_utils.basic import * - if __name__ == '__main__': main() From ed6d7123dbc91411ac2512882b1ceeb1983b9385 Mon Sep 17 00:00:00 2001 From: Jeff Loughridge Date: Wed, 23 Aug 2017 11:39:17 -0400 Subject: [PATCH 159/426] Add text for ansible-galaxy installation from GitHub (#204) Fix #202 --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 582a1e24..bac698ef 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,18 @@ This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the inst [jeremy@ansible-junos-stdlib]$ echo $ANSIBLE_LIBRARY /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible ``` +An alternative to the above is installing the role from GitHub using ansible-galaxy. The first step is creating a yaml file for +input data to ansible-galaxy. We'll use install_role.yml. + +```yaml +--- +- src: https://github.com/Juniper/ansible-junos-stdlib + name: Juniper.junos +``` + +Now run `sudo ansible-galaxy install -r install_role.yml` to install the role. + + ### Docker To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. ``` @@ -118,6 +130,7 @@ You may have noticed that the base command is almost always the same. We can als $ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook" $ pb-ansible -i hosts playbook.yml ``` + ## Example Playbook This example outlines how to use Ansible to install or upgrade the software image on a device running Junos OS. From 60f4260a5775b711f44d269cb501eb539a2d07a5 Mon Sep 17 00:00:00 2001 From: mkomon Date: Mon, 18 Sep 2017 03:43:23 +0200 Subject: [PATCH 160/426] Add do_not_fragment to junos_ping (#262) * add option to set DF bit --- library/junos_ping | 13 +++++++++++- tests/pb.junos_ping.yaml | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/library/junos_ping b/library/junos_ping index f120c1a0..f966a436 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -118,6 +118,11 @@ options: - Number of packet to send required: false default: 5 + do_not_fragment: + description: + - Set Don't Fragment bit + required: false + default: false ''' EXAMPLES = ''' @@ -152,6 +157,7 @@ EXAMPLES = ''' passwd={{ ansible_ssh_pass }} dest_ip={{ item.peer_ip }} source_ip={{ item.local_ip }} + do_not_fragment=True ttl=1 with_items: "{{underlay.neighbors}}" ''' @@ -179,7 +185,8 @@ def main(): routing_instance=dict(required=False, default=None), ttl=dict(required=False, default=None), acceptable_packet_loss=dict(required=False, default=0), - count=dict(required=False, default='5') + count=dict(required=False, default='5'), + do_not_fragment=dict(required=False, default=False) ), supports_check_mode=False ) @@ -218,6 +225,7 @@ def main(): results['dest_ip'] = m_args['dest_ip'] results['count'] = m_args['count'] results['rapid'] = m_args['rapid'] + results['do_not_fragment'] = m_args['do_not_fragment'] results['changed'] = False @@ -229,6 +237,9 @@ def main(): if m_args['rapid'] is True: ping_params['rapid'] = m_args['rapid'] + if m_args['do_not_fragment']: + ping_params['do_not_fragment'] = m_args['do_not_fragment'] + if m_args['source_ip'] is not None: ping_params['source'] = m_args['source_ip'] results['source_ip'] = m_args['source_ip'] diff --git a/tests/pb.junos_ping.yaml b/tests/pb.junos_ping.yaml index 3bdd07a7..a12e6726 100644 --- a/tests/pb.junos_ping.yaml +++ b/tests/pb.junos_ping.yaml @@ -57,3 +57,46 @@ assert: that: - test3.packets_sent == '3' + +################# + + - name: "TEST 4 - Ping with DF-bit set" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + count: 3 + do_not_fragment: True + size: 64 + register: test4 + ignore_errors: True +# - debug: var=test4 + + - name: Check TEST 4 + assert: + that: + - test4.packets_received == '3' + +################# + + - name: "TEST 5 - Ping with DF-bit set and size that well exceeds jumbo sizes" + junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + count: 3 + do_not_fragment: True + size: 9999 + register: test5 + ignore_errors: True +# - debug: var=test5 + + - name: Check TEST 5 + assert: + that: + - test5.packets_received == '0' + From 724adc570408ac709fd6fb92a4d54f59d229a5fe Mon Sep 17 00:00:00 2001 From: Martin Komon Date: Mon, 18 Sep 2017 23:31:37 +0200 Subject: [PATCH 161/426] add junos_pmtud module (#263) * add junos_pmtud module * add check if max_range is power of 2 * limit module to IPv4 and improve XML response handling * rename mtu to inet_mtu --- README.md | 1 + library/junos_pmtud | 286 ++++++++++++++++++++++++++++++++++++++ tests/pb.junos_pmtud.yaml | 23 +++ 3 files changed, 310 insertions(+) create mode 100644 library/junos_pmtud create mode 100644 tests/pb.junos_pmtud.yaml diff --git a/README.md b/README.md index bac698ef..74022338 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ In addition to these modules, Since 2.1, Ansible natively include some [core mod - **junos_zeroize** — Remove all configuration information on the Routing Engines and reset all key values on a device. - **junos_get_table** - Retrieve data from a Junos device using Tables/Views - **junos_ping** - execute ping on junos devices +- **junos_pmtud** - execute path MTU discovery on junos devices - **junos_jsnapy** - Integrate JSNAPy to ansible which helps audit network devices - **junos_rpc** — To execute RPC on device and save output locally - **junos_cli** — To execute CLI on device and save output locally diff --git a/library/junos_pmtud b/library/junos_pmtud new file mode 100644 index 00000000..e778c1b1 --- /dev/null +++ b/library/junos_pmtud @@ -0,0 +1,286 @@ +#!/usr/bin/env python + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2017, Martin Komon +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +DOCUMENTATION = ''' +--- +module: junos_pmtud +author: Martin Komon +version_added: "2.4" +short_description: Perform path MTU discovery on junos devices +description: + - perform path MTU discovery on junos devices +requirements: + - junos-eznc >= 1.2.2 +options: + user: + description: + - Login username + required: false + default: $USER + passwd: + description: + - Login password + required: false + default: assumes ssh-key active + port: + description: + - port number to use when connecting to the device + required: false + default: 830 + ssh_private_key_file: + description: + - This can be used if you need to provide a private key rather than + loading the key into the ssh-key-ring/environment. if your + ssh-key requires a password, then you must provide it via + **passwd** + required: false + default: None + mode: + description: + - mode of console connection (telnet/serial). If mode is not + provided SSH connection is used. + required: false + default: None + dest_ip: + description: + - Destination IPv4 address or hostname + required: true + source_ip: + description: + - Source IPv4 address used to send the ping + required: false + routing_instance: + description: + - Name of the routing instance to use to send the ping + required: false + timeout: + description: + - Extend the NETCONF RPC timeout beyond the default value of + 30 seconds. Set this value to accommodate pings + that might take longer than the default timeout interval. + required: false + default: "0" + interface: + description: + - Interface used to send traffic out + required: false + max_size: + description: + - Start and max size for path MTU discovery. + required: false + default: 1472 + max_range: + description: + - Max range of path MTU discovery. Must be 2^n. + required: false + default: 512 + +returns: + inet_mtu: + description: + - IPv4 path MTU size to destination. +''' + +EXAMPLES = ''' +# Simple example + tasks: + - name: "Check MTU on backup circuit" + junos_pmtud: + host={{ junos_host }} + port={{ netconf_port }} + user={{ ansible_ssh_user }} + passwd={{ ansible_ssh_pass }} + dest_ip=8.8.8.8 + +# Using more parameters + tasks: + - name: "Check MTU on backup circuit" + junos_pmtud: + host={{ junos_host }} + port={{ netconf_port }} + user={{ ansible_ssh_user }} + passwd={{ ansible_ssh_pass }} + dest_ip=8.8.8.8 + routing_instance=internet + max_range=128 +''' + +import math +from distutils.version import LooseVersion + +def main(): + spec = dict( + host=dict(required=True), + user=dict(required=False, default=os.getenv('USER')), + passwd=dict(required=False, default=None, no_log=True), + port=dict(required=False, default=830), + ssh_private_key_file=dict(required=False, default=None), + mode=dict(required=False, default=None), + timeout=dict(required=False, default=0), + dest_ip=dict(required=True, default=None), + source_ip=dict(required=False, default=None), + interface=dict(required=False, default=None), + routing_instance=dict(required=False, default=None), + max_size=dict(type='int', required=False, default=1472), + max_range=dict(type='int', required=False, default=512), + ) + + module = AnsibleModule( + argument_spec=spec, + supports_check_mode=False + ) + + m_args = module.params + + try: + from jnpr.junos import Device + from jnpr.junos.version import VERSION + if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): + module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') + except ImportError as ex: + module.fail_json(msg='ImportError: %s' % ex.message) + + if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): + module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') + + # Open connection to device + dev = Device( + m_args['host'], + user=m_args['user'], + passwd=m_args['passwd'], + port=m_args['port'], + ssh_private_key_file=m_args['ssh_private_key_file'], + mode=m_args['mode'], + gather_facts=False) + + try: + dev.open() + except Exception as err: + msg = 'Unable to connect to {0}: {1}'.format(m_args['host'], str(err)) + module.fail_json(msg=msg) + return + + results = dict( + changed=False, + inet_mtu=0, + dest_ip=m_args['dest_ip'] + ) + + warnings = list() + + # check if max_range is a power of 2 + log_max_range = math.log(m_args['max_range']) / math.log(2) + if math.floor(log_max_range) != log_max_range: + warnings.append('Max_range must be a power of 2 between 2 and 1024; ' + 'ignoring value {0} and using default {1}.' + ''.format(m_args['max_range'], + spec['max_range']['default'])) + m_args['max_range'] = spec['max_range']['default'] + + # Prepare parameters + ping_params = dict( + host=m_args['dest_ip'], + inet=True, + count='3', + do_not_fragment=True, + ) + + if m_args['source_ip'] is not None: + ping_params['source'] = m_args['source_ip'] + results['source_ip'] = m_args['source_ip'] + + if m_args['routing_instance'] is not None: + ping_params['routing_instance'] = m_args['routing_instance'] + results['routing_instance'] = m_args['routing_instance'] + + if m_args['interface'] is not None: + ping_params['interface'] = m_args['interface'] + results['interface'] = m_args['interface'] + + ## Change default Timeout + timeout = int(m_args['timeout']) + if timeout > 0: + dev.timeout = timeout + + try: + # Check if ICMP passes + ping_params['size'] = str(64) + rpc_reply = dev.rpc.ping(**ping_params) + loss = int(rpc_reply.findtext('probe-results-summary/packet-loss', default='100').strip()) + + if loss == 100: + module.fail_json( + msg="Unknown error, PMTUD cannot run.") + + test_size = int(m_args['max_size']) + step = int(m_args['max_range']) + + while True: + step = step / 2 if step >= 2 else 0 + ping_params['size'] = str(test_size) + rpc_reply = dev.rpc.ping(**ping_params) + loss = int(rpc_reply.findtext('probe-results-summary/packet-loss', default='100').strip()) + if loss < 100 and test_size == int(m_args['max_size']): + # ping success with max test_size, save and break + results["inet_mtu"] = test_size + break + elif loss < 100: + # ping success, increase test_size + results["inet_mtu"] = test_size + test_size += step + else: + # ping fail, lower size + test_size -= step + if step < 1: + break + + except Exception as err: + results['failed'] = True + results['msg'] = "unable to execute ping due to:{0}".format(err.message) + dev.close() + raise err + + if not results["inet_mtu"]: + module.fail_json(msg='MTU too low, increase max_range.', **results) + else: + results["inet_mtu"] += 28 # adjust for IPv4 and ICMP headers + + dev.close() + results['warnings'] = warnings + module.exit_json(**results) + +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() diff --git a/tests/pb.junos_pmtud.yaml b/tests/pb.junos_pmtud.yaml new file mode 100644 index 00000000..605fb606 --- /dev/null +++ b/tests/pb.junos_pmtud.yaml @@ -0,0 +1,23 @@ +--- +- name: Test junos_pmtud module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Check path MTU to Google DNS" + junos_pmtud: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + register: test1 + ignore_errors: True +# - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - 768 <= test1.mtu <= 1500 From aad8ba8b755a2676ee20863a8ccffe7a18f6761c Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 19 Sep 2017 12:11:03 -0600 Subject: [PATCH 162/426] Fix do_not_fragment argument to junos_ping module (#264) Fix regression caused by #262. If the `do_not_fragment` argument was not set by the user, the default settings caused an incorrect value of `'False'` to be passed to the ping RPC. * Add size argument required for do_not_fragment to be useful. * Debugging failure of junos_ping TEST 5. * Properly set rapid and do_not_fragment options as booleans. * Remove Ansible 1.9.6 from testing. Tests use a filter only present in >= 2.0. --- library/junos_ping | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/junos_ping b/library/junos_ping index f966a436..38569465 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -181,12 +181,13 @@ def main(): dest_ip=dict(required=True, default=None), source_ip=dict(required=False, default=None), interface=dict(required=False, default=None), - rapid=dict(required=False, default=True), + rapid=dict(required=False, type='bool', default=True), routing_instance=dict(required=False, default=None), ttl=dict(required=False, default=None), acceptable_packet_loss=dict(required=False, default=0), count=dict(required=False, default='5'), - do_not_fragment=dict(required=False, default=False) + size=(dict(required=False, default=None)), + do_not_fragment=dict(required=False, type='bool', default=False) ), supports_check_mode=False ) @@ -237,7 +238,7 @@ def main(): if m_args['rapid'] is True: ping_params['rapid'] = m_args['rapid'] - if m_args['do_not_fragment']: + if m_args['do_not_fragment'] is True: ping_params['do_not_fragment'] = m_args['do_not_fragment'] if m_args['source_ip'] is not None: @@ -256,6 +257,10 @@ def main(): ping_params['ttl'] = str(m_args['ttl']) results['ttl'] = str(m_args['ttl']) + if m_args['size'] is not None: + ping_params['size'] = str(m_args['size']) + results['size'] = str(m_args['size']) + ## Change default Timeout timeout = int(m_args['timeout']) if timeout > 0: From e411f640f3f72c6c3513aecf872803d565bcd7fb Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 19 Sep 2017 15:56:58 -0600 Subject: [PATCH 163/426] Test fixes for junos_pmtud fixes (#265) * Preparing for v.1.4.1 release. * Update tests for pb.junos_pmtud.yaml * Fix documentation issue in junos_ping. Disable Ansible 2.4 test. Debug junos_pmtud tests. --- .travis.yml | 1 + library/junos_ping | 5 +++++ tests/pb.junos_pmtud.yaml | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 96bd3d8a..e9b81bcc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,4 +26,5 @@ script: ## Execute Tests with Docker - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_pmtud.yaml - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_jsnapy.yaml diff --git a/library/junos_ping b/library/junos_ping index 38569465..5a68a81a 100644 --- a/library/junos_ping +++ b/library/junos_ping @@ -118,6 +118,11 @@ options: - Number of packet to send required: false default: 5 + size: + description: + - The size of the ICMP payload of the ping. + required: false + default: None (default size) do_not_fragment: description: - Set Don't Fragment bit diff --git a/tests/pb.junos_pmtud.yaml b/tests/pb.junos_pmtud.yaml index 605fb606..7b390399 100644 --- a/tests/pb.junos_pmtud.yaml +++ b/tests/pb.junos_pmtud.yaml @@ -15,9 +15,9 @@ dest_ip: 8.8.8.8 register: test1 ignore_errors: True -# - debug: var=test1 + - debug: var=test1 - name: Check TEST 1 assert: that: - - 768 <= test1.mtu <= 1500 + - 768 <= test1.inet_mtu <= 1500 From 85097902ca31e1c9cfa0dbd98ddc334b39d8e91a Mon Sep 17 00:00:00 2001 From: Yannis Mitsos Date: Wed, 20 Sep 2017 07:25:33 +0300 Subject: [PATCH 164/426] check_commit option added (#163) check_commit option controlls whether a "commit check" or "show | compare" command will be executed. The latter requires less privileges and is preferred when one needs to check changes against a running configuration instance. Refers to #163 --- library/junos_install_config | 75 +++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/library/junos_install_config b/library/junos_install_config index dd5a91f9..81355163 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -165,6 +165,14 @@ options: - Provide a confirm in minutes to the commit of the configuration required: false default: None + check_commit: + description: + - Specify whether the configuration will be commit-checked or not. + Set to false, outputs similarly to the "show | compare" functionality, + while true returns the "commit check" result. + required: false + default: yes + choices: ['true','false','yes','no'] ignore_warning: description: - A boolean, string or list of string. @@ -250,6 +258,7 @@ def _load_via_netconf(module): logging.getLogger().name = 'CONFIG:' + args['host'] in_check_mode = module.check_mode + check_commit = module.boolean(module.params['check_commit']) overwrite = module.boolean(module.params['overwrite']) replace = module.boolean(module.params['replace']) update = module.boolean(module.params['update']) @@ -362,39 +371,40 @@ def _load_via_netconf(module): logging.error(msg) module.fail_json(msg=msg) - try: - logging.info("doing a commit-check, please be patient") - cu.commit_check() - if (in_check_mode): - logging.info("Ansible check mode complete") - module.exit_json(**results) - else: - logging.info("committing change, please be patient") - opts = {} - if args['ignore_warning'] is not None: - opts = {'ignore_warning': args['ignore_warning']} - if args['comment'] is not None: - opts['comment'] = args['comment'] - if args['confirm'] is not None: - opts['confirm'] = args['confirm'] - - if args['check_commit_wait']: - check_commit_wait = int(args['check_commit_wait']) - if 1 <= check_commit_wait <= 4: - time.sleep(check_commit_wait) - - cu.commit(**opts) - - except CommitError as err: - msg = "Unable to commit configuration: {0}".format(err) - logging.error(msg) + if check_commit: try: - logging.info("unlocking") - cu.unlock() - except UnlockError as err: - logging.error("Unable to unlock config: {0}".format(err)) - dev.close() - module.fail_json(msg=msg) + logging.info("doing a commit-check, please be patient") + cu.commit_check() + if (in_check_mode): + logging.info("Ansible check mode complete") + module.exit_json(**results) + else: + logging.info("committing change, please be patient") + opts = {} + if args['ignore_warning'] is not None: + opts = {'ignore_warning': args['ignore_warning']} + if args['comment'] is not None: + opts['comment'] = args['comment'] + if args['confirm'] is not None: + opts['confirm'] = args['confirm'] + + if args['check_commit_wait']: + check_commit_wait = int(args['check_commit_wait']) + if 1 <= check_commit_wait <= 4: + time.sleep(check_commit_wait) + + cu.commit(**opts) + + except CommitError as err: + msg = "Unable to commit configuration: {0}".format(err) + logging.error(msg) + try: + logging.info("unlocking") + cu.unlock() + except UnlockError as err: + logging.error("Unable to unlock config: {0}".format(err)) + dev.close() + module.fail_json(msg=msg) try: logging.info("unlocking") @@ -488,6 +498,7 @@ def main(): ssh_private_key_file=dict(required=False, default=None), mode=dict(required=False, default=None), confirm=dict(required=False, default=None), + check_commit=dict(required=False, type='bool', choices=BOOLEANS, default=True), ignore_warning=dict(required=False, default=None), check_commit_wait=dict(required=False, default=None) ), From c626f63639f9b61c261fc69de8126d14dad11152 Mon Sep 17 00:00:00 2001 From: Arzhel Younsi Date: Thu, 21 Sep 2017 12:19:21 -0700 Subject: [PATCH 165/426] Add a parameter to only do a commit check (#109) * Add a parameter to only do a commit check This is useful when doing first a "commit confirm" followed directly by a "commit check". That way, if the commit breaks connectivity to the device, the configuration rollbacks, if there is still connectivity the commit check is faster than a full commit and doesn't trigger configuration archival, etc.. Example: ```yaml - name: "Do a commit confirmed 2 or commit check" junos_install_config: host={{ inventory_hostname }} port=22 file="{{ assembled_conf }}" overwrite=false replace=true diffs_file="{{ logs_dir }}/{{ inventory_hostname }}.diff" logfile="{{ logs_dir }}/{{ inventory_hostname }}.log" timeout="300" comment="Ansible" confirm="2" register: push_result - name: "commit check to confirm the previous commit" junos_commit: host={{ inventory_hostname }} check=true when: not check_mode and push_result.changed ``` * Improve commit check description Add to the commit check description the fact that it can be used to confirm a `commit confirmed` --- library/junos_commit | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library/junos_commit b/library/junos_commit index e380d1ed..2f880441 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -100,6 +100,12 @@ options: - Provide a confirm in minutes to the commit of the configuration required: false default: None + check: + description: + - Do a commit check + Can be used to confirm a `commit confirmed` without performing an actual commit + required: false + default: None ''' EXAMPLES = ''' @@ -132,7 +138,8 @@ def main(): timeout=dict(required=False, default=0), logfile=dict(required=False, default=None), comment=dict(required=False, default=None), - confirm=dict(required=False, default=None) + confirm=dict(required=False, default=None), + check=dict(required=False, choices=BOOLEANS, default=False) ), supports_check_mode=True) @@ -180,8 +187,8 @@ def main(): logging.info("taking lock") cu.lock() - - if (in_check_mode): + check = args['check'] + if (in_check_mode or check): logging.info("doing a commit-check, please be patient") cu.commit_check() else: From 1b442c34fce31cf0d00f5e1cec3b737a86ff6601 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 21 Sep 2017 16:57:27 -0600 Subject: [PATCH 166/426] Fix junos_commit check arg (#266) Fix a regression caused by #109. The new check argument added to junos_commit by #109 was not correctly specified. Therefore, the following error was produced when the task ommitted the check argument (default behavior). ``` fatal: [r0]: FAILED! => {"changed": false, "failed": true, "msg": "value of check must be one of: yes,on,1,true,1,True,no,off,0,false,0,False, got: False"} ``` This change correctly specifies the argument's type as bool. --- library/junos_commit | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/junos_commit b/library/junos_commit index 2f880441..f952e82f 100644 --- a/library/junos_commit +++ b/library/junos_commit @@ -139,7 +139,8 @@ def main(): logfile=dict(required=False, default=None), comment=dict(required=False, default=None), confirm=dict(required=False, default=None), - check=dict(required=False, choices=BOOLEANS, default=False) + check=dict(required=False, type='bool', + choices=BOOLEANS, default=False) ), supports_check_mode=True) From c0c4bd07ecce39067c58bee566bc4e85b8798460 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sun, 1 Oct 2017 19:39:15 -0600 Subject: [PATCH 167/426] Fix doc string in junos_install_config and doc generation on Ansible 2.4 (#267) * Preparing for v.1.4.1 release. * Fix doc string in junos_install_config. Fix doc generation on Ansible 2.4. * Fix .travis.yaml to remove 1.9.6 --- docs/ansible2rst.py | 5 ++++- library/junos_install_config | 14 +++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/ansible2rst.py b/docs/ansible2rst.py index c4538ad4..74c1de68 100755 --- a/docs/ansible2rst.py +++ b/docs/ansible2rst.py @@ -29,7 +29,10 @@ from distutils.version import LooseVersion from jinja2 import Environment, FileSystemLoader -from ansible.utils import module_docs +try: + from ansible.utils import module_docs +except ImportError: + from ansible.utils import plugin_docs as module_docs from ansible import __version__ as ansible_version ##################################################################################### diff --git a/library/junos_install_config b/library/junos_install_config index 81355163..a5c0a970 100644 --- a/library/junos_install_config +++ b/library/junos_install_config @@ -47,7 +47,7 @@ description: either NETCONF or the CONSOLE port. Specify the I(console) option to use the CONSOLE port. - You provide the configuration data in a file. Supported formats when using + - You provide the configuration data in a file. Supported formats when using NETCONF include ASCII text, Junos XML elements, and Junos OS B(set) commands. Configurations performed through the console must only use ASCII text formatting. requirements: @@ -175,12 +175,12 @@ options: choices: ['true','false','yes','no'] ignore_warning: description: - - A boolean, string or list of string. - If the value is True, it will ignore all warnings regardless of the - warning message. If the value is a string, it will ignore - warning(s) if the message of each warning matches the string. If - the value is a list of strings, ignore warning(s) if the message of - each warning matches at least one of the strings in the list. + - A boolean, string or list of string. If the value is True, it + will ignore all warnings regardless of the warning message. If + the value is a string, it will ignore warning(s) if the message + of each warning matches the string. If the value is a list of + strings, ignore warning(s) if the message of each warning matches + at least one of the strings in the list. required: false default: None check_commit_wait: From f38d648170bfd6e03905cdf5a3a6d05b1ee28811 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 9 Oct 2017 10:38:59 -0600 Subject: [PATCH 168/426] Prepare Release 1.4.3 (#268) * Prepare for the 1.4.3 release. * Switch Docker to Ansible 2.4.0.0 * Modify Dockerfile to explicitly specify --roles-path to ansible-galaxy. --- .travis.yml | 1 + Dockerfile | 4 ++-- version.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index e9b81bcc..6ff52a58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ sudo: required dist: trusty env: + - ANSIBLE_VERSION=2.4.0.0 - ANSIBLE_VERSION=2.3.0.0 - ANSIBLE_VERSION=2.2.0.0 - ANSIBLE_VERSION=2.1.3.0 diff --git a/Dockerfile b/Dockerfile index 066dc1a2..e9604f46 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM juniper/pyez:latest MAINTAINER Stephen Steiner -ARG ver_ansible=2.3.0.0 +ARG ver_ansible=2.4.0.0 ARG ver_jsnapy=1.1.0 WORKDIR /tmp @@ -20,7 +20,7 @@ RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ pip install jxmlease &&\ pip install -q ansible==$ver_ansible &&\ pip install -q jsnapy==$ver_jsnapy &&\ - ansible-galaxy install Juniper.junos &&\ + ansible-galaxy install --roles-path=/etc/ansible/roles Juniper.junos &&\ apk del -r --purge gcc make g++ &&\ rm -rf /source/* &&\ rm -rf /var/cache/apk/* &&\ diff --git a/version.py b/version.py index 6b78ccab..fe2269bf 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0+dev0" -DATE = "2017-April-24" +VERSION = "1.4.3" +DATE = "2017-Oct-1" From d501a14f1e273defd47b907ec85a78ac730ca575 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 9 Oct 2017 11:16:26 -0600 Subject: [PATCH 169/426] Prepare 2 0 0 dev1 (#271) --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index fe2269bf..f241a5ed 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.4.3" -DATE = "2017-Oct-1" +VERSION = "2.0.0dev1" +DATE = "2017-Oct-9" From 57ced3bdd6dac150e1817beafefbb38cc901c10e Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 17 Oct 2017 21:22:07 -0600 Subject: [PATCH 170/426] Implementation of common library and rewrite of facts module. (#278) This is the initial commit toward a 2.0 release: - Implements a common library of code in module_utils/juniper_junos_common.py. - Reimplements the facts module using the new common library of code. - Implements new connection handling arguments, aliases, default values, etc. --- README.md | 14 +- action_plugins/_junos_get_facts.py | 1 + action_plugins/juniper_junos_facts.py | 50 +++ library/_junos_get_facts.py | 1 + library/juniper_junos_facts.py | 301 ++++++++++++++ library/junos_get_facts | 266 ------------ meta/main.yml | 117 +----- module_utils/juniper_junos_common.py | 576 ++++++++++++++++++++++++++ requirements.txt | 2 +- setup.py | 1 - version.py | 4 +- 11 files changed, 942 insertions(+), 391 deletions(-) create mode 120000 action_plugins/_junos_get_facts.py create mode 100644 action_plugins/juniper_junos_facts.py create mode 120000 library/_junos_get_facts.py create mode 100644 library/juniper_junos_facts.py delete mode 100644 library/junos_get_facts create mode 100644 module_utils/juniper_junos_common.py diff --git a/README.md b/README.md index 74022338..5bde4489 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ ## ABOUT -Juniper Networks provides support for using Ansible to deploy devices running the Junos operating system (Junos OS). The Juniper Networks Ansible library, which is hosted on the Ansible Galaxy website under the role [junos](https://galaxy.ansible.com/list#/roles/1116), enables you to use Ansible to perform specific operational and configuration tasks on devices running Junos OS, including installing and upgrading Junos OS, deploying specific devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to [INSTALLATION](#installation) section for setup. +Juniper Networks provides support for using Ansible to deploy devices running +the Junos operating system (Junos OS). The Juniper Networks Ansible library, +which is hosted on the Ansible Galaxy website under the role [Juniper.junos] +(https://galaxy.ansible.com/list#/roles/1116), enables you to use Ansible to perform specific operational and configuration tasks on devices running Junos OS, including installing and upgrading Junos OS, deploying specific devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to [INSTALLATION](#installation) section for setup. In addition to these modules, Since 2.1, Ansible natively include some [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). Both Core and Galaxy modules can cohexist on the same platform. ## OVERVIEW OF MODULES -- **junos_get_facts** — Retrieve device-specific information from the host. +- **juniper_junos_facts** — Retrieve device-specific information from a Junos device. - **junos_commit** — Commit candidate configuration on device. - **junos_get_config** — Retrieve configuration of device. - **junos_install_config** — Modify the configuration of a device running Junos OS. @@ -174,10 +177,9 @@ This example outlines how to use Ansible to install or upgrade the software imag Thes modules require the following to be installed on the Ansible server: -* Python 2.6 or 2.7 -* [Ansible](http://www.ansible.com) 1.5 or later -* Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.0.1 or -later (2.1.1 is recommended and required for some features) +* Python 2.7 +* [Ansible](http://www.ansible.com) 2.1 or later +* Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.1.7 or later ## LICENSE diff --git a/action_plugins/_junos_get_facts.py b/action_plugins/_junos_get_facts.py new file mode 120000 index 00000000..cbdd3b96 --- /dev/null +++ b/action_plugins/_junos_get_facts.py @@ -0,0 +1 @@ +juniper_junos_facts.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_facts.py b/action_plugins/juniper_junos_facts.py new file mode 100644 index 00000000..a87fd74b --- /dev/null +++ b/action_plugins/juniper_junos_facts.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + +# Use the custom behavior of JuniperJunosActionModule as our ActionModule. +# The Ansible core engine will call ActionModule.run() +from juniper_junos_common import JuniperJunosActionModule as ActionModule diff --git a/library/_junos_get_facts.py b/library/_junos_get_facts.py new file mode 120000 index 00000000..cbdd3b96 --- /dev/null +++ b/library/_junos_get_facts.py @@ -0,0 +1 @@ +juniper_junos_facts.py \ No newline at end of file diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py new file mode 100644 index 00000000..16018399 --- /dev/null +++ b/library/juniper_junos_facts.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_facts +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Retrieve facts from a Junos device +description: + - Retrieve facts from a Junos device using the PyEZ fact gathering system. + The specific facts returned are documented at: + U(http://junos-pyez.readthedocs.io/en/latest/jnpr.junos.facts.html) + Also returns the committed configuration of the Junos device if the + I(config_format) option has a value other than C(none). +extends_documentation_fragment: juniper_junos_common +options: + config_format: + description: + - The format of the configuration returned. The specified format must be + supported by the target Junos device. + required: false + default: none + choices: [none, 'xml', 'set', 'text', 'json'] + savedir: + description: + - A path to a directory, on the Ansible control machine, where facts + will be stored in a JSON file. The resulting JSON file is saved in: + C(savedir/hostname-facts.json). The directory is the value of + I(savedir). The filename begins with the value of the hostname fact + returned from the Junos device, which might be different than the + value of the I(host) option passed to the module. If the value of the + I(savedir) option is C(none), the default, then facts are NOT saved to + a file. + required: false + default: none + type: path + logfile: + description: + - A path to a file, on the Ansible control machine, where debugging + information is logged. This option is deprecated. It is present only + for backwards compatibility. Logging is now handled using Ansible's + standard logging scheme which does something I haven't quite figured + out yet. + required: false + default: None + type: path +requirements: + - junos-eznc >= 2.1.7 +notes: + - The NETCONF system service must be enabled on the target Junos device. +''' + +EXAMPLES = ''' +--- +- name: Gather facts from Junos devices + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: Gather Junos facts with no configuration + juniper_junos_facts: + +# Print a fact + +# Using config_format option + +# Print the config + +# Using savedir option + +# Print the JSON file + +# Using logile option + +# Print the logfile + +# Document connection arguments +# extends_documentation_fragment: juniper_junos_common +''' + +RETURN = ''' +ansible_facts.junos: + description: The facts collected from the Junos device. + returned: success + type: dict + sample: + +facts: + description: Returned for backwards compatibility. Returns the same keys and + values which are returned under I(ansible_facts.junos). + returned: success + type: dict + sample: + +changed: + +failed: + +''' + +import os.path +import json + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def get_facts_dict(junos_module): + """Retreive PyEZ facts and convert to a standard dict w/o custom types. + + Ansible >= 2.0 doesn't like custom objects in a modules return value. + Because PyEZ facts are a custom object rather than a true dict they must be + converted to a standard dict. Since facts are read-only, we must begin by + copying facts into a dict. Since PyEZ facts are "on-demand", the + junos_module.dev instance must be an open PyEZ Device instance ojbect + before this function is called. + + Args: + junos_module: An instance of a JuniperJunosModule. + + Returns: + A dict containing the device facts. + """ + # Retrieve all PyEZ-supported facts and copy to a standard dict. + facts = dict(junos_module.dev.facts) + # Add two useful facts that are implement as PyEZ Device attributes. + facts['re_name'] = junos_module.dev.re_name + facts['master_state'] = junos_module.dev.master + # Ansible doesn't allow keys starting with numbers. + # Replace the '2RE' key with the 'has_2RE' key. + if '2RE' in facts: + facts['has_2RE'] = facts['2RE'] + del facts['2RE'] + # The value of the 'version_info' key is a custom junos.version_info + # object. Convert this value to a dict. + if 'version_info' in facts: + facts['version_info'] = dict(facts['version_info']) + # The values of the ['junos_info'][re_name]['object'] keys are + # custom junos.version_info objects. Convert all of these to dicts. + if 'junos_info' in facts: + for key in facts['junos_info']: + facts['junos_info'][key]['object'] = dict( + facts['junos_info'][key]['object']) + return facts + + +def save_facts(junos_module, facts): + """If the savedir argument was specified, save the facts into a JSON file. + + Ansible >= 2.0 doesn't like custom objects in a modules return value. + Because PyEZ facts are a custom object rather than a true dict they must be + converted to a standard dict. Since facts are read-only, we must begin by + copying facts into a dict. Since PyEZ facts are "on-demand", the + junos_module.dev instance must be an open PyEZ Device instance ojbect + before this function is called.. + + Args: + junos_module: An instance of a JuniperJunosModule. + facts: The facts dict returned by get_facts_dict(). + + Raises: + IOError: Calls junos_module.fail_json if unable to open the facts + file for writing. + """ + if junos_module.params.get('savedir') is not None: + save_dir = junos_module.params.get('savedir') + file_name = '%s-facts.json' % (facts['hostname']) + file_path = os.path.normpath(os.path.join(save_dir, file_name)) + try: + with open(file_path, 'w') as fact_file: + json.dump(facts, fact_file) + except IOError: + junos_module.fail_json(msg="Unable to save facts. Faile to open " + "the %s file." % (file_path)) + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + config_format=dict(choices=[None, 'xml', 'set', 'text', 'json'], + required=False, + default=None), + savedir=dict(type='path', required=False, default=None), + logfile=dict(type='path', required=False, default=None), + ), + # Since this module doesn't change the device's configuration, there + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True + ) + + junos_module.log("Device opened. Gathering facts.") + + # Get the facts dictionary from the device. + facts = get_facts_dict(junos_module) + + # Add code to implement config_format option + + # Add code to implement the logfile option + + junos_module.log("Facts gathered.") + + # Save the facts. + save_facts(junos_module, facts) + + junos_module.log("Facts saved.") + + # Return response. + junos_module.exit_json( + changed=False, + failed=False, + ansible_facts={'junos': facts}, + facts=facts) + + +if __name__ == '__main__': + main() diff --git a/library/junos_get_facts b/library/junos_get_facts deleted file mode 100644 index 704b98cd..00000000 --- a/library/junos_get_facts +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2014, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_get_facts -author: Jeremy Schulman, Juniper Networks -version_added: "1.0.0" -short_description: Retrieve facts for a device running Junos OS. -description: - - Retrieve facts for a device running Junos OS, which includes information - such as the serial number, product model, and Junos OS version. - The module supports using both NETCONF and CONSOLE-based retrieval - and returns the information as a JSON dictionary. - The information is similar to facts gathered by other IT frameworks. -requirements: - - junos-eznc >= 1.2.2 - - junos-netconify >= 1.0.1, when using the I(console) option -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - console: - description: - - CONSOLE port, per the B(netconify) utility - required: false - default: None - savedir: - description: - - Path to the local server directory where device fact - files will be stored. Resulting file will be - I(savedir/hostname-facts.json) - required: false - default: $CWD - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes. This option is used only with the - I(console) option. - required: false - default: None - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None -''' - -EXAMPLES = ''' -# retrieve facts using NETCONF, assumes ssh-keys - -- junos_get_facts: host={{ inventory_hostname }} - register: junos - -# retrieve facts using CONSOLE, assumes Amnesiac system -# root login, no password - -- junos_get_facts: - host={{ inventory_hostname }} - user=root - console="--telnet={{TERMSERV}},{{TERMSERVPORT}}" - savedir=/usr/local/junos/inventory - register: junos - -# access the facts - -- name: version - debug: msg="{{ junos.facts.version }}" - -# retrieve facts using console server connection using PyEZ >= 2.0 - -- junos_get_facts: - host={{ inventory_hostname }} - mode="telnet" - port=7016 - register: junos - -# access the facts - -- name: version - debug: msg="{{ junos.facts }}" -''' - -import os -import json -from distutils.version import LooseVersion - -def main(): - module = AnsibleModule( - argument_spec=dict( - host=dict(required=True), - console=dict(required=False, default=None), - logfile=dict(required=False, default=None), - savedir=dict(required=False, default=None), - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None)), - supports_check_mode=True) - - m_args = module.params - m_results = dict(changed=False) - - if m_args['console'] is None: - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.1.2'): - module.fail_json(msg='junos-eznc >= 2.1.2 is required for this ' - 'module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - # ----------- - # via NETCONF - # ----------- - dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], port=m_args['port'], - ssh_private_key_file=m_args['ssh_private_key_file'], mode=m_args['mode'], - gather_facts=True) - try: - dev.open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(m_args['host'], str(err)) - module.fail_json(msg=msg) - return - else: - # Ansible 2 doesn't like custom objects in the return value, and - # PyEZ facts are now a custom object rather than a true dict. - # Since facts are read-only, we must begin by copying facts into a - # dict. Also, since PyEZ facts are now "on-demand", this must be - # done before closing the connection. These changes remain backwards - # compatible with older PyEZ versions that have older fact gathering - # code. - m_results['facts'] = dict(dev.facts) - m_results['facts']['re_name'] = dev.re_name - m_results['facts']['master_state'] = dev.master - dev.close() - # Ansible doesn't allow keys starting with numbers. - # Replace the '2RE' key with the 'has_2RE' key. - if '2RE' in m_results['facts']: - m_results['facts']['has_2RE'] = m_results['facts']['2RE'] - del m_results['facts']['2RE'] - # The value of the 'version_info' key is a custom junos.version_info - # object. Convert this value to a dict. - if 'version_info' in m_results['facts']: - m_results['facts']['version_info'] = dict( - m_results['facts']['version_info']) - # The values of the ['junos_info'][re_name]['object'] keys are - # custom junos.version_info objects. Convert all of these to dicts. - if 'junos_info' in m_results['facts']: - for key in m_results['facts']['junos_info']: - m_results['facts']['junos_info'][key]['object'] = dict( - m_results['facts']['junos_info'][key]['object']) - if m_args['savedir'] is not None: - fname = "{0}/{1}-facts.json".format(m_args['savedir'], dev.facts['hostname']) - with open(fname, 'w') as factfile: - json.dump(m_results['facts'], factfile) - else: - # ----------- - # via CONSOLE - # ----------- - try: - from netconify.cmdo import netconifyCmdo - from netconify.constants import version - if not LooseVersion(version) >= LooseVersion('1.0.1'): - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - except ImportError: - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - import logging - - c_args = [] - c_args.append(m_args['console']) - c_args.append('--facts') - if m_args['savedir'] is not None: - c_args.append('--savedir=' + m_args['savedir']) - c_args.append('--user=' + m_args['user']) - if m_args['passwd'] is not None: - c_args.append('--passwd=' + m_args['passwd']) - - c_args.append(m_args['host']) - - logfile = m_args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'NETCONIFY:' + module.params['host'] - - def log_notify(self, event, message): - logging.info("%s:%s" % (event, message)) - use_notifier = log_notify - else: - def silent_notify(self, event, message): - pass - use_notifier = silent_notify - - try: - nc = netconifyCmdo(notify=use_notifier) - c_results = nc.run(c_args) - except Exception as err: - module.fail_json(msg=str(err)) - m_results['args'] = m_args # for debug - m_results['facts'] = c_results['facts'] - - module.exit_json(**m_results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/meta/main.yml b/meta/main.yml index 46ce539e..3b4daf4b 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,131 +1,18 @@ --- galaxy_info: - author: Juniper #Jeremy Schulman + author: Juniper #Stacy W. Smith @stacywsmith description: Network build automation of Junos devices. company: Juniper Networks, Inc. - # Some suggested licenses: - # - BSD (default) - # - MIT - # - GPLv2 - # - GPLv3 - # - Apache - # - CC-BY license: Apache 2.0 - min_ansible_version: 1.9 - # - # Below are all platforms currently available. Just uncomment - # the ones that apply to your role. If you don't see your - # platform on this list, let us know and we'll get it added! - # + min_ansible_version: 2.1 platforms: - name: junos versions: - all - #- name: EL - # versions: - # - all - # - 5 - # - 6 - # - 7 - #- name: GenericUNIX - # versions: - # - all - # - any - #- name: Fedora - # versions: - # - all - # - 16 - # - 17 - # - 18 - # - 19 - # - 20 - #- name: opensuse - # versions: - # - all - # - 12.1 - # - 12.2 - # - 12.3 - # - 13.1 - # - 13.2 - #- name: Amazon - # versions: - # - all - # - 2013.03 - # - 2013.09 - #- name: GenericBSD - # versions: - # - all - # - any - #- name: FreeBSD - # versions: - # - all - # - 8.0 - # - 8.1 - # - 8.2 - # - 8.3 - # - 8.4 - # - 9.0 - # - 9.1 - # - 9.1 - # - 9.2 - #- name: Ubuntu - # versions: - # - all - # - lucid - # - maverick - # - natty - # - oneiric - # - precise - # - quantal - # - raring - # - saucy - # - trusty - #- name: SLES - # versions: - # - all - # - 10SP3 - # - 10SP4 - # - 11 - # - 11SP1 - # - 11SP2 - # - 11SP3 - #- name: GenericLinux - # versions: - # - all - # - any - #- name: Debian - # versions: - # - all - # - etch - # - lenny - # - squeeze - # - wheezy - # - # Below are all categories currently available. Just as with - # the platforms above, uncomment those that apply to your role. - # categories: - networking galaxy_tags: - #- cloud - #- cloud:ec2 - #- cloud:gce - #- cloud:rax - #- clustering - #- database - #- database:nosql - #- database:sql - #- development - #- monitoring - networking - #- packaging - #- system - #- web - junos - juniper dependencies: [] - # List your role dependencies here, one per line. Only - # dependencies available via galaxy should be listed here. - # Be sure to remove the '[]' above if you add dependencies - # to this list. - diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py new file mode 100644 index 00000000..3ab531a0 --- /dev/null +++ b/module_utils/juniper_junos_common.py @@ -0,0 +1,576 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# Ansible imports +from ansible.module_utils.basic import AnsibleModule +from ansible.plugins.action.normal import ActionModule as ActionNormal + +# Standard library imports +from distutils.version import LooseVersion +import os + +# Non-standard library imports and checks +try: + from jnpr.junos.version import VERSION + HAS_PYEZ_VERSION = VERSION +except ImportError: + HAS_PYEZ_VERSION = None + +try: + from jnpr.junos.device import Device + HAS_PYEZ_DEVICE = True +except ImportError: + HAS_PYEZ_DEVICE = False + +try: + import jnpr.junos.exception as pyez_exception + HAS_PYEZ_EXCEPTIONS = True +except ImportError: + HAS_PYEZ_EXCEPTIONS = False + + +# Constants +# Minimum PyEZ version required by shared code. +MIN_PYEZ_VERSION = "2.1.7" +# Installation URL for PyEZ. +PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" + + +class ModuleDocFragment(object): + """Documentation fragment for connection-related parameters. + + All juniper_junos_* modules share a common set of connection parameters + which are documented in this class. + + Attributes: + DOCUMENTATION: The documentation string defining the connection-related + parameters for juniper_junos_* modules. + """ + + # The connection-specific options. Defined here so it can be re-used as + # suboptions in provider. + CONNECTION_DOCUMENTATION = ''' + host: + description: + - The hostname or IP address of the Junos device to which the connection + should be established. This is normally the Junos device itself, but + is the hostname or IP address of a console server when connecting + to the console of the device by setting the I(mode) option to the value + C(telnet). This option is required, but does not have to be specified + explicitly by the user because it defaults to + C({{ inventory_hostname }}). + required: True + default: {{ inventory_hostname }} + type: str + aliases: + - hostname + - ip + user: + description: + - The username used to authenticate with the Junos device. This option + is required, but does not have to be specified explicitly by the user + due to the below algorithm for determining the default value. + required: True + default: The first defined value from the following list: + 1) The ANSIBLE_NET_USERNAME environment variable. + (used by Ansible Tower) + 2) The remote_user as defined by Ansible. Ansible sets this + value via several methods including: + a) -u or --user command line option. + b) ANSIBLE_REMOTE_USER environment variable. + c) remote_user configuration setting. + See the Ansible documentation for the precedence used to set + the remote_user value. + 3) The USER environment variable. + type: str + aliases: + - username + passwd: + description: + - The password, or ssh key's passphrase, used to authenticate with the + Junos device. If this option is not specified, authentication is + attempted using an empty password, or ssh key passphrase. The below + algorithm is used to determine the default value. + required: False + default: The first defined value from the following list: + 1) The ANSIBLE_NET_PASSWORD environment variable. + (used by Ansible Tower) + 2) The value specified using the -k or --ask-pass command line + argument. + 3) none + type: str + aliases: + - password + ssh_private_key_file: + description: + - The path to the SSH private key file used to authenticate with the + Junos device. If this option is not specified, and no default value is + found using the algorithm below, then the SSH private key file + specified in the user's SSH configuration, or the + operating-system-specific default is used. + default: The first defined value from the following list: + 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. + (used by Ansible Tower) + 2) The value specified using the --private-key or --key-file + command line argument. + 3) none + type: path + aliases: + - ssh_keyfile + mode: + description: + - The PyEZ mode used to establish a NETCONF connection to the Junos + device. A value of C(none) uses the default NETCONF over SSH mode. + A value of C(telnet) results in either a direct NETCONF over Telnet + connection to the Junos device, or a NETCONF over serial console + connection to the Junos device using Telnet to a console server + depending on the values of the C(host) and C(port) options. Mutually + exclusive with C(console). + required: False + default: none + choices: [ none, "telnet" ] + console: + description: + - An alternate method of specifying a NETCONF over serial console + connection to the Junos device using Telnet to a console server. + Its value must be a string in the format + '--telnet ,'. + This option is deprecated. It is present only for backwards + compatibility. The string value of this option is exactly equivalent to + specifying C(host) with a value of I(), C(mode) with + a value of I(telnet), and C(port) with a value of + I(). Mutually exclusive with C(mode) and C(port). + required: False + default: none + type: str + port: + description: + - The TCP port number used to establish the connection. Mutually + exclusive with C(console). + required: False + default: 830 + type: int + timeout: + description: + - Maximum number of seconds to wait for RPC responses from the Junos + device. This option does NOT control the initial connection timeout + value. + required: False + default: 30 +''' + + # SUB_CONNECTION_DOCUMENTATION is just CONNECTION_DOCUMENTATION with each + # line indented. + SUB_CONNECTION_DOCUMENTATION = '' + for line in CONNECTION_DOCUMENTATION.splitlines(True): + SUB_CONNECTION_DOCUMENTATION += ' ' + line + + # Build actual DOCUMENTATION string by putting the pieces together. + DOCUMENTATION = ''' +options:''' + CONNECTION_DOCUMENTATION + ''' + provider: + description: + - An alternative syntax for specifying the connection options. Rather + than specifying each connection-related top-level option, the + connection-related options may be specified as a dictionary of + suboptions using this option. All connection-related options must + either be specified as top-level options or as suboptions of the + C(provider) option. You can not combine the two methods of specifying + connection-related options. + required: False + default: None + type: dict + suboptions:''' + SUB_CONNECTION_DOCUMENTATION + ''' +requirements: + - junos-eznc >= ''' + MIN_PYEZ_VERSION + ''' + - Python >= 2.7 +''' + + +# The common argument specification for connecting to Junos devices. +connection_spec = { + 'host': dict(type='str', + # Required either in provider or at top-level. + required=False, + aliases=['hostname', 'ip'], + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosActionModule.run() + default=None), + 'user': dict(type='str', + # Required either in provider or at top-level. + required=False, + aliases=['username'], + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosActionModule.run() + default=None), + 'passwd': dict(type='str', + required=False, + aliases=['password'], + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosActionModule.run() + default=None, + no_log=True), + 'ssh_private_key_file': dict(type='path', + required=False, + aliases=['ssh_keyfile'], + # See documentation for real default behavior. + # Default behavior coded in + # JuniperJunosActionModule.run() + default=None), + 'mode': dict(choices=[None, 'telnet'], + default=None), + 'console': dict(type='str', + required=False, + default=None), + 'port': dict(type='int', + required=False, + default=830), + 'timeout': dict(type='int', + required=False, + default=30), +} +# Connection arguments which are mutually exclusive. +connection_spec_mutually_exclusive = [['mode', 'console'], ['port', 'console']] +# Keys are connection options. Values are a list of task_vars to use as the +# default value. +connection_spec_fallbacks = { + 'host': ['inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + +# Specify the provider spec with options matching connection_spec. +provider_spec = { + 'provider': dict(type='dict', + options=connection_spec) +} + +# The provider option is mutually exclusive with all top-level connection +# options. +provider_spec_mutually_exclusive = [] +for key in connection_spec: + provider_spec_mutually_exclusive.append(['provider', key]) + +# top_spec is connection_spec + provider_spec +top_spec = connection_spec +top_spec.update(provider_spec) +top_spec_mutually_exclusive = connection_spec_mutually_exclusive +top_spec_mutually_exclusive += provider_spec_mutually_exclusive + +# "Hidden" arguments which are passed between the action plugin and the +# Junos module, but which should not be visible to users. +internal_spec = { + '_module_utils_path': dict(type='path', + required=True, + default=None), +} + + +class JuniperJunosModule(AnsibleModule): + """A subclass of AnsibleModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. + + Attributes: + dev: An instance of a PyEZ Device() object. + + Methods: + exit_json: Close self.dev and call parent's exit_json(). + fail_json: Close self.dev and call parent's fail_json(). + check_pyez_version: Verify installed PyEZ version is >= minimum. + open: Open self.dev. + close: Close self.dev. + """ + + # Method overrides + def __init__(self, + argument_spec={}, + mutually_exclusive=[], + min_pyez_version=MIN_PYEZ_VERSION, + **kwargs): + """Initialize a new JuniperJunosModule instance. + + Combines module-specific parameters with the common parameters shared + by all juniper_junos_* modules. Performs additional checks on options. + Collapses any provider options to be top-level options. Checks the + minimum PyEZ version. Creates and opens the PyEZ Device instance. + + Args: + agument_spec: Module-specific argument_spec added to top_spec. + mutually_exclusive: Module-specific mutually exclusive added to + top_spec_mutually_exclusive. + min_pyez_version: The minimum PyEZ version required by the module. + **kwargs: All additional keyword arguments are passed to + AnsibleModule.__init__(). + + Returns: + A JuniperJunosModule instance object. + """ + # Initialize the dev attribute + self.dev = None + # Update argument_spec with the internal_spec + argument_spec.update(internal_spec) + # Update argument_spec with the top_spec + argument_spec.update(top_spec) + # Extend mutually_exclusive with connection_mutually_exclusive + mutually_exclusive += top_spec_mutually_exclusive + # Call parent's __init__() + super(JuniperJunosModule, self).__init__( + argument_spec=argument_spec, + mutually_exclusive=mutually_exclusive, + **kwargs) + # Remove any arguments in internal_spec + for arg_name in internal_spec: + self.params.pop(arg_name) + # Promote any provider arg_name into params + if 'provider' in self.params and self.params['provider'] is not None: + for arg_name, arg_value in self.params['provider'].items(): + if arg_name in self.aliases: + arg_name = self.aliases[arg_name] + self.params[arg_name] = arg_value + self.params.pop('provider') + # Parse the console option + self._parse_console_options() + # Check that we have a user and host + if not self.params.get('host'): + self.fail_json(msg="missing required arguments: host") + if not self.params.get('user'): + self.fail_json(msg="missing required arguments: user") + # Check PyEZ version + self.check_pyez_version(min_pyez_version) + # Open the PyEZ connection + self.open() + + def exit_json(self, **kwargs): + """Close self.dev and call parent's exit_json(). + + Args: + **kwargs: All keyword arguments are passed to + AnsibleModule.exit_json(). + """ + # Close the connection. + self.close() + # Call the parent's exit_json() + super(JuniperJunosModule, self).exit_json(**kwargs) + + def fail_json(self, **kwargs): + """Close self.dev and call parent's fail_json(). + + Args: + **kwargs: All keyword arguments are passed to + AnsibleModule.fail_json(). + """ + # Close the connection. + self.close() + # Call the parent's fail_json() + super(JuniperJunosModule, self).fail_json(**kwargs) + + # JuniperJunosModule-specific methods below this point. + + def _parse_console_options(self): + """Parse the console option value. + + Parse the console option value and turn it into the equivalent: + host, mode, and port options. + """ + if self.params.get('console') is not None: + try: + console_string = self.params.get('console') + # We only care about the value after --telnet + (_, _, after) = console_string.partition('--telnet') + # Split on , + host_port = after.split(',', 1) + # Strip any leading/trailing whitespace or equal sign + # from host + host = host_port[0].strip('= ') + # Try to convert port to an int. + port = int(host_port[1]) + # Successfully parsed. Set params values + self.params['mode'] = 'telnet' + self.params['host'] = host + self.params['port'] = port + self.params.pop('console') + except Exception: + self.fail_json(msg="Unable to parse the console value: '%s'. " + "The value of the console argument should " + "be in the format '--telnet " + "," + "'." % + (console_string)) + + def check_pyez_version(self, minimum): + """Check the minimum PyEZ version. + + Args: + minimum: The minimum PyEZ version required. + """ + if HAS_PYEZ_VERSION is None: + self.fail_json(msg='junos-eznc (aka PyEZ) >= %s is required for ' + 'this module. However junos-eznc does not ' + 'appear to be currently installed. See %s for ' + 'details on installing junos-eznc.' % + (minimum, PYEZ_INSTALLATION_URL)) + elif HAS_PYEZ_VERSION is not None: + if not LooseVersion(HAS_PYEZ_VERSION) >= LooseVersion(minimum): + self.fail_json( + msg='junos-eznc (aka PyEZ) >= %s is required for ' + 'this module. Version %s of junos-eznc is ' + 'currently installed. See %s for details on ' + 'upgrading junos-eznc.' % + (minimum, + HAS_PYEZ_VERSION, + PYEZ_INSTALLATION_URL)) + + def open(self): + """Open the self.dev PyEZ Device instance. + """ + if HAS_PYEZ_DEVICE is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but the ' + 'jnpr.junos.device.Device class could not be ' + 'imported.') + if HAS_PYEZ_EXCEPTIONS is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but the ' + 'jnpr.junos.exception module could not be ' + 'imported.') + + # Move all of the connection arguments into connect_args + connect_args = {} + for key in connection_spec: + if self.params.get(key) is not None: + connect_args[key] = self.params.get(key) + + try: + self.close() + self.dev = Device(**connect_args) + self.dev.open() + # Exceptions raised by close() or open() are all sub-classes of + # ConnectError, so this should catch all connection-related exceptions + # raised from PyEZ. + except pyez_exception.ConnectError as ex: + self.fail_json(msg='Unable to make a PyEZ connection: %s' % + (str(ex))) + + def close(self): + """Close the self.dev PyEZ Device instance. + """ + if self.dev is not None: + try: + # Because self.fail_json() calls self.close(), we must set + # self.dev = None BEFORE calling dev.close() in order to avoid + # the infinite recursion which would occur if dev.close() + # raised a ConnectError. + dev = self.dev + self.dev = None + dev.close() + # Exceptions raised by close() are all sub-classes of + # ConnectError, so this should catch all connection-related + # exceptions raised from PyEZ. + except pyez_exception.ConnectError as ex: + self.fail_json(msg='Unable to close PyEZ connection: %s' % + (str(ex))) + + +class JuniperJunosActionModule(ActionNormal): + """A subclass of ActionNormal used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + + # Call the parent action module. + return super(JuniperJunosActionModule, self).run(tmp, task_vars) + +class JuniperJunosDeprecatedActionModule(JuniperJunosActionModule): + def run(self, tmp=None, task_vars=None): + print("I'm running, running, running...") + for key in task_vars: + if key == 'junos_get_facts': + print("Found it: %s" (key)) + # Call the parent action module. + return super(JuniperJunosActionModule, self).run(tmp, task_vars) diff --git a/requirements.txt b/requirements.txt index 2dfdfb29..63df5afe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -junos-eznc +junos-eznc>=2.1.7 jxmlease diff --git a/setup.py b/setup.py index 863902aa..4c14f828 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,6 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Application Frameworks', diff --git a/version.py b/version.py index f241a5ed..a8a49824 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0dev1" -DATE = "2017-Oct-9" +VERSION = "2.0.0.dev2" +DATE = "2017-Oct-17" From 900a7b4d9fc3d6c2581c477f60355267ac580a20 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Wed, 18 Oct 2017 19:47:47 -0600 Subject: [PATCH 171/426] Adding logging functionality to common code and juniper_junos_facts. (#279) * Adding logging functionality to common code and juniper_junos_facts. --- library/juniper_junos_facts.py | 94 ++++++++----- module_utils/juniper_junos_common.py | 201 +++++++++++++++++++++++++-- 2 files changed, 250 insertions(+), 45 deletions(-) diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 16018399..d9e239f2 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -71,20 +71,6 @@ required: false default: none type: path - logfile: - description: - - A path to a file, on the Ansible control machine, where debugging - information is logged. This option is deprecated. It is present only - for backwards compatibility. Logging is now handled using Ansible's - standard logging scheme which does something I haven't quite figured - out yet. - required: false - default: None - type: path -requirements: - - junos-eznc >= 2.1.7 -notes: - - The NETCONF system service must be enabled on the target Junos device. ''' EXAMPLES = ''' @@ -137,6 +123,7 @@ ''' +# Standard library imports import os.path import json @@ -174,6 +161,9 @@ def import_juniper_junos_common(): # Avoids a warning about not specifying no_log for passwd. 'passwd': dict(no_log=True) }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, check_invalid_arguments=False, bypass_checks=True ) @@ -225,14 +215,12 @@ def get_facts_dict(junos_module): def save_facts(junos_module, facts): - """If the savedir argument was specified, save the facts into a JSON file. + """If the savedir option was specified, save the facts into a JSON file. - Ansible >= 2.0 doesn't like custom objects in a modules return value. - Because PyEZ facts are a custom object rather than a true dict they must be - converted to a standard dict. Since facts are read-only, we must begin by - copying facts into a dict. Since PyEZ facts are "on-demand", the - junos_module.dev instance must be an open PyEZ Device instance ojbect - before this function is called.. + If the savedir option was specified, save the facts into a JSON file named + savedir/hostname-facts.json. The filename begins with the value of the + hostname fact returned from the Junos device, which might be different than + the value of the host option passed to the module. Args: junos_module: An instance of a JuniperJunosModule. @@ -246,14 +234,46 @@ def save_facts(junos_module, facts): save_dir = junos_module.params.get('savedir') file_name = '%s-facts.json' % (facts['hostname']) file_path = os.path.normpath(os.path.join(save_dir, file_name)) + junos_module.logger.debug("Saving facts to: %s.", file_path) try: with open(file_path, 'w') as fact_file: json.dump(facts, fact_file) + junos_module.logger.debug("Facts saved to: %s.", file_path) except IOError: - junos_module.fail_json(msg="Unable to save facts. Faile to open " + junos_module.fail_json(msg="Unable to save facts. Failed to open " "the %s file." % (file_path)) +def save_inventory(junos_module, inventory): + """If the savedir option was specified, save the XML inventory. + + If the savedir option was specified, save the inventory XML output into + an XML file named savedir/hostname-inventory.xml. The filename begins with + the value of the hostname fact returned from the Junos device, which might + be different than the value of the host option passed to the module. + + Args: + junos_module: An instance of a JuniperJunosModule. + inventory: The XML string of inventory to save. + + Raises: + IOError: Calls junos_module.fail_json if unable to open the inventory + file for writing. + """ + if junos_module.params.get('savedir') is not None: + save_dir = junos_module.params.get('savedir') + file_name = '%s-inventory.xml' % (junos_module.dev.facts['hostname']) + file_path = os.path.normpath(os.path.join(save_dir, file_name)) + junos_module.logger.debug("Saving inventory to: %s.", file_path) + try: + with open(file_path, 'w') as fact_file: + fact_file.write(inventory) + junos_module.logger.debug("Inventory saved to: %s.", file_path) + except IOError: + junos_module.fail_json(msg="Unable to save inventory. Failed to " + "open the %s file." % (file_path)) + + def main(): # Import juniper_junos_common juniper_junos_common = import_juniper_junos_common() @@ -265,7 +285,6 @@ def main(): required=False, default=None), savedir=dict(type='path', required=False, default=None), - logfile=dict(type='path', required=False, default=None), ), # Since this module doesn't change the device's configuration, there # no additional work required to support check mode. It's inherently @@ -273,21 +292,28 @@ def main(): supports_check_mode=True ) - junos_module.log("Device opened. Gathering facts.") - + junos_module.logger.debug("Gathering facts.") # Get the facts dictionary from the device. facts = get_facts_dict(junos_module) + junos_module.logger.debug("Facts gathered.") - # Add code to implement config_format option - - # Add code to implement the logfile option - - junos_module.log("Facts gathered.") - - # Save the facts. - save_facts(junos_module, facts) + if junos_module.params.get('savedir') is not None: + # Save the facts. + save_facts(junos_module, facts) - junos_module.log("Facts saved.") + # Get and save the inventory + try: + junos_module.logger.debug("Gathering inventory.") + inventory = junos_module.dev.rpc.get_chassis_inventory() + junos_module.logger.debug("Inventory gathered.") + save_inventory(junos_module, + junos_module.etree.tostring(inventory, + pretty_print=True)) + except junos_module.pyez_exception.RpcError as ex: + junos_module.fail_json(msg='Unable to retrieve hardware ' + 'inventory: %s' % (str(ex))) + + # TODO: Add code to implement config_format option # Return response. junos_module.exit_json( diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 3ab531a0..dd9be866 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -37,6 +37,7 @@ # Standard library imports from distutils.version import LooseVersion +import logging import os # Non-standard library imports and checks @@ -58,12 +59,19 @@ except ImportError: HAS_PYEZ_EXCEPTIONS = False +try: + from lxml import etree + HAS_LXML_ETREE = True +except ImportError: + HAS_LXML_ETREE = False # Constants # Minimum PyEZ version required by shared code. MIN_PYEZ_VERSION = "2.1.7" # Installation URL for PyEZ. PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" +# Installation URL for LXML. +LXML_INSTALLATION_URL = "http://lxml.de/installation.html" class ModuleDocFragment(object): @@ -189,6 +197,62 @@ class ModuleDocFragment(object): default: 30 ''' + LOGGING_DOCUMENTATION = ''' + logfile: + description: + - The path to a file, on the Ansible control machine, where debugging + information for the particular task is logged. + - The log file must be writeable. If the file already exists, it is + appended. It is the users responsibility to delete/rotate log files. + - The level of information logged in this file is controlled by Ansible's + verbosity and debug options: + 1) By default, messages at level WARNING or higher are logged. + 2) If the -v or --verbose command-line options to ansible-playbook are + specified, messages at level INFO or higher are logged. + 3) If the -vv (or more verbose) command-line option to ansible-playbook + is specified, or the ANSIBLE_DEBUG environment variable is set, + then messages at level DEBUG or higher are logged. + - NOTE: When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + {{ inventory_hostname }} in the C(logfile) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the C(logdir) option in new playbooks. The + C(logfile) and C(logdir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - log_file + logdir: + description: + - The path to a directory, on the Ansible control machine, where + debugging information for the particular task is logged. Debugging + information will be logged to a file named {{ inventory_hostname }}.log + in the C(logdir) directory. + - The log file must be writeable. If the file already exists, it is + appended. It is the users responsibility to delete/rotate log files. + - The level of information logged in this file is controlled by Ansible's + verbosity and debug options: + 1) By default, messages at level WARNING or higher are logged. + 2) If the -v or --verbose command-line options to ansible-playbook are + specified, messages at level INFO or higher are logged. + 3) If the -vv (or more verbose) command-line option to ansible-playbook + is specified, or the ANSIBLE_DEBUG environment variable is set, + then messages at level DEBUG or higher are logged. + - The C(logfile) and C(logdir) options are mutually exclusive. The + C(logdir) option is recommended for all new playbooks. + required: false + default: None + type: path + aliases: + - log_dir +''' + # SUB_CONNECTION_DOCUMENTATION is just CONNECTION_DOCUMENTATION with each # line indented. SUB_CONNECTION_DOCUMENTATION = '' @@ -214,6 +278,8 @@ class ModuleDocFragment(object): requirements: - junos-eznc >= ''' + MIN_PYEZ_VERSION + ''' - Python >= 2.7 +notes: + - The NETCONF system service must be enabled on the target Junos device. ''' @@ -283,11 +349,25 @@ class ModuleDocFragment(object): for key in connection_spec: provider_spec_mutually_exclusive.append(['provider', key]) -# top_spec is connection_spec + provider_spec +# Specify the logging spec. +logging_spec = { + 'logfile': dict(type='path', required=False, default=None), + 'logdir': dict(type='path', required=False, default=None) +} + +# The logdir and logfile options are mutually exclusive. +logging_spec_mutually_exclusive = ['logfile', 'logdir'] + +# Other logging names which should be logged to the logfile +additional_logger_names = ['ncclient', 'paramiko'] + +# top_spec is connection_spec + provider_spec + logging_spec top_spec = connection_spec top_spec.update(provider_spec) +top_spec.update(logging_spec) top_spec_mutually_exclusive = connection_spec_mutually_exclusive top_spec_mutually_exclusive += provider_spec_mutually_exclusive +top_spec_mutually_exclusive += logging_spec_mutually_exclusive # "Hidden" arguments which are passed between the action plugin and the # Junos module, but which should not be visible to users. @@ -295,6 +375,9 @@ class ModuleDocFragment(object): '_module_utils_path': dict(type='path', required=True, default=None), + '_module_name': dict(type='str', + required=True, + default=None), } @@ -352,6 +435,7 @@ def __init__(self, argument_spec=argument_spec, mutually_exclusive=mutually_exclusive, **kwargs) + self.module_name = self.params.get('_module_name') # Remove any arguments in internal_spec for arg_name in internal_spec: self.params.pop(arg_name) @@ -371,6 +455,16 @@ def __init__(self, self.fail_json(msg="missing required arguments: user") # Check PyEZ version self.check_pyez_version(min_pyez_version) + # Check LXML Etree + self.check_lxml_etree() + self.etree = etree + # Check PyEZ exceptions + if HAS_PYEZ_EXCEPTIONS is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but the ' + 'jnpr.junos.exception module could not be ' + 'imported.') + self.pyez_exception = pyez_exception + self.logger = self._setup_logging() # Open the PyEZ connection self.open() @@ -383,6 +477,7 @@ def exit_json(self, **kwargs): """ # Close the connection. self.close() + self.logger.debug("Exit JSON: %s", kwargs) # Call the parent's exit_json() super(JuniperJunosModule, self).exit_json(**kwargs) @@ -395,6 +490,7 @@ def fail_json(self, **kwargs): """ # Close the connection. self.close() + self.logger.debug("Fail JSON: %s", kwargs) # Call the parent's fail_json() super(JuniperJunosModule, self).fail_json(**kwargs) @@ -431,6 +527,80 @@ def _parse_console_options(self): "'." % (console_string)) + def _setup_logging(self): + """Setup logging for the module. + + Performs several tasks to setup logging for the module. This includes: + 1) Creating a Logger instance object for the name + jnpr.ansible_module.. + 2) Sets the level for the Logger object depending on verbosity and + debug settings specified by the user. + 3) Sets the level for other Logger objects specified in + additional_logger_names depending on verbosity and + debug settings specified by the user. + 4) If the logfile or logdir option is specified, attach a FileHandler + instance which logs messages from jnpr.ansible_module. or + any of the names in additional_logger_names. + + Returns: + Logger instance object for the name jnpr.ansible_module.. + """ + class CustomAdapter(logging.LoggerAdapter): + """ + Prepend the hostname, in brackets, to the log message. + """ + def process(self, msg, kwargs): + return '[%s] %s' % (self.extra['host'], msg), kwargs + + # Default level to log. + level = logging.WARNING + # Log more if ANSIBLE_DEBUG or -v[v] is set. + if self._debug is True: + level = logging.DEBUG + elif self._verbosity == 1: + level = logging.INFO + elif self._verbosity > 1: + level = logging.DEBUG + # Get the logger object to be used for our logging. + logger = logging.getLogger('jnpr.ansible_module.' + self.module_name) + # Attach the NullHandler to avoid any errors if no logging is needed. + logger.addHandler(logging.NullHandler()) + # Set the logging level for the modules logging. This will also control + # the amount of logging which goes into Ansible's log file. + logger.setLevel(level) + # Set the logging level for additional names. This will also control + # the amount of logging which goes into Ansible's log file. + for name in additional_logger_names: + logging.getLogger(name).setLevel(level) + # Get the name of the logfile based on logfile or logdir options. + logfile = None + if self.params.get('logfile') is not None: + logfile = self.params.get('logfile') + elif self.params.get('logdir') is not None: + logfile = os.path.normpath(self.params.get('logdir') + '/' + + self.params.get('host') + '.log') + # Create the FileHandler and attach it. + if logfile is not None: + try: + handler = logging.FileHandler(logfile, mode='a') + handler.setLevel(level) + # Create a custom formatter. + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + # add formatter to handler + handler.setFormatter(formatter) + # Handler should log anything from the 'jnpr' namespace to + # catch PyEZ, JSNAPY, etc. logs. + jnpr_logger = logging.getLogger('jnpr') + jnpr_logger.addHandler(handler) + for name in additional_logger_names: + logging.getLogger(name).addHandler(handler) + except IOError as ex: + self.fail_json(msg="Unable to open the log file %s. %s" % + (logfile, str(ex))) + # Use the CustomAdapter to add host information. + return CustomAdapter(logger, {'host': self.params.get('host')}) + def check_pyez_version(self, minimum): """Check the minimum PyEZ version. @@ -439,7 +609,7 @@ def check_pyez_version(self, minimum): """ if HAS_PYEZ_VERSION is None: self.fail_json(msg='junos-eznc (aka PyEZ) >= %s is required for ' - 'this module. However junos-eznc does not ' + 'this module. However, junos-eznc does not ' 'appear to be currently installed. See %s for ' 'details on installing junos-eznc.' % (minimum, PYEZ_INSTALLATION_URL)) @@ -454,6 +624,15 @@ def check_pyez_version(self, minimum): HAS_PYEZ_VERSION, PYEZ_INSTALLATION_URL)) + def check_lxml_etree(self): + """Check that lxml is available. + """ + if HAS_LXML_ETREE is False: + self.fail_json(msg='lxml is required for this module. However, ' + 'lxml does not appear to be currently ' + 'installed. See %s for details on installing ' + 'lxml.' % (LXML_INSTALLATION_URL)) + def open(self): """Open the self.dev PyEZ Device instance. """ @@ -474,8 +653,14 @@ def open(self): try: self.close() + log_connect_args = dict(connect_args) + log_connect_args['passwd'] = 'NOT_LOGGING_PARAMETER' + self.logger.debug("Creating device parameters: %s", + log_connect_args) self.dev = Device(**connect_args) + self.logger.debug("Opening device.") self.dev.open() + self.logger.debug("Device opened.") # Exceptions raised by close() or open() are all sub-classes of # ConnectError, so this should catch all connection-related exceptions # raised from PyEZ. @@ -495,6 +680,7 @@ def close(self): dev = self.dev self.dev = None dev.close() + self.logger.debug("Device closed.") # Exceptions raised by close() are all sub-classes of # ConnectError, so this should catch all connection-related # exceptions raised from PyEZ. @@ -562,15 +748,8 @@ def run(self, tmp=None, task_vars=None): # Pass the hidden _module_utils_path option module_utils_path = os.path.normpath(os.path.dirname(__file__)) self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action # Call the parent action module. return super(JuniperJunosActionModule, self).run(tmp, task_vars) - -class JuniperJunosDeprecatedActionModule(JuniperJunosActionModule): - def run(self, tmp=None, task_vars=None): - print("I'm running, running, running...") - for key in task_vars: - if key == 'junos_get_facts': - print("Found it: %s" (key)) - # Call the parent action module. - return super(JuniperJunosActionModule, self).run(tmp, task_vars) From 536d09c3189ba9f1e075046c906aa7fb253e76e9 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 19 Oct 2017 18:15:59 -0600 Subject: [PATCH 172/426] Add config to facts (#280) * Add common code to retrive configuration with various options. Update facts to use it. --- library/juniper_junos_facts.py | 75 ++++++-- module_utils/juniper_junos_common.py | 277 +++++++++++++++++++++++---- requirements.txt | 3 +- 3 files changed, 298 insertions(+), 57 deletions(-) diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index d9e239f2..e469244a 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -49,6 +49,8 @@ U(http://junos-pyez.readthedocs.io/en/latest/jnpr.junos.facts.html) Also returns the committed configuration of the Junos device if the I(config_format) option has a value other than C(none). +# Document connection arguments +# Document logging arguments extends_documentation_fragment: juniper_junos_common options: config_format: @@ -93,34 +95,63 @@ # Using savedir option -# Print the JSON file - -# Using logile option - -# Print the logfile +# Print the saved JSON file -# Document connection arguments +# Document connection examples +# Document authentication examples +# Document logging examples # extends_documentation_fragment: juniper_junos_common ''' RETURN = ''' ansible_facts.junos: - description: The facts collected from the Junos device. + description: + - Facts collected from the Junos device. This dictionary contains the + keys listed in the I(contains) section of this documentation PLUS all + of the keys returned from PyEZ's fact gathering system. See + U(http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) + for a complete list of these keys and thier meaning. returned: success - type: dict - sample: - + type: complex + contains: + config: + description: + - The device's committed configuration, in the format specified by + I(config_format), as a single multi-line string. + returned: when I(config_format) is not none. + type: str + has_2RE: + description: + - Indicates if the device has more than one Routing Engine installed. + returned: success + type: bool + re_name: + description: + - The name of the current Routing Engine to which Ansible is connected. + returned: success + type: str + master_state: + description: + - The mastership state of the Routing Engine to which Ansible is + connected. True if the RE is the master Routing Engine. False if + the RE is not the master Routing Engine. + returned: success + type: bool facts: description: Returned for backwards compatibility. Returns the same keys and values which are returned under I(ansible_facts.junos). returned: success type: dict - sample: - changed: - + description: Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to false. + returned: always + type: bool failed: - + description: Indicates if the task failed. + returned: always + type: bool ''' # Standard library imports @@ -278,10 +309,13 @@ def main(): # Import juniper_junos_common juniper_junos_common = import_juniper_junos_common() + config_format_choices = [None] + config_format_choices += juniper_junos_common.CONFIG_FORMAT_CHOICES + # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - config_format=dict(choices=[None, 'xml', 'set', 'text', 'json'], + config_format=dict(choices=config_format_choices, required=False, default=None), savedir=dict(type='path', required=False, default=None), @@ -313,7 +347,16 @@ def main(): junos_module.fail_json(msg='Unable to retrieve hardware ' 'inventory: %s' % (str(ex))) - # TODO: Add code to implement config_format option + config_format = junos_module.params.get('config_format') + if config_format is not None: + (config, config_parsed) = junos_module.get_configuration( + format=config_format) + if config is not None: + facts.update({'config': config}) + # Need to wait until the ordering issues are figured out before + # using config_parsed. + # if config_parsed is not None: + # facts.update({'config_parsed': config_parsed}) # Return response. junos_module.exit_json( diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index dd9be866..84f92949 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -37,6 +37,7 @@ # Standard library imports from distutils.version import LooseVersion +import json import logging import os @@ -61,18 +62,29 @@ try: from lxml import etree - HAS_LXML_ETREE = True + HAS_LXML_ETREE_VERSION = '.'.join(map(str,etree.LXML_VERSION)) except ImportError: - HAS_LXML_ETREE = False + HAS_LXML_ETREE_VERSION = None + +try: + import jxmlease + HAS_JXMLEASE_VERSION = jxmlease.__version__ +except ImportError: + HAS_JXMLEASE_VERSION = None # Constants # Minimum PyEZ version required by shared code. MIN_PYEZ_VERSION = "2.1.7" # Installation URL for PyEZ. PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" +# Minimum lxml version required by shared code. +MIN_LXML_ETREE_VERSION = "3.2.4" # Installation URL for LXML. -LXML_INSTALLATION_URL = "http://lxml.de/installation.html" - +LXML_ETREE_INSTALLATION_URL = "http://lxml.de/installation.html" +# Minimum jxmlease version required by shared code. +MIN_JXMLEASE_VERSION = "1.0.1" +# Installation URL for jxmlease. +JXMLEASE_INSTALLATION_URL = "http://jxmlease.readthedocs.io/en/stable/install.html" class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -380,6 +392,10 @@ class ModuleDocFragment(object): default=None), } +# Known configuration formats +CONFIG_FORMAT_CHOICES = ['xml', 'set', 'text', 'json'] +# Known configuration databases +CONFIG_DATABASE_CHOICES = ['candidate', 'committed'] class JuniperJunosModule(AnsibleModule): """A subclass of AnsibleModule used by all juniper_junos_* modules. @@ -393,7 +409,10 @@ class JuniperJunosModule(AnsibleModule): Methods: exit_json: Close self.dev and call parent's exit_json(). fail_json: Close self.dev and call parent's fail_json(). - check_pyez_version: Verify installed PyEZ version is >= minimum. + check_pyez: Verify the PyEZ library is present and functional. + check_lxml_etree: Verify the lxml Etree library is present and + functional. + check_jxmlease: Verify the Jxmlease library is present and functional. open: Open self.dev. close: Close self.dev. """ @@ -403,6 +422,8 @@ def __init__(self, argument_spec={}, mutually_exclusive=[], min_pyez_version=MIN_PYEZ_VERSION, + min_lxml_etree_version=MIN_LXML_ETREE_VERSION, + min_jxmlease_version=MIN_JXMLEASE_VERSION, **kwargs): """Initialize a new JuniperJunosModule instance. @@ -416,6 +437,10 @@ def __init__(self, mutually_exclusive: Module-specific mutually exclusive added to top_spec_mutually_exclusive. min_pyez_version: The minimum PyEZ version required by the module. + min_lxml_etree_version: The minimum lxml Etree version required by + the module. + min_jxmlease_version: The minimum Jxmlease version required by the + module. **kwargs: All additional keyword arguments are passed to AnsibleModule.__init__(). @@ -454,16 +479,17 @@ def __init__(self, if not self.params.get('user'): self.fail_json(msg="missing required arguments: user") # Check PyEZ version - self.check_pyez_version(min_pyez_version) + self.check_pyez(min_pyez_version, + check_device=True, + check_exception=True) + self.pyez_exception = pyez_exception # Check LXML Etree - self.check_lxml_etree() + self.check_lxml_etree(min_lxml_etree_version) self.etree = etree - # Check PyEZ exceptions - if HAS_PYEZ_EXCEPTIONS is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but the ' - 'jnpr.junos.exception module could not be ' - 'imported.') - self.pyez_exception = pyez_exception + # Check jxmlease + self.check_jxmlease(min_jxmlease_version) + self.jxmlease = jxmlease + # Setup logging. self.logger = self._setup_logging() # Open the PyEZ connection self.open() @@ -490,7 +516,8 @@ def fail_json(self, **kwargs): """ # Close the connection. self.close() - self.logger.debug("Fail JSON: %s", kwargs) + if hasattr(self, 'logger'): + self.logger.debug("Fail JSON: %s", kwargs) # Call the parent's fail_json() super(JuniperJunosModule, self).fail_json(**kwargs) @@ -601,40 +628,118 @@ def process(self, msg, kwargs): # Use the CustomAdapter to add host information. return CustomAdapter(logger, {'host': self.params.get('host')}) - def check_pyez_version(self, minimum): - """Check the minimum PyEZ version. + def _check_library(self, + library_name, + installed_version, + installation_url, + minimum=None, + library_nickname=None): + """Check if library_name is installed and version is >= minimum. Args: - minimum: The minimum PyEZ version required. + library_name: The name of the library to check. + installed_version: The currently installed version, or None if it's + not installed. + installation_url: The URL with instructions on installing + library_name + minimum: The minimum version required. + Default = None which means no version check. + library_nickname: The library name with any nickname. + Default = library_name. + Failures: + - library_name not installed (unable to import). + - library_name installed_version < minimum. """ - if HAS_PYEZ_VERSION is None: - self.fail_json(msg='junos-eznc (aka PyEZ) >= %s is required for ' - 'this module. However, junos-eznc does not ' - 'appear to be currently installed. See %s for ' - 'details on installing junos-eznc.' % - (minimum, PYEZ_INSTALLATION_URL)) - elif HAS_PYEZ_VERSION is not None: - if not LooseVersion(HAS_PYEZ_VERSION) >= LooseVersion(minimum): + if library_nickname is None: + library_nickname = library_name + if installed_version is None: + if minimum is not None: + self.fail_json(msg='%s >= %s is required for this module. ' + 'However, %s does not appear to be ' + 'currently installed. See %s for ' + 'details on installing %s.' % + (library_nickname, minimum, library_name, + installation_url, library_name)) + else: + self.fail_json(msg='%s is required for this module. However, ' + '%s does not appear to be currently ' + 'installed. See %s for details on ' + 'installing %s.' % + (library_nickname, library_name, + installation_url, library_name)) + elif installed_version is not None and minimum is not None: + if not LooseVersion(installed_version) >= LooseVersion(minimum): self.fail_json( - msg='junos-eznc (aka PyEZ) >= %s is required for ' - 'this module. Version %s of junos-eznc is ' - 'currently installed. See %s for details on ' - 'upgrading junos-eznc.' % - (minimum, - HAS_PYEZ_VERSION, - PYEZ_INSTALLATION_URL)) - - def check_lxml_etree(self): - """Check that lxml is available. + msg='%s >= %s is required for this module. Version %s of ' + '%s is currently installed. See %s for details on ' + 'upgrading %s.' % + (library_nickname, minimum, installed_version, + library_name, installation_url, library_name)) + + def check_pyez(self, minimum=None, + check_device=False, + check_exception=False): + """Check PyEZ is available and version is >= minimum. + + Args: + minimum: The minimum PyEZ version required. + Default = None which means no version check. + check_device: Indicates whether to check for PyEZ Device object. + check_exception: Indicates whether to check for PyEZ exceptions. + + Failures: + - PyEZ not installed (unable to import). + - PyEZ version < minimum. + - check_device and PyEZ Device object can't be imported + - check_exception and PyEZ excepetions can't be imported """ - if HAS_LXML_ETREE is False: - self.fail_json(msg='lxml is required for this module. However, ' - 'lxml does not appear to be currently ' - 'installed. See %s for details on installing ' - 'lxml.' % (LXML_INSTALLATION_URL)) + self._check_library('junos-eznc', HAS_PYEZ_VERSION, + PYEZ_INSTALLATION_URL, minimum=minimum, + library_nickname='junos-eznc (aka PyEZ)') + if check_device is True: + if HAS_PYEZ_DEVICE is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' + 'the jnpr.junos.device.Device class could ' + 'not be imported.') + if check_exception is True: + if HAS_PYEZ_EXCEPTIONS is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' + 'the jnpr.junos.exception module could not ' + 'be imported.') + + def check_jxmlease(self, minimum=None): + """Check jxmlease is available and version is >= minimum. + + Args: + minimum: The minimum jxmlease version required. + Default = None which means no version check. + + Failures: + - jxmlease not installed. + - jxmlease version < minimum. + """ + self._check_library('jxmlease', HAS_JXMLEASE_VERSION, + JXMLEASE_INSTALLATION_URL, minimum=minimum) + + def check_lxml_etree(self, minimum=None): + """Check lxml etree is available and version is >= minimum. + + Args: + minimum: The minimum lxml version required. + Default = None which means no version check. + + Failures: + - lxml not installed. + - lxml version < minimum. + """ + self._check_library('lxml Etree', HAS_LXML_ETREE_VERSION, + LXML_ETREE_INSTALLATION_URL, minimum=minimum) def open(self): """Open the self.dev PyEZ Device instance. + + Failures: + - ConnectError: When unable to make a PyEZ connection. """ if HAS_PYEZ_DEVICE is False: self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but the ' @@ -688,6 +793,98 @@ def close(self): self.fail_json(msg='Unable to close PyEZ connection: %s' % (str(ex))) + def get_configuration(self, database='committed', format='text', + options={}, filter=None): + """Return the device configuration in the specified format. + + Return the datbase device configuration datbase in the format format. + Pass the options specified in the options dict and the filter specified + in the filter argument. + + Args: + database: The configuration database to return. Choices are defined + in CONFIG_DATABASE_CHOICES. + format: The format of the configuration to return. Choices are + defined in CONFIG_FORMAT_CHOICES. + Returns: + A tuple containing: + - The configuration in the requested format as a single + multi-line string. Returned for all formats. + - The "parsed" configuration as a JSON string. Set when + format == 'xml' or format == 'json'. None when format == 'text' + or format == 'set' + Failures: + - Invalid database. + - Invalid format. + - Options not a dict. + - Invalid filter. + - Format not understood by device. + """ + if database not in CONFIG_DATABASE_CHOICES: + self.fail_json(msg='The configuration database % is not in the ' + 'list of recognized configuration databases: ' + '%s.' % + (database, str(CONFIG_DATABASE_CHOICES))) + + if format not in CONFIG_FORMAT_CHOICES: + self.fail_json(msg='The configuration format % is not in the list ' + 'of recognized configuration formats: %s.' % + (format, str(CONFIG_FORMAT_CHOICES))) + + options.update({'database': database, + 'format': format}) + + if self.dev is None: + self.open() + + self.logger.debug("Retrieving device configuration. Options: %s " + "Filter %s", str(options), str(filter)) + config = None + try: + config = self.dev.rpc.get_config(options=options, + filter_xml=filter) + self.logger.debug("Configuration retrieved.") + except (self.pyez_exception.RPCError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Unable to retrieve the configuration: %s' % + (str(ex))) + + return_val = (None,None) + if format == 'text': + if not isinstance(config, self.etree._Element): + self.fail_json(msg='Unexpected configuration type returned. ' + 'Configuration is: %s' % (str(config))) + if config.tag != 'configuration-text': + self.fail_json(msg='Unexpected XML tag returned. ' + 'Configuration is: %s' % + (etree.tostring(config, pretty_print=True))) + return_val = (config.text, None) + elif format == 'set': + if not isinstance(config, self.etree._Element): + self.fail_json(msg='Unexpected configuration type returned. ' + 'Configuration is: %s' % (str(config))) + if config.tag != 'configuration-set': + self.fail_json(msg='Unexpected XML tag returned. ' + 'Configuration is: %s' % + (etree.tostring(config, pretty_print=True))) + return_val = (config.text, config.text.splitlines()) + elif format == 'xml': + if not isinstance(config, self.etree._Element): + self.fail_json(msg='Unexpected configuration type returned. ' + 'Configuration is: %s' % (str(config))) + if config.tag != 'configuration': + self.fail_json(msg='Unexpected XML tag returned. ' + 'Configuration is: %s' % + (etree.tostring(config, pretty_print=True))) + return_val = (etree.tostring(config, pretty_print=True), + jxmlease.parse_etree(config)) + elif format == 'json': + return_val = (json.dumps(config), config) + else: + self.fail_json(msg='Unable to return configuration in %s format.' % + (format)) + return return_val + class JuniperJunosActionModule(ActionNormal): """A subclass of ActionNormal used by all juniper_junos_* modules. diff --git a/requirements.txt b/requirements.txt index 63df5afe..ca1d307c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ junos-eznc>=2.1.7 -jxmlease +jxmlease>=1.0.1 +lxml>3.2.4 \ No newline at end of file From 54c28d1b0e75807f87109523a023865b19585638 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 19 Oct 2017 20:18:32 -0600 Subject: [PATCH 173/426] Update version.py --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index a8a49824..74932351 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev2" -DATE = "2017-Oct-17" +VERSION = "2.0.0.dev3" +DATE = "2017-Oct-19" From efe38d626adfa4df6e6fcafbb5b757b3c4c91ef8 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sat, 21 Oct 2017 10:39:44 -0600 Subject: [PATCH 174/426] Rewrite of junos_ping module to juniper_junos_ping. (#281) * Rewrite of junos_ping module to juniper_junos_ping. * Updating Dockerfile to fully install all needed directories of Juniper.junos role. --- Dockerfile | 7 + action_plugins/_junos_get_facts.py | 2 +- action_plugins/_junos_ping.py | 1 + action_plugins/juniper_junos_common_action.py | 50 ++ action_plugins/juniper_junos_facts.py | 51 +- action_plugins/juniper_junos_ping.py | 1 + library/_junos_ping.py | 1 + library/juniper_junos_facts.py | 8 +- library/juniper_junos_ping.py | 646 ++++++++++++++++++ library/junos_ping | 330 --------- module_utils/juniper_junos_common.py | 32 +- version.py | 4 +- 12 files changed, 732 insertions(+), 401 deletions(-) create mode 120000 action_plugins/_junos_ping.py create mode 100644 action_plugins/juniper_junos_common_action.py mode change 100644 => 120000 action_plugins/juniper_junos_facts.py create mode 120000 action_plugins/juniper_junos_ping.py create mode 120000 library/_junos_ping.py create mode 100644 library/juniper_junos_ping.py delete mode 100644 library/junos_ping diff --git a/Dockerfile b/Dockerfile index e9604f46..0ba2ce36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,15 @@ RUN mkdir /tmp/ansible-junos-stdlib &&\ mkdir /tmp/ansible-junos-stdlib/meta &&\ mkdir /project +ADD action_plugins /tmp/ansible-junos-stdlib/action_plugins +ADD callback_plugins /tmp/ansible-junos-stdlib/callback_plugins ADD library /tmp/ansible-junos-stdlib/library +ADD LICENSE /tmp/ansible-junos-stdlib/LICENSE ADD meta /tmp/ansible-junos-stdlib/meta +ADD module_utils /tmp/ansible-junos-stdlib/module_utils +ADD version.py /tmp/ansible-junos-stdlib/version.py + + RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ apk update && apk add ca-certificates &&\ diff --git a/action_plugins/_junos_get_facts.py b/action_plugins/_junos_get_facts.py index cbdd3b96..7470277b 120000 --- a/action_plugins/_junos_get_facts.py +++ b/action_plugins/_junos_get_facts.py @@ -1 +1 @@ -juniper_junos_facts.py \ No newline at end of file +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_ping.py b/action_plugins/_junos_ping.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_ping.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py new file mode 100644 index 00000000..a87fd74b --- /dev/null +++ b/action_plugins/juniper_junos_common_action.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + +# Use the custom behavior of JuniperJunosActionModule as our ActionModule. +# The Ansible core engine will call ActionModule.run() +from juniper_junos_common import JuniperJunosActionModule as ActionModule diff --git a/action_plugins/juniper_junos_facts.py b/action_plugins/juniper_junos_facts.py deleted file mode 100644 index a87fd74b..00000000 --- a/action_plugins/juniper_junos_facts.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -# Standard library imports -import os.path -import sys - -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] - -# Use the custom behavior of JuniperJunosActionModule as our ActionModule. -# The Ansible core engine will call ActionModule.run() -from juniper_junos_common import JuniperJunosActionModule as ActionModule diff --git a/action_plugins/juniper_junos_facts.py b/action_plugins/juniper_junos_facts.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_facts.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_ping.py b/action_plugins/juniper_junos_ping.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_ping.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_ping.py b/library/_junos_ping.py new file mode 120000 index 00000000..3ca2aa75 --- /dev/null +++ b/library/_junos_ping.py @@ -0,0 +1 @@ +juniper_junos_ping.py \ No newline at end of file diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index e469244a..297e7c88 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -2,6 +2,10 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. # # License: Apache 2.0 # @@ -146,7 +150,7 @@ description: Indicates if the device's state has changed. Since this module doesn't change the operational or configuration state of the device, the value is always set to false. - returned: always + returned: success type: bool failed: description: Indicates if the task failed. @@ -320,7 +324,7 @@ def main(): default=None), savedir=dict(type='path', required=False, default=None), ), - # Since this module doesn't change the device's configuration, there + # Since this module doesn't change the device's configuration, there is # no additional work required to support check mode. It's inherently # supported. supports_check_mode=True diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py new file mode 100644 index 00000000..ac8b82d4 --- /dev/null +++ b/library/juniper_junos_ping.py @@ -0,0 +1,646 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2016, Damien Garros +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_ping +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Execute ping from a Junos device +description: + - Execute the ping command from a Junos device in order to test network + reachability from the Junos device to a specified destination. +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common +options: + dest: + description: + - The IP address, or hostname if DNS is configured on the Junos device, + used as the destination of the ping. + required: true + default: none + type: str + aliases: + - dest_ip + - dest_host + - destination + - destination_ip + - destination_host + acceptable_percent_loss: + description: + - Maximum percentage of packets that may be lost and still consider the + task not to have failed. + required: false + default: 0 + type: int + aliases: + - acceptable_packet_loss + count: + description: + - Number of packets to send. + required: false + default: 5 + type: int + rapid: + description: + - Send ping requests rapidly + required: false + default: true + type: bool + ttl: + description: + - Maximum number of IP routers (hops) allowed between source and + destination. + required: false + default: None (default ttl for device) + type: int + size: + description: + - The size of the ICMP payload of the ping. NOTE: Total size of the IP + packet is I(size) + the 20 byte IP header + the 8 byte ICMP header. + Therefore, I(size) of 1472 generates an IP packet of size 1500. + required: false + default: None (default size for device) + type: int + do_not_fragment: + description: + - Set Do Not Fragment bit on ping packets. + required: false + default: false + type:bool + source: + description: + - The IP address, or hostname if DNS is configured on the Junos device, + used as the source address of the ping. If not specified, the Junos + default algorithm for determining the source address is used. + required: false + default: none + type: str + aliases: + - source_ip + - source_host + - src + - src_ip + - src_host + interface: + description: + - The source interface from which the the ping is sent. If not + specified, the default Junos algorithm for determining the source + interface is used. + required: false + default: none + type: str + routing_instance: + description: + - Name of the source routing instance from which the ping is + originated. If not specified, the default routing instance is used. + required: false + default: none + type: str +''' + +EXAMPLES = ''' +--- +# +# MODULE_EXAMPLES +# This playbook demonstrate the parameters supported by the juniper_junos_ping +# module. These examples use the default connection, authtentication and +# logging parameters. See the examples labeled CONNECTION_EXAMPLES for details +# on connection parameters. See the examples labeled AUTHENTICATION_EXAMPLES +# for details on authentication parameters. See the examples labeled +# LOGGING_EXAMPLES for details on logging parameters. +# +- name: Examples of juniper_junos_ping + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Ping 10.0.0.1 with default parameters. Fails if any packets lost. + juniper_junos_ping: + dest: "10.0.0.1" + + - name: Ping 10.0.0.1. Allow 50% packet loss. Register response. + juniper_junos_ping: + dest: "10.0.0.1" + acceptable_percent_loss: 50 + register: response + - name: Print all keys in the response. + debug: + var: response + + - name: Ping 10.0.0.1. Send 20 packets. Register response. + juniper_junos_ping: + dest: "10.0.0.1" + count: 20 + register: response + - name: Print packet sent from the response. + debug: + var: response.packets_sent + + - name: Ping 10.0.0.1. Send 10 packets wihtout rapid. Register response. + juniper_junos_ping: + dest: "10.0.0.1" + count: 10 + rapid: false + register: response + - name: Print the average round-trip-time from the response. + debug: + var: response.rtt_average + + - name: Ping www.juniper.net with ttl 15. Register response. + juniper_junos_ping: + dest: "www.juniper.net" + ttl: 15 + register: response + - name: Print the packet_loss percentage from the response. + debug: + var: response.packet_loss + + - name: Ping 10.0.0.1 with IP packet size of 1500. Register response. + juniper_junos_ping: + dest: "10.0.0.1" + size: 1472 + register: response + - name: Print the packets_received from the response. + debug: + var: response.packets_received + + - name: Ping 10.0.0.1 with do-not-fragment bit set. Register response. + juniper_junos_ping: + dest: "10.0.0.1" + do_not_fragment: true + register: response + - name: Print the maximum round-trip-time from the response. + debug: + var: response.rtt_maximum + + - name: Ping 10.0.0.1 with source set to 10.0.0.2. Register response. + juniper_junos_ping: + dest: "10.0.0.1" + source: "10.0.0.2" + register: response + - name: Print the source from the response. + debug: + var: response.source + + - name: Ping 192.168.1.1 from the red routing-instance. + juniper_junos_ping: + dest: "192.168.1.1" + routing_instance: "red" + + - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface + juniper_junos_ping: + dest: "224.0.0.1" + interface: "ge-0/0/0.0" + +# +# CONNECTION_EXAMPLES +# + +# +# AUTHENTICATION_EXAMPLES +# + +# +# LOGGING_EXAMPLES +# +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +packet_loss: + description: + - The percentage of packets lost. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +packets_sent: + description: + - The number of packets sent. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +packets_received: + description: + - The number of packets received. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +rtt_minimum: + description: + - The minimum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and packet_loss < 100%. + type: str +rtt_maximum: + description: + - The maximum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and packet_loss < 100%. + type: str +rtt_average: + description: + - The average round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and packet_loss < 100%. + type: str +rtt_stddev: + description: + - The standard deviation of round-trip-time, in microseconds, of all ping + responses received. + returned: when ping successfully executed, and packet_loss < 100%. + type: str +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list +changed: + description: + - Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to false. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +host: + description: + - The destination IP/host of the pings sent as specified by the I(dest) + option. NOTE: Key I(dest) and I(dest_ip) is also returned for backwards + compatibility. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +acceptable_percent_loss: + description: + - The acceptable packet loss (as a percentage) for this task as specified + by the I(acceptable_percent_loss) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +timeout: + description: + - The number of seconds to wait for a response from the ping RPC. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +count: + description: + - The number of pings sent, as specified by the I(count) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +rapid: + description: + - Whether or not the pings were sent rapidly, as specified by the + I(rapid) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +ttl: + description: + - The time-to-live set on the pings sent as specified by the + I(ttl) option. + returned: when ping successfully executed and the I(ttl) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +size: + description: + - The size in bytes of the ICMP payload on the pings sent as specified + by the I(size) option. NOTE: Total size of the IP packet is I(size) + + the 20 byte IP header + the 8 byte ICMP header. Therefore, I(size) + of 1472 generates an IP packet of size 1500. + returned: when ping successfully executed and the I(size) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +do_not_fragment: + description: + - Whether or not the do not fragment bit was set on the pings sent, as + specified by the I(do_not_fragment) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +source: + description: + - The source IP/host of the pings sent as specified by the I(source) + option. + NOTE: Key I(source_ip) is also returned for backwards compatibility. + returned: when ping successfully executed and the I(source) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +interface: + description: + - The source interface of the pings sent as specified by the + I(interface) option. + returned: when ping successfully executed and the I(interface) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +routing_instance: + description: + - The routing-instance from which the pings were sent as specified by + the I(routing_instance) option. + returned: when ping successfully executed and the I(routing_instance) + option was specified, even if the I(acceptable_percent_loss) was + exceeded. + type: str +''' + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # The argument spec for the module. + argument_spec = dict( + dest=dict(type='str', + required=True, + aliases=['dest_ip', 'dest_host', 'destination', + 'destination_ip', 'destination_host'], + default=None), + acceptable_percent_loss=dict(type='int', + required=False, + aliases=['acceptable_packet_loss'], + default=0), + ) + + # The portion of the argument spec that's specifically a parameter + # to the ping RPC. + ping_argument_spec = dict( + count=dict(type='int', + required=False, + default=5), + rapid=dict(type='bool', + required=False, + default=True), + ttl=dict(type='int', + required=False, + default=None), + size=dict(type='int', + required=False, + default=None), + do_not_fragment=dict(type='bool', + required=False, + default=False), + source=dict(type='str', + required=False, + aliases=['source_ip', 'source_host', 'src', + 'src_ip', 'src_host'], + default=None), + interface=dict(type='str', + required=False, + default=None), + routing_instance=dict(type='str', + required=False, + default=None), + ) + + # Add the ping RPC parameter argument spec fo the full argument_spec. + argument_spec.update(ping_argument_spec) + + argument_spec_keys = argument_spec.keys() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=argument_spec, + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + # acceptable packet loss is a percentage. Check to make sure it's between + # 0 and 100 inclusive + if (params['acceptable_percent_loss'] > 100 or + params['acceptable_percent_loss'] < 0): + junos_module.fail_json(msg='The value of the acceptable_percent_loss' + 'option (%d) is a percentage and must have ' + 'a value between 0 and 100.' % + (params['acceptable_percent_loss'])) + + # All of the params keys which are also keys in ping_argument_spec are the + # ping_params. Omit None and False values because they don't need to be + # passed to the RPC. + ping_params = {'host': params.get('dest')} + for key in ping_argument_spec: + value = params.get(key) + # Convert int (but not bool) to str + if not isinstance(value, bool) and isinstance(value, int): + params[key] = str(params[key]) + value = params.get(key) + # None and False values are the default for the RPC and shouldn't be + # passed to the device. + if value is not None and value is not False: + ping_params.update({key: value}) + + # Set initial results values. Assume failure until we know it's success. + results = {'msg': '', 'changed': False, 'failed': True} + # Results should include all the ping params in argument_spec_keys. + for key in argument_spec_keys: + results[key] = params.get(key) + # Overrite to be a string in the results + results['acceptable_percent_loss'] = str( + params.get('acceptable_percent_loss')) + # Add timeout to the response even though it's a connect parameter. + results['timeout'] = str(params.get('timeout')) + # Add aliases for backwards compatibility + results.update({'host': params.get('dest'), + 'dest_ip': params.get('dest'), + 'source_ip': params.get('source')}) + + # Execute the ping. + try: + junos_module.logger.debug("Executing ping with parameters: %s", + str(ping_params)) + resp = junos_module.dev.rpc.ping(normalize=True, + **ping_params) + junos_module.logger.debug("Ping executed.") + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + junos_module.fail_json(msg='Unable to execute ping: %s' % (str(ex))) + + if not isinstance(resp, junos_module.etree._Element): + junos_module.fail_json(msg='Unexpected ping response: %s' % + (str(resp))) + + resp_xml = junos_module.etree.tostring(resp, pretty_print=True) + + # Fail if any errors in the results + errors = resp.findall("rpc-error[error-severity='error']/error-message") + if len(errors) != 0: + # Create a comma-plus-space-seperated string of the errors. + # Calls the text attribute of each element in the errors list. + err_msg = ', '.join(map(lambda err: err.text, errors)) + results['msg'] = "Ping returned errors: %s" % (err_msg) + junos_module.exit_json(**results) + + # Add any warnings into the results + warns = resp.findall("rpc-error[error-severity='warning']/error-message") + if len(warns) != 0: + # Create list of the text attributes of each element in the warn list. + results['warnings'] = list(map(lambda warn: warn.text, warns)) + + # Try to find probe summary + probe_summary = resp.find('probe-results-summary') + if probe_summary is None: + results['msg'] = "Probe-results-summary not found in response: " \ + "%s" % (resp_xml) + junos_module.exit_json(**results) + + # Extract some required fields and some optional fields + r_fields = {} + r_fields['packet_loss'] = probe_summary.findtext('packet-loss') + r_fields['packets_sent'] = probe_summary.findtext('probes-sent') + r_fields['packets_received'] = probe_summary.findtext('responses-received') + o_fields = {} + o_fields['rtt_minimum'] = probe_summary.findtext('rtt-minimum') + o_fields['rtt_maximum'] = probe_summary.findtext('rtt-maximum') + o_fields['rtt_average'] = probe_summary.findtext('rtt-average') + o_fields['rtt_stddev'] = probe_summary.findtext('rtt-stddev') + + # Make sure we got values for required fields. + for key in r_fields: + if r_fields[key] is None: + results['msg'] = 'Expected field %s not found in response: %s' % \ + (key, resp_xml) + junos_module.exit_json(**results) + # Add the required fields to the result. + results.update(r_fields) + + # Extract integer packet loss + packet_loss = 100 + if results['packet_loss'] is not None: + try: + packet_loss = int(results['packet_loss']) + except ValueError: + results['msg'] = 'Packet loss %s not an integer. Response: %s' % \ + (results['packet_loss'], resp_xml) + junos_module.exit_json(**results) + + if packet_loss < 100: + # Optional fields are present if packet_loss < 100 + for key in o_fields: + if o_fields[key] is None: + results['msg'] = 'Expected field %s not found in ' \ + 'response: %s' % (key, resp_xml) + junos_module.exit_json(**results) + # Add the o_fields to the result (even if they're None) + results.update(o_fields) + + # Set the result message. + results['msg'] = 'Loss %s%%, (Sent %s | Received %s)' % \ + (results['packet_loss'], + results['packets_sent'], + results['packets_received']) + + # Was packet loss within limits? If so, we didn't fail. + if packet_loss <= params['acceptable_percent_loss']: + results['failed'] = False + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/junos_ping b/library/junos_ping deleted file mode 100644 index 5a68a81a..00000000 --- a/library/junos_ping +++ /dev/null @@ -1,330 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2016, Juniper Networks Inc. -# 2016, Damien Garros -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_ping -author: Damien Garros, Juniper Networks -version_added: "1.3.1" -short_description: execute ping on junos devices -description: - - execute ping on junos devices -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - dest_ip: - description: - - Destination ip - required: true - source_ip: - description: - - Source IP used to send the ping - required: false - rapid: - description: - - Execute ping at 100pps instead of 1pps - required: false - default: true - ttl: - description: - - Maximum number of IP routers (IP hops) allowed between source and destination - required: false - routing_instance: - description: - - Name of the routing instance to use to send the ping - required: false - timeout: - description: - - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate pings - that might take longer than the default timeout interval. - required: false - default: "0" - interface: - description: - - Interface used to send traffic out - required: false - acceptable_packet_loss: - description: - - Number of packets lost that is acceptable to consider the test PASS - required: false - default: "0" - count: - description: - - Number of packet to send - required: false - default: 5 - size: - description: - - The size of the ICMP payload of the ping. - required: false - default: None (default size) - do_not_fragment: - description: - - Set Don't Fragment bit - required: false - default: false -''' - -EXAMPLES = ''' -# Simple example - tasks: - - name: "Execute ping peer" - junos_ping: - host={{ junos_host }} - port={{ netconf_port }} - user={{ ansible_ssh_user }} - passwd={{ ansible_ssh_pass }} - dest_ip=8.8.8.8 - -# ping over console server connection using PyEZ >= 2.0 - tasks: - - name: "Execute ping peer" - junos_ping: - host={{ inventory_hostname }} - port=2011 - mode='telnet' - user={{ ansible_ssh_user }} - passwd={{ ansible_ssh_pass }} - dest_ip=8.8.8.8 - -# Using loop and more parameters - tasks: - - name: "Execute ping peer" - junos_ping: - host={{ junos_host }} - port={{ netconf_port }} - user={{ ansible_ssh_user }} - passwd={{ ansible_ssh_pass }} - dest_ip={{ item.peer_ip }} - source_ip={{ item.local_ip }} - do_not_fragment=True - ttl=1 - with_items: "{{underlay.neighbors}}" -''' - -import os -import sys -import re -from lxml import etree -from distutils.version import LooseVersion - -def main(): - module = AnsibleModule( - argument_spec=dict( - host=dict(required=True), - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - timeout=dict(required=False, default=0), - dest_ip=dict(required=True, default=None), - source_ip=dict(required=False, default=None), - interface=dict(required=False, default=None), - rapid=dict(required=False, type='bool', default=True), - routing_instance=dict(required=False, default=None), - ttl=dict(required=False, default=None), - acceptable_packet_loss=dict(required=False, default=0), - count=dict(required=False, default='5'), - size=(dict(required=False, default=None)), - do_not_fragment=dict(required=False, type='bool', default=False) - ), - supports_check_mode=False - ) - - results = {} - m_args = module.params - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - - # Open connection to device - dev = Device( - m_args['host'], - user=m_args['user'], - passwd=m_args['passwd'], - port=m_args['port'], - ssh_private_key_file=m_args['ssh_private_key_file'], - mode=m_args['mode'], - gather_facts=False) - - try: - dev.open() - except Exception as err: - msg = 'Unable to connect to {0}: {1}'.format(m_args['host'], str(err)) - module.fail_json(msg=msg) - return - - results['dest_ip'] = m_args['dest_ip'] - results['count'] = m_args['count'] - results['rapid'] = m_args['rapid'] - results['do_not_fragment'] = m_args['do_not_fragment'] - - results['changed'] = False - - # Prepare parameters - ping_params = dict( - host=m_args['dest_ip'], - count=str(m_args['count'])) - - if m_args['rapid'] is True: - ping_params['rapid'] = m_args['rapid'] - - if m_args['do_not_fragment'] is True: - ping_params['do_not_fragment'] = m_args['do_not_fragment'] - - if m_args['source_ip'] is not None: - ping_params['source'] = m_args['source_ip'] - results['source_ip'] = m_args['source_ip'] - - if m_args['routing_instance'] is not None: - ping_params['routing_instance'] = m_args['routing_instance'] - results['routing_instance'] = m_args['routing_instance'] - - if m_args['interface'] is not None: - ping_params['interface'] = m_args['interface'] - results['interface'] = m_args['interface'] - - if m_args['ttl'] is not None: - ping_params['ttl'] = str(m_args['ttl']) - results['ttl'] = str(m_args['ttl']) - - if m_args['size'] is not None: - ping_params['size'] = str(m_args['size']) - results['size'] = str(m_args['size']) - - ## Change default Timeout - timeout = int(m_args['timeout']) - if timeout > 0: - dev.timeout = timeout - - try: - - # Execute Ping - rpc_reply = dev.rpc.ping(**ping_params) - - except Exception as err: - results['failed'] = True - results['msg'] = "unable to execute ping due to:{0}".format(err.message) - dev.close() - raise err - - try: - # Try to Find probe summary - probe_summary = rpc_reply.find('probe-results-summary') - if probe_summary is None: - results['failed'] = True - results['msg'] = rpc_reply.findtext('rpc-error/error-message').strip() - dev.close() - module.exit_json(**results) - - # Extract packet loss - packet_loss = probe_summary.findtext("packet-loss") - packets_sent = probe_summary.findtext("probes-sent") - responses_received = probe_summary.findtext("responses-received") - - if packet_loss is None: - results['msg'] = 'Unable to collect results from ping, response was not found or was empty' - results['failed'] = True - dev.close() - module.exit_json(**results) - - packet_loss = packet_loss.strip() - packets_sent = packets_sent.strip() - responses_received = responses_received.strip() - results['msg'] = 'Loss {0}% packets, (Sent {1} | Received {2})'.format(str(packet_loss), str(packets_sent), str(responses_received)) - results['packet_loss'] = packet_loss - results['packets_sent'] = packets_sent - results['packets_received'] = responses_received - - if int(packet_loss) > int(m_args['acceptable_packet_loss']): - results['failed'] = True - dev.close() - module.exit_json(**results) - - except Exception as err: - results['failed'] = True - results['msg'] = "unable to analyze ping results due to:{0}".format(err.message) - dev.close() - raise err - - dev.close() - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 84f92949..b4b8e6a5 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -62,7 +62,7 @@ try: from lxml import etree - HAS_LXML_ETREE_VERSION = '.'.join(map(str,etree.LXML_VERSION)) + HAS_LXML_ETREE_VERSION = '.'.join(map(str, etree.LXML_VERSION)) except ImportError: HAS_LXML_ETREE_VERSION = None @@ -72,6 +72,7 @@ except ImportError: HAS_JXMLEASE_VERSION = None + # Constants # Minimum PyEZ version required by shared code. MIN_PYEZ_VERSION = "2.1.7" @@ -86,6 +87,7 @@ # Installation URL for jxmlease. JXMLEASE_INSTALLATION_URL = "http://jxmlease.readthedocs.io/en/stable/install.html" + class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -397,6 +399,7 @@ class ModuleDocFragment(object): # Known configuration databases CONFIG_DATABASE_CHOICES = ['candidate', 'committed'] + class JuniperJunosModule(AnsibleModule): """A subclass of AnsibleModule used by all juniper_junos_* modules. @@ -741,15 +744,6 @@ def open(self): Failures: - ConnectError: When unable to make a PyEZ connection. """ - if HAS_PYEZ_DEVICE is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but the ' - 'jnpr.junos.device.Device class could not be ' - 'imported.') - if HAS_PYEZ_EXCEPTIONS is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but the ' - 'jnpr.junos.exception module could not be ' - 'imported.') - # Move all of the connection arguments into connect_args connect_args = {} for key in connection_spec: @@ -762,10 +756,14 @@ def open(self): log_connect_args['passwd'] = 'NOT_LOGGING_PARAMETER' self.logger.debug("Creating device parameters: %s", log_connect_args) + timeout = connect_args.pop('timeout') self.dev = Device(**connect_args) self.logger.debug("Opening device.") self.dev.open() self.logger.debug("Device opened.") + self.logger.debug("Setting default device timeout to %d.", timeout) + self.dev.timeout = timeout + self.logger.debug("Device timeout set.") # Exceptions raised by close() or open() are all sub-classes of # ConnectError, so this should catch all connection-related exceptions # raised from PyEZ. @@ -787,11 +785,13 @@ def close(self): dev.close() self.logger.debug("Device closed.") # Exceptions raised by close() are all sub-classes of - # ConnectError, so this should catch all connection-related + # ConnectError or RpcError, so this should catch all # exceptions raised from PyEZ. - except pyez_exception.ConnectError as ex: - self.fail_json(msg='Unable to close PyEZ connection: %s' % - (str(ex))) + except (pyez_exception.ConnectError, + pyez_exception.RpcError): + # Ignore exceptions from closing. We're about to exit anyway + # and they will just mask the real error that happened. + pass def get_configuration(self, database='committed', format='text', options={}, filter=None): @@ -844,12 +844,12 @@ def get_configuration(self, database='committed', format='text', config = self.dev.rpc.get_config(options=options, filter_xml=filter) self.logger.debug("Configuration retrieved.") - except (self.pyez_exception.RPCError, + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: self.fail_json(msg='Unable to retrieve the configuration: %s' % (str(ex))) - return_val = (None,None) + return_val = (None, None) if format == 'text': if not isinstance(config, self.etree._Element): self.fail_json(msg='Unexpected configuration type returned. ' diff --git a/version.py b/version.py index 74932351..9c5e66e7 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev3" -DATE = "2017-Oct-19" +VERSION = "2.0.0.dev4" +DATE = "2017-Oct-21" From 9e3ccbb518055dfa8598dac8cbb708a03b5e4153 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sat, 21 Oct 2017 17:37:45 -0600 Subject: [PATCH 175/426] Juniper junos pmtud (#282) * Rewrite of junos_pmtud to juniper_junos_pmtud and use common library. --- action_plugins/_junos_pmtud.py | 1 + action_plugins/juniper_junos_pmtud.py | 1 + library/_junos_pmtud.py | 1 + library/juniper_junos_ping.py | 91 +---- library/juniper_junos_pmtud.py | 464 ++++++++++++++++++++++++++ library/junos_pmtud | 286 ---------------- module_utils/juniper_junos_common.py | 135 ++++++++ 7 files changed, 606 insertions(+), 373 deletions(-) create mode 120000 action_plugins/_junos_pmtud.py create mode 120000 action_plugins/juniper_junos_pmtud.py create mode 120000 library/_junos_pmtud.py create mode 100644 library/juniper_junos_pmtud.py delete mode 100644 library/junos_pmtud diff --git a/action_plugins/_junos_pmtud.py b/action_plugins/_junos_pmtud.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_pmtud.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_pmtud.py b/action_plugins/juniper_junos_pmtud.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_pmtud.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_pmtud.py b/library/_junos_pmtud.py new file mode 120000 index 00000000..b7bdfe9b --- /dev/null +++ b/library/_junos_pmtud.py @@ -0,0 +1 @@ +juniper_junos_pmtud.py \ No newline at end of file diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index ac8b82d4..1cba8743 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -550,93 +550,10 @@ def main(): 'source_ip': params.get('source')}) # Execute the ping. - try: - junos_module.logger.debug("Executing ping with parameters: %s", - str(ping_params)) - resp = junos_module.dev.rpc.ping(normalize=True, - **ping_params) - junos_module.logger.debug("Ping executed.") - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - junos_module.fail_json(msg='Unable to execute ping: %s' % (str(ex))) - - if not isinstance(resp, junos_module.etree._Element): - junos_module.fail_json(msg='Unexpected ping response: %s' % - (str(resp))) - - resp_xml = junos_module.etree.tostring(resp, pretty_print=True) - - # Fail if any errors in the results - errors = resp.findall("rpc-error[error-severity='error']/error-message") - if len(errors) != 0: - # Create a comma-plus-space-seperated string of the errors. - # Calls the text attribute of each element in the errors list. - err_msg = ', '.join(map(lambda err: err.text, errors)) - results['msg'] = "Ping returned errors: %s" % (err_msg) - junos_module.exit_json(**results) - - # Add any warnings into the results - warns = resp.findall("rpc-error[error-severity='warning']/error-message") - if len(warns) != 0: - # Create list of the text attributes of each element in the warn list. - results['warnings'] = list(map(lambda warn: warn.text, warns)) - - # Try to find probe summary - probe_summary = resp.find('probe-results-summary') - if probe_summary is None: - results['msg'] = "Probe-results-summary not found in response: " \ - "%s" % (resp_xml) - junos_module.exit_json(**results) - - # Extract some required fields and some optional fields - r_fields = {} - r_fields['packet_loss'] = probe_summary.findtext('packet-loss') - r_fields['packets_sent'] = probe_summary.findtext('probes-sent') - r_fields['packets_received'] = probe_summary.findtext('responses-received') - o_fields = {} - o_fields['rtt_minimum'] = probe_summary.findtext('rtt-minimum') - o_fields['rtt_maximum'] = probe_summary.findtext('rtt-maximum') - o_fields['rtt_average'] = probe_summary.findtext('rtt-average') - o_fields['rtt_stddev'] = probe_summary.findtext('rtt-stddev') - - # Make sure we got values for required fields. - for key in r_fields: - if r_fields[key] is None: - results['msg'] = 'Expected field %s not found in response: %s' % \ - (key, resp_xml) - junos_module.exit_json(**results) - # Add the required fields to the result. - results.update(r_fields) - - # Extract integer packet loss - packet_loss = 100 - if results['packet_loss'] is not None: - try: - packet_loss = int(results['packet_loss']) - except ValueError: - results['msg'] = 'Packet loss %s not an integer. Response: %s' % \ - (results['packet_loss'], resp_xml) - junos_module.exit_json(**results) - - if packet_loss < 100: - # Optional fields are present if packet_loss < 100 - for key in o_fields: - if o_fields[key] is None: - results['msg'] = 'Expected field %s not found in ' \ - 'response: %s' % (key, resp_xml) - junos_module.exit_json(**results) - # Add the o_fields to the result (even if they're None) - results.update(o_fields) - - # Set the result message. - results['msg'] = 'Loss %s%%, (Sent %s | Received %s)' % \ - (results['packet_loss'], - results['packets_sent'], - results['packets_received']) - - # Was packet loss within limits? If so, we didn't fail. - if packet_loss <= params['acceptable_percent_loss']: - results['failed'] = False + results = junos_module.ping( + ping_params, + acceptable_percent_loss=['acceptable_percent_loss'], + results=results) # Return results. junos_module.exit_json(**results) diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py new file mode 100644 index 00000000..567d6be1 --- /dev/null +++ b/library/juniper_junos_pmtud.py @@ -0,0 +1,464 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2017, Martin Komon +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_pmtud +version_added: "2.0.0" # of Juniper.junos role +author: "Martin Komon (@mkomon)" # Updates to use common code by @stacywsmith +short_description: Perform path MTU discovery from a Junos device to a dest +description: + - Determine the maximum IP MTU supported along a path from a Junos device to + a user-specified destination by performing path MTU discovery (PMTUD) using + the ping command. The reported MTU will be between min_test_size and + I(max_size) where min_test_size = (I(max_size) - I(max_range) + 1). + If the actual path MTU is greater than I(max_size), then I(max_size) will + be reported. If the actual path MTU is less than min_test_size, then a + failure will be reported. +# connection arguments will be automatically added +# logging arguments will be automatically added +extends_documentation_fragment: juniper_junos_common +options: + dest: + description: + - The IPv4 address, or hostname if DNS is configured on the Junos device, + used as the destination of the PMTUD. + required: true + default: none + type: str + aliases: + - dest_ip + - dest_host + - destination + - destination_ip + - destination_host + max_size: + description: + - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU + discovery. The value returned for I(inet_mtu) will be no more + than this value even if the path actually supports a higher MTU. This + value must be between 68 and 65496. + required: false + default: 1500 + type: int + max_range: + description: + - The maximum range of MTU values, in bytes, which will be searched + when performing path MTU discovery. This value must be 0 or + a power of 2 (2^n) between 2 and 65536. The minimum IPv4 MTU value + attempted when performing path MTU discovery is: + min_test_size = (I(max_size) - I(max_range) + 1) + required: false + default: 512 + type: int + source: + description: + - The IPv4 address, or hostname if DNS is configured on the Junos device, + used as the source address of the PMTUD. If not specified, the Junos + default algorithm for determining the source address is used. + required: false + default: none + type: str + aliases: + - source_ip + - source_host + - src + - src_ip + - src_host + interface: + description: + - The source interface from which the the PMTUD is performed. If not + specified, the default Junos algorithm for determining the source + interface is used. + required: false + default: none + type: str + routing_instance: + description: + - Name of the source routing instance from which the ping is + originated. If not specified, the default routing instance is used. + required: false + default: none + type: str +''' + +EXAMPLES = ''' +--- +# +# MODULE_EXAMPLES +# This playbook demonstrate the parameters supported by the juniper_junos_ping +# module. These examples use the default connection, authtentication and +# logging parameters. See the examples labeled CONNECTION_EXAMPLES for details +# on connection parameters. See the examples labeled AUTHENTICATION_EXAMPLES +# for details on authentication parameters. See the examples labeled +# LOGGING_EXAMPLES for details on logging parameters. +# +- name: Examples of juniper_junos_mtud + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Perform PMTUD to 10.0.0.1 with default parameters. + juniper_junos_pmtud: + dest: "10.0.0.1" + + - name: Perform PMTUD to 10.0.0.1. Register response. + juniper_junos_pmtud: + dest: "10.0.0.1" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 10.0.0.1. Search all possible MTU values. + juniper_junos_pmtud: + dest: "10.0.0.1" + max_size: 65496 + max_range: 65536 + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 10.0.0.1. Source from ge-0/0/0.0 interface. + juniper_junos_pmtud: + dest: "10.0.0.1" + interface: "ge-0/0/0.0" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 10.0.0.1. Source from 192.168.1.1. + juniper_junos_pmtud: + dest: "10.0.0.1" + source: "192.168.1.1" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 10.0.0.1. Source from the red routing-instance. + juniper_junos_pmtud: + dest: "10.0.0.1" + routing_instance: "red" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + +# +# CONNECTION_EXAMPLES +# + +# +# AUTHENTICATION_EXAMPLES +# + +# +# LOGGING_EXAMPLES +# +''' + +RETURN = ''' +inet_mtu: + description: + - The IPv4 path MTU size in bytes to the I(dest). This is the lesser of + I(max_size) and the actual path MTU to I(dest). If the actual path + MTU is less than min_test_size, then a failure is reported. Where + min_test_size = (I(max_size) - I(max_range) + 1) +changed: + description: + - Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to false. + returned: when PMTUD successfully executed. + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list +host: + description: + - The destination IP/host of the PMTUD as specified by the I(dest) + option. NOTE: Keys I(dest) and I(dest_ip) are also returned for backwards + compatibility. + returned: when PMTUD successfully executed. + type: str +source: + description: + - The source IP/host of the PMTUD as specified by the I(source) + option. + NOTE: Key I(source_ip) is also returned for backwards compatibility. + returned: when the I(source) option was specified. + type: str +interface: + description: + - The source interface of the PMTUD as specified by the I(interface) + option. + returned: when the I(interface) option was specified. + type: str +routing_instance: + description: + - The routing-instance from which the PMTUD was performed as specified by + the I(routing_instance) option. + returned: when the I(routing_instance) option was specified. + type: str +''' + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def main(): + # Constants for MTU size + INET_MIN_MTU_SIZE = 68 # As prescribed by RFC 791, Section 3.2 - + # Fragmentation and Reassembly. + INET_MAX_MTU_SIZE = 65496 # Size of inet header's total length field is + # 16 bits. Therefore max inet packet size is 2^16 + # or 65536, but Junos only supports max IP size + # of 65496 for the ping command in order to + # accomodate a (potentially) maximum sized IP + # header. + + # Constants for the size of headers + INET_HEADER_SIZE = 20 + ICMP_HEADER_SIZE = 8 + INET_AND_ICMP_HEADER_SIZE = INET_HEADER_SIZE + ICMP_HEADER_SIZE + + # Choices for max_size + MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2 ** x, range(1, 17))) + + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + dest=dict(type='str', + required=True, + aliases=['dest_ip', 'dest_host', 'destination', + 'destination_ip', 'destination_host'], + default=None), + max_size=dict(type='int', + required=False, + default=1500), + max_range=dict(type='int', + required=False, + choices=MAX_SIZE_CHOICES, + default=512), + source=dict(type='str', + required=False, + aliases=['source_ip', 'source_host', 'src', + 'src_ip', 'src_host'], + default=None), + interface=dict(type='str', + required=False, + default=None), + routing_instance=dict(type='str', + required=False, + default=None), + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + # max_size must be between INET_MIN_MTU_SIZE and INET_MAX_MTU_SIZE + if (params['max_size'] < INET_MIN_MTU_SIZE or + params['max_size'] > INET_MAX_MTU_SIZE): + junos_module.fail_json(msg='The value of the max_size option(%d) ' + 'must be between %d and %d.' % + (params['max_size'], INET_MIN_MTU_SIZE, + INET_MAX_MTU_SIZE)) + + # Initialize ping parameters. + ping_params = {'host': params.get('dest'), + 'count': '3', + 'rapid': True, + 'inet': True, + 'do_not_fragment': True} + + # Add optional ping parameters + o_ping_params = {} + if params['source'] is not None: + o_ping_params['source'] = params['source'] + if params['interface'] is not None: + o_ping_params['interface'] = params['interface'] + if params['routing_instance'] is not None: + o_ping_params['routing_instance'] = params['routing_instance'] + ping_params.update(o_ping_params) + + # Set initial results values. Assume failure until we know it's success. + results = {'changed': False, + 'failed': True, + 'inet_mtu': 0, + 'host': params.get('dest')} + # Results should include all the o_ping_params. + for key in o_ping_params: + results[key] = ping_params.get(key) + # Add aliases for backwards compatibility + results.update({'dest': ping_params.get('host'), + 'dest_ip': ping_params.get('host'), + 'source_ip': ping_params.get('source')}) + + # Execute a minimally-sized ping just to verify basic connectivity. + junos_module.logger.debug("Verifying basic connectivity.") + ping_params['size'] = str(INET_MIN_MTU_SIZE - + INET_AND_ICMP_HEADER_SIZE) + results_for_minimal = dict(results) + results_for_minimal = junos_module.ping(ping_params, + acceptable_percent_loss=100, + results=results_for_minimal) + if int(results_for_minimal.get('packet_loss', 100)) == 100: + results['msg'] = "Basic connectivity to %s failed." % (results['host']) + junos_module.exit_json(**results) + + # Initialize test_size and step + test_size = params['max_size'] + step = params['max_range'] + min_test_size = test_size - (params['max_range'] - 1) + if min_test_size < INET_MIN_MTU_SIZE: + min_test_size = INET_MIN_MTU_SIZE + + while True: + if test_size < INET_MIN_MTU_SIZE: + test_size = INET_MIN_MTU_SIZE + if test_size > params['max_size']: + test_size = params['max_size'] + junos_module.logger.debug("Probing with size: %d", test_size) + step = step // 2 if step >= 2 else 0 + ping_params['size'] = str(test_size - INET_AND_ICMP_HEADER_SIZE) + current_results = dict(results) + current_results = junos_module.ping(ping_params, + acceptable_percent_loss=100, + results=current_results) + loss = int(current_results.get('packet_loss', 100)) + if loss < 100 and test_size == params['max_size']: + # ping success with max test_size, save and break + results['failed'] = False + results['inet_mtu'] = test_size + break + elif loss < 100: + # ping success, increase test_size + results['failed'] = False + results['inet_mtu'] = test_size + test_size += step + else: + # ping fail, lower size + test_size -= step + if step < 1: + break + + if results.get('inet_mtu', 0) == 0: + junos_module.fail_json(msg='The MTU of the path to %s is less than ' + 'the minimum tested size(%d). Try ' + 'decreasing max_size(%d) or increasing ' + 'max_range(%d).' % (results['host'], + min_test_size, + params['max_size'], + params['max_range']), + **results) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/junos_pmtud b/library/junos_pmtud deleted file mode 100644 index e778c1b1..00000000 --- a/library/junos_pmtud +++ /dev/null @@ -1,286 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2017, Juniper Networks Inc. -# 2017, Martin Komon -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_pmtud -author: Martin Komon -version_added: "2.4" -short_description: Perform path MTU discovery on junos devices -description: - - perform path MTU discovery on junos devices -requirements: - - junos-eznc >= 1.2.2 -options: - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - dest_ip: - description: - - Destination IPv4 address or hostname - required: true - source_ip: - description: - - Source IPv4 address used to send the ping - required: false - routing_instance: - description: - - Name of the routing instance to use to send the ping - required: false - timeout: - description: - - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate pings - that might take longer than the default timeout interval. - required: false - default: "0" - interface: - description: - - Interface used to send traffic out - required: false - max_size: - description: - - Start and max size for path MTU discovery. - required: false - default: 1472 - max_range: - description: - - Max range of path MTU discovery. Must be 2^n. - required: false - default: 512 - -returns: - inet_mtu: - description: - - IPv4 path MTU size to destination. -''' - -EXAMPLES = ''' -# Simple example - tasks: - - name: "Check MTU on backup circuit" - junos_pmtud: - host={{ junos_host }} - port={{ netconf_port }} - user={{ ansible_ssh_user }} - passwd={{ ansible_ssh_pass }} - dest_ip=8.8.8.8 - -# Using more parameters - tasks: - - name: "Check MTU on backup circuit" - junos_pmtud: - host={{ junos_host }} - port={{ netconf_port }} - user={{ ansible_ssh_user }} - passwd={{ ansible_ssh_pass }} - dest_ip=8.8.8.8 - routing_instance=internet - max_range=128 -''' - -import math -from distutils.version import LooseVersion - -def main(): - spec = dict( - host=dict(required=True), - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - timeout=dict(required=False, default=0), - dest_ip=dict(required=True, default=None), - source_ip=dict(required=False, default=None), - interface=dict(required=False, default=None), - routing_instance=dict(required=False, default=None), - max_size=dict(type='int', required=False, default=1472), - max_range=dict(type='int', required=False, default=512), - ) - - module = AnsibleModule( - argument_spec=spec, - supports_check_mode=False - ) - - m_args = module.params - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - - # Open connection to device - dev = Device( - m_args['host'], - user=m_args['user'], - passwd=m_args['passwd'], - port=m_args['port'], - ssh_private_key_file=m_args['ssh_private_key_file'], - mode=m_args['mode'], - gather_facts=False) - - try: - dev.open() - except Exception as err: - msg = 'Unable to connect to {0}: {1}'.format(m_args['host'], str(err)) - module.fail_json(msg=msg) - return - - results = dict( - changed=False, - inet_mtu=0, - dest_ip=m_args['dest_ip'] - ) - - warnings = list() - - # check if max_range is a power of 2 - log_max_range = math.log(m_args['max_range']) / math.log(2) - if math.floor(log_max_range) != log_max_range: - warnings.append('Max_range must be a power of 2 between 2 and 1024; ' - 'ignoring value {0} and using default {1}.' - ''.format(m_args['max_range'], - spec['max_range']['default'])) - m_args['max_range'] = spec['max_range']['default'] - - # Prepare parameters - ping_params = dict( - host=m_args['dest_ip'], - inet=True, - count='3', - do_not_fragment=True, - ) - - if m_args['source_ip'] is not None: - ping_params['source'] = m_args['source_ip'] - results['source_ip'] = m_args['source_ip'] - - if m_args['routing_instance'] is not None: - ping_params['routing_instance'] = m_args['routing_instance'] - results['routing_instance'] = m_args['routing_instance'] - - if m_args['interface'] is not None: - ping_params['interface'] = m_args['interface'] - results['interface'] = m_args['interface'] - - ## Change default Timeout - timeout = int(m_args['timeout']) - if timeout > 0: - dev.timeout = timeout - - try: - # Check if ICMP passes - ping_params['size'] = str(64) - rpc_reply = dev.rpc.ping(**ping_params) - loss = int(rpc_reply.findtext('probe-results-summary/packet-loss', default='100').strip()) - - if loss == 100: - module.fail_json( - msg="Unknown error, PMTUD cannot run.") - - test_size = int(m_args['max_size']) - step = int(m_args['max_range']) - - while True: - step = step / 2 if step >= 2 else 0 - ping_params['size'] = str(test_size) - rpc_reply = dev.rpc.ping(**ping_params) - loss = int(rpc_reply.findtext('probe-results-summary/packet-loss', default='100').strip()) - if loss < 100 and test_size == int(m_args['max_size']): - # ping success with max test_size, save and break - results["inet_mtu"] = test_size - break - elif loss < 100: - # ping success, increase test_size - results["inet_mtu"] = test_size - test_size += step - else: - # ping fail, lower size - test_size -= step - if step < 1: - break - - except Exception as err: - results['failed'] = True - results['msg'] = "unable to execute ping due to:{0}".format(err.message) - dev.close() - raise err - - if not results["inet_mtu"]: - module.fail_json(msg='MTU too low, increase max_range.', **results) - else: - results["inet_mtu"] += 28 # adjust for IPv4 and ICMP headers - - dev.close() - results['warnings'] = warnings - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index b4b8e6a5..c60e87b8 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -885,6 +885,141 @@ def get_configuration(self, database='committed', format='text', (format)) return return_val + def ping(self, params, acceptable_percent_loss=0, results={}): + """Execute a ping command with the parameters specified in params. + + Args: + params: dict of parameters passed directly to the ping RPC. + acceptable_percent_loss: integer specifying maximum percentage of + packets that may be lost and still + consider the ping not to have failed. + results: dict of results which should be included in the return + value, or which should be included if fail_json() is + called due to a failure. + + Returns: + A dict of results. It contains all key/value pairs in the results + argument plus the keys below. (The keys below will overwrite + any corresponding key which exists in the results argument): + + msg: (str) A human-readable message indicating the result. + packet_loss: (str) The percentage of packets lost. + packets_sent: (str) The number of packets sent. + packets_received: (str) The number of packets received. + rtt_minimum: (str) The minimum round-trip-time, in microseconds, + of all ping responses received. + rtt_maximum: (str) The maximum round-trip-time, in microseconds, + of all ping responses received. + rtt_average: (str) The average round-trip-time, in microseconds, + of all ping responses received. + rtt_stddev: (str) The standard deviation of round-trip-time, in + microseconds, of all ping responses received. + warnings: (list of str) A list of warning strings, if any, produced + from the ping. + failed: (bool) Indicates if the ping failed. The ping fails + when packet_loss > acceptable_percent_loss. + + Fails: + - If the ping RPC produces an exception. + - If there are errors present in the results. + """ + # Assume failure until we know success. + results['failed'] = True + + # Execute the ping. + try: + self.logger.debug("Executing ping with parameters: %s", + str(params)) + resp = self.dev.rpc.ping(normalize=True, **params) + self.logger.debug("Ping executed.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Unable to execute ping: %s' % (str(ex))) + + if not isinstance(resp, self.etree._Element): + self.fail_json(msg='Unexpected ping response: %s' % (str(resp))) + + resp_xml = self.etree.tostring(resp, pretty_print=True) + + # Fail if any errors in the results + errors = resp.findall( + "rpc-error[error-severity='error']/error-message") + if len(errors) != 0: + # Create a comma-plus-space-seperated string of the errors. + # Calls the text attribute of each element in the errors list. + err_msg = ', '.join(map(lambda err: err.text, errors)) + results['msg'] = "Ping returned errors: %s" % (err_msg) + self.exit_json(**results) + + # Add any warnings into the results + warnings = resp.findall( + "rpc-error[error-severity='warning']/error-message") + if len(warnings) != 0: + # Create list of the text attributes of each element in the + # warnings list. + results['warnings'] = list(map(lambda warn: warn.text, warnings)) + + # Try to find probe summary + probe_summary = resp.find('probe-results-summary') + if probe_summary is None: + results['msg'] = "Probe-results-summary not found in response: " \ + "%s" % (resp_xml) + self.exit_json(**results) + + # Extract some required fields and some optional fields + r_fields = {} + r_fields['packet_loss'] = probe_summary.findtext('packet-loss') + r_fields['packets_sent'] = probe_summary.findtext('probes-sent') + r_fields['packets_received'] = probe_summary.findtext( + 'responses-received') + o_fields = {} + o_fields['rtt_minimum'] = probe_summary.findtext('rtt-minimum') + o_fields['rtt_maximum'] = probe_summary.findtext('rtt-maximum') + o_fields['rtt_average'] = probe_summary.findtext('rtt-average') + o_fields['rtt_stddev'] = probe_summary.findtext('rtt-stddev') + + # Make sure we got values for required fields. + for key in r_fields: + if r_fields[key] is None: + results['msg'] = 'Expected field %s not found in ' \ + 'response: %s' % (key, resp_xml) + self.exit_json(**results) + # Add the required fields to the result. + results.update(r_fields) + + # Extract integer packet loss + packet_loss = 100 + if results['packet_loss'] is not None: + try: + packet_loss = int(results['packet_loss']) + except ValueError: + results['msg'] = 'Packet loss %s not an integer. ' \ + 'Response: %s' % \ + (results['packet_loss'], resp_xml) + self.exit_json(**results) + + if packet_loss < 100: + # Optional fields are present if packet_loss < 100 + for key in o_fields: + if o_fields[key] is None: + results['msg'] = 'Expected field %s not found in ' \ + 'response: %s' % (key, resp_xml) + self.exit_json(**results) + # Add the o_fields to the result (even if they're None) + results.update(o_fields) + + # Set the result message. + results['msg'] = 'Loss %s%%, (Sent %s | Received %s)' % \ + (results['packet_loss'], + results['packets_sent'], + results['packets_received']) + + # Was packet loss within limits? If so, we didn't fail. + if packet_loss <= acceptable_percent_loss: + results['failed'] = False + + return results + class JuniperJunosActionModule(ActionNormal): """A subclass of ActionNormal used by all juniper_junos_* modules. From 05a65d5d4121570f7774e7b06f5eff9cf53232d6 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sun, 22 Oct 2017 21:37:11 -0600 Subject: [PATCH 176/426] Rewrite the junos_shutdown and junos_zeroize modules to juniper_junos_system. (#283) * Rewrite the junos_shutdown and junos_zeroize modules to juniper_junos_system. The new juniper_junos_system module replaces both the junos_shutdown and junos_zeroize modules. The old modules can still be called and all arguments should still work for backwards compatibility. The new module provides takes a required action option and provides some additional capabilities. --- action_plugins/_junos_shutdown.py | 88 +++++ action_plugins/_junos_zeroize.py | 75 ++++ action_plugins/juniper_junos_system.py | 1 + library/_junos_shutdown.py | 1 + library/_junos_zeroize.py | 1 + library/juniper_junos_system.py | 513 +++++++++++++++++++++++++ library/junos_shutdown | 196 ---------- library/junos_zeroize | 232 ----------- module_utils/juniper_junos_common.py | 45 ++- 9 files changed, 719 insertions(+), 433 deletions(-) create mode 100644 action_plugins/_junos_shutdown.py create mode 100644 action_plugins/_junos_zeroize.py create mode 120000 action_plugins/juniper_junos_system.py create mode 120000 library/_junos_shutdown.py create mode 120000 library/_junos_zeroize.py create mode 100644 library/juniper_junos_system.py delete mode 100644 library/junos_shutdown delete mode 100644 library/junos_zeroize diff --git a/action_plugins/_junos_shutdown.py b/action_plugins/_junos_shutdown.py new file mode 100644 index 00000000..54678dd0 --- /dev/null +++ b/action_plugins/_junos_shutdown.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + +# Use the custom behavior of JuniperJunosActionModule as the superclass of +# our ActionModule. +from juniper_junos_common import JuniperJunosActionModule + + +class ActionModule(JuniperJunosActionModule): + """Translates junos_shutdown args to juniper_junos_system args. + + This class is a subclass of JuniperJunosActionModule. It exists solely + for backwards compatibility. It translates the arguments from the old + junos_shutdown module into the arguments on the new juniper_junos_system + module. + """ + def run(self, tmp=None, task_vars=None): + # Check for the 'shutdown' option which was mandatory for + # the junos_shutdown module. + if 'shutdown' in self._task.args: + shutdown = self._task.args.pop('shutdown') + # 'shutdown' was the only valid value for the 'shutdown' option. + if shutdown == 'shutdown': + reboot = False + # Check for the 'reboot' option which was an optional boolean + # argument for the junos_shutdown module. + if 'reboot' in self._task.args: + reboot = self._task.args.pop('reboot') + if reboot is True: + # Translate to action="reboot" + self._task.args['action'] = 'reboot' + else: + # Translate to action="shutdown" + self._task.args['action'] = 'shutdown' + else: + # This isn't a valid value for action/shutdown + # We'll pass it through and the module will complain + # appropriately. + self._task.args['action'] = shutdown + + # Remaining arguments can be passed through transparently. + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_zeroize.py b/action_plugins/_junos_zeroize.py new file mode 100644 index 00000000..fb4a736b --- /dev/null +++ b/action_plugins/_junos_zeroize.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + +# Use the custom behavior of JuniperJunosActionModule as the superclass of +# our ActionModule. +from juniper_junos_common import JuniperJunosActionModule + + +class ActionModule(JuniperJunosActionModule): + """Translates junos_zeroize args to juniper_junos_system args. + + This class is a subclass of JuniperJunosActionModule. It exists solely + for backwards compatibility. It translates the arguments from the old + junos_zeroize module into the arguments on the new juniper_junos_system + module. + """ + def run(self, tmp=None, task_vars=None): + # Check for the 'zeroize' option which was mandatory for + # the junos_zeroize module. + if 'zeroize' in self._task.args: + # Delete the zeroize option. + zeroize = self._task.args.pop('zeroize') + # Add the action option with the value from the zeroize option. + # This should normally be the value 'zeroize'. If it's not, then + # the juniper_junos_system module will throw an appropriate error. + self._task.args['action'] = zeroize + + # Remaining arguments can be passed through transparently. + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/juniper_junos_system.py b/action_plugins/juniper_junos_system.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_system.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_shutdown.py b/library/_junos_shutdown.py new file mode 120000 index 00000000..95f32974 --- /dev/null +++ b/library/_junos_shutdown.py @@ -0,0 +1 @@ +juniper_junos_system.py \ No newline at end of file diff --git a/library/_junos_zeroize.py b/library/_junos_zeroize.py new file mode 120000 index 00000000..95f32974 --- /dev/null +++ b/library/_junos_zeroize.py @@ -0,0 +1 @@ +juniper_junos_system.py \ No newline at end of file diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py new file mode 100644 index 00000000..55071c84 --- /dev/null +++ b/library/juniper_junos_system.py @@ -0,0 +1,513 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_system +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Initiate operational actions on the Junos system. +description: + - Initiate an operational action (shutdown, reboot, halt or zeroize) on a + Junos system. The particular action to execute is defined by the mandatory + I(action) option. This module only INITIATES the action. It does NOT wait + for the action to complete. + - NOTE: Some Junos devices are effected by a Junos bug which causes this + Ansible module to hang indefinitely when connected to the Junos device via + the console. This problem is not seen when connecting to the Junos device + using the normal NETCONF over SSH transport connection. Therefore, it is + recommended to use this module only with a NETCONF over SSH transport + connection. However, this module does still permit connecting to Junos + devices via the console port and this functionality may still be used for + Junos devices running Junos versions less than 15.1. +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common +options: + action: + description: + - The action performed by the module. The choices include: + C(shutdown) - Power off the Junos devices. The values: C(off), + C(power-off), and C(power_off) are aliases for this value. + This is the equivalent of the 'request system power-off' + CLI command. + C(halt) - Stop the Junos OS running on the RE, but do not power off the + system. Once the system is halted, it will reboot if a + keystroke is entered on the console. This is the equivalent of + the 'request system halt' CLI command. + C(reboot) - Reboot the system. This is the equivalent of the + 'request system reboot' CLI command. + C(zeroize) - Restore the system (configuration, log files, etc.) to a + factory default state. This is the equivalent of + the 'request system zeroize' CLI command. + required: true + default: none + type: str + choices: ['shutdown', 'halt', 'reboot', 'zeroize, + 'off', 'power-off', 'power_off'] + at: + description: + - The time at which to shutdown, halt, or reboot the system. The value + may be specified in one of the following ways: + C(now) - The action takes effect immediately. + C(+minutes) — The action takes effect in C(minutes) minutes from now. + C(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute time, + specified as year, month, day, hour, and minute. + C(hh:mm) — The action takes effect at C(hh:mm) absolute time on the + current day, specified in 24-hour time. + The I(at) option can not be used when the I(action) option has a + value of C(zeroize). The I(at) option is mutually exclusive with the + I(in_min) option. + required: false + default: none + type: str + in_min: + description: + - Specify a delay, in minutes, before the shutdown, halt, or reboot. The + I(in_min) option can not be used when the I(action) option has a + value of C(zeroize). The I(in_min) option is mutually exclusive with + the I(at) option. + required: false + default: none + type: int + all_re: + description: + - If the system has multiple Routing Engines and this option is true, + then the action is performed on all REs in the system. If the system + does not have multiple Routing Engines, then this option has no effect. + This option applies to all I(action) values. The I(all_re) option is + mutually exclusive with the I(other_re) option. + required: false + default: true + type: bool + other_re: + description: + - If the system has dual Routing Engines and this option is true, + then the action is performed on the other REs in the system. If the + system does not have dual Routing Engines, then this option has no + effect. The I(other_re) option can not be used when the I(action) + option has a value of C(zeroize).The I(other_re) option is mutually + exclusive with the I(all_re) option. + required: false + default: false + type: bool + media: + description: + - Overwrite media when performing the zeroize operation. This option is + only valid when the I(action) option has a value of C(zeroize). + required: false + default: false + type: bool +''' + +EXAMPLES = ''' +--- +# +# MODULE_EXAMPLES +# This playbook demonstrate the parameters supported by the juniper_junos_ping +# module. These examples use the default connection, authtentication and +# logging parameters. See the examples labeled CONNECTION_EXAMPLES for details +# on connection parameters. See the examples labeled AUTHENTICATION_EXAMPLES +# for details on authentication parameters. See the examples labeled +# LOGGING_EXAMPLES for details on logging parameters. +# +- name: Examples of juniper_junos_system + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Reboot all REs of the device + juniper_junos_system: + action: "reboot" + + - name: Power off the other RE of the device. + juniper_junos_system: + action: "shutdown" + othe_re: True + + - name: Reboot this RE at 8pm today. + juniper_junos_system: + action: "reboot" + all_re: False + at: "20:00" + + - name: Halt the system on 25 January 2018 at 4pm. + juniper_junos_system: + action: "halt" + at: "1801251600" + + - name: Reboot the system in 30 minutes. + juniper_junos_system: + action: "reboot" + in_min: 30 + + - name: Reboot the system in 30 minutes. + juniper_junos_system: + action: "reboot" + at: "+30m" + + - name: Zeroize the local RE only. + juniper_junos_system: + action: "zeroize" + all_re: False + + - name: Zeroize all REs and overwrite medea. + juniper_junos_system: + action: "zeroize" + media: True + +# +# CONNECTION_EXAMPLES +# + +# +# AUTHENTICATION_EXAMPLES +# + +# +# LOGGING_EXAMPLES +# +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +changed: + description: + - Indicates if the device's state has changed. If the action is performed + (or if it would have been performed when in check mode) then the value + will be true. If there was an error before the action, then the value + will be false. + returned: always + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +action: + description: + - The value of the I(action) option. + returned: always + type: str +all_re: + description: + - The value of the I(all_re) option. + returned: always + type: str +other_re: + description: + - The value of the I(other_re) option. + returned: always + type: str +media: + description: + - The value of the I(media) option. + returned: always + type: str +''' + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + action=dict(type='str', + required=True, + choices=['shutdown', 'off', 'power-off', 'power_off', + 'halt', 'reboot', 'zeroize'], + default=None), + at=dict(type='str', + required=False, + default=None), + in_min=dict(type='int', + required=False, + aliases=['in'], + default=None), + all_re=dict(type='bool', + required=False, + default=True), + other_re=dict(type='bool', + required=False, + default=False), + media=dict(type='bool', + required=False, + default=False), + ), + mutually_exclusive=[['at', 'in_min'], ['all_re', 'other_re']], + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + action = params['action'] + # Synonymns for shutdown + if action == 'off' or action == 'power_off' or action == 'power-off': + action = 'shutdown' + + # at option only applies to reboot, shutdown, or halt actions. + if (params.get('at') is not None and + action != 'reboot' and + action != 'shutdown' and + action != 'halt'): + junos_module.fail_json(msg='The at option can only be used when ' + 'the action option has the value "reboot", ' + '"shutdown", or "halt".') + + # in_min option only applies to reboot, shutdown, or halt actions. + if (params.get('in_min') is not None and + action != 'reboot' and + action != 'shutdown' and + action != 'halt'): + junos_module.fail_json(msg='The in_min option can only be used when ' + 'the action option has the value "reboot", ' + '"shutdown", or "halt".') + + # other_re option only applies to reboot, shutdown, or halt actions. + if (params.get('other_re') is True and + action != 'reboot' and + action != 'shutdown' and + action != 'halt'): + junos_module.fail_json(msg='The other_re option can only be used when ' + 'the action option has the value "reboot", ' + '"shutdown", or "halt".') + + # media option only applies to zeroize action. + if params['media'] is True and action != 'zeroize': + junos_module.fail_json(msg='The media option can only be used when ' + 'the action option has the value ' + '"zeroize".') + + # If other_re, then we should turn off all_re + if params['other_re'] is True: + params['all_re'] = False + + # Set initial results values. Assume failure until we know it's success. + # Assume we haven't changed the state until we do. (Might be in check mode) + results = {'changed': False, + 'msg': '', + 'reboot': bool(action == 'reboot'), + 'action': action, + 'all_re': params.get('all_re'), + 'other_re': params.get('other_re'), + 'media': params.get('media'), + 'failed': True} + + # Map the action to an RPC. + rpc = None + xpath_list = [] + if action == 'reboot': + rpc = junos_module.etree.Element('request-reboot') + xpath_list.append('.//request-reboot-status') + elif action == 'shutdown': + rpc = junos_module.etree.Element('request-power-off') + xpath_list.append('.//request-reboot-status') + elif action == 'halt': + rpc = junos_module.etree.Element('request-halt') + xpath_list.append('.//request-reboot-status') + elif action == 'zeroize': + rpc = junos_module.etree.Element('request-system-zeroize') + else: + results['msg'] = 'No RPC found for the %s action.' % (action) + junos_module.fail_json(**results) + + # Add the arguments + if action == 'zeroize': + if params['all_re'] is False: + if junos_module.dev.facts['2RE']: + junos_module.etree.SubElement(rpc, 'local') + if params['media'] is True: + junos_module.etree.SubElement(rpc, 'media') + else: + if params['in_min'] is not None: + junos_module.etree.SubElement(rpc, 'in', + text=str(params['in_min'])) + elif params['at'] is not None: + junos_module.etree.SubElement(rpc, 'at', text=params['at']) + if params['other_re'] is True: + if junos_module.dev.facts['2RE']: + junos_module.etree.SubElement(rpc, 'other-routing-engine') + # At least on some platforms stopping/rebooting the other RE + # just produces messages. + xpath_list.append('..//output') + elif params['all_re'] is True: + junos_module.add_sw() + if (junos_module.sw._multi_RE is True and + junos_module.sw._multi_VC is False): + junos_module.etree.SubElement(rpc, 'both-routing-engines') + # At least on some platforms stopping/rebooting both REs + # produces messages and + # messages. + xpath_list.append('..//output') + elif junos_module.sw._mixed_VC is True: + junos_module.etree.SubElement(rpc, 'all-members') + + # OK, we're ready to do something. Set changed and log the RPC. + results['changed'] = True + junos_module.logger.debug("Ready to execute RPC: %s", + junos_module.etree.tostring(rpc, + pretty_print=True)) + + if not junos_module.check_mode: + if action != 'zeroize': + # If we're going to do a shutdown, reboot, or halt right away then + # try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this happens + # relatively quickly. + if (params['at'] == 'now' or params['in_min'] == 0 or + (params['at'] is None and params['in_min'] is None)): + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + + # Execute the RPC. + try: + junos_module.logger.debug( + "Executing RPC: %s", + junos_module.etree.tostring(rpc, pretty_print=True)) + resp = junos_module.dev.rpc(rpc, + ignore_warning=True, + normalize=True) + junos_module.logger.debug("RPC executed cleanly.") + if len(xpath_list) > 0: + for xpath in xpath_list: + got = resp.findtext(xpath) + if got is not None: + results['msg'] = '%s successfully initiated.' % \ + (action) + results['failed'] = False + break + else: + # This is the else clause of the for loop. + # It only gets executed if the loop finished without + # hitting the break. + results['msg'] = 'Did not find expected RPC response.' + results['changed'] = False + else: + results['msg'] = '%s successfully initiated.' % (action) + results['failed'] = False + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates there was a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['changed'] = False + results['msg'] = '%s failed. %s may not have been ' \ + 'initiated.' % (action, action) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] = '%s succeeded.' % (action) + results['failed'] = False + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + results['changed'] = False + results['msg'] = '%s failed. Error: %s' % (action, str(ex)) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/junos_shutdown b/library/junos_shutdown deleted file mode 100644 index febddc30..00000000 --- a/library/junos_shutdown +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2014, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_shutdown -author: Jeremy Schulman, Juniper Networks -version_added: "1.0.0" -short_description: Shut down or reboot a device running Junos OS. -description: - - Shut down (power off) or reboot a device running Junos OS. This includes - all Routing Engines in a Virtual Chassis or a dual Routing Engine system. - This is equivalent to executing either the Junos OS B(request system power-off) - or B(request system reboot) operational command. -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - reboot: - description: - - If set to B(yes), then the device is rebooted rather than powered off. - required: false - default: no - choices: ['yes','no'] - at: - description: - - Specify a time for the reboot (yyyymmddhhmm). Can be used only with reboot=yes. - required: false - default: None - in_min: - description: - - Specify a delay, in minutes, before the shutdown or reboot. - If both "at" and "in_min" are specified, "in_min" will be ignored. - required: false - default: 0 - shutdown: - description: - - Safety mechanism. You B(MUST) set this to 'shutdown'. - required: yes - default: None - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None -''' - -EXAMPLES = ''' -- junos_shutdown: - host={{ inventory_hostname }} - shutdown="shutdown" - reboot=yes - -# over console server connection using PyEZ >= 2.0 -- junos_shutdown: - host={{ inventory_hostname }} - shutdown="shutdown" - reboot=yes - mode='telnet' - port=7016 -''' -from distutils.version import LooseVersion - -def main(): - - module = AnsibleModule( - argument_spec=dict( - shutdown=dict(required=True, default=None), # must be set to 'shutdown' - host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - reboot=dict(required=False, type='bool', choices=BOOLEANS, default=False), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - in_min=dict(required=False, default=0), - at=dict(required=False, type='str', default=None) - ), - supports_check_mode=False) - - args = module.params - - try: - from jnpr.junos import Device - from jnpr.junos.utils.sw import SW - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') - - if args['shutdown'] != 'shutdown': - module.fail_json(msg='Say "shutdown" to proceed') - - restart_mode = module.boolean(args['reboot']) - delay = args['in_min'] - - # because PyEZ does not support 'at' option on shutdown, only with reboot - if args['at'] and not restart_mode: - module.fail_json(msg="Argument *at* must be used with argument *reboot='yes'*") - # --- UNREACHABLE --- - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - sw = SW(dev) - do_it = sw.reboot if restart_mode is True else sw.poweroff - - try: - if args['at']: - msg = do_it(at=args['at']) - dev.close() - else: - msg = do_it(in_min=delay) - # no need to close session with immediate shutdown/reboot because - # the device will be shutting and we will lose connectivity anyway - if delay > 0: - dev.close() - except Exception as err: - msg = 'error during device reboot or shutdown {0}: {1}'.format(args['host'], str(err)) - module.fail_json(msg=msg) - - results = {'changed': True, 'reboot': restart_mode, 'msg':msg} - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/library/junos_zeroize b/library/junos_zeroize deleted file mode 100644 index ce7305b3..00000000 --- a/library/junos_zeroize +++ /dev/null @@ -1,232 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2014, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_zeroize -author: Jeremy Schulman, Juniper Networks -version_added: "1.0.0" -short_description: Erase all data, including configuration and log files, on a device running Junos OS. -description: - - Execute the Junos OS B(request system zeroize) command to remove all - configuration information on the Routing Engines and reset all key - values on a device running Junos OS. The command removes all data files, - including customized configuration and log files, by unlinking the files - from their directories. The command also removes all user-created files - from the system including all plain-text passwords, secrets, and - private keys for SSH, local encryption, local authentication, IPsec, - RADIUS, TACACS+, and SNMP. - - This command reboots the device and sets it to the factory default - configuration. After the reboot, you must log in through the console - as root in order to access the device. -requirements: - - junos-eznc >= 1.2.2 - - junos-netconify >= 1.0.1, when using the I(console) option -options: - host: - description: - - Set to {{ inventory_hostname }} - required: false - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - console: - description: - - SERIAL or TERMINAL-SERVER port setting, per use with - the B(netconify) utility - required: false - default: None - notes: - - ex: "--telnet=termserv101,2000" - zeroize: - description: - - Safety mechanism. You B(MUST) set this to 'zeroize'. - required: yes - default: None - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None -notes: - - You B(MUST) either use the I(host) option or the I(console) option to designate - how the device is accessed. -''' - -EXAMPLES = ''' -- junos_zeroize: - host={{ inventory_hostname }} - zeroize="zeroize" - -# over console server connection using PyEZ >= 2.0 -- junos_zeroize: - host={{ inventory_hostname }} - zeroize="zeroize" - port=7011 - mode="telnet" -''' - -import os -import logging -from distutils.version import LooseVersion - - -def main(): - module = AnsibleModule( - argument_spec=dict( - zeroize=dict(required=True, default=None), # must be set to 'zeroize' - host=dict(required=False, default=None), # host or ipaddr - console=dict(required=False, default=None), # param to netconify - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - logfile=dict(required=False, default=None), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None) - ), - supports_check_mode=False) - - args = module.params - - if args['zeroize'] != 'zeroize': - results = dict(changed=False, failed=True) - results['msg'] = "You must set 'zeroize=zeroize' ({0})".format(args['zeroize']) - module.exit_json(**results) - # - # ! UNREACHABLE - - results = {} - - logfile = module.params['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'NETCONIFY:' + module.params['host'] - - def log_notify(self, event, message): - logging.info("%s:%s" % (event, message)) - use_notifier = log_notify - else: - def silent_notify(self, event, message): - pass - use_notifier = silent_notify - - if args.get('console') is None: - # via NETCONF - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False) - try: - use_notifier(None, 'LOGIN', 'host={0}'.format(args['host'])) - dev.open() - use_notifier(None, 'LOGIN', 'OK') - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - use_notifier(None, 'ZEROIZE', 'invoking command') - dev.cli('request system zeroize') - results['changed'] = True - # no close, we're done after this point. - else: - try: - from netconify.cmdo import netconifyCmdo - from netconify.constants import version - if not LooseVersion(version) >= LooseVersion('1.0.1'): - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - except ImportError: - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - nc_args = [] - nc_args.append(args['console']) - nc_args.append('--zeroize') - if args['user'] is not None: - nc_args.append('--user=' + args['user']) - if args['passwd'] is not None: - nc_args.append('--passwd=' + args['passwd']) - - try: - nc = netconifyCmdo(notify=use_notifier) - nc.run(nc_args) - except Exception as err: - module.fail_json(msg=str(err)) - results['changed'] = True - - # indicate done in the logfile and return results - use_notifier(None, 'DONE', 'OK') - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index c60e87b8..cf938fa3 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -54,6 +54,18 @@ except ImportError: HAS_PYEZ_DEVICE = False +try: + import jnpr.junos.utils.sw + HAS_PYEZ_SW = True +except ImportError: + HAS_PYEZ_SW = False + +try: + import jnpr.junos.utils.config + HAS_PYEZ_CONFIG = True +except ImportError: + HAS_PYEZ_CONFIG = False + try: import jnpr.junos.exception as pyez_exception HAS_PYEZ_EXCEPTIONS = True @@ -484,6 +496,8 @@ def __init__(self, # Check PyEZ version self.check_pyez(min_pyez_version, check_device=True, + check_sw=True, + check_config=True, check_exception=True) self.pyez_exception = pyez_exception # Check LXML Etree @@ -681,6 +695,8 @@ def _check_library(self, def check_pyez(self, minimum=None, check_device=False, + check_sw=False, + check_config=False, check_exception=False): """Check PyEZ is available and version is >= minimum. @@ -704,6 +720,16 @@ def check_pyez(self, minimum=None, self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' 'the jnpr.junos.device.Device class could ' 'not be imported.') + if check_sw is True: + if HAS_PYEZ_SW is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' + 'the jnpr.junos.utils.sw class could ' + 'not be imported.') + if check_config is True: + if HAS_PYEZ_CONFIG is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' + 'the jnpr.junos.utils.config class could ' + 'not be imported.') if check_exception is True: if HAS_PYEZ_EXCEPTIONS is False: self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' @@ -771,7 +797,12 @@ def open(self): self.fail_json(msg='Unable to make a PyEZ connection: %s' % (str(ex))) - def close(self): + def add_sw(self): + """Add an instance of jnp.junos.utils.sw.SW() to self. + """ + self.sw = jnpr.junos.utils.sw.SW(self.dev) + + def close(self, raise_exceptions=False): """Close the self.dev PyEZ Device instance. """ if self.dev is not None: @@ -788,10 +819,14 @@ def close(self): # ConnectError or RpcError, so this should catch all # exceptions raised from PyEZ. except (pyez_exception.ConnectError, - pyez_exception.RpcError): - # Ignore exceptions from closing. We're about to exit anyway - # and they will just mask the real error that happened. - pass + pyez_exception.RpcError) as ex: + if raise_exceptions is True: + raise ex + else: + # Ignore exceptions from closing. We're about to exit + # anyway and they will just mask the real error that + # happened. + pass def get_configuration(self, database='committed', format='text', options={}, filter=None): From 06f0ac4a51380f13a38f12569e4ec849d43db988 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 24 Oct 2017 21:13:53 -0600 Subject: [PATCH 177/426] Rewrite of junos_cli to juniper_junos_command. (#285) * Rewrite of junos_cli to juniper_junos_command. --- action_plugins/_junos_cli.py | 1 + action_plugins/_junos_shutdown.py | 2 + action_plugins/_junos_zeroize.py | 2 + action_plugins/juniper_junos_command.py | 1 + action_plugins/juniper_junos_common_action.py | 2 + library/__init__.py | 0 library/_junos_cli.py | 1 + library/juniper_junos_command.py | 559 ++++++++++++++++++ library/juniper_junos_facts.py | 1 - library/juniper_junos_system.py | 14 +- library/junos_cli | 220 ------- module_utils/juniper_junos_common.py | 5 + 12 files changed, 580 insertions(+), 228 deletions(-) create mode 120000 action_plugins/_junos_cli.py create mode 120000 action_plugins/juniper_junos_command.py delete mode 100644 library/__init__.py create mode 120000 library/_junos_cli.py create mode 100644 library/juniper_junos_command.py delete mode 100644 library/junos_cli diff --git a/action_plugins/_junos_cli.py b/action_plugins/_junos_cli.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_cli.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_shutdown.py b/action_plugins/_junos_shutdown.py index 54678dd0..5bab33fe 100644 --- a/action_plugins/_junos_shutdown.py +++ b/action_plugins/_junos_shutdown.py @@ -31,6 +31,8 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from __future__ import absolute_import, division, print_function + # Standard library imports import os.path import sys diff --git a/action_plugins/_junos_zeroize.py b/action_plugins/_junos_zeroize.py index fb4a736b..d086c37b 100644 --- a/action_plugins/_junos_zeroize.py +++ b/action_plugins/_junos_zeroize.py @@ -31,6 +31,8 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from __future__ import absolute_import, division, print_function + # Standard library imports import os.path import sys diff --git a/action_plugins/juniper_junos_command.py b/action_plugins/juniper_junos_command.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_command.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py index a87fd74b..66248d74 100644 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -31,6 +31,8 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from __future__ import absolute_import, division, print_function + # Standard library imports import os.path import sys diff --git a/library/__init__.py b/library/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/library/_junos_cli.py b/library/_junos_cli.py new file mode 120000 index 00000000..cc8aaf8f --- /dev/null +++ b/library/_junos_cli.py @@ -0,0 +1 @@ +juniper_junos_command.py \ No newline at end of file diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py new file mode 100644 index 00000000..2b00ec76 --- /dev/null +++ b/library/juniper_junos_command.py @@ -0,0 +1,559 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_command +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Execute one or more CLI commands on a Junos device +description: + - Execute one or more CLI commands on a Junos device. + NOTE: This module does NOT use the Junos CLI to execute the CLI command. + Instead, it uses the RPC over a NETCONF channel. The + RPC takes a CLI command as it's input and is very similar to executing the + command on the CLI, but you can NOT include any pipe modifies + (i.e. '| match', '| count', etc.) with the CLI commands executed by this + module. +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common +options: + commands: + description: + - A list of one or more CLI commands to execute on the Junos device. + required: true + default: none + type: 'list' + aliases: + - cli + - command + - cmd + - cmds + formats: + description: + - The format of the reply for the CLI command(s) specified by the + I(commands) option. The specified format(s) must be supported by the + target Junos device. The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all command(s) specified by the I(commands) option. If a + list of formats are specified, there must be one value in the list for + each command specified by the I(commands) option. Specifying the value + C(xml) for the I(formats) option is similar to appending + '| display xml' to a CLI command, and specifying the value C(json) + for the I(formats) option is similar to appending '| display json' to + a CLI command. + required: false + default: 'text' + type: 'str or list of str' + choices: ['text', 'xml', 'json'] + aliases: + - format + - display + - output + dest: + description: + - The path to a file, on the Ansible control machine, where the output of + the cli command will be saved. + - The file must be writeable. If the file already exists, it is + overwritten. + - NOTE: When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + {{ inventory_hostname }} in the I(dest) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine, where + the output of the cli command will be saved. The output will be logged + to a file named {{ inventory_hostname }}_C(cmd).C(format) + in the I(dest_dir) directory. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: None + type: path + aliases: + - destination_dir + - destdir + return_output: + description: + - Indicates if the output of the command should be returned in the + module's response. You might want to set this option to C(false), + and set the I(dest_dir) option, if the command output is very large + and you only need to save the output rather than using it's content in + subsequent tasks/plays of your playbook. + required: false + default: true + type: bool +''' + +EXAMPLES = ''' +--- +# +# MODULE_EXAMPLES +# This playbook demonstrate the parameters supported by the +# juniper_junos_command module. These examples use the default connection, +# authtentication and logging parameters. See the examples labeled +# CONNECTION_EXAMPLES for details on connection parameters. See the examples +# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. +# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. +# +- name: Examples of juniper_junos_command + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Execute single "show version" command. + juniper_junos_command: + commands: "show version" + register: response + + - name: Print the command output + debug: + var: response.stdout + + - name: Execute three commands. + juniper_junos_command: + commands: + - "show version" + - "show system uptime" + - "show interface terse" + register: response + + - name: Print the command output of each. + debug: + var: item.stdout + with_items: "{{ response.results }}" + + - name: Two commands with XML output. + juniper_junos_command: + commands: + - "show route" + - "show lldp neighbors" + format: xml + + - name: show route with XML output - show version with JSON output + juniper_junos_command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + + - name: save outputs in dest_dir + juniper_junos_command: + commands: + - "show route" + - "show version" + dest_dir: "./output" + + - name: save output to dest + juniper_junos_command: + command: "show system uptime" + dest: "/tmp/{{ inventory_hostname }}.uptime.output" + + - name: save output to dest + juniper_junos_command: + command: + - "show route" + - "show lldp neighbors" + dest: "/tmp/{{ inventory_hostname }}.commands.output" + + - name: Multiple commands, save outputs, but don't return them + juniper_junos_command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + dest_dir: "/tmp/outputs/" + return_output: false + +# +# CONNECTION_EXAMPLES +# + +# +# AUTHENTICATION_EXAMPLES +# + +# +# LOGGING_EXAMPLES +# +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +command: + description: + - The CLI command which was executed. + returned: always + type: str +format: + description: + - The format of the command response. +stdout: + description: + - The command reply from the Junos device as a single multi-line string. + returned: when command executed successfully and I(return_output) is true. + type: str +stdout_lines: + description: + - The command reply from the Junos device as a list of single-line strings. + returned: when command executed successfully and I(return_output) is true. + type: list of str +parsed_output: + description: + - The command reply from the Junos device parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the jxmlease + library. For JSON the response is parsed using the Python json library. + - NOTE: When Ansible converts the jxmlease or native Python data structure + into JSON, it does not guarantee that the order of dictionary/object keys + are maintained. + returned: when command executed successfully, I(return_output) is true, + and the command format is xml or json. + type: dict +changed: + description: + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to false. + - NOTE: You could use this module to execute a command which + changes the operational state of the the device. For example, + 'clear ospf neighbors'. Beware, this module is unable to detect + this situation, and will still return a I(changed) value + C(False) in this case. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. See I(results) option below for additional + details. + returned: always + type: bool +results: + description: + - The above keys are returned when a single command is specified for the + I(commands) option. When the value of the I(commands) option is a list + of commands, this key is returned instead. The value of this key is a + list of dictionaries. Each element in the list corresponds to the + commands in the I(commands) option. The keys for each element in the list + include all of the keys listed above. The I(failed) key indicates if the + individual command failed. In this case, there is also a top-level + I(failed) key. The top-level I(failed) key will have a value of C(false) + if ANY of the commands ran successfully. In this case, check the value + of the I(failed) key for each element in the I(results) list for the + results of individual commands. + returned: when the I(commands) option is a list value. + type: list of dict +''' + +import os.path + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def save_output(junos_module, command, format, text): + file_path = None + mode = 'w' + if junos_module.params.get('dest') is not None: + file_path = os.path.normpath(junos_module.params.get('dest')) + if getattr(junos_module, 'destfile', None) is None: + junos_module.destfile = junos_module.params.get('dest') + else: + mode = 'a' + elif junos_module.params.get('dest_dir') is not None: + dest_dir = junos_module.params.get('dest_dir') + hostname = junos_module.params.get('host') + # Substitute underscore for spaces. + command_name = command.replace(' ', '_') + file_name = '%s_%s.%s' % (hostname, command_name, format) + file_path = os.path.normpath(os.path.join(dest_dir, file_name)) + if file_path is not None: + try: + with open(file_path, mode) as save_file: + save_file.write(text) + junos_module.logger.debug("Output saved to: %s.", file_path) + except IOError: + junos_module.fail_json(msg="Unable to save output. Failed to " + "open the %s file." % (file_path)) + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + commands=dict(required=True, + type='list', + aliases=['cli', 'command', 'cmd', 'cmds'], + default=None), + formats=dict(required=False, + type='list', + aliases=['format', 'display', 'output'], + default=None), + dest=dict(required=False, + type='path', + aliases=['destination'], + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir'], + default=None), + return_output=dict(required=False, + type='bool', + default=True) + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. Well, that's not completely true. It does depend on the + # command executed. See the I(changed) key in the RETURN documentation + # for more details. + supports_check_mode=True + ) + + # Check over commands + commands = junos_module.params.get('commands') + # Ansible allows users to specify a commands argument with no value. + if commands is None: + junos_module.fail_json(msg="The commands option must have a value.") + # Make sure the commands don't include any pipe modifiers. + for command in commands: + pipe_index = command.find('|') + if (pipe_index != -1 and + command[pipe_index:].strip() != 'display xml rpc'): + for valid_format in juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES: + if 'display ' + valid_format in command[pipe_index:]: + junos_module.fail_json( + msg='The pipe modifier (%s) in the command ' + '(%s) is not supported. Use format: "%s" ' + 'instead.' % + (command[pipe_index:], command, valid_format)) + junos_module.fail_json(msg='The pipe modifier (%s) in the command ' + '(%s) is not supported.' % + (command[pipe_index:], command)) + + # Check over formats + formats = junos_module.params.get('formats') + if formats is None: + # Default to text format + formats = ['text'] + valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES + # Check format values + for format in formats: + # Is it a valid format? + if format not in valid_formats: + junos_module.fail_json(msg="The value %s in formats is invalid. " + "Must be one of: %s" % + (format, ', '.join(map(str, + valid_formats)))) + # Correct number of format values? + if len(formats) != 1 and len(formats) != len(commands): + junos_module.fail_json(msg="The formats option must have a single " + "value, or one value per command. There " + "are %d commands and %d formats." % + (len(commands), len(formats))) + # Same format for all commands + elif len(formats) == 1 and len(commands) > 1: + formats = formats * len(commands) + + results = list() + for (command, format) in zip(commands, formats): + # Set initial result values. Assume failure until we know it's success. + result = {'msg': '', + 'command': command, + 'format': format, + 'changed': False, + 'failed': True} + + # Execute the CLI command + try: + junos_module.logger.debug('Executing command "%s".', + command) + rpc = junos_module.etree.Element('command', format=format) + rpc.text = command + resp = junos_module.dev.rpc(rpc, normalize=bool(format == 'xml')) + result['msg'] = 'The command executed successfully.' + junos_module.logger.debug('Command "%s" executed successfully.', + command) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Unable to execute "%s". Error: %s', + command, str(ex)) + result['msg'] = 'Unable to execute the command: %s. Error: %s' % \ + (command, str(ex)) + results.append(result) + continue + + text_output = None + parsed_output = None + if resp is True: + text_output = '' + elif (resp, junos_module.etree._Element): + # Handle the output based on format + if format == 'text': + text_output = resp.text + junos_module.logger.debug('Text output set.') + elif format == 'xml': + text_output = junos_module.etree.tostring(resp, + pretty_print=True) + parsed_output = junos_module.jxmlease.parse_etree(resp) + junos_module.logger.debug('XML output set.') + elif format == 'json': + text_output = str(resp) + parsed_output = resp + junos_module.logger.debug('JSON output set.') + else: + result['msg'] = 'Unexpected format %s.' % (format) + results.append(result) + junos_module.logger.debug('Unexpected format %s.', format) + continue + else: + result['msg'] = 'Unexpected response type %s.' % (type(resp)) + results.append(result) + junos_module.logger.debug('Unexpected response type %s.', + type(resp)) + continue + + # Set the output keys + if junos_module.params['return_output'] is True: + if text_output is not None: + result['stdout'] = text_output + result['stdout_lines'] = text_output.splitlines() + if parsed_output is not None: + result['parsed_output'] = parsed_output + # Save the output + save_output(junos_module, command, format, text_output) + # This command succeeded. + result['failed'] = False + # Append to the list of results + results.append(result) + + # Return response. + if len(results) == 1: + junos_module.exit_json(**results[0]) + else: + # Calculate the overall failed. Only failed if all commands failed. + failed = True + for result in results: + if result.get('failed') is False: + failed = False + break + junos_module.exit_json(results=results, + changed=False, + failed=failed) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 297e7c88..5fe1d524 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -1,7 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. # Copyright (c) 1999-2017, Juniper Networks Inc. # 2014, Jeremy Schulman # diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index 55071c84..dbc4dad1 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -143,12 +143,12 @@ --- # # MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the juniper_junos_ping -# module. These examples use the default connection, authtentication and -# logging parameters. See the examples labeled CONNECTION_EXAMPLES for details -# on connection parameters. See the examples labeled AUTHENTICATION_EXAMPLES -# for details on authentication parameters. See the examples labeled -# LOGGING_EXAMPLES for details on logging parameters. +# This playbook demonstrate the parameters supported by the +# juniper_junos_system module. These examples use the default connection, +# authtentication and logging parameters. See the examples labeled +# CONNECTION_EXAMPLES for details on connection parameters. See the examples +# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. +# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. # - name: Examples of juniper_junos_system hosts: junos-all @@ -379,7 +379,7 @@ def main(): params['all_re'] = False # Set initial results values. Assume failure until we know it's success. - # Assume we haven't changed the state until we do. (Might be in check mode) + # Assume we haven't changed the state until we do. results = {'changed': False, 'msg': '', 'reboot': bool(action == 'reboot'), diff --git a/library/junos_cli b/library/junos_cli deleted file mode 100644 index 72ce84bd..00000000 --- a/library/junos_cli +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2015, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_cli -author: Damien Garros, Juniper Networks -version_added: "1.2.0" -short_description: Execute CLI on device and save the output locally -description: - - Execute CLI on device and save the output locally on a file -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - cli: - description: - - CLI command to execute on the host - required: true - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - timeout: - description: - - Set the NETCONF RPC timeout. Set this value to accommodate Cli - commands that might take longer than the default timeout interval. - Setting to 0 will use the PyEZ default (30 seconds). - required: false - default: "0" - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - dest: - description: - - Path to the local server directory where cli output will - be saved. - required: false - default: None - format: - description: - - text - Cli output saved in text format - - xml - Cli output saved as XML - required: false - choices: ['text','xml'] - default: 'text' -''' - -EXAMPLES = ''' -- junos_cli: - host: "{{ inventory_hostname }}" - cli: "show chassis hardware" - logfile: cli.log - dest: "{{ inventory_hostname }}.xml" - format: xml - -# Run cli over console server connection using PyEZ >= 2.0 -- junos_cli: - cli="show chassis hardware" - host={{ inventory_hostname }} - port=7001 - mode='telnet' - dest="{{ inventory_hostname }}.xml" - format='xml' -''' -from distutils.version import LooseVersion -import logging -from lxml import etree -from lxml.builder import E - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr - cli=dict(required=True, default=None), - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - timeout=dict(required=False, type='int', default=0), - logfile=dict(required=False, default=None), - dest=dict(required=False, default=None), - format=dict(required=False, choices=['text', 'xml'], default='text') - ), - supports_check_mode=True) - - args = module.params - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'CONFIG:' + args['host'] - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - ## Change default Timeout - if args['timeout'] > 0: - dev.timeout = args['timeout'] - - try: - options = {} - options['format'] = args['format'] - - logging.info("Getting cli output") - - cli_output = dev.cli(command=args['cli'], format=args['format'] ) - - if args['dest'] is not None: - with open(args['dest'], 'w') as outputfile: - if args['format'] == 'text': - outputfile.write(cli_output) - elif args['format'] == 'xml': - outputfile.write(etree.tostring(cli_output)) - - except (ValueError, RpcError) as err: - msg = 'Unable to get cli output: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - - module.exit_json() - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index cf938fa3..7a9bf618 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -31,6 +31,8 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from __future__ import absolute_import, division, print_function + # Ansible imports from ansible.module_utils.basic import AnsibleModule from ansible.plugins.action.normal import ActionModule as ActionNormal @@ -406,6 +408,9 @@ class ModuleDocFragment(object): default=None), } +# Known RPC output formats +RPC_OUTPUT_FORMAT_CHOICES = ['text', 'xml', 'json'] + # Known configuration formats CONFIG_FORMAT_CHOICES = ['xml', 'set', 'text', 'json'] # Known configuration databases From 18a6547739390e040cfeeafffa867e50eb6e399c Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Wed, 25 Oct 2017 06:47:38 -0600 Subject: [PATCH 178/426] Pep8 cleanup (#286) * PEP8 cleanup. --- library/juniper_junos_facts.py | 4 ++-- library/juniper_junos_pmtud.py | 32 ++++++++++++------------- library/juniper_junos_system.py | 42 ++++++++++++++++----------------- setup.py | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) mode change 100644 => 100755 setup.py diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 5fe1d524..66a7a921 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -108,9 +108,9 @@ RETURN = ''' ansible_facts.junos: - description: + description: - Facts collected from the Junos device. This dictionary contains the - keys listed in the I(contains) section of this documentation PLUS all + keys listed in the I(contains) section of this documentation PLUS all of the keys returned from PyEZ's fact gathering system. See U(http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) for a complete list of these keys and thier meaning. diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index 567d6be1..02c7fdee 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -49,7 +49,7 @@ description: - Determine the maximum IP MTU supported along a path from a Junos device to a user-specified destination by performing path MTU discovery (PMTUD) using - the ping command. The reported MTU will be between min_test_size and + the ping command. The reported MTU will be between min_test_size and I(max_size) where min_test_size = (I(max_size) - I(max_range) + 1). If the actual path MTU is greater than I(max_size), then I(max_size) will be reported. If the actual path MTU is less than min_test_size, then a @@ -73,16 +73,16 @@ - destination_host max_size: description: - - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU - discovery. The value returned for I(inet_mtu) will be no more - than this value even if the path actually supports a higher MTU. This - value must be between 68 and 65496. + - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU + discovery. The value returned for I(inet_mtu) will be no more + than this value even if the path actually supports a higher MTU. This + value must be between 68 and 65496. required: false default: 1500 type: int max_range: description: - - The maximum range of MTU values, in bytes, which will be searched + - The maximum range of MTU values, in bytes, which will be searched when performing path MTU discovery. This value must be 0 or a power of 2 (2^n) between 2 and 65536. The minimum IPv4 MTU value attempted when performing path MTU discovery is: @@ -151,7 +151,7 @@ - name: Print the discovered MTU. debug: var: response.inet_mtu - + - name: Perform PMTUD to 10.0.0.1. Search all possible MTU values. juniper_junos_pmtud: dest: "10.0.0.1" @@ -169,7 +169,7 @@ register: response - name: Print the discovered MTU. debug: - var: response.inet_mtu + var: response.inet_mtu - name: Perform PMTUD to 10.0.0.1. Source from 192.168.1.1. juniper_junos_pmtud: @@ -178,7 +178,7 @@ register: response - name: Print the discovered MTU. debug: - var: response.inet_mtu + var: response.inet_mtu - name: Perform PMTUD to 10.0.0.1. Source from the red routing-instance. juniper_junos_pmtud: @@ -187,8 +187,8 @@ register: response - name: Print the discovered MTU. debug: - var: response.inet_mtu - + var: response.inet_mtu + # # CONNECTION_EXAMPLES # @@ -427,8 +427,8 @@ def main(): ping_params['size'] = str(test_size - INET_AND_ICMP_HEADER_SIZE) current_results = dict(results) current_results = junos_module.ping(ping_params, - acceptable_percent_loss=100, - results=current_results) + acceptable_percent_loss=100, + results=current_results) loss = int(current_results.get('packet_loss', 100)) if loss < 100 and test_size == params['max_size']: # ping success with max test_size, save and break @@ -451,9 +451,9 @@ def main(): 'the minimum tested size(%d). Try ' 'decreasing max_size(%d) or increasing ' 'max_range(%d).' % (results['host'], - min_test_size, - params['max_size'], - params['max_range']), + min_test_size, + params['max_size'], + params['max_range']), **results) # Return results. diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index dbc4dad1..0bb49005 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -47,16 +47,16 @@ author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Initiate operational actions on the Junos system. description: - - Initiate an operational action (shutdown, reboot, halt or zeroize) on a + - Initiate an operational action (shutdown, reboot, halt or zeroize) on a Junos system. The particular action to execute is defined by the mandatory I(action) option. This module only INITIATES the action. It does NOT wait for the action to complete. - NOTE: Some Junos devices are effected by a Junos bug which causes this Ansible module to hang indefinitely when connected to the Junos device via - the console. This problem is not seen when connecting to the Junos device + the console. This problem is not seen when connecting to the Junos device using the normal NETCONF over SSH transport connection. Therefore, it is recommended to use this module only with a NETCONF over SSH transport - connection. However, this module does still permit connecting to Junos + connection. However, this module does still permit connecting to Junos devices via the console port and this functionality may still be used for Junos devices running Junos versions less than 15.1. # Document connection arguments @@ -66,14 +66,14 @@ action: description: - The action performed by the module. The choices include: - C(shutdown) - Power off the Junos devices. The values: C(off), + C(shutdown) - Power off the Junos devices. The values: C(off), C(power-off), and C(power_off) are aliases for this value. This is the equivalent of the 'request system power-off' CLI command. C(halt) - Stop the Junos OS running on the RE, but do not power off the - system. Once the system is halted, it will reboot if a - keystroke is entered on the console. This is the equivalent of - the 'request system halt' CLI command. + system. Once the system is halted, it will reboot if a + keystroke is entered on the console. This is the equivalent + of the 'request system halt' CLI command. C(reboot) - Reboot the system. This is the equivalent of the 'request system reboot' CLI command. C(zeroize) - Restore the system (configuration, log files, etc.) to a @@ -87,14 +87,14 @@ at: description: - The time at which to shutdown, halt, or reboot the system. The value - may be specified in one of the following ways: + may be specified in one of the following ways: C(now) - The action takes effect immediately. C(+minutes) — The action takes effect in C(minutes) minutes from now. - C(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute time, + C(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute time, specified as year, month, day, hour, and minute. - C(hh:mm) — The action takes effect at C(hh:mm) absolute time on the + C(hh:mm) — The action takes effect at C(hh:mm) absolute time on the current day, specified in 24-hour time. - The I(at) option can not be used when the I(action) option has a + The I(at) option can not be used when the I(action) option has a value of C(zeroize). The I(at) option is mutually exclusive with the I(in_min) option. required: false @@ -103,7 +103,7 @@ in_min: description: - Specify a delay, in minutes, before the shutdown, halt, or reboot. The - I(in_min) option can not be used when the I(action) option has a + I(in_min) option can not be used when the I(action) option has a value of C(zeroize). The I(in_min) option is mutually exclusive with the I(at) option. required: false @@ -114,7 +114,7 @@ - If the system has multiple Routing Engines and this option is true, then the action is performed on all REs in the system. If the system does not have multiple Routing Engines, then this option has no effect. - This option applies to all I(action) values. The I(all_re) option is + This option applies to all I(action) values. The I(all_re) option is mutually exclusive with the I(other_re) option. required: false default: true @@ -122,10 +122,10 @@ other_re: description: - If the system has dual Routing Engines and this option is true, - then the action is performed on the other REs in the system. If the - system does not have dual Routing Engines, then this option has no + then the action is performed on the other REs in the system. If the + system does not have dual Routing Engines, then this option has no effect. The I(other_re) option can not be used when the I(action) - option has a value of C(zeroize).The I(other_re) option is mutually + option has a value of C(zeroize).The I(other_re) option is mutually exclusive with the I(all_re) option. required: false default: false @@ -172,7 +172,7 @@ action: "reboot" all_re: False at: "20:00" - + - name: Halt the system on 25 January 2018 at 4pm. juniper_junos_system: action: "halt" @@ -182,21 +182,21 @@ juniper_junos_system: action: "reboot" in_min: 30 - + - name: Reboot the system in 30 minutes. juniper_junos_system: action: "reboot" at: "+30m" - + - name: Zeroize the local RE only. juniper_junos_system: action: "zeroize" - all_re: False + all_re: False - name: Zeroize all REs and overwrite medea. juniper_junos_system: action: "zeroize" - media: True + media: True # # CONNECTION_EXAMPLES diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 4c14f828..62585c2b --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from version import VERSION setup( - name="ansible-junos-stdlib", + name="ansible-junos-stdlib", version=VERSION, author="Jeremy Schulman, Nitin Kumar, Rick Sherman, Stacy Smith", author_email="jnpr-community-netdev@juniper.net", From fb8b0885ad80c64355fbfe97b1cf87bb3c2bcaf0 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 26 Oct 2017 20:40:30 -0600 Subject: [PATCH 179/426] Rewrite of junos_rpc to juniper_junos_rpc. (#287) * Rewrite of junos_rpc to juniper_junos_rpc. * PEP8 cleanup --- action_plugins/_junos_rpc.py | 1 + action_plugins/juniper_junos_rpc.py | 1 + library/_junos_rpc.py | 1 + library/juniper_junos_command.py | 31 +- library/juniper_junos_rpc.py | 667 +++++++++++++++++++++++++++ library/junos_rpc | 321 ------------- module_utils/juniper_junos_common.py | 170 +++++++ 7 files changed, 844 insertions(+), 348 deletions(-) create mode 120000 action_plugins/_junos_rpc.py create mode 120000 action_plugins/juniper_junos_rpc.py create mode 120000 library/_junos_rpc.py create mode 100644 library/juniper_junos_rpc.py delete mode 100755 library/junos_rpc diff --git a/action_plugins/_junos_rpc.py b/action_plugins/_junos_rpc.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_rpc.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_rpc.py b/action_plugins/juniper_junos_rpc.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_rpc.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_rpc.py b/library/_junos_rpc.py new file mode 120000 index 00000000..9e5b8b3f --- /dev/null +++ b/library/_junos_rpc.py @@ -0,0 +1 @@ +juniper_junos_rpc.py \ No newline at end of file diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index 2b00ec76..314a1adb 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -256,6 +256,9 @@ format: description: - The format of the command response. + returned: always + type: str + choices: ['text', 'xml', 'json'] stdout: description: - The command reply from the Junos device as a single multi-line string. @@ -362,32 +365,6 @@ def import_juniper_junos_common(): return juniper_junos_common -def save_output(junos_module, command, format, text): - file_path = None - mode = 'w' - if junos_module.params.get('dest') is not None: - file_path = os.path.normpath(junos_module.params.get('dest')) - if getattr(junos_module, 'destfile', None) is None: - junos_module.destfile = junos_module.params.get('dest') - else: - mode = 'a' - elif junos_module.params.get('dest_dir') is not None: - dest_dir = junos_module.params.get('dest_dir') - hostname = junos_module.params.get('host') - # Substitute underscore for spaces. - command_name = command.replace(' ', '_') - file_name = '%s_%s.%s' % (hostname, command_name, format) - file_path = os.path.normpath(os.path.join(dest_dir, file_name)) - if file_path is not None: - try: - with open(file_path, mode) as save_file: - save_file.write(text) - junos_module.logger.debug("Output saved to: %s.", file_path) - except IOError: - junos_module.fail_json(msg="Unable to save output. Failed to " - "open the %s file." % (file_path)) - - def main(): # Import juniper_junos_common juniper_junos_common = import_juniper_junos_common() @@ -534,7 +511,7 @@ def main(): if parsed_output is not None: result['parsed_output'] = parsed_output # Save the output - save_output(junos_module, command, format, text_output) + junos_module.save_text_output(command, format, text_output) # This command succeeded. result['failed'] = False # Append to the list of results diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py new file mode 100644 index 00000000..b7c5c404 --- /dev/null +++ b/library/juniper_junos_rpc.py @@ -0,0 +1,667 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2016, Nitin Kumar +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_command +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Execute one or more NETCONF RPCs on a Junos device +description: + - Execute one or more NETCONF RPCs on a Junos device. + NOTE: Use the '| display xml rpc' modifier to determine the equivalent RPC + name for a Junos CLI command. For example, + 'show version | display xml rpc' reveals the equivalent RPC name is + 'get-software-information'. +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common +options: + rpcs: + description: + - A list of one or more NETCONF RPCs to execute on the Junos device. + required: true + default: none + type: 'list' + aliases: + - rpc + formats: + description: + - The format of the reply for the RPC(s) specified by the + I(rpcs) option. The specified format(s) must be supported by the + target Junos device. The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all RPC(s) specified by the I(rpcs) option. If a + list of formats are specified, there must be one value in the list for + each RPC specified by the I(rpcs) option. + required: false + default: 'xml' + type: 'str or list of str' + choices: ['text', 'xml', 'json'] + aliases: + - format + - display + - output + kwargs: + description: + - The keyword arguments and values to the RPC(s) specified by the + I(rpcs) option. The value of this option can either be a single + dictionary of keywords and values, or a list of dictionaries + containing keywords and values. There is a one-to-one correspondence + between the elements in the I(kwargs) list and the RPCs in the I(rpcs) + list. In other words, the two lists must always contain the same + number of elements. For RPC arguments which do not require a value, + specify the value of True as shown in the EXAMPLES. + required: false + default: none + type: 'dict or list of dict' + aliases: + - kwarg + - args + - arg + attrs: + description: + - The attributes and values to the RPC(s) specified by the + I(rpcs) option. The value of this option can either be a single + dictionary of keywords and values, or a list of dictionaries + containing keywords and values. There is a one-to-one correspondence + between the elements in the I(kwargs) list and the RPCs in the I(rpcs) + list. In other words, the two lists must always contain the same + number of elements. + required: false + default: none + type: 'dict or list of dict' + aliases: + - attr + filter_xml: + description: + - This argument only applies if the I(rpcs) option contains a single + RPC with the value 'get-config'. When used, this value specifies an + XML filter used to restrict the portions of the configuration which are + retrieved. See + U(http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + for details on the value of this option. + required: false + default: none + type: 'str' + dest: + description: + - The path to a file, on the Ansible control machine, where the output of + the RPC will be saved. + - The file must be writeable. If the file already exists, it is + overwritten. + - NOTE: When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + {{ inventory_hostname }} in the I(dest) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine, where + the output of the RPC will be saved. The output will be logged + to a file named {{ inventory_hostname }}_C(rpc).C(format) + in the I(dest_dir) directory. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: None + type: path + aliases: + - destination_dir + - destdir + return_output: + description: + - Indicates if the output of the RPC should be returned in the + module's response. You might want to set this option to C(false), + and set the I(dest_dir) option, if the RPC output is very large + and you only need to save the output rather than using it's content in + subsequent tasks/plays of your playbook. + required: false + default: true + type: bool +''' + +EXAMPLES = ''' +--- +# +# MODULE_EXAMPLES +# This playbook demonstrate the parameters supported by the +# juniper_junos_rpc module. These examples use the default connection, +# authtentication and logging parameters. See the examples labeled +# CONNECTION_EXAMPLES for details on connection parameters. See the examples +# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. +# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. +# +- name: Examples of juniper_junos_rpc + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Execute single get-software-information RPC. + juniper_junos_rpc: + rpcs: "get-software-information" + register: response + - name: Print the RPC's output as a single multi-line string. + debug: + var: response.stdout + +###### OLD EXAMPLES ########## +- junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + dest=get_interface_information.conf + register=junos + +- junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + kwargs="interface_name=em0" + format=xml/text/json + dest=get_interface_information.conf + register=junos + +# Example to fetch device configuration +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + dest=get_config.conf + +# Fetch configuration over console server connection using PyEZ >= 2.0 +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + port=7005 + mode='telnet' + rpc=get-config + dest=get_config.conf + +# Example to fetch device configuration +- name: Get Device Configuration for interface + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + filter_xml="" + dest=get_config.conf + register: junos + +# Example to fetch configuration in json for >=14.2 +# and use it with rpc_reply +- name: Get Device Configuration + hosts: all + roles: + - Juniper.junos + connection: local + gather_facts: no + tasks: + - name: Get interface information + junos_rpc: + host: "{{ inventory_hostname }}" + rpc: get-interface-information + kwargs: + interface_name: em0 + media: True + format: json + dest: get_interface_information.conf + register: junos + + - name: Print configuration + debug: msg="{{ junos.rpc_reply }}" +###### OLD EXAMPLES ########## + +# +# CONNECTION_EXAMPLES +# + +# +# AUTHENTICATION_EXAMPLES +# + +# +# LOGGING_EXAMPLES +# +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +rpc: + description: + - The RPC which was executed from the list of RPCs in the I(rpcs) option. + returned: always + type: str +format: + description: + - The format of the RPC response from the list of formats in the I(formats) + option. + returned: always + type: str + choices: ['text', 'xml', 'json'] +kwargs: + description: + - The keyword arguments from the list of dictionaries in the I(kwargs) + option. This will be none if no kwargs are applied to the RPC. + returned: always + type: dict +attrs: + description: + - The RPC attributes and values from the list of dictionaries in the + I(attrs) option. This will be none if no attributes are applied to the RPC. + returned: always + type: dict +stdout: + description: + - The RPC reply from the Junos device as a single multi-line string. + returned: when RPC executed successfully and I(return_output) is true. + type: str +stdout_lines: + description: + - The RPC reply from the Junos device as a list of single-line strings. + returned: when RPC executed successfully and I(return_output) is true. + type: list of str +parsed_output: + description: + - The RPC reply from the Junos device parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the jxmlease + library. For JSON the response is parsed using the Python json library. + - NOTE: When Ansible converts the jxmlease or native Python data structure + into JSON, it does not guarantee that the order of dictionary/object keys + are maintained. + returned: when RPC executed successfully, I(return_output) is true, + and the RPC format is xml or json. + type: dict +changed: + description: + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to false. + - NOTE: You could use this module to execute a RPC which + changes the operational state of the the device. For example, + 'clear-ospf-neighbor-information'. Beware, this module is unable to + detect this situation, and will still return a I(changed) value + C(False) in this case. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. See I(results) option below for additional + details. + returned: always + type: bool +results: + description: + - The above keys are returned when a single RPC is specified for the + I(rpcs) option. When the value of the I(rpcs) option is a list + of RPCs, this key is returned instead. The value of this key is a + list of dictionaries. Each element in the list corresponds to the + RPCs in the I(rpcs) option. The keys for each element in the list + include all of the keys listed above. The I(failed) key indicates if the + individual RPC failed. In this case, there is also a top-level + I(failed) key. The top-level I(failed) key will have a value of C(false) + if ANY of the RPCs ran successfully. In this case, check the value + of the I(failed) key for each element in the I(results) list for the + results of individual RPCs. + returned: when the I(rpcs) option is a list value. + type: list of dict +''' + +import os.path + + +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + rpcs=dict(required=True, + type='list', + aliases=['rpc'], + default=None), + formats=dict(required=False, + type='list', + aliases=['format', 'display', 'output'], + default=None), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='str', + default=None), + attrs=dict(required=False, + type='str', + aliases=['attr'], + default=None), + filter_xml=dict(required=False, + type='str', + default=None), + dest=dict(required=False, + type='path', + aliases=['destination'], + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir'], + default=None), + return_output=dict(required=False, + type='bool', + default=True) + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. Well, that's not completely true. It does depend on the + # RPC executed. See the I(changed) key in the RETURN documentation + # for more details. + supports_check_mode=True + ) + + # Check over rpcs + rpcs = junos_module.params.get('rpcs') + # Ansible allows users to specify a rpcs argument with no value. + if rpcs is None: + junos_module.fail_json(msg="The rpcs option must have a value.") + + # Check over formats + formats = junos_module.params.get('formats') + if formats is None: + # Default to xml format + formats = ['xml'] + valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES + # Check format values + for format in formats: + # Is it a valid format? + if format not in valid_formats: + junos_module.fail_json(msg="The value %s in formats is invalid. " + "Must be one of: %s" % + (format, ', '.join(map(str, + valid_formats)))) + # Correct number of format values? + if len(formats) != 1 and len(formats) != len(rpcs): + junos_module.fail_json(msg="The formats option must have a single " + "value, or one value per rpc. There " + "are %d rpcs and %d formats." % + (len(rpcs), len(formats))) + # Same format for all rpcs + elif len(formats) == 1 and len(rpcs) > 1: + formats = formats * len(rpcs) + + # Check over kwargs + kwstring = junos_module.params.get('kwargs') + kwargs = junos_module.parse_arg_to_list_of_dicts('kwargs', + kwstring, + allow_bool_values=True) + if kwargs is not None: + if len(kwargs) != len(rpcs): + junos_module.fail_json(msg="The kwargs option must have one value " + "per rpc. There are %d rpcs and %d " + "kwargs." % + (len(rpcs), len(kwargs))) + else: + kwargs = [None] * len(rpcs) + + # Check over attrs + attrstring = junos_module.params.get('attrs') + attrs = junos_module.parse_arg_to_list_of_dicts('attrs', + attrstring) + if attrs is not None: + if len(attrs) != len(rpcs): + junos_module.fail_json(msg="The attrs option must have one value" + "per rpc. There are %d rpcs and %d " + "attrs." % + (len(rpcs), len(attrs))) + else: + attrs = [None] * len(rpcs) + + # Check filter_xml + if junos_module.params.get('filter_xml') is not None: + if (len(rpcs) != 1 or (rpcs[0] != 'get-config' and + rpcs[0] != 'get_config')): + junos_module.fail_json(msg="The filter_xml option is only valid " + "when the rpcs option value is a " + "single 'get-config' RPC.") + + results = list() + for (rpc_string, format, kwarg, attr) in zip(rpcs, formats, kwargs, attrs): + # Replace underscores with dashes in RPC name. + rpc_string = rpc_string.replace('_', '-') + # Set initial result values. Assume failure until we know it's success. + result = {'msg': '', + 'rpc': rpc_string, + 'format': format, + 'kwargs': kwarg, + 'attrs': attr, + 'changed': False, + 'failed': True} + + # Execute the RPC + try: + if rpc_string == 'get-config': + filter_xml = junos_module.params.get('filter_xml') + if attr is None: + attr = {} + if kwarg is None: + kwarg = {} + junos_module.logger.debug('Executing "get-config" RPC. ' + 'filter_xml=%s, options=%s, ' + 'kwargs=%s', + filter_xml, str(attr), str(kwarg)) + resp = junos_module.dev.rpc.get_config(filter_xml=filter_xml, + options=attr, **kwarg) + result['msg'] = 'The "get-config" RPC executed successfully.' + junos_module.logger.debug('The "get-config" RPC executed ' + 'successfully.') + else: + rpc = junos_module.etree.Element(rpc_string, format=format) + if kwarg is not None: + # Add kwarg + for (key, value) in kwarg.items(): + # Replace underscores with dashes in key name. + key = key.replace('_', '-') + sub_element = junos_module.etree.SubElement(rpc, key) + if not isinstance(value, bool): + sub_element.text = value + if attr is not None: + # Add attr + for (key, value) in attr.items(): + # Replace underscores with dashes in key name. + key = key.replace('_', '-') + rpc.set(key, value) + junos_module.logger.debug('Executing RPC "%s".', + junos_module.etree.tostring( + rpc, + pretty_print=True)) + resp = junos_module.dev.rpc(rpc, + normalize=bool(format == 'xml')) + result['msg'] = 'The RPC executed successfully.' + junos_module.logger.debug('RPC "%s" executed successfully.', + junos_module.etree.tostring( + rpc, + pretty_print=True)) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Unable to execute RPC "%s". Error: %s', + junos_module.etree.tostring( + rpc, + pretty_print=True), str(ex)) + result['msg'] = 'Unable to execute the RPC: %s. Error: %s' % \ + (junos_module.etree.tostring(rpc, + pretty_print=True), + str(ex)) + results.append(result) + continue + + text_output = None + parsed_output = None + if resp is True: + text_output = '' + elif (resp, junos_module.etree._Element): + # Handle the output based on format + if format == 'text': + text_output = resp.text + junos_module.logger.debug('Text output set.') + elif format == 'xml': + text_output = junos_module.etree.tostring(resp, + pretty_print=True) + parsed_output = junos_module.jxmlease.parse_etree(resp) + junos_module.logger.debug('XML output set.') + elif format == 'json': + text_output = str(resp) + parsed_output = resp + junos_module.logger.debug('JSON output set.') + else: + result['msg'] = 'Unexpected format %s.' % (format) + results.append(result) + junos_module.logger.debug('Unexpected format %s.', format) + continue + else: + result['msg'] = 'Unexpected response type %s.' % (type(resp)) + results.append(result) + junos_module.logger.debug('Unexpected response type %s.', + type(resp)) + continue + + # Set the output keys + if junos_module.params['return_output'] is True: + if text_output is not None: + result['stdout'] = text_output + result['stdout_lines'] = text_output.splitlines() + if parsed_output is not None: + result['parsed_output'] = parsed_output + # Save the output + junos_module.save_text_output(rpc_string, format, text_output) + # This command succeeded. + result['failed'] = False + # Append to the list of results + results.append(result) + + # Return response. + if len(results) == 1: + junos_module.exit_json(**results[0]) + else: + # Calculate the overall failed. Only failed if all commands failed. + failed = True + for result in results: + if result.get('failed') is False: + failed = False + break + junos_module.exit_json(results=results, + changed=False, + failed=failed) + + +if __name__ == '__main__': + main() diff --git a/library/junos_rpc b/library/junos_rpc deleted file mode 100755 index 772b6352..00000000 --- a/library/junos_rpc +++ /dev/null @@ -1,321 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2016, Juniper Networks Inc. -# 2016, Nitin Kumar -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_rpc -author: Nitin Kumar, Juniper Networks -version_added: "1.9" -short_description: run given rpc -description: - - run given rpc -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes. This option is used only with the - I(console) option. - required: false - default: None - rpc: - description: - - rpc to be executed - kwargs: - description: - - params need to be passed to rpc - kwargs="interface_name=em0" or kwargs="interface_name=em0,media=True" - kwargs={interface_name:em0} - kwargs={interface_name:em0,media:True} - kwargs={interface_name:em0,media:True} - filter_xml: - description: - - This options can be used with get-config rpc only, - to retrieve specific config - format: - description: - - text - configuration saved as text (curly-brace) format. - xml - configuration saved as XML. - json - configuration saved as json, supported only for devices >=14.2 - Also with this format, rpc_reply attribute can be used with results - required: false - choices: ['text','xml', 'json'] - default: 'xml' - timeout: - description: - - Set the NETCONF RPC timeout. Set this value to accommodate Cli - commands that might take longer than the default timeout interval. - Setting to 0 will use the PyEZ default (30 seconds). - required: false - default: "0" - dest: - description: - - Path to the local server directory where configuration will - be saved. - required: false - default: None -''' - -EXAMPLES = ''' -# retrieve rpc response using NETCONF - -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - dest=get_interface_information.conf - register=junos - -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - kwargs="interface_name=em0" - format=xml/text/json - dest=get_interface_information.conf - register=junos - -# Example to fetch device configuration -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - dest=get_config.conf - -# Fetch configuration over console server connection using PyEZ >= 2.0 -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - port=7005 - mode='telnet' - rpc=get-config - dest=get_config.conf - -# Example to fetch device configuration -- name: Get Device Configuration for interface - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - filter_xml="" - dest=get_config.conf - register: junos - -# Example to fetch configuration in json for >=14.2 -# and use it with rpc_reply -- name: Get Device Configuration - hosts: all - roles: - - Juniper.junos - connection: local - gather_facts: no - tasks: - - name: Get interface information - junos_rpc: - host: "{{ inventory_hostname }}" - rpc: get-interface-information - kwargs: - interface_name: em0 - media: True - format: json - dest: get_interface_information.conf - register: junos - - - name: Print configuration - debug: msg="{{ junos.rpc_reply }}" -''' - -import os -import sys -import logging -import re -from distutils.version import LooseVersion -from lxml import etree - -def junos_rpc(module, dev): - args = module.params - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'JUNOS_RPC:' + args['host'] - - results = {} - kwargs = module.params['kwargs'] - rpc = module.params['rpc'] - results['rpc'] = rpc - results['changed'] = False - results['check_mode'] = module.check_mode - logging.info("calling RPC: {0}".format(rpc)) - try: - if kwargs is None: - values = {} - elif isinstance(kwargs, dict): - values = kwargs - elif re.search(r'\{.*\}', kwargs): - try: - # When data is provided like - # kwargs: - # interface_name: em0 - # media: True - values = eval(kwargs) - except: - # In case when data is provided as below - # kwargs={interface-name:em0,media:True} - values = {k:v for k,v in re.findall(r'([\w-]+)\s?:\s?\'?\"?([\w\.-]+)\'?\"?',kwargs)} - else: - # kwargs="interface_name=em0,media=True" - values = {k:v for k,v in re.findall('([\w-]+)=([\w\.\-\/]+)', kwargs)} - for k,v in values.items(): - if v in ['True', 'true']: - values[k]=True - elif v in ['False', 'false']: - values[k]=False - results['kwargs'] = values - if rpc in ['get-config', 'get_config']: - filter_xml = module.params['filter_xml'] - if filter_xml is not None: - filter_xml = etree.XML(filter_xml) - values['format'] = args['format'] - rpc_reply = getattr(dev.rpc, rpc.replace('-','_'))(filter_xml, options=values) - else: - rpc_reply = getattr(dev.rpc, rpc.replace('-','_'))({'format':args['format']}, **values) - if module.params['dest'] is not None: - with open(module.params['dest'], 'w') as confile: - if isinstance(rpc_reply, etree._Element): - if args.get('format') == 'text': - confile.write(rpc_reply.text) - else: - confile.write(etree.tostring(rpc_reply)) - else: - if args.get('format') == 'json': - results['rpc_reply'] = rpc_reply - confile.write(str(rpc_reply)) - else: - confile.write(rpc_reply) - - results['changed'] = True - except Exception as err: - results['failed'] = True - logging.error("unable to get rpc due to:{0}".format(err.message)) - raise err - logging.info("rpc reponse recvd") - return results - -def main(): - module = AnsibleModule( - argument_spec=dict( - host=dict(required=True), - logfile=dict(required=False, default=None), - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - timeout=dict(required=False, type='int', default=0), - rpc=dict(required=True, default=None), - kwargs=dict(required=False, default=None), - filter_xml=dict(required=False, default=None), - format=dict(required=False, default='xml'), - dest=dict(required=False, default=None)), - supports_check_mode=False) - - m_args = module.params - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if m_args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') - # ----------- - # via NETCONF - # ----------- - dev = Device(m_args['host'], user=m_args['user'], passwd=m_args['passwd'], - port=m_args['port'], ssh_private_key_file=m_args['ssh_private_key_file'], - mode=m_args['mode'], gather_facts=False) - try: - dev.open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(m_args['host'], str(err)) - module.fail_json(msg=msg) - return - - ## Change default Timeout - if m_args['timeout'] > 0: - dev.timeout = m_args['timeout'] - - results = junos_rpc(module, dev) - - dev.close() - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 7a9bf618..154298e8 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -35,6 +35,7 @@ # Ansible imports from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE from ansible.plugins.action.normal import ActionModule as ActionNormal # Standard library imports @@ -87,6 +88,14 @@ HAS_JXMLEASE_VERSION = None +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + + # Constants # Minimum PyEZ version required by shared code. MIN_PYEZ_VERSION = "2.1.7" @@ -769,6 +778,115 @@ def check_lxml_etree(self, minimum=None): self._check_library('lxml Etree', HAS_LXML_ETREE_VERSION, LXML_ETREE_INSTALLATION_URL, minimum=minimum) + def convert_to_bool(self, arg): + """Try converting arg to a bool value using Ansible's aliases for bool. + + Args: + arg: The value to convert. + + Returns: + A boolean value if successfully converted, or None if not. + """ + if arg is None or type(arg) == bool: + return arg + if isinstance(arg, basestring): + arg = arg.lower() + if arg in BOOLEANS_TRUE: + return True + elif arg in BOOLEANS_FALSE: + return False + else: + return None + + def parse_arg_to_list_of_dicts(self, + option_name, + string_val, + allow_bool_values=False): + """Parses string_val into a list of dicts with bool and/or str values. + + In order to handle all of the different ways that list of dict type + options may be specified, the arg_spec must set the option type to + 'str'. This requires us to parse the string_val ourselves into the + required list of dicts. Handles Ansible-style keyword=value format for + specifying dictionaries. Also handles Ansible aliases for boolean + values if allow_bool_values is True. + + Args: + option_name - The name of the option being parsed. + string_val - The string to be parse. + allow_bool_values - Whether or not boolean values are allowed. + + Returns: + The list of dicts + + Fails: + If there is an error parsing + """ + # Nothing to do if no string_val were specified. + if string_val is None: + return None + + # Evaluate the string + kwargs = self.safe_eval(string_val) + + if isinstance(kwargs, basestring): + # This might be a keyword1=value1 keyword2=value2 type string. + # The _check_type_dict method will parse this into a dict for us. + try: + kwargs = self._check_type_dict(kwargs) + except TypeError as exc: + self.fail_json(msg="The value of the %s option (%s) is " + "invalid. Unable to translate into " + "a list of dicts." % + (option_name, string_val, str(exc))) + + # Now, if it's a dict, let's make it a list of one dict + if isinstance(kwargs, dict): + kwargs = [kwargs] + # Now, if it's not a list, we've got a problem. + if not isinstance(kwargs, list): + self.fail_json(msg="The value of the %s option (%s) is invalid. " + "Unable to translate into a list of dicts." % + (option_name, string_val)) + # We've got a list, traverse each element to make sure it's a dict. + return_val = [] + for kwarg in kwargs: + # If it's now a string, see if it can be parsed into a dictionary. + if isinstance(kwarg, basestring): + # This might be a keyword1=value1 keyword2=value2 type string. + # The _check_type_dict method will parse this into a dict. + try: + kwarg = self._check_type_dict(kwarg) + except TypeError as exc: + self.fail_json(msg="The value of the %s option (%s) is " + "invalid. Unable to translate into a " + "list of dicts." % + (option_name, string_val, str(exc))) + # Now if it's not a dict, there's a problem. + if not isinstance(kwarg, dict): + self.fail_json(msg="The value of the kwargs option (%s) is " + "invalid. Unable to translate into a list " + "of dicts." % + (option_name, string_val)) + # Now we just need to make sure the key is a string and the value + # is a string or bool. + return_item = {} + for (k, v) in kwarg.items(): + if not isinstance(k, basestring): + self.fail_json(msg="The value of the %s option (%s) " + "is invalid. Unable to translate into " + "a list of dicts." % + (option_name, string_val)) + if allow_bool_values is True: + # Try to convert it to a boolean value. Will be None if it + # can't be converted. + bool_val = self.convert_to_bool(v) + if bool_val is not None: + v = bool_val + return_item[k] = v + return_val.append(return_item) + return return_val + def open(self): """Open the self.dev PyEZ Device instance. @@ -1060,6 +1178,58 @@ def ping(self, params, acceptable_percent_loss=0, results={}): return results + def save_text_output(self, name, format, text): + """Save text output into a file based on 'dest' and 'dest_dir' params. + + The text provided in the text parameter is saved to a file on the + local Ansible control machine based on the 'dest' and 'dest_dir' + module parameters. If neither parameter is specified, then this method + is a no-op. If the 'dest' parameter is specified, the value of the + 'dest' parameter is used as the path name for the destination file. In + this case, the name and format parameters are ignored. + If the 'dest_dir' parameter is specified, the path name for the + destination file is: _.. If the + destination file already exists, and the 'dest_dir' option is + specified, or the 'dest' parameter is specified and the self.destfile + attribute is not present, the file is overwritten. If the 'dest' + parameter is specified and the self.destfile attribute is present, then + the file is appended. This allows multiple text outputs to be written + to the same file. + + Args: + name: The name portion of the destination filename when the + 'dest_dir' parameter is specified. + format: The format portion of the destination filename when the + 'dest_dir' parameter is specified. + text: The text to be written into the destination file. + + Fails: + - If the destination file is not writable. + """ + file_path = None + mode = 'w' + if self.params.get('dest') is not None: + file_path = os.path.normpath(self.params.get('dest')) + if getattr(self, 'destfile', None) is None: + self.destfile = self.params.get('dest') + else: + mode = 'a' + elif self.params.get('dest_dir') is not None: + dest_dir = self.params.get('dest_dir') + hostname = self.params.get('host') + # Substitute underscore for spaces. + name = name.replace(' ', '_') + file_name = '%s_%s.%s' % (hostname, name, format) + file_path = os.path.normpath(os.path.join(dest_dir, file_name)) + if file_path is not None: + try: + with open(file_path, mode) as save_file: + save_file.write(text) + self.logger.debug("Output saved to: %s.", file_path) + except IOError: + self.fail_json(msg="Unable to save output. Failed to " + "open the %s file." % (file_path)) + class JuniperJunosActionModule(ActionNormal): """A subclass of ActionNormal used by all juniper_junos_* modules. From 6c01f05bd267554ef67a1116d6384c902ba3bc83 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 31 Oct 2017 17:03:39 -0600 Subject: [PATCH 180/426] Implement juniper_junos_config module. (#290) * Change filter_xml option to filter to match juniper_junos_config. * Feature complete with juniper_junos_config. * Replace junos_install_config * Fixes to get junos_install_config tests to pass. * junos_rollback functionality now migrated to juniper_junos_config. * Handle situation where rollback not specified for junos_rollback. * Ensuring junos_commit functionality now works via juniper_junos_config. * Don't need to set 'commit_empty_changes'. * Cleanup of import in several action plugins. Fix issue with checks on commit_empty_changes option. * junos_get_config functionality now in juniper_junos_config. * PEP8 cleanup. * Example documentation and minor bug fix. --- action_plugins/_junos_commit.py | 72 ++ action_plugins/_junos_get_config.py | 72 ++ action_plugins/_junos_install_config.py | 84 ++ action_plugins/_junos_rollback.py | 73 ++ action_plugins/_junos_shutdown.py | 6 +- action_plugins/_junos_zeroize.py | 6 +- action_plugins/juniper_junos_common_action.py | 1 + action_plugins/juniper_junos_config.py | 1 + library/_junos_commit.py | 1 + library/_junos_get_config.py | 1 + library/_junos_install_config.py | 1 + library/_junos_rollback.py | 1 + library/juniper_junos_config.py | 1108 +++++++++++++++++ library/juniper_junos_rpc.py | 23 +- library/junos_commit | 241 ---- library/junos_get_config | 256 ---- library/junos_install_config | 521 -------- library/junos_rollback | 284 ----- module_utils/juniper_junos_common.py | 383 +++++- version.py | 4 +- 20 files changed, 1788 insertions(+), 1351 deletions(-) create mode 100755 action_plugins/_junos_commit.py create mode 100755 action_plugins/_junos_get_config.py create mode 100755 action_plugins/_junos_install_config.py create mode 100755 action_plugins/_junos_rollback.py mode change 100644 => 100755 action_plugins/_junos_shutdown.py mode change 100644 => 100755 action_plugins/_junos_zeroize.py mode change 100644 => 100755 action_plugins/juniper_junos_common_action.py create mode 120000 action_plugins/juniper_junos_config.py create mode 120000 library/_junos_commit.py create mode 120000 library/_junos_get_config.py create mode 120000 library/_junos_install_config.py create mode 120000 library/_junos_rollback.py create mode 100644 library/juniper_junos_config.py delete mode 100644 library/junos_commit delete mode 100755 library/junos_get_config delete mode 100644 library/junos_install_config delete mode 100755 library/junos_rollback mode change 100644 => 100755 version.py diff --git a/action_plugins/_junos_commit.py b/action_plugins/_junos_commit.py new file mode 100755 index 00000000..e07073be --- /dev/null +++ b/action_plugins/_junos_commit.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + + +# Use the custom behavior of JuniperJunosActionModule as the superclass of +# our ActionModule. +class ActionModule(juniper_junos_common.JuniperJunosActionModule): + """Translates junos_commit args to juniper_junos_config args. + + This class is a subclass of JuniperJunosActionModule. It exists solely + for backwards compatibility. It translates the arguments from the old + junos_commit module into the arguments on the new + juniper_junos_config module. + """ + def run(self, tmp=None, task_vars=None): + check = self._task.args.get('check') + if check is True: + # In the old module, check and commit were mutually exclusive. + # In the new module, you can potentially do both. + # If check is set on the old module, then we need to not commit. + self._task.args['commit'] = False + + # Remaining arguments can be passed through transparently. + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_get_config.py b/action_plugins/_junos_get_config.py new file mode 100755 index 00000000..dabf9aa2 --- /dev/null +++ b/action_plugins/_junos_get_config.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + + +# Use the custom behavior of JuniperJunosActionModule as the superclass of +# our ActionModule. +class ActionModule(juniper_junos_common.JuniperJunosActionModule): + """Translates junos_get_config args to juniper_junos_config args. + + This class is a subclass of JuniperJunosActionModule. It exists solely + for backwards compatibility. It translates the arguments from the old + junos_get_config module into the arguments on the new + juniper_junos_config module. + """ + def run(self, tmp=None, task_vars=None): + # No diff, check, or commit + self._task.args['diff'] = False + self._task.args['check'] = False + self._task.args['commit'] = False + # Retrieve candidate + self._task.args['retrieve'] = 'candidate' + + # Remaining arguments can be passed through transparently. + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_install_config.py b/action_plugins/_junos_install_config.py new file mode 100755 index 00000000..9fd2672f --- /dev/null +++ b/action_plugins/_junos_install_config.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + + +# Use the custom behavior of JuniperJunosActionModule as the superclass of +# our ActionModule. +class ActionModule(juniper_junos_common.JuniperJunosActionModule): + """Translates junos_install_config args to juniper_junos_config args. + + This class is a subclass of JuniperJunosActionModule. It exists solely + for backwards compatibility. It translates the arguments from the old + junos_install_config module into the arguments on the new + juniper_junos_config module. + """ + def run(self, tmp=None, task_vars=None): + # Pop the action arguments + update = self._task.args.pop('update', False) + overwrite = self._task.args.pop('overwrite', False) + replace = self._task.args.pop('replace', False) + action = '' + if update is True: + action += 'update' + if overwrite is True: + action += 'overwrite' + if replace is True: + action += 'replace' + if not action: + action = 'merge' + # Set the load argument based on action + self._task.args['load'] = action + + # Always commit changes to mimic the previous behavior + self._task.args['commit_empty_changes'] = True + + # Remaining arguments can be passed through transparently. + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_rollback.py b/action_plugins/_junos_rollback.py new file mode 100755 index 00000000..352120b0 --- /dev/null +++ b/action_plugins/_junos_rollback.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +# Standard library imports +import os.path +import sys + +# The module_utils path must be added to sys.path in order to import +# juniper_junos_common. The module_utils path is relative to the path of this +# file. +module_utils_path = os.path.normpath(os.path.dirname(__file__) + + '/../module_utils') +if module_utils_path is not None: + sys.path.insert(0, module_utils_path) + import juniper_junos_common + del sys.path[0] + + +# Use the custom behavior of JuniperJunosActionModule as the superclass of +# our ActionModule. +class ActionModule(juniper_junos_common.JuniperJunosActionModule): + """Translates junos_rollback args to juniper_junos_config args. + + This class is a subclass of JuniperJunosActionModule. It exists solely + for backwards compatibility. It translates the arguments from the old + junos_rollback module into the arguments on the new + juniper_junos_config module. + """ + def run(self, tmp=None, task_vars=None): + rollback = self._task.args.get('rollback') + if rollback is None: + # rollback is mandatory when called as junos_rollback. + # Mimic this behavior be setting rollback to 'value not specified'. + self._task.args['rollback'] = 'value not specified' + # Always commit changes to mimic the previous behavior + self._task.args['commit_empty_changes'] = True + + # Remaining arguments can be passed through transparently. + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_shutdown.py b/action_plugins/_junos_shutdown.py old mode 100644 new mode 100755 index 5bab33fe..de8d4267 --- a/action_plugins/_junos_shutdown.py +++ b/action_plugins/_junos_shutdown.py @@ -47,12 +47,10 @@ import juniper_junos_common del sys.path[0] + # Use the custom behavior of JuniperJunosActionModule as the superclass of # our ActionModule. -from juniper_junos_common import JuniperJunosActionModule - - -class ActionModule(JuniperJunosActionModule): +class ActionModule(juniper_junos_common.JuniperJunosActionModule): """Translates junos_shutdown args to juniper_junos_system args. This class is a subclass of JuniperJunosActionModule. It exists solely diff --git a/action_plugins/_junos_zeroize.py b/action_plugins/_junos_zeroize.py old mode 100644 new mode 100755 index d086c37b..49fde22b --- a/action_plugins/_junos_zeroize.py +++ b/action_plugins/_junos_zeroize.py @@ -47,12 +47,10 @@ import juniper_junos_common del sys.path[0] + # Use the custom behavior of JuniperJunosActionModule as the superclass of # our ActionModule. -from juniper_junos_common import JuniperJunosActionModule - - -class ActionModule(JuniperJunosActionModule): +class ActionModule(juniper_junos_common.JuniperJunosActionModule): """Translates junos_zeroize args to juniper_junos_system args. This class is a subclass of JuniperJunosActionModule. It exists solely diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py old mode 100644 new mode 100755 index 66248d74..a8611213 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -47,6 +47,7 @@ import juniper_junos_common del sys.path[0] + # Use the custom behavior of JuniperJunosActionModule as our ActionModule. # The Ansible core engine will call ActionModule.run() from juniper_junos_common import JuniperJunosActionModule as ActionModule diff --git a/action_plugins/juniper_junos_config.py b/action_plugins/juniper_junos_config.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_config.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_commit.py b/library/_junos_commit.py new file mode 120000 index 00000000..5383605d --- /dev/null +++ b/library/_junos_commit.py @@ -0,0 +1 @@ +juniper_junos_config.py \ No newline at end of file diff --git a/library/_junos_get_config.py b/library/_junos_get_config.py new file mode 120000 index 00000000..5383605d --- /dev/null +++ b/library/_junos_get_config.py @@ -0,0 +1 @@ +juniper_junos_config.py \ No newline at end of file diff --git a/library/_junos_install_config.py b/library/_junos_install_config.py new file mode 120000 index 00000000..5383605d --- /dev/null +++ b/library/_junos_install_config.py @@ -0,0 +1 @@ +juniper_junos_config.py \ No newline at end of file diff --git a/library/_junos_rollback.py b/library/_junos_rollback.py new file mode 120000 index 00000000..5383605d --- /dev/null +++ b/library/_junos_rollback.py @@ -0,0 +1 @@ +juniper_junos_config.py \ No newline at end of file diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py new file mode 100644 index 00000000..45fcabc9 --- /dev/null +++ b/library/juniper_junos_config.py @@ -0,0 +1,1108 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2014, Jeremy Schulman +# 2015, Rick Sherman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_config +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Manipulate the configuration of a Junos device. +description: + - Manipulate the configuration of a Junos device. This module allows a + combination of loading or rolling back, checking, diffing, retrieving, and + committing the configuration of a Junos device. It performs the following + steps in order: + 1) Open a candidate configuration database. + - If the I(config_mode) option has a value of C(exclusive), the default, + take a lock on the candidate configuration database. If the lock fails + the module fails and reports an error. + - If the I(config_mode) option has a value of C(private), open a private + candidate configuration database. If opening the private configuration + database fails the module fails and reports an error. + 2) Load configuration data into the candidate configuration database. + - Configuration data may be loaded using the I(load) or I(rollback) + options. If either of these options are specified, new configuration + data is loaded. If neither option is specified, this step is skipped. + - If the I(rollback) option is specified, replace the candidate + configuration with the previous configuration specified by the value + of the I(rollback) option. + - If the I(load) option is specified, load new configuration data. + - The value of the I(load) option defines the type of load which is + performed. + - The source of the new configuration data is one of the following: + - I(src) - A file path on the local Ansible control machine. + - I(lines) - A list of strings containing the configuration data. + - I(template) - A file path to a Jinja2 template on the local + Ansible control machine. This template is rendered + with the variables specified by the I(vars) option. + If the I(template) option is specified, the I(vars) + option must also be specified. + - I(url) - A URL reachable from the target Junos device. + - If the I(format) option is specified, the configuration file being + loaded is in the specified format, rather than the format being + determined from the file name. + 3) Check the validity of the candidate configuration database. + - If the I(check) option is C(true), the default, check the validity + of the configuration by performing a "commit check" operation. + - This option may be specified with I(diff) C(false) and I(commit) + C(false) to confirm a previous "commit confirmed " operation + without actually performing an additional commit. + - If the configuration check fails, further processing stops, the module + fails, and an error is reported. + 4) Determine differences between the candidate and committed configuration + databases. + - If step 2 was not skipped, and the I(diff) option is C(true), + the default, perform a diff between the candidate and committed + configuration databases. + - If the I(diffs_file) or I(dest_dir) option is specified, save the + generated configuration differences. + - If the I(return_output) option is C(true), the default, include the + generated configuration difference in the I(diff) and I(diff_lines) + keys of the module's response. + 5) Retrieve the configuration database from the Junos device. + - If the I(retrieve) option is specified, retrieve the configuration + database specified by the I(retrieve) value from the target Junos + device to the local Ansible control machine. + - The format in which the configuration is retrieved is specified by the + value of the I(format) option. + - The optional I(filter) controls which portions of the configuration + are retrieved. + - If I(options) are specified, they control the content of the + configuration retrieved. + - If the I(dest) or I(dest_dir) option is specified, save the + retrieved configuration to a file on the local Ansible control + machine. + - If the I(return_output) option is C(true), the default, include the + retrieved configuration in the I(config), I(config_lines), and + I(config_parsed) keys of the module's response. + 6) Commit the configuration changes. + - If the I(commit) option is C(true), the default, commit the + configuration changes. + - This option may be specified with I(diff) C(false) and I(check) + C(false) to confirm a previous "commit confirmed " operation. + - If the I(comment) option is specified, add the comment to the commit. + - If the I(confirmed) option is specified, perform a + "commit confirmed " operation where is the value of the + I(confirmed) option. + - If the I(check) option is C(true) and the I(check_commit_wait) + option is specified, wait I(check_commit_wait) seconds before + performing the commit. + 7) Close the candidate configuration database. + - Close and discard the candidate configuration database. + - If the I(config_mode) option has a value of C(exclusive), the default, + unlock the candidate configuration database. + +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common +options: + ignore_warning: + description: + - A boolean, string or list of strings. If the value is C(true), + ignore all warnings regardless of the warning message. If the value + is a string, it will ignore warning(s) if the message of each warning + matches the string. If the value is a list of strings, ignore + warning(s) if the message of each warning matches at least one of the + strings in the list. The C(ignore_warning) value is applied to the + load and commit operations performed by this module. + load and commit operations performed by this module. + required: false + default: none + type: bool, str, or list of str + config_mode: + description + - The mode used to access the candidate configuration database. + required: false + default: 'exclusive' + choices: ['exclusive', 'private'] + type: 'str' + aliases: + - config_access + - edit_mode + - edit_access + rollback: + description: + - Populate the candidate configuration from a previously committed + configuration. This value be a configuration number between 0 and 49, + or the keyword C(rescue) to load the previously saved rescue + configuration. + - By default, some Junos platforms store fewer than 50 previous + configurations. Specifying a value greater than the number + of previous configurations available, or specifying C(rescue) when no + rescue configuration has been saved, will result in an error when the + module attempts to perform the rollback. + - The I(rollback) and I(load) options are mutually exclusive. + required: false + default: none + choices: [0-49, 'rescue'] + type: 'int' or 'str' + load: + description: + - Specifies the type of load operation to be performed. + - The I(load) and I(rollback) options are mutually exclusive. + - The choices have the following meanings: + none - Do not perform a load operation. + merge - Combine the new configuration with the existing configuration. + If statements in the new configuration conflict with statements + in the existing configuration, the statements in the new + configuration replace those in the existing configuration. + replace - This option is a superset of the 'merge' option. It combines + the new configuration with the existing configuration. If the + new configuration is in text format and a hierarchy level in + the new configuration is prefixed with the string + 'replace:', then the hierarchy level in the new configuration + replaces the entire correpsonding hierarhcy level in the + existing configuration, regardless of the existence or + content of that hierarchy level in the existing + configuration. If the configuration is in XML format, the + XML attribute replace="replace" is equivalent to the text + format's 'replace:' prefix. If a configuration hierarhcy in + the new configuration is not prefixed with 'replace:', then + the 'merge' behavior is used. Specifically, for any + statements in the new configuration which conflict with + statements in the existing configuration, the statements in + the new configuration replace those in the existing + configuration. + override - Discard the entire existing configuration and replace it + with the new configuration. When the configuration is + later committed, all system processes are notified and the + entire new configuration is marked as 'changed' even if + some statements previously existing in the configuration. + The value 'overwrite' is a synonym for 'override'. + update - This option is similar to the 'override' option. The new + configuration completely replaces the existing configuration. + The difference comes when the configuration is later + committed. This option performs a 'diff' between the new + candidate configuration and the existing committed + configuration. It then only notifies system processes + responsible for the changed portions of the configuration, and + only marks the actual configuration changes as 'changed'. + set - This option is used when the new configuraiton data is in set + format (a series of configuration mode commands). The new + configuration data is loaded line by line and may contain any + configuration mode commands, such as set, delete, edit, + or deactivate. This value must be specified if the new + configuration is in set format. + required: false + default: none + choices: [none, 'set', 'merge', 'update', + 'replace', 'override', 'overwrite'] + type: str + src: + description: + - Used with the I(load) option. Specifies the path to a file, on the + local Ansible control machine, containing the configuration to be + loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is determined by the + file extension of this path name. If the file has a C(.conf) + extension, the content is treated as text format. If the file has a + C(.xml) extension, the content is treated as XML format. If the file + has a C(.set) extension, the content is treated as Junos B(set) + commands. + - If the I(format) option is specified, the I(format) value overrides the + file-extension based format detection. + required: false + default: none + type: 'path' + aliases: + - source + - file + lines: + description: + - Used with the I(load) option. Specifies a list of list of + configuration strings containing the configuration to be loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is auto-dectected by + the content of the first line in the I(lines) list. + - If the I(format) option is specified, the I(format) value overrides the + format auto-detection. + required: false + default: none + type: 'list' + template: + description: + - The path to a Jinja2 template file, on the local Ansible control + machine, used to generate, along with the I(vars) option, the + configuration to be loaded on the target Junos device. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. + required: false + default: none + type: 'path' + aliases: + - template_path + vars: + description: + - A dictionary of keys and values used to render the Jinja2 template + specified by the I(template) option. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. + required: false + default: none + type: 'dict' + aliases: + - template_vars + url: + description: + - A URL, as documented at the 'url' section of: + U(https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html) + - Specifies the configuration data to load on the target Junos device. + required: false + default: none + type: 'str' + format: + description: + - The format of the configuration retrieved, if I(retrieve) is not none, + and/or loaded, if I(load) is not none. The specified format must be + supported by the target Junos device. + required: false + default: none (auto-detect on load, 'text' on retrieve) + choices: ['xml', 'set', 'text', 'json'] + check: + - Perform a commit check operation. + required: false + default: true + type: bool + aliases: + - check_commit + - commit_check + diff: + - Perform a configuration compare (diff) operation. + required: false + default: true + type: bool + aliases: + - compare + - diffs + diffs_file: + description: + - The path to a file, on the Ansible control machine, where the + configuration differences will be saved if the I(diff) option is + specified. + - The file must be writeable. If the file already exists, it is + overwritten. + - This option is only valid if the I(diff) option is C(true). + - NOTE: When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + {{ inventory_hostname }} in the I(diffs_file) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(diffs_file) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + dest_dir: + description: + - The path to a directory, on the Ansible control machine. This is the + directory where the configuration will be saved if the I(retrieve) + option is specified. It is also the directory where the configuration + diff will be specified if the I(diff) option is C(true). + - This option is only valid if the I(retrieve) option is not none or the + I(diff) option is I(true). + - The retrieved configuration will be saved to a file named + {{ inventory_hostname }}.C(format_extension) in the I(dest_dir) + directory. Where C(format_extension) is C(conf) for text format, C(xml) + for XML format, C(json) for JSON format, and C(set) for set format. + - If the I(diff) option is true, the configuration diff will be save to + a file named {{ inventory_hostname }}.diff in the I(dest_dir) + directory. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + - The I(dest_dir) and I(diff_file) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: none + type: path + aliases: + - destination_dir + - destdir + - savedir + - save_dir + return_output: + description: + - Indicates if the output of the I(diff) and I(retreive) options should + be returned in the module's response. You might want to set this option + to C(false), and set the I(dest_dir) option, if the configuration or + diff output is very large and you only need to save the output rather + than using it's content in subsequent tasks/plays of your playbook. + required: false + default: true + type: bool + retrieve: + description: + - The configuration database to be retrieved. + required: false + default: none + choices: [none, 'candidate', 'committed'] + options: + description: + - Additional options, specified as a dictionary of key/value pairs, used + when retrieving the configuration. See + U(https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) + for information on available options. + required: false + default: None + type: dict + filter: + description: + - A string of XML, or '/'-separated configuration hierarchies, + which specifies a filter used to restrict the portions of the + configuration which are retrieved. See + U(http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + for details on the value of this option. + required: false + default: none + type: 'str' + aliases: + - filter_xml + dest: + description: + - The path to a file, on the local Ansible control machine, where the + configuration will be saved if the I(retrieve) option is specified. + - The file must be writeable. If the file already exists, it is + overwritten. + - This option is only valid if the I(retrieve) option is not none. + - NOTE: When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + {{ inventory_hostname }} in the I(dest) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - destination + commit: + description: + - Perform a commit operation. + required: false + default: true + type: bool + commit_empty_changes: + description: + - Perform a commit operation, even if there are no changes between the + candidate configuration and the committed configuration. + required: false + default: false + type: bool + confirmed: + description: + - Provide a confirmed timeout, in minutes, to be used with the commit + operation. This option is only valid if the I(commit) option is true. + The value of this option is the number of minutes to wait for another + commit operation before automatically rolling back the configuration + change performed by this task. In other words, this option causes the + module to perform a "commit confirmed " where is the value + of the I(confirmed) option. This option DOES NOT confirm a previous + "commit confirmed " operation. To confirm a previous commit + operation, invoke this module with the I(check) or I(commit) + option set to true. + required: false + default: none + type: int + aliases: + - confirm + comment: + description: + - Provide a comment to be used with the commit operation. This option is + only valid if the I(commit) option is true. + required: false + default: none + type: str + check_commit_wait: + description: + - The number of seconds to wait between check and commit operations. + - This option is only valid if I(check) is C(true) and I(commit) is + C(true). + - This option should not normally be needed. It works around an issue in + some versions of Junos. + required: false + default: none + type: int + +# Options to load from other sources +# Templates, url, variable, argument +# +''' + +EXAMPLES = ''' +--- +- name: Manipulate the configuration of Junos devices + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: Retrieve the committed configuration + juniper_junos_config: + retrieve: 'committed' + diff: false + check: false + commit: false + register: response + - name: Print the lines in the config. + debug: + var: response.config_lines + + - name: Append .foo to the hostname using private config mode. + juniper_junos_config: + config_mode: 'private' + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + register: response + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the previous config. + juniper_junos_config: + config_mode: 'private' + rollback: 1 + register: response + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the rescue config. + juniper_junos_config: + rollback: 'rescue' + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load override from a file. + juniper_junos_config: + load: 'override' + src: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a Jinja2 template. + juniper_junos_config: + load: 'merge' + format: 'xml' + template: "{{ inventory_hostname }}.j2" + vars: + host: "{{ inventory_hostname }}" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device. + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device, skip the commit check + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + check: false + register: response + - name: Print the msg. + debug: + var: response.msg + + - name: Print diff between current and rollback 10. No check. No commit. + juniper_junos_config: + rollback: 11 + diff: true + check: false + commit: false + register: response + - name: Print the msg. + debug: + var: response + + - name: Retrieve [edit system services] of current committed config. + juniper_junos_config: + retrieve: 'committed' + filter: 'system/services' + diff: true + check: false + commit: false + register: response + - name: Print the resulting config lines. + debug: + var: response.config_lines + + - name: Enable NETCONF SSH and traceoptions, save config, and diffs. + juniper_junos_config: + load: 'merge' + lines: + - 'set system services netconf ssh' + - 'set system services netconf traceoptions flag all' + - 'set system services netconf traceoptions file netconf.log' + format: 'set' + retrieve: 'candidate' + filter: 'system/services' + comment: 'Enable NETCONF with traceoptions' + dest_dir: './output' + register: response + - name: Print the complete response + debug: + var: response + + - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + confirm: 5 + check_commit_wait: 3 + register: response + - name: Print the complete response + debug: + var: response + - name: Confirm the previous commit with a commit check (but no commit) + juniper_junos_config: + check: true + diff: false + commit: false + register: response + - name: Print the complete response + debug: + var: response + +# Document connection examples +# Document authentication examples +# Document logging examples +# extends_documentation_fragment: juniper_junos_common +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +file: + description: + - The value of the I(src) option. + returned: when I(load) is not None and I(src) is not None + type: str +config: + description: The retrieved configuration. The value is a single multi-line + string in the format specified by the I(format) option. + returned: when I(retrieved) is not None and I(return_output) is true. + type: str +config_lines: The retrieved configuration. The value is a list of single-line + strings in the format specified by the I(format) option. + returned: when I(retrieved) is not None and I(return_output) is true. + type: list +config_parsed: The retrieved configuration parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the + jxmlease library. For JSON the response is parsed using the + Python json library. + NOTE: When Ansible converts the jxmlease or native Python data + structure into JSON, it does not guarantee that the order of + dictionary/object keys are maintained. + returned: when I(retrieved) is not None, the I(format) option is 'xml' or + 'json' and I(return_output) is true. + type: complex +diff: + description: The configuration differences between the previous and new + configurations. The value is a single multi-line string in + "diff" format. + returned: when I(load) or I(rollback) is specified, I(diff) is true, and + I(return_output) is true. + type: str +diff_lines: The configuration differences between the previous and new + configurations. The value is a list of single-line strings in + "diff" format. + returned: when I(load) or I(rollback) is specified, I(diff) is true, and + I(return_output) is true. + type: list +changed: + description: Indicates if the device's configuration has changed, or would + have changed when in check mode. + returned: success + type: bool +failed: + description: Indicates if the task failed. + returned: always + type: bool +''' + +# Standard library imports +import time + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Choices which are defined in the common module. + config_format_choices = juniper_junos_common.CONFIG_FORMAT_CHOICES + config_database_choices = [None] + \ + juniper_junos_common.CONFIG_DATABASE_CHOICES + config_action_choices = [None] + juniper_junos_common.CONFIG_ACTION_CHOICES + config_mode_choices = juniper_junos_common.CONFIG_MODE_CHOICES + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + ignore_warning=dict(required=False, + type='list', + default=None), + config_mode=dict(choices=config_mode_choices, + type='str', + required=False, + aliases=['config_access', 'edit_mode', + 'edit_access'], + default='exclusive'), + rollback=dict(type='str', + required=False, + default=None), + load=dict(choices=config_action_choices, + type='str', + required=False, + default=None), + src=dict(type='path', + required=False, + aliases=['source', 'file'], + default=None), + lines=dict(type='list', + required=False, + default=None), + template=dict(type='path', + required=False, + aliases=['template_path'], + default=None), + vars=dict(type='dict', + required=False, + aliases=['template_vars'], + default=None), + url=dict(type='str', + required=False, + default=None), + format=dict(choices=config_format_choices, + type='str', + required=False, + default=None), + check=dict(required=False, + type='bool', + aliases=['check_commit', 'commit_check'], + default=True), + diff=dict(required=False, + type='bool', + aliases=['compare', 'diffs'], + default=True), + diffs_file=dict(type='path', + required=False, + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir', 'savedir', + 'save_dir'], + default=None), + return_output=dict(required=False, + type='bool', + default=True), + retrieve=dict(choices=config_database_choices, + type='str', + required=False, + default=None), + options=dict(type='dict', + required=False, + default={}), + filter=dict(required=False, + type='str', + aliases=['filter_xml'], + default=None), + dest=dict(type='path', + required=False, + aliases=['destination'], + default=None), + commit=dict(required=False, + type='bool', + default=True), + commit_empty_changes=dict(required=False, + type='bool', + default=False), + confirmed=dict(required=False, + type='int', + aliases=['confirm'], + default=None), + comment=dict(required=False, + type='str', + default=None), + check_commit_wait=dict(required=False, + type='int', + default=None) + ), + # Mutually excluisive options. + mutually_exclusive=[['load', 'rollback'], + ['src', 'lines', 'template', 'url'], + ['diffs_file', 'dest_dir'], + ['dest', 'dest_dir']], + # Required together options. + required_together=[['template', 'vars']], + # Check mode is implemented. + supports_check_mode=True + ) + # Do additional argument verification. + + # Parse ignore_warning value + ignore_warning = junos_module.parse_ignore_warning_option() + + # Straight from params + config_mode = junos_module.params.get('config_mode') + + # Parse rollback value + rollback = junos_module.parse_rollback_option() + + # Straight from params + load = junos_module.params.get('load') + src = junos_module.params.get('src') + lines = junos_module.params.get('lines') + template = junos_module.params.get('template') + vars = junos_module.params.get('vars') + url = junos_module.params.get('url') + format = junos_module.params.get('format') + check = junos_module.params.get('check') + diff = junos_module.params.get('diff') + diffs_file = junos_module.params.get('diffs_file') + dest_dir = junos_module.params.get('dest_dir') + return_output = junos_module.params.get('return_output') + retrieve = junos_module.params.get('retrieve') + options = junos_module.params.get('options') + filter = junos_module.params.get('filter') + dest = junos_module.params.get('dest') + commit = junos_module.params.get('commit') + commit_empty_changes = junos_module.params.get('commit_empty_changes') + confirmed = junos_module.params.get('confirmed') + comment = junos_module.params.get('comment') + check_commit_wait = junos_module.params.get('check_commit_wait') + + # If load is not None, must have one of src, template, url, lines + if load is not None: + for option in ['src', 'lines', 'template', 'url']: + if junos_module.params.get(option) is not None: + break + # for/else only executed if we didn't break out of the loop. + else: + junos_module.fail_json(msg="The load option (%s) is specified, " + "but none of 'src', 'lines', " + "'template', or 'url' are specified. " + "Must specify one of the 'src', " + "'lines', 'template', or 'url' options." + % (load)) + + # format is valid if retrieve is not None or load is not None. + if format is not None: + if load is None and retrieve is None: + junos_module.fail_json(msg="The format option (%s) is specified, " + "but neither 'load' or 'retrieve' are " + "specified. Must specify one of " + "'load' or 'retrieve' options." + % (format)) + + # dest_dir is valid if retrieve is not None or diff is True. + if dest_dir is not None: + if retrieve is None and diff is False: + junos_module.fail_json(msg="The dest_dir option (%s) is specified," + " but neither 'retrieve' or 'diff' " + "are specified. Must specify one of " + "'retrieve' or 'diff' options." + % (dest_dir)) + + # dest is valid if retrieve is not None + if dest is not None: + if retrieve is None: + junos_module.fail_json(msg="The dest option (%s) is specified," + " but 'retrieve' is not specified. " + "Must specify the 'retrieve' option." + % (dest)) + + # diffs_file is valid if diff is True + if diffs_file is not None: + if diff is False: + junos_module.fail_json(msg="The diffs_file option (%s) is " + "specified, but 'diff' is false." + % (diffs_file)) + + # commit_empty_changes is valid if commit is True + if commit_empty_changes is True: + if commit is False: + junos_module.fail_json(msg="The commit_empty_changes option " + "is true, but 'commit' is false. " + "The commit_empty_changes option " + "may only be specified when " + "'commit' is true.") + + # comment is valid if commit is True + if comment is not None: + if commit is False: + junos_module.fail_json(msg="The comment option (%s) is " + "specified, but 'commit' is false." + % (comment)) + + # confirmed is valid if commit is True + if confirmed is not None: + if commit is False: + junos_module.fail_json(msg="The confirmed option (%s) is " + "specified, but 'commit' is false." + % (confirmed)) + # Must be greater >= 1. + if confirmed < 1: + junos_module.fail_json(msg="The confirmed option (%s) must have a " + "positive integer value." % (confirmed)) + + # check_commit_wait is valid if check is True and commit is True + if check_commit_wait is not None: + if commit is False: + junos_module.fail_json(msg="The check_commit_wait option (%s) is " + "specified, but 'commit' is false." + % (check_commit_wait)) + if check is False: + junos_module.fail_json(msg="The check_commit_wait option (%s) is " + "specified, but 'check' is false." + % (check_commit_wait)) + # Must be greater >= 1. + if check_commit_wait < 1: + junos_module.fail_json(msg="The check_commit_wait option (%s) " + "must have a positive integer value." % + (check_commit_wait)) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': 'Configuration has been: ', + 'changed': False, + 'failed': True} + + junos_module.logger.debug("Step 1 - Open a candidate configuration " + "database.") + junos_module.open_configuration(mode=config_mode) + results['msg'] += 'opened' + + junos_module.logger.debug("Step 2 - Load configuration data into the " + "candidate configuration database.") + if rollback is not None: + junos_module.rollback_configuration(id=rollback) + # Assume configuration changed in case we don't perform a diff later. + # If diff is set, we'll check for actual differences later. + results['changed'] = True + results['msg'] += ', rolled back' + elif load is not None: + if src is not None: + junos_module.load_configuration(action=load, + src=src, + ignore_warning=ignore_warning, + format=format) + results['file'] = src + elif lines is not None: + junos_module.load_configuration(action=load, + lines=lines, + ignore_warning=ignore_warning, + format=format) + elif template is not None: + junos_module.load_configuration(action=load, + template=template, + vars=vars, + ignore_warning=ignore_warning, + format=format) + elif url is not None: + junos_module.load_configuration(action=load, + url=url, + ignore_warning=ignore_warning, + format=format) + else: + junos_module.fail_json(msg="The load option was set to: %s, but " + "no 'src', 'lines', 'template', or " + "'url' option was set." % + (load)) + # Assume configuration changed in case we don't perform a diff later. + # If diff is set, we'll check for actual differences later. + results['changed'] = True + results['msg'] += ', loaded' + + junos_module.logger.debug("Step 3 - Check the validity of the candidate " + "configuration database.") + if check is True: + junos_module.check_configuration() + results['msg'] += ', checked' + + junos_module.logger.debug("Step 4 - Determine differences between the " + "candidate and committed configuration " + "databases.") + if diff is True: + diff = junos_module.diff_configuration() + if diff is not None: + results['changed'] = True + if return_output is True: + results['diff'] = diff + results['diff_lines'] = diff.splitlines() + # Save the diff output + junos_module.save_text_output('diff', 'diff', diff) + else: + results['changed'] = False + results['msg'] += ', diffed' + + junos_module.logger.debug("Step 5 - Retrieve the configuration database " + "from the Junos device.") + if retrieve is not None: + if format is None: + format = 'text' + (config, config_parsed) = junos_module.get_configuration( + database=retrieve, + format=format, + options=options, + filter=filter) + if return_output is True: + if config is not None: + results['config'] = config + results['config_lines'] = config.splitlines() + if config_parsed is not None: + results['config_parsed'] = config_parsed + # Save the output + format_extension = 'config' if format == 'text' else format + junos_module.save_text_output('config', format_extension, config) + results['msg'] += ', retrieved' + + junos_module.logger.debug("Step 6 - Commit the configuration changes.") + if commit is True and not junos_module.check_mode: + # Perform the commit if: + # 1) commit_empty_changes is True + # 2) Neither rollback or load is set. i.e. confirming a previous commit + # 3) rollback or load is set, and there were actual changes. + if (commit_empty_changes is True or + (rollback is None and load is None) or + ((rollback is not None or load is not None) and + results['changed'] is True)): + if check_commit_wait is not None: + time.sleep(check_commit_wait) + junos_module.commit_configuration(ignore_warning=ignore_warning, + comment=comment, + confirmed=confirmed) + results['msg'] += ', committed' + else: + junos_module.logger.debug("Skipping commit. Nothing changed.") + + junos_module.logger.debug("Step 7 - Close the candidate configuration " + "database.") + junos_module.close_configuration() + results['msg'] += ', closed.' + + # If we made it this far, everything was successful. + results['failed'] = False + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index b7c5c404..75b90a06 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -112,7 +112,7 @@ type: 'dict or list of dict' aliases: - attr - filter_xml: + filter: description: - This argument only applies if the I(rpcs) option contains a single RPC with the value 'get-config'. When used, this value specifies an @@ -123,6 +123,8 @@ required: false default: none type: 'str' + aliases: + - filter_xml dest: description: - The path to a file, on the Ansible control machine, where the output of @@ -446,9 +448,10 @@ def main(): type='str', aliases=['attr'], default=None), - filter_xml=dict(required=False, - type='str', - default=None), + filter=dict(required=False, + type='str', + aliases=['filter_xml'], + default=None), dest=dict(required=False, type='path', aliases=['destination'], @@ -526,11 +529,11 @@ def main(): else: attrs = [None] * len(rpcs) - # Check filter_xml - if junos_module.params.get('filter_xml') is not None: + # Check filter + if junos_module.params.get('filter') is not None: if (len(rpcs) != 1 or (rpcs[0] != 'get-config' and rpcs[0] != 'get_config')): - junos_module.fail_json(msg="The filter_xml option is only valid " + junos_module.fail_json(msg="The filter option is only valid " "when the rpcs option value is a " "single 'get-config' RPC.") @@ -550,7 +553,7 @@ def main(): # Execute the RPC try: if rpc_string == 'get-config': - filter_xml = junos_module.params.get('filter_xml') + filter = junos_module.params.get('filter') if attr is None: attr = {} if kwarg is None: @@ -558,8 +561,8 @@ def main(): junos_module.logger.debug('Executing "get-config" RPC. ' 'filter_xml=%s, options=%s, ' 'kwargs=%s', - filter_xml, str(attr), str(kwarg)) - resp = junos_module.dev.rpc.get_config(filter_xml=filter_xml, + filter, str(attr), str(kwarg)) + resp = junos_module.dev.rpc.get_config(filter_xml=filter, options=attr, **kwarg) result['msg'] = 'The "get-config" RPC executed successfully.' junos_module.logger.debug('The "get-config" RPC executed ' diff --git a/library/junos_commit b/library/junos_commit deleted file mode 100644 index f952e82f..00000000 --- a/library/junos_commit +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2015, Juniper Networks Inc. -# 2014, Jeremy Schulman -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_commit -author: Rick Sherman, Juniper Networks -version_added: "1.2.0" -short_description: Execute commit on device -description: - - Execute a Commit on a device running Junos independently of loading a configuration -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - timeout: - description: - - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate commits that - might take longer than the default timeout interval. - required: false - default: "0" - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - comment: - description: - - Provide a comment to the commit of the configuration - required: false - default: None - confirm: - description: - - Provide a confirm in minutes to the commit of the configuration - required: false - default: None - check: - description: - - Do a commit check - Can be used to confirm a `commit confirmed` without performing an actual commit - required: false - default: None -''' - -EXAMPLES = ''' -- junos_commit: - host: "{{ inventory_hostname }}" - logfile=changes.log - comment="Non load commit" - -# over console server connection using PyEZ >= 2.0 -- name: junos commit - junos_commit: - host={{ inventory_hostname }} - port=7016 - mode='telnet' - comment="commit with console connection via PyEZ" -''' - -from distutils.version import LooseVersion -import logging - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - timeout=dict(required=False, default=0), - logfile=dict(required=False, default=None), - comment=dict(required=False, default=None), - confirm=dict(required=False, default=None), - check=dict(required=False, type='bool', - choices=BOOLEANS, default=False) - ), - supports_check_mode=True) - - args = module.params - in_check_mode = module.check_mode - results = dict(changed=False) - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.utils.config import Config - from jnpr.junos.exception import CommitError, LockError, UnlockError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'CONFIG:' + args['host'] - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - try: - timeout = int(args['timeout']) - if timeout > 0: - dev.timeout = timeout - - cu = Config(dev) - - logging.info("taking lock") - cu.lock() - check = args['check'] - if (in_check_mode or check): - logging.info("doing a commit-check, please be patient") - cu.commit_check() - else: - logging.info("committing change, please be patient") - opts = {} - if args['comment'] is not None: - opts['comment'] = args['comment'] - if args['confirm'] is not None: - opts['confirm'] = args['confirm'] - - cu.commit(**opts) - results['changed'] = True - logging.info("change completed") - - logging.info("unlocking") - cu.unlock() - - except LockError as err: - msg = 'Unable to lock configuration - will not commit: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except UnlockError as err: - msg = 'Unable to unlock configuration - commit should succeed: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except CommitError as err: - msg = 'Unable to commit: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/library/junos_get_config b/library/junos_get_config deleted file mode 100755 index 029bcf8a..00000000 --- a/library/junos_get_config +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2015, Juniper Networks Inc. -# 2014, Jeremy Schulman -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_get_config -author: Rick Sherman, Juniper Networks -version_added: "1.2.0" -short_description: Retrieve configuration of device -description: - - Retrieve the configuration of a device running Junos and save it to a file. - B(Note) unicode chars will be converted to '??' as also done in PyEZ -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - dest: - description: - - Path to the local server directory where configuration will - be saved. - required: true - default: None - format: - description: - - text - configuration saved as text (curly-brace) format - - xml - configuration saved as XML - required: false - choices: ['text','xml'] - default: 'text' - options: - description: - - Additional options to pass to get_config. Refer - to B(jnpr.junos.rpcmeta.get_config) for details. - required: false - default: None - filter: - description: - - Defines heircachy of configuration to retrieve. If omitted - entire configuration is retrieved. Format is slash notation - ex I(groups/routeinst/routing-instances/ISP-1) - required: false - default: None -''' - -EXAMPLES = ''' -- junos_get_config: - host: "{{ inventory_hostname }}" - logfile: get_config.log - dest: "{{ inventory_hostname }}.xml" - format: xml - filter: "interfaces" - options: {inherit: inherit, groups: groups} - -# over console server connection using PyEZ >= 2.0 -- junos_get_config: - host: "{{ inventory_hostname }}" - logfile: get_config.log - dest: "{{ inventory_hostname }}.xml" - port: 7016 - mode: 'telnet' - format: xml -''' -from distutils.version import LooseVersion -import logging -from lxml import etree - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - logfile=dict(required=False, default=None), - dest=dict(required=True, default=None), - format=dict(required=False, choices=['text', 'xml'], default='text'), - options=dict(required=False, default=None, type='dict'), - filter=dict(required=False, default=None) - ), - supports_check_mode=True) - - args = module.params - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') - - results = {} - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'CONFIG:' + args['host'] - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - try: - options = args['options'] or {} - options['format'] = args['format'] - - filter_xml = None - if args['filter']: - flist = args['filter'].split('/') - if flist[0] == 'configuration': - filter_xml = etree.Element(flist.pop(0)) - else: - filter_xml = etree.Element('configuration') - current_element = filter_xml - - for f in flist: - current_element = etree.SubElement(current_element,f) - - logging.info("Getting config with filter={0}".format(etree.tostring(filter_xml))) - - logging.info("Getting config with options={0}".format(options)) - - config = dev.rpc.get_config(options=options, filter_xml=filter_xml) - - ## Compare existing file (if exists) with new output - if args['format'] == 'text': - newconf = config.text.encode('ascii', 'replace') - elif args['format'] == 'xml': - newconf = etree.tostring(config) - - oldconf = '' - if os.path.isfile(args['dest']): - with open(args['dest'], 'r') as confile: - oldconf = confile.read() - - ## Correctly report 'changed' attribute - if oldconf != newconf: - results['changed'] = True - if getattr(module,'_diff',False) is True: - results['diff'] = dict( - before = oldconf, - after = newconf, - before_header = args['dest'], - after_header = args['host'], - ) - - ## if check_mode, then don't actually change the dest file! - if not module.check_mode: - with open(args['dest'], 'w') as confile: - confile.write(newconf) - - except (ValueError, RpcError) as err: - msg = 'Unable to get config: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/library/junos_install_config b/library/junos_install_config deleted file mode 100644 index a5c0a970..00000000 --- a/library/junos_install_config +++ /dev/null @@ -1,521 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2014, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_install_config -author: Jeremy Schulman, Juniper Networks -version_added: "1.0.0" -short_description: Load a configuration file or snippet onto a device running Junos OS. -description: - - Load a complete Junos OS configuration (overwrite) or merge a configuration - snippet onto a device running Junos OS and commit it. The default behavior - is to perform a B(load merge) operation (overwrite='no'). This module - performs an atomic lock/edit/unlock. If the process fails at any step, then - all configuration changes are discarded. You can load the configuration using - either NETCONF or the CONSOLE port. Specify the I(console) option to use the - CONSOLE port. - - - You provide the configuration data in a file. Supported formats when using - NETCONF include ASCII text, Junos XML elements, and Junos OS B(set) commands. - Configurations performed through the console must only use ASCII text formatting. -requirements: - - junos-eznc >= 2.1.1 - - junos-netconify >= 1.0.1, when using the I(console) option -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - file: - description: - - Path to the file containing the Junos OS configuration data. - If the file has a C(*.conf) extension, the content is treated - as text format. If the file has a C(*.xml) extension, the - content is treated as XML format. If the file has a C(*.set) - extension, the content is treated as Junos OS B(set) - commands. - required: true - overwrite: - description: - - Specify whether the configuration I(file) completely replaces - the existing configuration. - required: false - default: no - choices: ['true','false','yes','no'] - update: - description: - - If set to ``True`` Compare a complete loaded configuration against - the candidate configuration. For each hierarchy level or - configuration object that is different in the two configurations, - the version in the loaded configuration replaces the version in the - candidate configuration. When the configuration is later committed, - only system processes that are affected by the changed configuration - elements parse the new configuration. This action is supported from - PyEZ 2.1 - required: false - default: no - choices: ['true','false','yes','no'] - replace: - description: - - Specify whether the configuration I(file) uses "replace:" statements. - (NETCONF only) B(NOT) compatible with B(set) format - required: false - default: no - choices: ['true','false','yes','no'] - timeout: - description: - - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate configuration - changes (commits) that might take longer than the default - timeout interval. - required: false - default: "0" - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - diffs_file: - description: - - Path to the file where any diffs will be written - required: false - default: None - console: - description: - - Port configuration, per the B(netconify) utility - required: false - default: None - savedir: - description: - - Path to the local server directory where device facts and - inventory files will be stored. This option is used only - with the I(console) option. - Refer to the B(netconify) utility for details. - required: false - default: None - comment: - description: - - Provide a comment to the commit of the configuration - required: false - default: None - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - confirm: - description: - - Provide a confirm in minutes to the commit of the configuration - required: false - default: None - check_commit: - description: - - Specify whether the configuration will be commit-checked or not. - Set to false, outputs similarly to the "show | compare" functionality, - while true returns the "commit check" result. - required: false - default: yes - choices: ['true','false','yes','no'] - ignore_warning: - description: - - A boolean, string or list of string. If the value is True, it - will ignore all warnings regardless of the warning message. If - the value is a string, it will ignore warning(s) if the message - of each warning matches the string. If the value is a list of - strings, ignore warning(s) if the message of each warning matches - at least one of the strings in the list. - required: false - default: None - check_commit_wait: - description: - - Set to number of seconds to wait between check and commit. - required: false - default: None - choices: [1 - 4] -''' - -EXAMPLES = ''' -# load merge a change to the Junos OS configuration using NETCONF - -- junos_install_config: - host={{ inventory_hostname }} - file=banner.conf - -# load overwrite a new Junos OS configuration using the CONSOLE port - -- junos_install_config: - host={{ inventory_hostname }} - console="--telnet={{TERMSERV}},{{TERMSERV_PORT}}" - file=default_new_switch.conf - overwrite=yes - -# load merge a change to the Junos OS configuration using NETCONF and supplying a commit log message -- junos_install_config: - host={{ inventory_hostname }} - file=banner.conf - comment="configured by ansible" - -# load replace a change to the Junos OS configuration using NETCONF -- junos_install_config: - host={{ inventory_hostname }} - file=snmp.conf - replace=yes - -# Install/load config using console server connection using PyEZ >= 2.0 - -- junos_install_config: - host={{ inventory_hostname }} - port=7016 - mode='telnet' - file=default_new_switch.conf -''' - -import logging -from os.path import isfile -import os -from distutils.version import LooseVersion -import re -import ast - -from ansible.module_utils.basic import * - - -def _load_via_netconf(module): - args = module.params - - try: - from jnpr.junos import Device - from jnpr.junos.exception import LockError, UnlockError, \ - ConfigLoadError, CommitError - from jnpr.junos.utils.config import Config - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('2.1.1'): - module.fail_json(msg='junos-eznc >= 2.1.1 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'CONFIG:' + args['host'] - - in_check_mode = module.check_mode - check_commit = module.boolean(module.params['check_commit']) - overwrite = module.boolean(module.params['overwrite']) - replace = module.boolean(module.params['replace']) - update = module.boolean(module.params['update']) - - actions = filter(lambda item: module.params.get(item, False), - ('overwrite', 'replace', 'update')) - if len(list(actions)) >= 2: - msg = 'action can be only one among %s' % ', '.join(actions) - logging.error(msg) - module.fail_json(msg=msg) - - if str(args['ignore_warning']).lower() in BOOLEANS: - args['ignore_warning'] = module.boolean(module.params['ignore_warning']) - elif isinstance(args['ignore_warning'], str) and \ - re.search('\[.*\]', args['ignore_warning']): - args['ignore_warning'] = ast.literal_eval(args['ignore_warning']) - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], - args['host'], - args['port'])) - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], - ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], - gather_facts=False) - dev.open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - return - - timeout = int(args['timeout']) - if timeout > 0: - dev.timeout = timeout - - cu = Config(dev) - - results = {} - - file_path = module.params['file'] - file_path = os.path.abspath(file_path) - - results['file'] = file_path - results['changed'] = False - - logging.info("pushing file: {0}".format(file_path)) - try: - logging.info("taking lock") - cu.lock() - - except LockError: - msg = "Unable to lock configuration" - logging.error(msg) - module.fail_json(msg=msg) - - logging.info("loading config") - load_args = {} - if args['ignore_warning'] is not None: - load_args = {'ignore_warning': args['ignore_warning']} - try: - # load the config. the cu.load will raise - # an exception if there is even a warning. - # so we want to avoid that condition. - load_args.update({'path': file_path}) - if replace is True: - load_args['merge'] = False - elif overwrite is True: - load_args['overwrite'] = True - elif update is True: - load_args['update'] = True - elif overwrite is False: - load_args['merge'] = True - cu.load(**load_args) - except (ValueError, ConfigLoadError, Exception) as err: - msg = "Unable to load config: {0}".format(err) - logging.error(msg) - try: - logging.info("unlocking") - cu.unlock() - except UnlockError as err: - logging.error("Unable to unlock config: {0}".format(err)) - dev.close() - module.fail_json(msg=msg) - - diff = cu.diff() - - if diff is not None: - results['changed'] = True - if getattr(module,'_diff',False) is True: - results['diff'] = dict( - prepared = diff - ) - diffs_file = args['diffs_file'] - if diffs_file is not None: - try: - f = open(diffs_file, 'w') - f.write(diff.encode('utf-8', 'ignore')) - f.close() - except IOError as (errno, strerror): - msg = "Problem with diffs_file {0}: ".format(diffs_file) - msg += "I/O Error: ({0}): {1}".format(errno, strerror) - logging.error(msg) - module.fail_json(msg=msg) - except Exception as err: - msg = "Problem with diffs_file {0}: ".format(diffs_file) - msg += "Unexpected error: {0}".format(err) - logging.error(msg) - module.fail_json(msg=msg) - - if check_commit: - try: - logging.info("doing a commit-check, please be patient") - cu.commit_check() - if (in_check_mode): - logging.info("Ansible check mode complete") - module.exit_json(**results) - else: - logging.info("committing change, please be patient") - opts = {} - if args['ignore_warning'] is not None: - opts = {'ignore_warning': args['ignore_warning']} - if args['comment'] is not None: - opts['comment'] = args['comment'] - if args['confirm'] is not None: - opts['confirm'] = args['confirm'] - - if args['check_commit_wait']: - check_commit_wait = int(args['check_commit_wait']) - if 1 <= check_commit_wait <= 4: - time.sleep(check_commit_wait) - - cu.commit(**opts) - - except CommitError as err: - msg = "Unable to commit configuration: {0}".format(err) - logging.error(msg) - try: - logging.info("unlocking") - cu.unlock() - except UnlockError as err: - logging.error("Unable to unlock config: {0}".format(err)) - dev.close() - module.fail_json(msg=msg) - - try: - logging.info("unlocking") - cu.unlock() - except UnlockError as err: - logging.error("Unable to unlock config: {0}".format(err)) - - logging.info("change completed") - - dev.close() - module.exit_json(**results) - - -def _load_via_console(module): - try: - from netconify.cmdo import netconifyCmdo - from netconify.constants import version - if not LooseVersion(version) >= LooseVersion('1.0.1'): - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - except ImportError: - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - - m_args = module.params - - c_args = [] - c_args.append(m_args['console']) - c_args.append('--file=' + m_args['file']) - if m_args['savedir'] is not None: - c_args.append('--savedir=' + m_args['savedir']) - c_args.append('--user=' + m_args['user']) - if m_args['passwd'] is not None: - c_args.append('--passwd=' + m_args['passwd']) - - # the default mode for loading a config via the console - # is to load-overwrite. So we need to check the module - # option and set the "--merge" option if overwrite is False - - overwrite = module.boolean(module.params['overwrite']) - if overwrite is False: - c_args.append('--merge') - - c_args.append(m_args['host']) - - logfile = m_args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'NETCONIFY:' + m_args['host'] - - def log_notify(self, event, message): - logging.info("%s:%s" % (event, message)) - use_notifier = log_notify - else: - def silent_notify(self, event, message): - pass - use_notifier = silent_notify - - try: - nc = netconifyCmdo(notify=use_notifier) - c_results = nc.run(c_args) - except Exception as err: - module.fail_json(msg=str(err)) - m_results = dict(changed=c_results['changed']) - if c_results['failed'] is True: - module.fail_json(msg=c_results['errmsg']) - else: - module.exit_json(**m_results) - -# --------------------------------------------------------------------------- -# MAIN -# --------------------------------------------------------------------------- - - -def main(): - module = AnsibleModule( - argument_spec=dict( - host=dict(required=True), - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - console=dict(required=False, default=None), - file=dict(required=True), - overwrite=dict(required=False, type='bool', choices=BOOLEANS, default=False), - update=dict(required=False, type='bool', choices=BOOLEANS, default=False), - replace=dict(required=False, type='bool', choices=BOOLEANS, default=False), - logfile=dict(required=False, default=None), - diffs_file=dict(required=False, default=None), - savedir=dict(required=False), - timeout=dict(required=False, default=0), - comment=dict(required=False, default=None), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - confirm=dict(required=False, default=None), - check_commit=dict(required=False, type='bool', choices=BOOLEANS, default=True), - ignore_warning=dict(required=False, default=None), - check_commit_wait=dict(required=False, default=None) - ), - supports_check_mode=True) - - args = module.params - - # ------------------------------ - # make sure file actually exists - # ------------------------------ - - if not isfile(args['file']): - module.fail_json(msg="file not found: {0}".format(args['file'])) - return - - _ldr = _load_via_netconf if args['console'] is None else _load_via_console - _ldr(module) - -if __name__ == '__main__': - main() diff --git a/library/junos_rollback b/library/junos_rollback deleted file mode 100755 index 581ad292..00000000 --- a/library/junos_rollback +++ /dev/null @@ -1,284 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2015, Juniper Networks Inc. -# 2014, Jeremy Schulman -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_rollback -author: Rick Sherman, Juniper Networks -version_added: "1.2.0" -short_description: Rollback configuration of device -description: - - Rollback the configuration of a device running Junos -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - rollback: - description: - - The rollback id value [0-49] - required: true - default: None - timeout: - description: - - Extend the NETCONF RPC timeout beyond the default value of - 30 seconds. Set this value to accommodate rollbacks - that might take longer than the default timeout interval. - required: false - default: "0" - comment: - description: - - Provide a comment to the commit of the configuration - required: false - default: None - confirm: - description: - - Provide a confirm in minutes to the commit of the configuration - required: false - default: None - diffs_file: - description: - - Path to the file where any diffs will be written - required: false - default: None -''' - -EXAMPLES = ''' -- junos_rollback: - host: "{{ inventory_hostname }}" - logfile=rollback.log - diffs_file=rollback.diff - rollback=1 - comment="Rolled back by Ansible" - confirm=5 - -# over console server connection using PyEZ >= 2.0 -- junos_rollback: - host: "{{ inventory_hostname }}" - logfile=rollback.log - diffs_file=rollback.diff - rollback=1 - comment="Rolled back by Ansible" - confirm=5 - mode='telnet' - port=7015 -''' - -from distutils.version import LooseVersion -import logging - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - logfile=dict(required=False, default=None), - rollback=dict(required=True, default=None), - timeout=dict(required=False, default=0), - comment=dict(required=False, default=None), - confirm=dict(required=False, default=None), - diffs_file=dict(required=False, default=None) - ), - supports_check_mode=True) - - args = module.params - in_check_mode = module.check_mode - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.utils.config import Config - from jnpr.junos.exception import CommitError, LockError, UnlockError, RpcError - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - - results = dict(changed=False) - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'CONFIG:' + args['host'] - - rb_id = int(args['rollback']) - if not 0 <= rb_id <= 49: - msg = "Rollback must be an integer [0-49]" - logging.error(msg) - module.fail_json(msg=msg) - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - timeout = int(args['timeout']) - if timeout > 0: - dev.timeout = timeout - - try: - cu = Config(dev) - - logging.info("taking lock") - cu.lock() - - logging.info("Rolling back to: {0}".format(rb_id)) - cu.rollback(rb_id=rb_id) - - diff = cu.diff() - if diff is not None: - diffs_file = args['diffs_file'] - if diffs_file is not None: - try: - f = open(diffs_file, 'w') - f.write(diff) - f.close() - except IOError as (errno, strerror): - msg = "Problem with diffs_file {0}: ".format(diffs_file) - msg += "I/O Error: ({0}): {1}".format(errno, strerror) - logging.error(msg) - except: - msg = "Problem with diffs_file {0}: ".format(diffs_file) - msg += "Unexpected error:", sys.exc_info()[0] - logging.error(msg) - - if (in_check_mode): - logging.info("doing a commit-check, please be patient") - cu.commit_check() - else: - logging.info("committing change, please be patient") - opts = {} - if args['comment'] is not None: - opts['comment'] = args['comment'] - if args['confirm'] is not None: - opts['confirm'] = args['confirm'] - - cu.commit(**opts) - results['changed'] = True - logging.info("change completed") - - logging.info("unlocking") - cu.unlock() - - except LockError as err: - msg = 'Unable to lock configuration - will not rollback: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except UnlockError as err: - msg = 'Unable to unlock configuration - commit should succeed: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except CommitError as err: - msg = 'Unable to commit: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except (ValueError, RpcError) as err: - msg = 'Unable to rollback: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 154298e8..eae4123f 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -52,7 +52,7 @@ HAS_PYEZ_VERSION = None try: - from jnpr.junos.device import Device + import jnpr.junos.device HAS_PYEZ_DEVICE = True except ImportError: HAS_PYEZ_DEVICE = False @@ -108,7 +108,8 @@ # Minimum jxmlease version required by shared code. MIN_JXMLEASE_VERSION = "1.0.1" # Installation URL for jxmlease. -JXMLEASE_INSTALLATION_URL = "http://jxmlease.readthedocs.io/en/stable/install.html" +JXMLEASE_INSTALLATION_URL = \ + "http://jxmlease.readthedocs.io/en/stable/install.html" class ModuleDocFragment(object): @@ -424,6 +425,11 @@ class ModuleDocFragment(object): CONFIG_FORMAT_CHOICES = ['xml', 'set', 'text', 'json'] # Known configuration databases CONFIG_DATABASE_CHOICES = ['candidate', 'committed'] +# Known configuration actions +CONFIG_ACTION_CHOICES = ['set', 'merge', 'update', + 'replace', 'override', 'overwrite'] +# Supported configuration modes +CONFIG_MODE_CHOICES = ['exclusive', 'private'] class JuniperJunosModule(AnsibleModule): @@ -478,6 +484,8 @@ def __init__(self, """ # Initialize the dev attribute self.dev = None + # Initialize the config attribute + self.config = None # Update argument_spec with the internal_spec argument_spec.update(internal_spec) # Update argument_spec with the top_spec @@ -545,6 +553,8 @@ def fail_json(self, **kwargs): **kwargs: All keyword arguments are passed to AnsibleModule.fail_json(). """ + # Close the configuration + self.close_configuration() # Close the connection. self.close() if hasattr(self, 'logger'): @@ -887,6 +897,81 @@ def parse_arg_to_list_of_dicts(self, return_val.append(return_item) return return_val + def parse_ignore_warning_option(self): + """Parses the ignore_warning option. + + ignore_warning may be a bool, str, or list of str. The Ansible type + checking doesn't support the possibility of more than one type. + + Returns: + The validated value of the ignore_warning option. None if + ignore_warning is not specified. + + Fails: + If there is an error parsing ignore_warning. + """ + # Nothing to do if ignore_warning wasn't specified. + ignore_warn_list = self.params.get('ignore_warning') + if ignore_warn_list is None: + return ignore_warn_list + if len(ignore_warn_list) == 1: + bool_val = self.convert_to_bool(ignore_warn_list[0]) + if bool_val is not None: + return bool_val + elif isinstance(ignore_warn_list[0], basestring): + return ignore_warn_list[0] + else: + self.fail_json(msg="The value of the ignore_warning option " + "(%s) is invalid. Unexpected type (%s)." % + (ignore_warn_list[0], + type(ignore_warn_list[0]))) + elif len(ignore_warn_list) > 1: + for ignore_warn in ignore_warn_list: + if not isinstance(ignore_warn, basestring): + self.fail_json(msg="The value of the ignore_warning " + "option (%s) is invalid. " + "Element (%s) has unexpected " + "type (%s)." % + (str(ignore_warn_list), + ignore_warn, + type(ignore_warn))) + return ignore_warn_list + else: + self.fail_json(msg="The value of the ignore_warning option " + "(%s) is invalid." % + (ignore_warn_list)) + + def parse_rollback_option(self): + """Parses the rollback option. + + rollback may be a str of 'rescue' or an int between 0 and 49. The + Ansible type checking doesn't support the possibility of more than + one type. + + Returns: + The validate value of the rollback option. None if + rollback is not specified. + + Fails: + If there is an error parsing rollback. + """ + # Nothing to do if rollback wasn't specified or is 'rescue'. + rollback = self.params.get('rollback') + if rollback is None or rollback == 'rescue': + return rollback + if isinstance(rollback, basestring): + try: + # Is it an int between 0 and 49? + int_val = int(rollback) + if int_val >= 0 and int_val <= 49: + return int_val + except ValueError: + # Fall through to fail_json() + pass + self.fail_json(msg="The value of the rollback option (%s) is invalid. " + "Must be the string 'rescue' or an int between " + "0 and 49." % (str(rollback))) + def open(self): """Open the self.dev PyEZ Device instance. @@ -906,7 +991,7 @@ def open(self): self.logger.debug("Creating device parameters: %s", log_connect_args) timeout = connect_args.pop('timeout') - self.dev = Device(**connect_args) + self.dev = jnpr.junos.device.Device(**connect_args) self.logger.debug("Opening device.") self.dev.open() self.logger.debug("Device opened.") @@ -920,6 +1005,48 @@ def open(self): self.fail_json(msg='Unable to make a PyEZ connection: %s' % (str(ex))) + def open_configuration(self, mode): + # Already have an open configuration? + if self.config is None: + if mode not in CONFIG_MODE_CHOICES: + self.fail_json(msg='Invalid configuration mode: %s' % (mode)) + if self.dev is None: + self.open() + config = jnpr.junos.utils.config.Config(self.dev, mode=mode) + try: + if config.mode == 'exclusive': + config.lock() + elif config.mode == 'private': + self.dev.rpc.open_configuration( + private=True, + ignore_warning='uncommitted changes will be ' + 'discarded on exit') + except (pyez_exception.ConnectError, + pyez_exception.RpcError) as ex: + self.fail_json(msg='Unable to open the configuration in %s ' + 'mode: %s' % (config.mode, str(ex))) + self.config = config + self.logger.debug("Configuration opened in %s mode.", config.mode) + + def close_configuration(self): + if self.config is not None: + # Because self.fail_json() calls self.close_configuration(), we + # must set self.config = None BEFORE closing the config in order to + # avoid the infinite recursion which would occur if closing the + # configuration raised an exception. + config = self.config + self.config = None + try: + if config.mode == 'exclusive': + config.unlock() + elif config.mode == 'private': + self.dev.rpc.close_configuration() + self.logger.debug("Configuration closed.") + except (pyez_exception.ConnectError, + pyez_exception.RpcError) as ex: + self.fail_json(msg='Unable to close the configuration: %s' % + (str(ex))) + def add_sw(self): """Add an instance of jnp.junos.utils.sw.SW() to self. """ @@ -1043,6 +1170,194 @@ def get_configuration(self, database='committed', format='text', (format)) return return_val + def rollback_configuration(self, id): + """Rolback the device configuration to the specified id. + + Rolls back the configuration to the specified id. Assumes the + configuration is already opened. Does NOT commit the configuration. + + Args: + id: The id to which the configuration should be rolled back. Either + an integer rollback value or the string 'rescue' to roll back + to the previously saved rescue configuration. + + Failures: + - Unable to rollback the configuration due to an RpcError or + ConnectError. + """ + if self.dev is None or self.config is None: + self.fail_json(msg='The device or configuration is not open.') + + if id == 'rescue': + self.logger.debug("Rolling back to the rescue configuration.") + try: + self.config.rescue(action='reload') + self.logger.debug("Rescue configuration loaded.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Unable to load the rescue configuraton: ' + '%s' % (str(ex))) + elif id >= 0 and id <= 49: + self.logger.debug("Loading rollback %d configuration.", id) + try: + self.config.rollback(rb_id=id) + self.logger.debug("Rollback %d configuration loaded.", id) + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Unable to load the rollback %d ' + 'configuraton: %s' % (id, str(ex))) + else: + self.fail_json(msg='Unrecognized rollback configuraton value: %s' + % (id)) + + def check_configuration(self): + """Check the device configuration. + + Check the configuration. Assumes the configuration is already opened. + Performs the equivalent of a "commit check", but does NOT commit the + configuration. + + Failures: + - An error returned from checking the configuration. + """ + if self.dev is None or self.config is None: + self.fail_json(msg='The device or configuration is not open.') + + self.logger.debug("Checking the configuration.") + try: + self.config.commit_check() + self.logger.debug("Configuration checked.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Failure checking the configuraton: %s' % + (str(ex))) + + def diff_configuration(self): + """Diff the candidate and committed configurations. + + Diff the candidate and committed configurations. + + Returns: + A string with the configuration differences in text "diff" format. + + Failures: + - An error returned from diffing the configuration. + """ + if self.dev is None or self.config is None: + self.fail_json(msg='The device or configuration is not open.') + + self.logger.debug("Diffing candidate and committed configurations.") + try: + diff = self.config.diff(rb_id=0) + self.logger.debug("Configuration diff completed.") + return diff + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Failure diffing the configuraton: %s' % + (str(ex))) + + def load_configuration(self, + action, + lines=None, + src=None, + template=None, + vars=None, + url=None, + ignore_warning=None, + format=None): + """Load the candidate configuration. + + Load the candidate configuration from the specified src file using the + specified action. + + Args: + action - The type of load to perform: 'merge', 'replace', 'set', + 'override', 'overwrite', and + 'update' + lines - A list of strings containing the configuration. + src - The file path on the local Ansible control machine to the + configuration to be loaded. + template - The Jinja2 template used to renter the configuration. + vars - The variables used to render the template. + url - The URL to the candidate configuration. + ignore_warning - What warnings to ignore. + format - The format of the configuration being loaded. + + Failures: + - An error returned from loading the configuration. + """ + if self.dev is None or self.config is None: + self.fail_json(msg='The device or configuration is not open.') + + load_args = {} + config = None + if ignore_warning is not None: + load_args['ignore_warning'] = ignore_warning + if action == 'set': + format = 'set' + if format is not None: + load_args['format'] = format + if action == 'merge': + load_args['merge'] = True + if action == 'override' or action == 'overwrite': + load_args['overwrite'] = True + if action == 'update': + load_args['update'] = True + if lines is not None: + config = '\n'.join(map(lambda line: line.rstrip('\n'), lines)) + self.logger.debug("Loading the supplied configuration.") + if src is not None: + load_args['path'] = src + self.logger.debug("Loading the configuration from: %s.", src) + if template is not None: + load_args['template_path'] = template + load_args['template_vars'] = vars + self.logger.debug("Loading the configuration from the %s " + "template.", template) + if url is not None: + load_args['url'] = url + self.logger.debug("Loading the configuration from %s.", url) + + try: + if config is not None: + self.config.load(config, **load_args) + else: + self.logger.debug("Load args %s.", str(load_args)) + self.config.load(**load_args) + self.logger.debug("Configuration loaded.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Failure loading the configuraton: %s' % + (str(ex))) + + def commit_configuration(self, ignore_warning=None, comment=None, + confirmed=None): + """Commit the candidate configuration. + + Commit the configuration. Assumes the configuration is already opened. + + Args: + ignore_warning - Which warnings to ignore. + comment - The commit comment + confirmed - Number of minutes for commit confirmed. + + Failures: + - An error returned from committing the configuration. + """ + if self.dev is None or self.config is None: + self.fail_json(msg='The device or configuration is not open.') + + self.logger.debug("Committing the configuration.") + try: + self.config.commit(ignore_warning=ignore_warning, + comment=comment, + confirm=confirmed) + self.logger.debug("Configuration committed.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + self.fail_json(msg='Failure committing the configuraton: %s' % + (str(ex))) + def ping(self, params, acceptable_percent_loss=0, results={}): """Execute a ping command with the parameters specified in params. @@ -1182,19 +1497,19 @@ def save_text_output(self, name, format, text): """Save text output into a file based on 'dest' and 'dest_dir' params. The text provided in the text parameter is saved to a file on the - local Ansible control machine based on the 'dest' and 'dest_dir' - module parameters. If neither parameter is specified, then this method - is a no-op. If the 'dest' parameter is specified, the value of the - 'dest' parameter is used as the path name for the destination file. In - this case, the name and format parameters are ignored. - If the 'dest_dir' parameter is specified, the path name for the - destination file is: _.. If the - destination file already exists, and the 'dest_dir' option is - specified, or the 'dest' parameter is specified and the self.destfile - attribute is not present, the file is overwritten. If the 'dest' - parameter is specified and the self.destfile attribute is present, then - the file is appended. This allows multiple text outputs to be written - to the same file. + local Ansible control machine based on the 'diffs_file', 'dest', and + 'dest_dir' module parameters. If neither parameter is specified, + then this method is a no-op. If the 'dest' or 'diffs_file' parameter is + specified, the value of the 'dest' or 'diffs_file' parameter is used as + the path name for the destination file. In this case, the name and + format parameters are ignored. If the 'dest_dir' parameter is + specified, the path name for the destination file is: + _.. If the destination file already exists, + and the 'dest_dir' option is specified, or the 'dest' parameter is + specified and the self.destfile attribute is not present, the file is + overwritten. If the 'dest' parameter is specified and the + self.destfile attribute is present, then the file is appended. This + allows multiple text outputs to be written to the same file. Args: name: The name portion of the destination filename when the @@ -1208,19 +1523,29 @@ def save_text_output(self, name, format, text): """ file_path = None mode = 'w' - if self.params.get('dest') is not None: - file_path = os.path.normpath(self.params.get('dest')) - if getattr(self, 'destfile', None) is None: - self.destfile = self.params.get('dest') - else: - mode = 'a' - elif self.params.get('dest_dir') is not None: - dest_dir = self.params.get('dest_dir') - hostname = self.params.get('host') - # Substitute underscore for spaces. - name = name.replace(' ', '_') - file_name = '%s_%s.%s' % (hostname, name, format) - file_path = os.path.normpath(os.path.join(dest_dir, file_name)) + if name == 'diff': + if self.params.get('diffs_file') is not None: + file_path = os.path.normpath(self.params.get('diffs_file')) + elif self.params.get('dest_dir') is not None: + dest_dir = self.params.get('dest_dir') + hostname = self.params.get('host') + file_name = '%s.diff' % (hostname) + file_path = os.path.normpath(os.path.join(dest_dir, file_name)) + else: + if self.params.get('dest') is not None: + file_path = os.path.normpath(self.params.get('dest')) + if getattr(self, 'destfile', None) is None: + self.destfile = self.params.get('dest') + else: + mode = 'a' + elif self.params.get('dest_dir') is not None: + dest_dir = self.params.get('dest_dir') + hostname = self.params.get('host') + # Substitute underscore for spaces. + name = name.replace(' ', '_') + name = '' if name == 'config' else '_' + name + file_name = '%s%s.%s' % (hostname, name, format) + file_path = os.path.normpath(os.path.join(dest_dir, file_name)) if file_path is not None: try: with open(file_path, mode) as save_file: diff --git a/version.py b/version.py old mode 100644 new mode 100755 index 9c5e66e7..8016f73d --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev4" -DATE = "2017-Oct-21" +VERSION = "2.0.0.dev5" +DATE = "2017-Oct-30" From 0454455388af62e7ef3a41583eb45ea7eaf563b5 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Wed, 1 Nov 2017 13:36:11 -0600 Subject: [PATCH 181/426] Rewrite of junos_srx_cluster to juniper_junos_srx_cluster (#291) --- action_plugins/_junos_srx_cluster.py | 1 + action_plugins/juniper_junos_srx_cluster.py | 1 + library/_junos_srx_cluster.py | 1 + library/juniper_junos_srx_cluster.py | 336 ++++++++++++++++++++ library/junos_srx_cluster | 271 ---------------- version.py | 4 +- 6 files changed, 341 insertions(+), 273 deletions(-) create mode 120000 action_plugins/_junos_srx_cluster.py create mode 120000 action_plugins/juniper_junos_srx_cluster.py create mode 120000 library/_junos_srx_cluster.py create mode 100644 library/juniper_junos_srx_cluster.py delete mode 100644 library/junos_srx_cluster diff --git a/action_plugins/_junos_srx_cluster.py b/action_plugins/_junos_srx_cluster.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_srx_cluster.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_srx_cluster.py b/action_plugins/juniper_junos_srx_cluster.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_srx_cluster.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_srx_cluster.py b/library/_junos_srx_cluster.py new file mode 120000 index 00000000..511a3de3 --- /dev/null +++ b/library/_junos_srx_cluster.py @@ -0,0 +1 @@ +juniper_junos_srx_cluster.py \ No newline at end of file diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py new file mode 100644 index 00000000..e16ade15 --- /dev/null +++ b/library/juniper_junos_srx_cluster.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2014, Patrik Bok +# 2015, Rick Sherman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_srx_cluster +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Add or remove SRX chassis cluster configuration. +description: + - Add an SRX chassis cluster configuration and reboot the device. Assuming + the device is capable of forming an SRX cluster and has the correct + cables connected, this will form an SRX cluster. + If an SRX chassis cluster is already present, setting I(cluster_enable) to + C(false) will remove the SRX chassis cluster configuration and reboot + the device causing the SRX cluster to be broken and the device to return + to stand-alone mode. + +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common + enable: + description: + - Enable or disable cluster mode. When C(true) cluster mode is enabled + and I(cluster_id) and I(node_id) must also be specified. When C(false) + cluster mode is disabled and the device returns to stand-alone mode. + required: true + default: none + type: 'bool' + aliases: + - cluster_enable + cluster_id: + description: + - The cluster ID to configure. + required: when I(enable)=C(true) + default: none + type: 'int' + aliases: + - cluster + node_id: + description: + - The node ID to configure. (0 or 1) + required: when I(enable)=C(true) + default: none + type: 'int' + aliases: + - node +''' + +EXAMPLES = ''' +--- +- name: Manipulate the SRX cluster configuration of Junos SRX devices + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: Enable an SRX cluster + juniper_junos_srx_cluster: + enable: true + cluster_id: 4 + node_id: 0 + register: response + - name: Print the response. + debug: + var: response.config_lines + + - name: Disable an SRX cluster + juniper_junos_srx_cluster: + enable: false + register: response + - name: Print the response. + debug: + var: response.config_lines + +# Document connection examples +# Document authentication examples +# Document logging examples +# extends_documentation_fragment: juniper_junos_common +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +reboot: + description: Indicates if a reboot of the device has been initiated. + returned: success + type: bool +changed: + description: Indicates if the device's configuration has changed, or would + have changed when in check mode. + returned: success + type: bool +failed: + description: Indicates if the task failed. + returned: always + type: bool +''' + +# Standard library imports + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + enable=dict(type='bool', + required=True, + aliases=['cluster_enable'], + default=None), + cluster_id=dict(type='int', + required=False, + aliases=['cluster'], + default=None), + node_id=dict(type='int', + required=False, + aliases=['node'], + default=None) + ), + # Required if options + # If enable is True, then cluster_id and node_id must be set. + required_if=[['enable', True, ['cluster_id', 'node_id']]], + # Check mode is implemented. + supports_check_mode=True + ) + # Do additional argument verification. + + # Straight from params + enable = junos_module.params.get('enable') + cluster_id = junos_module.params.get('cluster_id') + node_id = junos_module.params.get('node_id') + + # cluster_id must be between 0 and 255 + if cluster_id is not None: + if cluster_id < 0 or cluster_id > 255: + junos_module.fail_json(msg="The cluster_id option (%s) must have " + "an integer value between 0 and 255." % + (cluster_id)) + + # node_id must be between 0 and 1 + if node_id is not None: + if node_id < 0 or node_id > 1: + junos_module.fail_json(msg="The node_id option (%s) must have a " + "value of 0 or 1." % (node_id)) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': '', + 'changed': False, + 'reboot': False, + 'failed': True} + + junos_module.logger.debug("Check current SRX cluster operational state.") + current_cluster_state = junos_module.dev.facts['srx_cluster'] + current_cluster_id = junos_module.dev.facts['srx_cluster_id'] + if current_cluster_id is not None: + current_cluster_id = int(current_cluster_id) + current_node_name = junos_module.dev.re_name + current_node_id = None + if current_node_name is not None: + (_, _, current_node_id) = current_node_name.partition('node') + if current_node_id: + current_node_id = int(current_node_id) + junos_module.logger.debug( + "Current SRX cluster operational state: %s, cluster_id: %s, " + "node_id: %s", + 'enabled' if current_cluster_state else 'disabled', + str(current_cluster_id), + str(current_node_id)) + + # Is a state change needed? + if current_cluster_state != enable: + junos_module.logger.debug( + "SRX cluster configuration change needed. Current state: %s. " + "Desired state: %s", + 'enabled' if current_cluster_state else 'disabled', + 'enabled' if enable else 'disabled') + results['changed'] = True + + # Is a cluster ID change needed? + if (enable is True and current_cluster_id is not None and + current_cluster_id != cluster_id): + junos_module.logger.debug( + "SRX cluster ID change needed. Current cluster ID: %d. " + "Desired cluster ID: %d", + current_cluster_id, cluster_id) + results['changed'] = True + + # Is a node ID change needed? + if (enable is True and current_node_id is not None and + current_node_id != node_id): + junos_module.logger.debug( + "SRX node ID change needed. Current node ID: %d. " + "Desired cluster ID: %d", + current_node_id, node_id) + results['changed'] = True + + results['msg'] = 'Current state: %s, cluster_id: %s, node_id: %s' % \ + ('enabled' if current_cluster_state else 'disabled', + str(current_cluster_id), + str(current_node_id)) + + if results['changed'] is True: + results['msg'] += ' Desired state: %s, cluster_id: %s, ' \ + 'node_id: %s' % \ + ('enabled' if enable else 'disabled', + str(cluster_id), + str(node_id)) + + if not junos_module.check_mode: + results['msg'] += ' Initiating change.' + try: + output = None + if enable is True: + resp = junos_module.dev.rpc.set_chassis_cluster_enable( + cluster_id=str(cluster_id), node=str(node_id), + reboot=True, normalize=True + ) + else: + resp = junos_module.dev.rpc.set_chassis_cluster_disable( + reboot=True, normalize=True + ) + if resp is not None: + output = resp.getparent().findtext('.//output') + if output is None: + output = resp.getparent().findtext('.//message') + results['msg'] += ' Reboot initiated. Response: %s' % (output) + results['reboot'] = True + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Error: %s', str(ex)) + results['msg'] += ' Error: %s' % (str(ex)) + junos_module.fail_json(**results) + + # If we made it this far, everything was successful. + results['failed'] = False + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/junos_srx_cluster b/library/junos_srx_cluster deleted file mode 100644 index 653f771f..00000000 --- a/library/junos_srx_cluster +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2014, Juniper Networks Inc. -# 2014, Patrik Bok -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_srx_cluster -author: Patrik Bok, Ashley Burston, Rick Sherman, Juniper Networks -version_added: "1.2.0" -short_description: Create an srx chassis cluster for cluster capable srx running Junos OS. -description: - - Create an srx chassis cluster and reboot the device. - The device must be capable of forming an srx cluster and have the correct - cables installed. -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - console: - description: - - SERIAL or TERMINAL-SERVER port setting, per use with - the B(netconify) utility - required: false - default: None - notes: - - ex: "--telnet=termserv101,2000" - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - cluster_enable: - description: - - yes/true - set device to cluster mode (specify cluster_id and node) - - no/false - set device to stand alone mode (disable cluster mode) - required: true - choices: ['true','false','yes','no'] - cluster_id: - description: - - set to the cluster id , required for cluster_enable=YES - required: false - default: None - node: - description: - - set to the node required (0 or 1) - required: false - default: None - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None -''' - -EXAMPLES = ''' --junos_srx_cluster: - host={{ inventory_hostname }} - console="--port={{ serial }}" - user=rick - passwd=password123 - cluster_enable=true - logfile=cluster.log - cluster_id={{ cluster_id }} - node={{ node_id }} - --junos_srx_cluster: - host={{ inventory_hostname }} - user=rick - passwd=password123 - cluster_enable=false - logfile=cluster.log - -# over console server connection using PyEZ >= 2.0 --junos_srx_cluster: - host={{ inventory_hostname }} - user=rick - passwd=password123 - mode="telnet" - port=7032 - cluster_enable=true - logfile=cluster.log -''' - -import os -import logging -from distutils.version import LooseVersion - - -def main(): - - module = AnsibleModule( - argument_spec=dict( - host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - console=dict(required=False, default=None), # param to netconify - cluster_enable=dict(required=True, type='bool', choices=BOOLEANS, default=None), - cluster_id=dict(required=False, default=None), - node=dict(required=False, default=None), - logfile=dict(required=False, default=None)), - supports_check_mode=False) - - args = module.params - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'SRX_Cluster:' + args['host'] - - def log_notify(self, event, message): - logging.info("%s:%s" % (event, message)) - use_notifier = log_notify - else: - def silent_notify(self, event, message): - pass - use_notifier = silent_notify - - cluster = module.boolean(module.params['cluster_enable']) - - if args.get('console') is None: - # via NETCONF - logging.info('CONSOLE: no console parameter, using NETCONF') - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False) - try: - logging.info('LOGIN: host={0} port={1}'.format(args['host'], args['port'])) - dev.open() - logging.info('LOGIN: OK') - except Exception as err: - msg = 'unable to connect to {0}:{1}: {2}'.format(args['host'], args['port'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - return - # --- UNREACHABLE --- - - logging.info('SRX cluster: invoking command') - if cluster is True: - if (args['cluster_id'] and args['node']) is not None: - do_it = dev.rpc.set_chassis_cluster_enable(cluster_id=args['cluster_id'], node=args['node'], reboot=True) - logging.info('Device message: {0}'.format(do_it.getparent().findtext('.//output').strip())) - results = {"changed": True, "reboot": True} - else: - msg = 'FAIL: Cluster and Node ID required with enable_cluster=YES' - logging.error(msg) - dev.close() - results = {'changed': False, 'msg': msg} - module.fail_json(**results) - else: - do_it = dev.rpc.set_chassis_cluster_disable(reboot=True, normalize=True) - if do_it.tag == 'output': - msg = do_it.text - else: - msg = do_it.findtext('.//message') - logging.info('Device message: {0}'.format(msg)) - results = {'changed': True, 'reboot': True} - else: - # connection using NETCONIFY - try: - from netconify.cmdo import netconifyCmdo - from netconify.constants import version - if not LooseVersion(version) >= LooseVersion('1.0.1'): - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - except ImportError: - module.fail_json(msg='junos-netconify >= 1.0.1 is required for this module') - nc_args = [] - nc_args.append(args['console']) - if cluster is True: - if (args['cluster_id'] and args['node']) is not None: - nc_args.append('--srx_cluster=' + args['cluster_id'] + ':' + args['node']) - else: - msg = 'FAIL: Cluster and Node ID required with enable_cluster=YES' - logging.error(msg) - results = {'changed': False, 'msg': msg} - module.fail_json(**results) - else: - nc_args.append('--srx_cluster_disable') - if args['user'] is not None: - nc_args.append('--user=' + args['user']) - if args['passwd'] is not None: - nc_args.append('--passwd=' + args['passwd']) - logging.info('Status: passing argument to netconify: {}'.format(nc_args)) - nc = netconifyCmdo(notify=use_notifier) - nc.run(nc_args) - results = {'changed': True, 'reboot': True} - - if cluster is True: - logging.info('Cluster set...device rebooting... DONE') - else: - logging.info('Cluster disabled...device rebooting... DONE') - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/version.py b/version.py index 8016f73d..d7a7d41f 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev5" -DATE = "2017-Oct-30" +VERSION = "2.0.0.dev6" +DATE = "2017-Nov-01" From ac1ebf6a2879aca70d3743d5497d737b98a9570d Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 2 Nov 2017 08:15:38 -0600 Subject: [PATCH 182/426] Handle all Ansible aliases for bool for junos_shutdown reboot option. Fixes #293. (#294) --- action_plugins/_junos_shutdown.py | 9 ++++-- module_utils/juniper_junos_common.py | 43 +++++++++++++++++++++------- version.py | 4 +-- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/action_plugins/_junos_shutdown.py b/action_plugins/_junos_shutdown.py index de8d4267..6e294150 100755 --- a/action_plugins/_junos_shutdown.py +++ b/action_plugins/_junos_shutdown.py @@ -70,12 +70,17 @@ def run(self, tmp=None, task_vars=None): # argument for the junos_shutdown module. if 'reboot' in self._task.args: reboot = self._task.args.pop('reboot') - if reboot is True: + if reboot is not None and self.convert_to_bool(reboot) is True: # Translate to action="reboot" self._task.args['action'] = 'reboot' - else: + elif reboot is None or self.convert_to_bool(reboot) is False: # Translate to action="shutdown" self._task.args['action'] = 'shutdown' + else: + # This isn't a valid value for action/reboot + # We'll pass it through and the module will complain + # appropriately. + self._task.args['action'] = reboot else: # This isn't a valid value for action/shutdown # We'll pass it through and the module will complain diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index eae4123f..e715ec81 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -112,6 +112,27 @@ "http://jxmlease.readthedocs.io/en/stable/install.html" +def convert_to_bool_func(arg): + """Try converting arg to a bool value using Ansible's aliases for bool. + + Args: + arg: The value to convert. + + Returns: + A boolean value if successfully converted, or None if not. + """ + if arg is None or type(arg) == bool: + return arg + if isinstance(arg, basestring): + arg = arg.lower() + if arg in BOOLEANS_TRUE: + return True + elif arg in BOOLEANS_FALSE: + return False + else: + return None + + class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -797,16 +818,7 @@ def convert_to_bool(self, arg): Returns: A boolean value if successfully converted, or None if not. """ - if arg is None or type(arg) == bool: - return arg - if isinstance(arg, basestring): - arg = arg.lower() - if arg in BOOLEANS_TRUE: - return True - elif arg in BOOLEANS_FALSE: - return False - else: - return None + return convert_to_bool_func(arg) def parse_arg_to_list_of_dicts(self, option_name, @@ -1620,3 +1632,14 @@ def run(self, tmp=None, task_vars=None): # Call the parent action module. return super(JuniperJunosActionModule, self).run(tmp, task_vars) + + def convert_to_bool(self, arg): + """Try converting arg to a bool value using Ansible's aliases for bool. + + Args: + arg: The value to convert. + + Returns: + A boolean value if successfully converted, or None if not. + """ + return convert_to_bool_func(arg) diff --git a/version.py b/version.py index d7a7d41f..04a6815c 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev6" -DATE = "2017-Nov-01" +VERSION = "2.0.0.dev7" +DATE = "2017-Nov-02" From 1df1d40a27f0823b28a84dc422e0d34d561b5e76 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 2 Nov 2017 19:37:47 -0600 Subject: [PATCH 183/426] Rewrite junos_get_table to juniper_junos_table (#295) * Rewrite junos_get_table to juniper_junos_table * Delete old junos_get_table file completely. --- action_plugins/_junos_get_table.py | 1 + action_plugins/juniper_junos_table.py | 1 + library/_junos_get_table.py | 1 + library/juniper_junos_table.py | 515 ++++++++++++++++++++++++++ library/junos_get_table | 268 -------------- module_utils/juniper_junos_common.py | 66 +++- requirements.txt | 3 +- version.py | 2 +- 8 files changed, 586 insertions(+), 271 deletions(-) create mode 120000 action_plugins/_junos_get_table.py create mode 120000 action_plugins/juniper_junos_table.py create mode 120000 library/_junos_get_table.py create mode 100644 library/juniper_junos_table.py delete mode 100644 library/junos_get_table diff --git a/action_plugins/_junos_get_table.py b/action_plugins/_junos_get_table.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_get_table.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_table.py b/action_plugins/juniper_junos_table.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_table.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_get_table.py b/library/_junos_get_table.py new file mode 120000 index 00000000..32df1a46 --- /dev/null +++ b/library/_junos_get_table.py @@ -0,0 +1 @@ +juniper_junos_table.py \ No newline at end of file diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py new file mode 100644 index 00000000..526e4fdf --- /dev/null +++ b/library/juniper_junos_table.py @@ -0,0 +1,515 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright 2016 Jason Edelman +# Network to Code, LLC +# +# Copyright (c) 2017, Juniper Networks Inc. +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_table +version_added: "2.0.0" # of Juniper.junos role +author: "Jason Edelman (@jedelman8) updated by Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Retrieve data from a Junos device using table/views. +description: + - Retrieve data from a Junos device using PyEZ's operational table/views. + This module may be used with the tables/views which are included in the + PyEZ distribution or it may be used with user-defined tables/views. + NOTE: The module only works with operational tables/views; it does not + work with configuration tables/views. + +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common + file: + description: + - Name of the YAML file, relative to the I(path) option, that contains + the table/view definition. The file name must end with the '.yml' or + '.yaml' extension. + required: true + default: none + type: 'path' + table: + description: + - Name of the PyEZ table used to retrieve data. If not specified, + defaults to the name of the table defined in the I(file) option. Any + table names in I(file) which begin with '_' are ignored. If more than + one table is defined in I(file), the module fails with an error + message. In this case, you must manually specify the name of the table + by setting this option. + required: false + default: The name of the table defined in the I(file) option. + type: 'str' + path: + description: + - The directory containing the YAML table/view definition file as + specified by the I(file) option. The default value is the op directory + in jnpr.junos.op. This is the directory containing the table/view + definitions which are included in the PyEZ distribution. + required: false + default: op directory in jnpr.junos.op + type: 'path' + aliases: + - directory + - dir + kwargs: + description: + - Optional keyword arguments and values to the table's get() method. The + value of this option is a dictionary of keywords and values which are + used to refine the data return from performing a get() on the table. + The exact keywords and values which are supported are specific to the + table's definition and the underlying RPC which the table invokes. + required: false + default: none + type: 'dict' + aliases: + - kwarg + - args + - arg + response_type: + description: + - Defines the format of data returned by the module. See RETURN. + The value of the I(resource) key in the module's response is either + a list of dictionaries C(list_of_dicts) or PyEZ's native return + format C(juniper_items). Because Ansible module's may only return JSON + data, PyEZ's native return format C(juniper_items) is translated into + a list of lists. + required: false + default: 'list_of_dicts' + choices: ['list_of_dicts', 'juniper_items'] + type: 'str' +''' + +EXAMPLES = ''' +--- +- name: Retrieve data from a Junos device using a PyEZ table/view. + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table + juniper_junos_table: + file: "lldp.yml" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve routes within 10.0.0/8 + juniper_junos_table: + file: "routes.yml" + table: "RouteTable" + kwargs: + destination: "10.0.0.0/8" + response_type: "juniper_items" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve from custom table in playbook directory + juniper_junos_table: + file: "fpc.yaml" + path: "." + register: response + - name: Print response + debug: + var: response + +# Document connection examples +# Document authentication examples +# Document logging examples +# extends_documentation_fragment: juniper_junos_common +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating a summary of the result. + returned: always + type: str +resource: + description: + - The items retrieved by the table/view. + returned: success + type: list of dicts if I(response_type) is C(list_of_dicts) or list of + lists if I(respsonse_type) is C(juniper_items). + sample: + # when response_type == 'list_of_dicts' + [ + { + "local_int": "ge-0/0/3", + "local_parent": "-", + "remote_chassis_id": "00:05:86:08:d4:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/0", + "remote_sysname": "r5", + "remote_type": "Mac address" + }, + { + "local_int": "ge-0/0/0", + "local_parent": "-", + "remote_chassis_id": "00:05:86:18:f3:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/2", + "remote_sysname": "r4", + "remote_type": "Mac address" + } + ] + # when response_type == 'juniper_items' + [ + [ + "ge-0/0/3", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/0" + ], + [ + "remote_chassis_id", + "00:05:86:08:d4:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/3" + ], + [ + "remote_sysname", + "r5" + ] + ] + ], + [ + "ge-0/0/0", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/2" + ], + [ + "remote_chassis_id", + "00:05:86:18:f3:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/0" + ], + [ + "remote_sysname", + "r4" + ] + ] + ] + ] +changed: + description: Indicates if the device's configuration has changed. Since this + module doesn't change the operational or configuration state + of the device, the value is always set to false. + returned: success + type: bool +failed: + description: Indicates if the task failed. + returned: always + type: bool +''' + +# Standard library imports +import os.path + +# Constants +RESPONSE_CHOICES = ['list_of_dicts', 'juniper_items'] + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def expand_items(module, data): + """Recursively expand any table items + """ + resources = [] + # data.items() is a list of tuples + for table_key, table_fields in data.items(): + # sample: + # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), + # ('neighbor', 'vmx2')] + # table_key - element 0 is the key from the Table - not using at all + # table_fields - element 1 is also a list of tuples + temp = [] + for key, value in table_fields: + # calling it normalized value because YOU/WE created the keys + if value and isinstance(value, module.pyez_factory_table.Table): + value = expand_items(module, value) + temp.append((key, value)) + resources.append((table_key, temp)) + return resources + + +def juniper_items_to_list_of_dicts(module, data): + """Recursively convert Juniper PyEZ Table/View items to list of dicts. + """ + resources = [] + # data.items() is a list of tuples + for table_key, table_fields in data.items(): + # sample: + # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), + # ('neighbor', 'vmx2')] + # table_key - element 0 is the key from the Table - not using at all + # table_fields - element 1 is also a list of tuples + temp = {} + for key, value in table_fields: + if (value and isinstance(value, module.pyez_factory_table.Table)): + value = juniper_items_to_list_of_dicts(module, value) + temp[key] = value + resources.append(temp) + return resources + + +def main(): + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + file=dict(type='path', + required=True, + default=None), + table=dict(type='str', + required=False, + default=None), + path=dict(type='path', + required=False, + aliases=['directory', 'dir'], + default=None), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='dict', + default=None), + response_type=dict(choices=RESPONSE_CHOICES, + type='str', + required=False, + default='list_of_dicts'), + ), + # Check mode is implemented. + supports_check_mode=True + ) + + # Straight from params + file = junos_module.params.get('file') + table = junos_module.params.get('table') + path = junos_module.params.get('path') + kwargs = junos_module.params.get('kwargs') + response_type = junos_module.params.get('response_type') + + if not file.endswith('.yml') and not file.endswith('.yaml'): + junos_module.fail_json(msg='The value of the file option must end ' + 'with the .yml or .yaml extension') + + # If needed, get the default path + if path is None: + path = os.path.dirname( + os.path.abspath(junos_module.pyez_op_table.__file__)) + + # file_name is path + file + file_name = os.path.join(path, file) + + junos_module.logger.debug("Attempting to open: %s.", file_name) + try: + with open(file_name, 'r') as fp: + try: + junos_module.logger.debug("Attempting to parse YAML from : " + "%s.", file_name) + table_view = junos_module.yaml.load(fp) + junos_module.logger.debug("YAML from %s successfully parsed.", + file_name) + except junos_module.yaml.YAMLError as ex: + junos_module.fail_json(msg='Failed parsing YAML file %s. ' + 'Error: %s' % (file_name, str(ex))) + except IOError: + junos_module.fail_json(msg='The file name %s could not be opened for' + 'reading.' % (file_name)) + junos_module.logger.debug("%s successfully read.", file_name) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': '', + 'changed': False, + 'failed': True} + + # Default to the table defined in file_name. + # Ignore table names which begin with an underscore. + if table is None: + for key in table_view: + if not key.startswith('_') and 'Table' in key: + if table is not None: + junos_module.fail_json( + msg='The file name %s contains multiple table ' + 'definitions. Specify the desired table with the ' + 'table option.' % (file_name)) + table = key + + if table is None: + junos_module.fail_json( + msg='No table definition was found in the %s file. Specify a ' + 'value for the file option which contains a valid table/view ' + 'definition.' % (file_name)) + junos_module.logger.debug("Table: %s", table) + + try: + loader = \ + junos_module.pyez_factory_loader.FactoryLoader().load(table_view) + junos_module.logger.debug("Loader created successfully.") + except Exception as ex: + junos_module.fail_json(msg='Unable to create a table loader from the ' + '%s file. Error: %s' % (file_name, str(ex))) + try: + data = loader[table](junos_module.dev) + junos_module.logger.debug("Table %s created successfully.", table) + if kwargs is None: + data.get() + else: + data.get(**kwargs) + junos_module.logger.debug("Data retrieved from %s successfully.", + table) + except KeyError: + junos_module.fail_json(msg='Unable to find table %s in the ' + '%s file.' % (table, file_name)) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.fail_json(msg='Unable to retrieve data from table %s. ' + 'Error: %s' % (table, str(ex))) + + if data is not None: + try: + len_data = len(data) + except Exception as ex: + junos_module.fail_json(msg='Unable to parse table %s data into ' + 'items. Error: %s' % (table, str(ex))) + junos_module.logger.debug('Successfully retrieved %d items from %s.', + len_data, table) + results['msg'] = 'Successfully retrieved %d items from %s.' % \ + (len_data, table) + + if response_type == 'list_of_dicts': + junos_module.logger.debug('Converting data to list of dicts.') + resource = juniper_items_to_list_of_dicts(junos_module, data) + else: + resource = expand_items(junos_module, data) + + # If we made it this far, everything was successful. + results['failed'] = False + results['resource'] = resource + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/junos_get_table b/library/junos_get_table deleted file mode 100644 index 45141c86..00000000 --- a/library/junos_get_table +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2016 Jason Edelman -# Network to Code, LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DOCUMENTATION = ''' ---- -module: junos_get_table -author: Jason Edelman (@jedelman8) -version_added: "1.9.0" -short_description: Retrieve data from a Junos device using Tables/Views -description: - - Retrieve data from a Junos device using Tables/Views -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - table: - description: - - Name of PyEZ Table - required: false - default: None - file: - description: - - YAML file that has the table specified in table parameter - required: false - default: None - path: - description: - - Path of location of the YAML file - required: false - default: op directory in jnpr.junos.op - response_type: - description: - - Option to change how data is returned from the - module. Either list of dictionaries or the - Juniper PyEZ default (list of tuples, which becomes - lists by the time it gets to Ansible) - required: false - options: ['list_of_dicts', 'juniper_items'] - default: list_of_dicts - -''' - -EXAMPLES = ''' -# GET NEIGHBOR INFO USING STD LLDP TABLE -- junos_get_table: table=LLDPNeighborTable file=lldp.yml host={{ inventory_hostname }} user={{ un }} passwd={{ pwd }} - -# GET NEIGHBOR INFO USING CUSTOM LLDP TABLE IN CUSTOM PATH -- junos_get_table: table=NTCNeighborTable path=tables/ file=ntclldp.yaml host={{ inventory_hostname }} user={{ un }} passwd={{ pwd }} - -# -- name: Table/View example via console server connection using PyEZ >= 2.0 - junos_get_table: - table=RouteTable - file=routes.yml - host={{ inventory_hostname }} - port=7016 - mode='telnet' -''' - -RETURN = ''' - -resource: - description: Dictionary of facts - returned: always - type: list of dictionaries or list of tuples (default items from PyEZ) - sample: - [ - { - "neighbor_interface": "fxp0", - "local_interface": "fxp0", - "neighbor": "vmx2" - }, - { - "neighbor_interface": "ge-0/0/2", - "local_interface": "ge-0/0/2", - "neighbor": "vmx2" - }, - { - "neighbor_interface": "fxp0", - "local_interface": "fxp0", - "neighbor": "vmx3" - } - ] - -''' - -from distutils.version import LooseVersion -import logging -import os -from lxml import etree -from lxml.builder import E -import yaml - -import_err_message = None -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - import jnpr.junos.op as tables_dir - from jnpr.junos.factory.factory_loader import FactoryLoader - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - import_err_message = 'junos-eznc >= 1.2.2 is required for this module' -except ImportError as ex: - import_err_message = 'ImportError: %s' % ex.message - - -TABLE_PATH = os.path.dirname(os.path.abspath(tables_dir.__file__)) -CHOICES = ['list_of_dicts', 'juniper_items'] - - -def juniper_items_to_list_of_dicts(data): - """Convert Juniper PyEZ Table/View items to list of dictionaries - """ - - list_of_resources = [] - # data.items() is a list of tuples - for table_key, table_fields in data.items(): - # sample: - # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), - # ('neighbor', 'vmx2')] - # table_key - element 0 is the key from the Table - not using at all - # table_fields - element 1 is also a list of uples - temp = {} - for normalized_key, normalized_value in table_fields: - # calling it normalized value because YOU/WE created the keys - if normalized_value and not isinstance(normalized_value, (str, list, int, dict)): - normalized_value = juniper_items_to_list_of_dicts(normalized_value) - temp[normalized_key] = normalized_value - list_of_resources.append(temp) - return list_of_resources - - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, - default=None), # host or ipaddr - user=dict(required=False, - default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - logfile=dict(required=False, default=None), - file=dict(required=True, default=None), - path=dict(required=False, default=TABLE_PATH), - table=dict(required=True, default=None), - response_type=dict(choices=CHOICES, - default='list_of_dicts') - ), - supports_check_mode=False) - - args = module.params - - if import_err_message is not None: - module.fail_json(msg=import_err_message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection') - - logfile = args['logfile'] - response_type = args['response_type'] - - if not args['file'].endswith('.yml'): - module.fail_json(msg='file must end with .yml extension') - - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'CONFIG:' + args['host'] - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], - args['host'], - args['port'])) - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - resource = [] - try: - file_name = os.path.join(args['path'], args['file']) - try: - globals().update(FactoryLoader().load( - yaml.load(open(file_name).read()))) - except IOError as err: - module.fail_json(msg='Unable to find file: {0}'.format(file_name)) - logging.info("Getting data from device") - try: - data = globals()[args['table']](dev) - except KeyError: - module.fail_json(msg='Unable to find Table in provided yaml file', - table=args['table'], file=file_name) - data.get() - if response_type == 'list_of_dicts': - resource = juniper_items_to_list_of_dicts(data) - else: - resource = data.items() - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - - module.exit_json(resource=resource) - -from ansible.module_utils.basic import * - -if __name__ == "__main__": - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index e715ec81..7c24c26c 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -69,6 +69,14 @@ except ImportError: HAS_PYEZ_CONFIG = False +try: + import jnpr.junos.op + import jnpr.junos.factory.factory_loader + import jnpr.junos.factory.table + HAS_PYEZ_OP_TABLE = True +except ImportError: + HAS_PYEZ_OP_TABLE = False + try: import jnpr.junos.exception as pyez_exception HAS_PYEZ_EXCEPTIONS = True @@ -87,6 +95,11 @@ except ImportError: HAS_JXMLEASE_VERSION = None +try: + import yaml + HAS_YAML_VERSION = yaml.__version__ +except ImportError: + HAS_YAML_VERSION = None try: # Python 2 @@ -110,7 +123,9 @@ # Installation URL for jxmlease. JXMLEASE_INSTALLATION_URL = \ "http://jxmlease.readthedocs.io/en/stable/install.html" - +# Minimum yaml version required by shared code. +MIN_YAML_VERSION = "3.08" +YAML_INSTALLATION_URL = "http://pyyaml.org/wiki/PyYAMLDocumentation" def convert_to_bool_func(arg): """Try converting arg to a bool value using Ansible's aliases for bool. @@ -453,6 +468,27 @@ class ModuleDocFragment(object): CONFIG_MODE_CHOICES = ['exclusive', 'private'] +def convert_to_bool_func(arg): + """Try converting arg to a bool value using Ansible's aliases for bool. + + Args: + arg: The value to convert. + + Returns: + A boolean value if successfully converted, or None if not. + """ + if arg is None or type(arg) == bool: + return arg + if isinstance(arg, basestring): + arg = arg.lower() + if arg in BOOLEANS_TRUE: + return True + elif arg in BOOLEANS_FALSE: + return False + else: + return None + + class JuniperJunosModule(AnsibleModule): """A subclass of AnsibleModule used by all juniper_junos_* modules. @@ -480,6 +516,7 @@ def __init__(self, min_pyez_version=MIN_PYEZ_VERSION, min_lxml_etree_version=MIN_LXML_ETREE_VERSION, min_jxmlease_version=MIN_JXMLEASE_VERSION, + min_yaml_version=MIN_YAML_VERSION, **kwargs): """Initialize a new JuniperJunosModule instance. @@ -541,7 +578,11 @@ def __init__(self, check_device=True, check_sw=True, check_config=True, + check_op_table=True, check_exception=True) + self.pyez_factory_loader = jnpr.junos.factory.factory_loader + self.pyez_factory_table = jnpr.junos.factory.table + self.pyez_op_table = jnpr.junos.op self.pyez_exception = pyez_exception # Check LXML Etree self.check_lxml_etree(min_lxml_etree_version) @@ -549,6 +590,9 @@ def __init__(self, # Check jxmlease self.check_jxmlease(min_jxmlease_version) self.jxmlease = jxmlease + # Check yaml + self.check_yaml(min_yaml_version) + self.yaml = yaml # Setup logging. self.logger = self._setup_logging() # Open the PyEZ connection @@ -742,6 +786,7 @@ def check_pyez(self, minimum=None, check_device=False, check_sw=False, check_config=False, + check_op_table=False, check_exception=False): """Check PyEZ is available and version is >= minimum. @@ -775,6 +820,11 @@ def check_pyez(self, minimum=None, self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' 'the jnpr.junos.utils.config class could ' 'not be imported.') + if check_op_table is True: + if HAS_PYEZ_OP_TABLE is False: + self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' + 'the jnpr.junos.op class could not be ' + 'imported.') if check_exception is True: if HAS_PYEZ_EXCEPTIONS is False: self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' @@ -795,6 +845,20 @@ def check_jxmlease(self, minimum=None): self._check_library('jxmlease', HAS_JXMLEASE_VERSION, JXMLEASE_INSTALLATION_URL, minimum=minimum) + def check_yaml(self, minimum=None): + """Check yaml is available and version is >= minimum. + + Args: + minimum: The minimum PyYAML version required. + Default = None which means no version check. + + Failures: + - yaml not installed. + - yaml version < minimum. + """ + self._check_library('yaml', HAS_YAML_VERSION, + YAML_INSTALLATION_URL, minimum=minimum) + def check_lxml_etree(self, minimum=None): """Check lxml etree is available and version is >= minimum. diff --git a/requirements.txt b/requirements.txt index ca1d307c..702a4553 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ junos-eznc>=2.1.7 jxmlease>=1.0.1 -lxml>3.2.4 \ No newline at end of file +lxml>3.2.4 +yaml>=3.08 \ No newline at end of file diff --git a/version.py b/version.py index 04a6815c..38debc24 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev7" +VERSION = "2.0.0.dev8" DATE = "2017-Nov-02" From 2d682bd29b41ed6b79a52b400edb696e5c2e4cbd Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sat, 4 Nov 2017 19:59:54 -0600 Subject: [PATCH 184/426] Rewrite of junos_install_os to juniper_junos_software. (#296) --- action_plugins/_junos_install_os.py | 1 + action_plugins/juniper_junos_software.py | 1 + library/_junos_install_os.py | 1 + library/juniper_junos_config.py | 2 +- library/juniper_junos_software.py | 797 +++++++++++++++++++++++ library/junos_install_os | 317 --------- 6 files changed, 801 insertions(+), 318 deletions(-) create mode 120000 action_plugins/_junos_install_os.py create mode 120000 action_plugins/juniper_junos_software.py create mode 120000 library/_junos_install_os.py create mode 100644 library/juniper_junos_software.py delete mode 100644 library/junos_install_os diff --git a/action_plugins/_junos_install_os.py b/action_plugins/_junos_install_os.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_install_os.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_software.py b/action_plugins/juniper_junos_software.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_software.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_install_os.py b/library/_junos_install_os.py new file mode 120000 index 00000000..0a63f359 --- /dev/null +++ b/library/_junos_install_os.py @@ -0,0 +1 @@ +juniper_junos_software.py \ No newline at end of file diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index 45fcabc9..a2fcc21f 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -845,7 +845,7 @@ def main(): type='int', default=None) ), - # Mutually excluisive options. + # Mutually exclusive options. mutually_exclusive=[['load', 'rollback'], ['src', 'lines', 'template', 'url'], ['diffs_file', 'dest_dir'], diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py new file mode 100644 index 00000000..466eadd3 --- /dev/null +++ b/library/juniper_junos_software.py @@ -0,0 +1,797 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_software +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Jeremy Schulman, Stacy Smith (@stacywsmith)" +short_description: Install software on a Junos device +description: + - Install a Junos OS image, or other software package, on a Junos device. + This action is generally equivalent to the C(request system software add) + operational-mode CLI command. + - The steps performed are: + 1) Compare the currently installed Junos version to the desired version + specified by the I(version) option. If the current and desired versions + are the same, stop and return I(changed) with a value of C(false). If + running in check mode, and the current and desired versions differ, + stop and return I(changed) with a value of C(true). Otherwise, proceed. + 2) If the I(local_package) option is specified, compute the MD5 checksum + of the I(local_package) file on the local Ansible control machine. + 3) Check if the file exists at the I(remote_package) location on the target + Junos device. If so, compute the MD5 checksum of the file on the target + Junos device. + 4) If the checksums computed in steps 2 and 3 differ, or if the + I(remote_package) file does not exist on the target Junos device, then + copy the package from I(local_package) on the local Ansible control + machine to I(remote_package) on the target Junos device. + 5) Install the software pacakge from the I(remote_package) location on the + target Junos device using the specified options. + 6) If the I(reboot) option is true, the default, initiate a reboot of the + target Junos device. + - NOTE: This module does support connecting to the console of a Junos + device, but does not support copying the software package from the + local Ansible control machine to the target Junos device while + connected via the console. In this situation, the I(remote_package) + option must be specified, and the specified software package must + already exist on the target Junos device. + - NOTE: This module returns after installing the software and, optionally, + initiating a reboot of the target Junos device. It does not wait for + the reboot to complete, and it does not verify that the desired + version of software specified by the I(version) option is actually + activated on the target Junos device. It is the user's responsibility + to confirm the software installation using additional follow on + tasks in their playbook. + +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common +options: + local_package: + description: + - The path, on the local Ansible control machine, to the file + containing the software package to be installed on the target Junos + device. If this option is not specified, it is assumed that the + software package already exists on the target Junos device. In this + case, the I(remote_package) option must be specified. If this option + is specified, and a file with the same MD5 checksum doesn't already + exist at the I(remote_package) location on the target Junos device, + then the file is copied from the local Ansible control machine to the + target Junos device. + required: if I(remote_package) is not specified + default: none + type: 'path' + aliases: + - package + remote_package: + description: + - This option may take one of two formats. It is either: + 1) A URL, from the perspective of the target Junos device, from which + the device retrieves the software package to be installed. The + acceptable formats for a URL value may be found at: + U(https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-software-formats-filenames-urls.html) + - The I(local_package) and I(no_copy) options must not be specified. + 2) A file path, on the taget Junos device, to the software package. + - If the I(local_package) option is also specified, and the + I(no_copy) option is C(false), the software package will be copied + from I(local_package) to I(remote_package), if necessary. + - If the I(no_copy) option is C(true) or the I(local_package) option + is not specified, then the file specified by this option must + already exist on the target Junos device. + - If this option is not specified, it is assumed that the software + package will be copied into the C(/var/tmp) directory on the + target Junos device using the filename portion of the + I(local_package) option. In this case, the I(local_package) option + must be specified. + - Specifying the I(remote_package) option and not specifying the + I(local_package) option is equivalent to specifying the + I(local_package) option and the I(no_copy) option. In this case, + you no longer have to explicitly specify the I(no_copy) option. + - If the I(remote_package) value is a directory (ends with /), then + the filename portion of I(local_package) will be appended to the + I(remote_package) value. + - If the I(remote_package) value is a file (does not end with /), + then the filename portion of I(remote_package) must be the same as + the filename portion of I(local_package). + required: if I(local_package) is not specified + default: '/var/tmp/' + filename portion of I(local_package) + type: 'path' + version: + description: + - The version of software contained in the file specified by the + I(local_package) and/or I(remote_package) options. This value should + match the Junos version which will be reported by the device once the + new software is installed. If the device is already running a version + of software which matches the I(version) option value, the software + install is not necessary. In this case the module returns a I(changed) + value of C(false) and an I(failed) value of C(false) and does not + attempt to perform the software install. + required: false + default: Attempt to extract the version from the file name specified by + the I(local_package) or I(remote_package) option values IF the + package appears to be a Junos software package. Otherwise, None. + type: 'str' + aliases: + - target_version + - new_version + - desired_version + no_copy: + description: + - Indicates if the file containing the software package should be copied + from the I(local_package) location on the local Ansible control + machine to the I(remote_package) location on the target Junos device. + If the value is C(true), or if the I(local_package) option is not + specified, then the copy is skipped and the file must already exist + at the I(remote_package) location on the target Junos device. + required: false + default: false + type: 'bool' + reboot: + description: + - Indicates if the target Junos device should be rebooted after + performing the software install. + required: false + default: true + type: 'bool' + reboot_pause: + description: + - The amount of time, in seconds, to wait after the reboot is issued + before the module returns. This gives time for the reboot to begin. The + default value of 10 seconds is designed to ensure the device is no + longer reachable (because the reboot has begun) when the next task + begins. The value must be an integer greater than or equal to 0. + required: false + default: 10 + type: 'int' + issu: + description: + - Indicates if a unified in-service software upgrade (ISSU) should be + attempted. ISSU enables the upgrade between two different + Junos OS releases with no control plane disruption and minimal data + plane traffic disruption. + - In order for an ISSU to succeed, ISSU must be supported. This includes + support for the current to desired Junos versions, the hardware + of the target Junos device, and the current software configuration of + the target Junos device. + - The I(issu) and I(nssu) options are mutually exclusive. + required: false + default: false + type: 'bool' + nssu: + description: + - Indicates if a non-stop software upgrade (NSSU) should be + attempted. NSSU enables the upgrade between two different + Junos OS releases with minimal data plane traffic disruption. + - NSSU is specific to EX-series Virtual Chassis systems or EX-series + stand-alone systems with redundant Routing Engines. + - In order for an NSSU to succeed, NSSU must be supported. This includes + support for the current to desired Junos versions, the hardware + of the target Junos device, and the current software configuration of + the target Junos device. + - The I(nssu) and I(issu) options are mutually exclusive. + required: false + default: false + type: 'bool' + force_host: + description: + - Forces the upgrade of the Host Software package on QFX-series devices. + required: false + default: false + type: 'bool' + validate: + description: + - Whether or not to have the target Junos device valide the current + configuration against the new software package. + required: false + default: false + type: 'bool' + cleanfs: + description: + - Whether or not to perform a "request system storage cleanup" prior to + copying or installing the software. + required: false + default: true (unless no_copy is true, then false) + type: 'bool' + all_re: + description: + - Whether or not to install the software on all Routing Engines of the + target Junos device. If true, and the device has multiple Routing + Engines, the software is installed on all Routing Engines. If false, + the software is only installed on the current Routing Engine. + required: false + default: true + type: 'bool' + vmhost: + description: + - Whether or not this is a vmhost software installation. + required: false + default: false + type: 'bool' + checksum: + description: + - The pre-calculated checksum, using the I(checksum_algorithm) of the + file specified by the I(local_package) option. Specifying this option + is simply an optimization to avoid repeatedly computing the checksum of + the I(local_package) file once for each target Junos host. + required: false + default: none + type: 'str' + checksum_algorithm: + description: + - The algorithm to use when calculating the checksum of the local and + remote software packages. + required: false + default: 'md5' + type: 'str' + checksum_timeout: + description: + - The number of seconds to wait for the calculation of the checksum to + complete on the target Junos device. + required: false + default: 300 (5 minutes) + type: 'int' + cleanfs_timeout: + description: + - The number of seconds to wait for the "request system storage cleanup" + to complete on the target Junos device. + required: false + default: 300 (5 minutes) + type: 'int' + install_timeout: + description: + - The number of seconds to wait for the software installation to + complete on the target Junos device. + required: false + default: 1800 (30 minutes) + type: 'int' + kwargs: + description: + - Additional keyword arguments and values which are passed to the + RPC used to install the software package. The + value of this option is a dictionary of keywords and values. + required: false + default: none + type: 'dict' + aliases: + - kwarg + - args + - arg +''' + +EXAMPLES = ''' +--- +# +# MODULE_EXAMPLES +# This playbook demonstrate the parameters supported by the +# juniper_junos_software module. These examples use the default connection, +# authentication and logging parameters. See the examples labeled +# CONNECTION_EXAMPLES for details on connection parameters. See the examples +# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. +# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. +# +- name: Examples of juniper_junos_software + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Execute a basic Junos software upgrade. + juniper_junos_software: + local_package: "./images/" + register: response + - name: Print the complete response. + debug: + var: response + +###### OLD EXAMPLES ########## + - junos_install_os: + host={{ inventory_hostname }} + version=12.1X46-D10.2 + package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz + logfile=/usr/local/junos/log/software.log +###### OLD EXAMPLES ########## + +# +# CONNECTION_EXAMPLES +# + +# +# AUTHENTICATION_EXAMPLES +# + +# +# LOGGING_EXAMPLES +# +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result of the software + installation. + returned: always + type: str +changed: + description: + - Indicates if the device's state has changed, or if the state would have + changed when executing in check mode. This value is set to C(true) when + the version of software currently running on the target Junos device does + not match the desired version of software specified by the I(version) + option. If the current and desired software versions match, the value + of this key is set to C(false). + returned: success + type: bool +check_mode: + description: + - Indicates whether or not the module ran in check mode. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +''' + +# Standard Library imports +import os.path +import re +import time +try: + # Python 3.x + from urllib.parse import urlparse +except ImportError: + # Python 2.x + from urlparse import urlparse + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def parse_version_from_filename(filename): + """Attempts to parse a version string from the filename of a Junos package. + + There is wide variety in the naming schemes used by Junos software + packages. This function attempts to parse the version string from the + filename, but may not be able to accurately do so. It's also not + guaranteed that the filename of a package accurately reflects the version + of software in the file. (A user may have renamed it.) + If the filename does not appear to be a Junos package (maybe some other + type of package which can be installed on Junos devices), then return None. + + Args: + filename - The filename from which to parse the version string. + + Returns: + The version string, or None if unable to parse. + """ + # Known prefixes for filenames which contain Junos software packages. + JUNOS_PACKAGE_PREFIXES = ['jbundle', 'jinstall', 'junos-install', + 'junos-srx', 'junos-vmhost-install', 'junos-vrr', + 'vmx-bundle'] + for prefix in JUNOS_PACKAGE_PREFIXES: + if filename.startswith(prefix): + # Assumes the version string will be prefixed by -. + # Assume major version will begin with two digits followed by dot. + # Assume the version string ends with the last digit in filename. + match = re.search('-(\d{2}\..*\d).*', filename) + if match is not None: + return match.group(1) + # Not a known Junos package name. + else: + return None + + +def define_progress_callback(junos_module): + """Create callback which can be passed to SW.install(progress=progress) + """ + def myprogress(_, report): + """A progress function which logs report at level INFO. + + Args: + _: The PyEZ device object. Unused because the logger already knows. + report: The string to be logged. + """ + junos_module.logger.info(report) + return myprogress + + +def main(): + CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] + + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + #Define the argument spec. + software_argument_spec=dict( + local_package=dict(required=False, + aliases=['package'], + type='path', + default=None), + remote_package=dict(required=False, + type='path', + # Default is '/var/tmp/' + filename from the + # local_package option, if set. + default=None), + version=dict(required=False, + aliases=['target_version', 'new_version', + 'desired_version'], + type='str', + # Default is determined from filename portion of + # remote_package option. + default=None), + no_copy=dict(required=False, + type='bool', + default=False), + reboot=dict(required=False, + type='bool', + default=True), + reboot_pause=dict(required=False, + type='int', + default=10), + issu=dict(required=False, + type='bool', + default=False), + nssu=dict(required=False, + type='bool', + default=False), + force_host=dict(required=False, + type='bool', + default=False), + validate=dict(required=False, + type='bool', + default=False), + cleanfs=dict(required=False, + type='bool', + default=True), + all_re=dict(required=False, + type='bool', + default=True), + vmhost=dict(required=False, + type='bool', + default=False), + checksum=dict(required=False, + type='str', + default=None), + checksum_algorithm=dict(required=False, + choices=CHECKSUM_ALGORITHM_CHOICES, + type='str', + default='md5'), + checksum_timeout=dict(required=False, + type='int', + default=300), + cleanfs_timeout=dict(required=False, + type='int', + default=300), + install_timeout=dict(required=False, + type='int', + default=1800), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='dict', + default=None), + ) + # Save keys for later. Must do because software_argument_spec gets + # modified. + option_keys = software_argument_spec.keys() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec = software_argument_spec, + # Mutually exclusive options. + mutually_exclusive=[['issu', 'nssu']], + # One of local_package and remote_package is required. + required_one_of=[['local_package', 'remote_package']], + supports_check_mode=True + ) + + # Straight from params + local_package = junos_module.params.pop('local_package') + remote_package = junos_module.params.pop('remote_package') + target_version = junos_module.params.pop('version') + no_copy = junos_module.params.pop('no_copy') + reboot = junos_module.params.pop('reboot') + reboot_pause = junos_module.params.pop('reboot_pause') + install_timeout = junos_module.params.pop('install_timeout') + cleanfs = junos_module.params.pop('cleanfs') + all_re = junos_module.params.pop('all_re') + kwargs = junos_module.params.pop('kwargs') + + url = None + remote_dir = None + if remote_package is not None: + # Is the remote package a URL? + parsed_url = urlparse(remote_package) + if parsed_url.scheme == '': + # A file on the remote host. + (remote_dir, remote_filename) = os.path.split(remote_package) + else: + url = remote_package + (_, remote_filename) = os.path.split(parsed_url.path) + else: + # Default remote_dir value + remote_dir = '/var/tmp' + remote_filename = '' + + if url is not None and local_package is not None: + junos_module.fail_json(msg='There remote_package (%s) is a URL. ' + 'The local_package option is not allowed.' % + (remote_package)) + + if url is not None and no_copy is True: + junos_module.fail_json(msg='There remote_package (%s) is a URL. ' + 'The no_copy option is not allowed.' % + (remote_package)) + + if url is None: + if local_package is not None: + # Expand out the path. + local_package = os.path.abspath(local_package) + (local_dir, local_filename) = os.path.split(local_package) + if local_filename == '': + junos_module.fail_json(msg='There is no filename component to ' + 'the local_package (%s).' % + (local_package)) + else: + # Local package was not specified, so we must assume no_copy. + no_copy = True + local_filename = None + + if no_copy is False and not os.path.isfile(local_package): + junos_module.fail_json(msg='The local_package (%s) is not a valid ' + 'file on the local Ansible control ' + 'machine.' % (local_package)) + + if remote_filename == '': + # Use the same name as local_filename + remote_filename = local_filename + + if local_filename is not None and remote_filename != local_filename: + junos_module.fail_json(msg='The filename of the remote_package ' + '(%s) must be the same as the filename ' + 'of the local_package (%s).' % + (remote_filename, local_filename)) + + # If no_copy is True, then we need to turn off cleanfs to keep from + # deleting the software package which is already present on the device. + if no_copy is True: + cleanfs = False + + if target_version is None: + target_version = parse_version_from_filename(remote_filename) + junos_module.logger.debug("New target version is: %s.", target_version) + + # Initialize the results. Assume not changed and failure until we know. + results = {'msg': '', + 'changed': False, + 'check_mode': junos_module.check_mode, + 'failed': True} + + # Check version info to see if we need to do the install. + if target_version is not None: + if all_re is True: + junos_info = junos_module.dev.facts['junos_info'] + for current_re in junos_info: + current_version = junos_info[current_re]['text'] + if target_version != current_version: + junos_module.logger.debug("Current version on %s: %s. " + "Target version: %s.", + current_version, current_re, + target_version) + results['changed'] = True + else: + current_version = junos_module.dev.facts['version'] + if target_version != current_version: + re_name = junos_module.dev.re_name + junos_module.logger.debug("Current version on %s: %s. " + "Target version: %s.", + current_version, re_name, + target_version) + results['changed'] = True + else: + # A non-Junos install. Always attempt to install. + results['changed'] = True + + # Do the install if necessary + if results['changed'] is True and not junos_module.check_mode: + junos_module.logger.debug("Beginning installation of %s.", + remote_filename) + # Calculate the install parameters + install_params = {} + if url is not None: + install_params['package'] = url + elif local_package is not None: + install_params['package'] = local_package + else: + install_params['package'] = remote_filename + if remote_dir is not None: + install_params['remote_path'] = remote_dir + install_params['progress'] = define_progress_callback(junos_module) + install_params['cleanfs'] = cleanfs + install_params['no_copy'] = no_copy + install_params['timeout'] = install_timeout + install_params['all_re'] = all_re + for key in option_keys: + value = junos_module.params.get(key) + if value is not None: + install_params[key] = value + if kwargs is not None: + install_params.update(kwargs) + try: + junos_module.logger.debug("Install parameters are: %s", + str(install_params)) + junos_module.add_sw() + ok = junos_module.sw.install(**install_params) + if ok is not True: + results['msg'] = 'Unable to install the software' + junos_module.fail_json(**results) + results['msg'] = 'Package %s successfully installed.' % \ + (install_params['package']) + junos_module.logger.debug('Package %s successfully installed.', + install_params['package']) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + results['msg'] = 'Installation failed. Error: %s' % str(ex) + junos_module.fail_json(**results) + if reboot is True: + try: + # Try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this + # happens relatively quickly. + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + junos_module.logger.debug('Initiating reboot.') + rpc = junos_module.etree.Element('request-reboot') + xpath_list = ['.//request-reboot-status'] + if all_re is True: + if (junos_module.sw._multi_RE is True and + junos_module.sw._multi_VC is False): + junos_module.etree.SubElement(rpc, + 'both-routing-engines') + # At least on some platforms stopping/rebooting both + # REs produces messages and + # messages. + xpath_list.append('..//output') + elif junos_module.sw._mixed_VC is True: + junos_module.etree.SubElement(rpc, 'all-members') + resp = junos_module.dev.rpc(rpc, + ignore_warning=True, + normalize=True) + junos_module.logger.debug("Reboot RPC executed cleanly.") + if len(xpath_list) > 0: + for xpath in xpath_list: + got = resp.findtext(xpath) + if got is not None: + results['msg'] += ' Reboot successfully initiated.' + break + else: + # This is the else clause of the for loop. + # It only gets executed if the loop finished without + # hitting the break. + results['msg'] += ' Did not find expected response ' \ + 'from reboot RPC.' + junos_module.fail_json(**results) + else: + results['msg'] += ' Reboot successfully initiated.' + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['msg'] += ' Reboot failed. It may not have been ' \ + 'initiated.' + junos_module.fail_json(**results) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] += ' Reboot succeeded.' + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) + junos_module.fail_json(**results) + junos_module.logger.debug("Reboot RPC successfully initiated.") + if reboot_pause > 0: + junos_module.logger.debug("Sleeping for %d seconds", + reboot_pause) + time.sleep(reboot_pause) + + # If we made it this far, it's success. + results['failed'] = False + + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/junos_install_os b/library/junos_install_os deleted file mode 100644 index f7b66516..00000000 --- a/library/junos_install_os +++ /dev/null @@ -1,317 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 1999-2014, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_install_os -author: Jeremy Schulman, Juniper Networks -version_added: "1.0.0" -short_description: Install a Junos OS image. -description: - - Install a Junos OS image on one or more Routing Engines. - This module supports installations on single Routing Engine devices, - MX Series routers with dual Routing Engines, and EX Series switches in - a non-mixed Virtual Chassis. This action is equivalent to performing the - Junos OS B(request system software add) operational command. - - If the existing Junos OS version matches the desired version, - no action is performed, and the "changed" attribute reports False. - If the existing version does not match, then the module performs the - following actions - - (1) Computes the MD5 checksum of the package located on the server. - (2) Copies the Junos OS software package to the device running Junos OS. - (3) Computes the MD5 checksum on the device running Junos OS and compares the two. - (4) Installs the Junos OS software package. - (5) Reboots the device (default). - - Running the module in check mode reports whether the current Junos OS - version matches the desired version. - -requirements: - - py-junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - no_copy: - description: - - Installer need to be copied or not on the device. - required: false - default: false - choices: ['true','false'] - reboot: - description: - - If set to B(yes), the device reboots - after the installation completes. - required: false - default: yes - choices: ['true','false', 'yes','no'] - issu: - description: - - If set to B(true), allows unified in-service software upgrade - (ISSU) feature enables you to upgrade between two different - Junos OS releases with no disruption on the control plane and - with minimal disruption of traffic. - required: false - default: false - choices: ['true','false', 'yes','no'] - nssu: - description: - - If set to B(true), allows nonstop software upgrade (NSSU) - enables you to upgrade the software running on a Juniper - Networks EX Series Virtual Chassis or a Juniper Networks EX - Series Ethernet Switch with redundant Routing Engines with a - single command and minimal disruption to network traffic. - required: false - default: false - choices: ['true','false', 'yes','no'] - force_host: - description: - - If set to B(true), forces the upgrade of the Host Software - package on QFX-series devices. - required: false - default: false - choices: ['true','false', 'yes','no'] - reboot_pause: - description: - - Amount of time in seconds to wait after the reboot is issued - required: false - default: "10" - version: - description: - - Junos OS version string as it would be reported by the - B(show version) command - required: true - package: - description: - - Absolute path on the local server to the Junos OS software package - required: true - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None -''' - -EXAMPLES = ''' -- junos_install_os: - host={{ inventory_hostname }} - version=12.1X46-D10.2 - package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz - logfile=/usr/local/junos/log/software.log -''' - -import logging -import re -import os -from distutils.version import LooseVersion - - -def junos_install_os(module, dev): - args = module.params - - # ------------------------------------------------------------------------- - # logging - # ------------------------------------------------------------------------- - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = args['host'] - - def do_log(msg, level='info'): - getattr(logging, level)(msg) - else: - def do_log(msg, level=None): - pass - - # ------------------------------------------------------------------------- - # check installed version against desired version - # ------------------------------------------------------------------------- - - if args['version'] is None: - # extract version string from package file - m = re.search('-(\d{2}\.\d.*)\..*$', args['package']) - args['version'] = m.group(1) - - has_ver = dev.facts['version'] - should_ver = args['version'] - need_upgrade = bool(has_ver != should_ver) - - results = dict(changed=need_upgrade, ver=dict(has=has_ver, should=should_ver)) - - if need_upgrade is False: - do_log("No need to perform upgrade: {0}".format(has_ver)) - return results - if module.check_mode is True: - do_log("upgrade REQUIRED has: {0}, shoud: {1}".format(has_ver, should_ver)) - return results - - # ------------------------------------------------------------------------- - # proceeed with software install - # ------------------------------------------------------------------------- - - from jnpr.junos.utils.sw import SW - sw = SW(dev) - - package = args['package'] - do_log("Starting the software upgrade process: {0}".format(package)) - - def update_my_progress(dev, report): - # log the progress of the installing process - do_log(report) - - sw_args = dict(progress=update_my_progress) - sw_args['no_copy'] = module.boolean(args['no_copy']) - if module.boolean(args['issu']): - sw_args['issu'] = module.boolean(args['issu']) - if module.boolean(args['nssu']): - sw_args['nssu'] = module.boolean(args['nssu']) - if module.boolean(args['force_host']): - sw_args['force_host'] = module.boolean(args['force_host']) - ok = sw.install(package, **sw_args) - - if ok is not True: - results['failed'] = True - results['msg'] = "Unable to install the software" - do_log(results['msg'], level='error') - return results - - # ------------------------------------------------------------------------- - # reboot the box if desired - # ------------------------------------------------------------------------- - - if module.boolean(args['reboot']) is True: - do_log("") - rsp = sw.reboot() - - # ------------------------------------------------------------------------- - # all done. - # ------------------------------------------------------------------------- - do_log("upgrade pending reboot cycle, please be patient.") - return results - - -def main(): - module = AnsibleModule( - argument_spec=dict( - host=dict(required=True), - package=dict(required=True), - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - version=dict(required=False, default=None), - logfile=dict(required=False, default=None), - no_copy=dict(required=False, type='bool', choices=BOOLEANS, default=False), - reboot=dict(required=False, type='bool', choices=BOOLEANS, default=True), - issu=dict(required=False, type='bool', choices=BOOLEANS, default=False), - nssu=dict(required=False, type='bool', choices=BOOLEANS, default=False), - force_host=dict(required=False, type='bool', choices=BOOLEANS, default=False), - reboot_pause=dict(required=False, type='int', default=10), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None) - ), - supports_check_mode=True - ) - args = module.params - - try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - module.fail_json(msg='junos-eznc >= 1.2.2 is required for this module') - except ImportError as ex: - module.fail_json(msg='ImportError: %s' % ex.message) - - if int(args['port']) == 23 and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for telnet connection.') - - if (module.boolean(args['issu']) or module.boolean(args['nssu'])) and \ - LooseVersion(VERSION) < LooseVersion('2.1.0'): - module.fail_json(msg='junos-eznc >= 2.1.0 is required for ISSU/NSSU support.') - - # @@@ need to verify that the package file actually exists - # @@@ before proceeding. - - dev = Device(args['host'], user=args['user'], password=args['passwd'], port=args['port'], - ssh_private_key_file=args['ssh_private_key_file']) - try: - dev.open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - module.fail_json(msg=msg) - return - - results = junos_install_os(module, dev) - results['check_mode'] = module.check_mode - dev.close() - - if not module.check_mode and results['changed'] is True: - logging.info('pausing: {0}'.format(args['reboot_pause'])) - import time - time.sleep(args['reboot_pause']) - logging.info('process completed OK.') - - module.exit_json(**results) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() From 2f5dc324b53ae00fef0d94e98ded77b7d27da3d7 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sun, 5 Nov 2017 18:53:06 -0700 Subject: [PATCH 185/426] Allow "show configuration | display set" from junos_cli and juniper_junos_cmmand. Fixes #297. (#298) --- library/juniper_junos_command.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index 314a1adb..f76a44d8 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -315,7 +315,7 @@ type: list of dict ''' -import os.path +import sys def import_juniper_junos_common(): @@ -410,6 +410,12 @@ def main(): pipe_index = command.find('|') if (pipe_index != -1 and command[pipe_index:].strip() != 'display xml rpc'): + # Allow "show configuration | display set" + if ('show configuration' in command and + 'display set' in command[pipe_index:] and + '|' not in command[pipe_index+1:]): + continue + # Any other "| display " should use the format option instead. for valid_format in juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES: if 'display ' + valid_format in command[pipe_index:]: junos_module.fail_json( @@ -417,6 +423,8 @@ def main(): '(%s) is not supported. Use format: "%s" ' 'instead.' % (command[pipe_index:], command, valid_format)) + # Any other "| " is going to produce an error anyway, so fail + # with a meaningful message. junos_module.fail_json(msg='The pipe modifier (%s) in the command ' '(%s) is not supported.' % (command[pipe_index:], command)) @@ -480,11 +488,24 @@ def main(): elif (resp, junos_module.etree._Element): # Handle the output based on format if format == 'text': - text_output = resp.text - junos_module.logger.debug('Text output set.') + if resp.tag in ['output', 'rpc-reply']: + text_output = resp.text + junos_module.logger.debug('Text output set.') + elif resp.tag == 'configuration-information': + text_output = resp.findtext('configuration-output') + junos_module.logger.debug('Text configuration output set.') + else: + result['msg'] = 'Unexpected text response tag: %s.' % ( + (resp.tag)) + results.append(result) + junos_module.logger.debug('Unexpected text response tag ' + '%s.', resp.tag) + continue elif format == 'xml': + encode = None if sys.version < '3' else 'unicode' text_output = junos_module.etree.tostring(resp, - pretty_print=True) + pretty_print=True, + encoding=encode) parsed_output = junos_module.jxmlease.parse_etree(resp) junos_module.logger.debug('XML output set.') elif format == 'json': From 146efe68c284f615d20670f039309d89bfb1dbcc Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sun, 5 Nov 2017 20:00:17 -0700 Subject: [PATCH 186/426] Better defaults for juniper_junos_config. (#299) When `retrieve` is set and `load` and `rollback` are not set, then `check`, `diff`, and `commit` should all default to `False`. This allows a simple config retrieval (similar to the old `junos_get_config`) without performing a check, diff, and commit. --- library/juniper_junos_config.py | 30 ++++++++++++++++++++++++------ version.py | 4 ++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index a2fcc21f..86c141a9 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -304,7 +304,7 @@ check: - Perform a commit check operation. required: false - default: true + default: true (false if retrieve is set and load and rollback are not set) type: bool aliases: - check_commit @@ -312,7 +312,7 @@ diff: - Perform a configuration compare (diff) operation. required: false - default: true + default: true (false if retrieve is set and load and rollback are not set) type: bool aliases: - compare @@ -434,7 +434,7 @@ description: - Perform a commit operation. required: false - default: true + default: true (false if retrieve is set and load and rollback are not set) type: bool commit_empty_changes: description: @@ -797,11 +797,11 @@ def main(): check=dict(required=False, type='bool', aliases=['check_commit', 'commit_check'], - default=True), + default=None), diff=dict(required=False, type='bool', aliases=['compare', 'diffs'], - default=True), + default=None), diffs_file=dict(type='path', required=False, default=None), @@ -830,7 +830,7 @@ def main(): default=None), commit=dict(required=False, type='bool', - default=True), + default=None), commit_empty_changes=dict(required=False, type='bool', default=False), @@ -889,6 +889,24 @@ def main(): comment = junos_module.params.get('comment') check_commit_wait = junos_module.params.get('check_commit_wait') + # If retrieve is set and load and rollback are not set, then + # check, diff, and commit default to False. + if retrieve is not None and load is None and rollback is None: + if diff is None: + diff = False + if check is None: + check = False + if commit is None: + commit = False + # Otherwise, diff, check, and commit default to True. + else: + if diff is None: + diff = True + if check is None: + check = True + if commit is None: + commit = True + # If load is not None, must have one of src, template, url, lines if load is not None: for option in ['src', 'lines', 'template', 'url']: diff --git a/version.py b/version.py index 38debc24..4d8989a3 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev8" -DATE = "2017-Nov-02" +VERSION = "2.0.0.dev9" +DATE = "2017-Nov-05" From bf3700886df932fe43559ca7d7fd8807bbf3a50f Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 6 Nov 2017 08:00:59 -0700 Subject: [PATCH 187/426] Rewrite junos_jsnapy module to juniper_junos_jsnapy. (#300) * Rewrite junos_jsnapy module to juniper_junos_jsnapy. * Update version. * Netconify no longer used by this Ansible role. * Reverting to JSNAPy 1.1.0 because of bug in 1.2.0. --- Dockerfile | 6 +- action_plugins/_junos_jsnapy.py | 1 + action_plugins/juniper_junos_jsnapy.py | 1 + library/_junos_jsnapy.py | 1 + library/juniper_junos_jsnapy.py | 414 +++++++++++++++++++++++++ library/junos_jsnapy | 358 --------------------- module_utils/juniper_junos_common.py | 28 ++ requirements.txt | 1 + version.py | 2 +- 9 files changed, 450 insertions(+), 362 deletions(-) create mode 120000 action_plugins/_junos_jsnapy.py create mode 120000 action_plugins/juniper_junos_jsnapy.py create mode 120000 library/_junos_jsnapy.py create mode 100644 library/juniper_junos_jsnapy.py delete mode 100644 library/junos_jsnapy diff --git a/Dockerfile b/Dockerfile index 0ba2ce36..9fbcba55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,10 +23,10 @@ ADD version.py /tmp/ansible-junos-stdlib/version.py RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ apk update && apk add ca-certificates &&\ apk add build-base gcc g++ make python-dev &&\ - pip install junos-netconify &&\ + pip install --upgrade pip setuptools &&\ pip install jxmlease &&\ - pip install -q ansible==$ver_ansible &&\ - pip install -q jsnapy==$ver_jsnapy &&\ + pip install ansible==$ver_ansible &&\ + pip install jsnapy==$ver_jsnapy &&\ ansible-galaxy install --roles-path=/etc/ansible/roles Juniper.junos &&\ apk del -r --purge gcc make g++ &&\ rm -rf /source/* &&\ diff --git a/action_plugins/_junos_jsnapy.py b/action_plugins/_junos_jsnapy.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/_junos_jsnapy.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_jsnapy.py b/action_plugins/juniper_junos_jsnapy.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/action_plugins/juniper_junos_jsnapy.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/library/_junos_jsnapy.py b/library/_junos_jsnapy.py new file mode 120000 index 00000000..e14ff8d8 --- /dev/null +++ b/library/_junos_jsnapy.py @@ -0,0 +1 @@ +juniper_junos_jsnapy.py \ No newline at end of file diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py new file mode 100644 index 00000000..ddbcbe54 --- /dev/null +++ b/library/juniper_junos_jsnapy.py @@ -0,0 +1,414 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2017, Juniper Networks Inc. +# 2016, Roslan Zaki +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +module: juniper_junos_jsnapy +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Roslan Zaki, Damien Garros, & Stacy Smith (@stacywsmith)" +short_description: Execute JSNAPy tests on a Junos device +description: + - Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. + JSNAPy is documented at: U(https://github.com/Juniper/jsnapy) and + U(https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) + - This module only reports "failed" if the module encounters an error and + fails to execute the JSNAPy tests. If does NOT report "failed" if one or + more of the JSNAPy tests fail. To check the test results, register the + module's response and use the assert module to verify the expected result + in the response. (See MODULE_EXAMPLES.) + - A callback plugin which formats and prints JSNAPy test results for human + consumption is also available. This callback plugin is enabled by adding + "callback_whitelist = jsnapy" to the Ansible configuration file. + +# Document connection arguments +# Document logging arguments +extends_documentation_fragment: juniper_junos_common +options: + action: + description: + - The JSNAPy action to perform. + required: true + default: none + type: 'str' + choices: ['check', 'snapcheck', 'snap_pre', 'snap_post'] + test_files: + description: + - The filename of file(s) in the I(dir) directory. Each file contains + JSNAPy test case definitions. The I(test_files) option and the + I(config_file) option are mutually exclusive. Either the I(test_files) + option or the I(config_file) option is required. + required: false + type: 'list of path' + default: none + config_file: + description: + - The filename of a JSNAPy configuration file (in YAML format). The + I(test_files) option and the I(config_file) option are mutually + exclusive. Either the I(test_files) option or the I(config_file) + option is required. + required: false + type: 'path' + default: none + dir: + description: + - The path to the directory containing the JSNAPy test file(s) specified + by the I(test_files) option or the JSNAPy configuration file specified + by the I(config_file) option. + required: false + type: 'path' + default: '/etc/jsnapy/testfiles' + aliases: + - directory +''' + +EXAMPLES = ''' +--- +# +# MODULE_EXAMPLES +# This playbook demonstrate the parameters supported by the +# juniper_junos_jsnapy module. These examples use the default connection, +# authentication and logging parameters. See the examples labeled +# CONNECTION_EXAMPLES for details on connection parameters. See the examples +# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. +# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. +# +- name: Examples of juniper_junos_jsnapy + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: JUNOS Post Checklist + juniper_junos_jsnapy: + action: "snap_post" + config_file: "first_test.yml" + logfile: "migration_post.log" + register: test1 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test1.passPercentage == 100" + - name: Print the full test response + debug: + var: test1 + + - name: Test based on a test_file directly + juniper_junos_jsnapy: + action: "snapcheck" + test_files: "tests/test_junos_interface.yaml" + register: test2 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test2.passPercentage == 100" + - name: Print the full test response + debug: + var: test2 + + - name: "Collect Pre Snapshot" + juniper_junos_jsnapy: + action: "snap_pre" + test_files: "tests/test_loopback.yml" + + - name: "Collect Post Snapshot" + juniper_junos_jsnapy: + action: "snap_post" + test_files: "tests/test_loopback.yml" + + - name: "Check after Pre and Post Snapshots" + juniper_junos_jsnapy: + action: "check" + test_files: "tests/test_loopback.yml" + register: test3 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test3.|succeeded" + - "test3.passPercentage == 100" + - name: Print the full test response + debug: + var: test3 + +# +# CONNECTION_EXAMPLES +# + +# +# AUTHENTICATION_EXAMPLES +# + +# +# LOGGING_EXAMPLES +# +''' + +RETURN = ''' +msg: + description: + - A human-readable message indicating the result of the JSNAPy tests. + returned: always + type: str +action: + description: + - The JSNAPy action performed as specified by the I(action) option. + returned: success + type: str + + +final_result: +total_passed: +total_failed: + + +changed: + description: + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to false. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +''' + +# Standard Library imports +import os.path + + +def import_juniper_junos_common(): + """Imports the juniper_junos_common module from _module_utils_path. + + Ansible versions < 2.4 do not provide a way to package common code in a + role. This function solves that problem for juniper_junos_* modules by + reading the module arguments passed on stdin and interpreting the special + option _module_utils_path as a path to the the directory where the + juniper_junos_common module resides. It temporarily inserts this path at + the head of sys.path, imports the juniper_junos_common module, and removes + the path from sys.path. It then returns the imported juniper_junos_common + module object. All juniper_junos_* modules must include this boilerplate + function in order to import the shared juniper_junos_common module. + + Args: + None. + + Returns: + The juniper_junos_common module object. + + Raises: + ImportError: If the juniper_junos_common object can not be imported + from the path specified by the module_utils_path argument. + """ + from ansible.module_utils.basic import AnsibleModule + import sys + + juniper_junos_common = None + module = AnsibleModule( + argument_spec={ + '_module_utils_path': dict(type='path', default=None), + # Avoids a warning about not specifying no_log for passwd. + 'passwd': dict(no_log=True) + }, + # Doesn't really work due to Ansible bug. Keeping it here for when + # Ansible bug is fixed. + no_log=True, + check_invalid_arguments=False, + bypass_checks=True + ) + import_path = module.params.get('_module_utils_path') + if import_path is not None: + sys.path.insert(0, import_path) + import juniper_junos_common + del sys.path[0] + return juniper_junos_common + + +def main(): + JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] + + # Import juniper_junos_common + juniper_junos_common = import_juniper_junos_common() + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + action=dict(required=True, + choices=JSNAPY_ACTION_CHOICES, + type='str', + default=None), + test_files=dict(required=False, + type='list', + default=None), + config_file=dict(required=False, + type='path', + default=None), + dir=dict(required=False, + type='path', + aliases=['directory'], + default='/etc/jsnapy/testfiles')), + # Mutually exclusive options. + mutually_exclusive=[['test_files', 'config_file']], + # One of test_files or config_file is required. + required_one_of=[['test_files', 'config_file']], + supports_check_mode=True + ) + + # Straight from params + action = junos_module.params.get('action') + test_files = junos_module.params.get('test_files') + config_file = junos_module.params.get('config_file') + dir = junos_module.params.get('dir') + + # Initialize the results. Assume failure until we know otherwise. + results = {'msg': '', + 'action': action, + 'changed': False, + 'failed': True} + + if config_file is not None: + junos_module.logger.debug('Checking config file: %s.', config_file) + config_file_path = os.path.abspath(config_file) + config_dir_file_path = os.path.abspath(os.path.join(dir, config_file)) + if os.path.isfile(config_file_path): + data = config_file_path + elif os.path.isfile(config_dir_file_path): + data = config_dir_file_path + else: + junos_module.fail_json(msg="Unable to locate the %s config file " + "at %s or %s." % (config_file, + config_file_path, + config_dir_file_path)) + elif test_files is not None and len(test_files) > 0: + data = {'tests': []} + for test_file in test_files: + junos_module.logger.debug('Checking test file: %s.', test_file) + test_file_path = os.path.abspath(test_file) + test_dir_file_path = os.path.abspath(os.path.join(dir, test_file)) + if os.path.isfile(test_file_path): + data['tests'].append(test_file_path) + elif os.path.isfile(test_dir_file_path): + data['tests'].append(test_dir_file_path) + else: + junos_module.fail_json(msg="Unable to locate the %s test file " + "at %s or %s." % + (test_file, + test_file_path, + test_dir_file_path)) + else: + junos_module.fail_json(msg="No config_file or test_files specified.") + + try: + junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') + jsa = junos_module.jsnapy.SnapAdmin() + junos_module.logger.debug('Executing %s action.', action) + if action == 'check': + responses = jsa.check(data=data, + dev=junos_module.dev, + pre_file='PRE', + post_file='POST') + elif action == 'snapcheck': + responses = jsa.snapcheck(data=data, + dev=junos_module.dev) + elif action == 'snap_pre': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='PRE') + elif action == 'snap_post': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='POST') + else: + junos_module.fail_json(msg="Unexpected action: %s." % (action)) + junos_module.logger.debug('The %s action executed successfully.', + action) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + junos_module.fail_json(msg="Error communicating with the device: %s" % + (str(ex))) + except Exception as ex: + junos_module.fail_json(msg="Uncaught exception - please report: %s" % + (str(ex))) + + if isinstance(responses, list) and len(responses) == 1: + if action in ('snapcheck', 'check'): + for response in responses: + results['device'] = response.device + results['router'] = response.device + results['final_result'] = response.result + results['total_passed'] = response.no_passed + results['total_failed'] = response.no_failed + results['test_results'] = response.test_results + total_tests = int(response.no_passed) + int(response.no_failed) + results['total_tests'] = total_tests + pass_percentage = 0 + if total_tests > 0: + pass_percentage = ((int(response.no_passed) * 100) // + total_tests) + results['passPercentage'] = pass_percentage + results['pass_percentage'] = pass_percentage + if results['final_result'] == 'Failed': + results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + else: + results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + elif action in ('snap_pre', 'snap_post'): + results['msg'] = "The %s action successfully executed." % (action) + else: + junos_module.fail_json(msg="Unexpected JSNAPy responses. Type: %s." + "Responses: %s" % + (type(responses), str(responses))) + + # If we made it this far, it's success. + results['failed'] = False + + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/junos_jsnapy b/library/junos_jsnapy deleted file mode 100644 index 4fe6b794..00000000 --- a/library/junos_jsnapy +++ /dev/null @@ -1,358 +0,0 @@ -#!/usr/bin/env python - -#Copyright (c) 1999-2015, Juniper Networks Inc. -# 2016, Roslan Zaki -# -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -DOCUMENTATION = ''' ---- -module: junos_jsnapy -author: Roslan Zaki & Damien Garros, Juniper Networks -version_added: "1.4.0" -version_control: - 13-Apr-2016 v1.0.0 - Basic working model - -Short_description: Integrate JSNAPy to ansible. -description: - - Execute JSNAPy test from Ansible. - Attention, to not break Ansible behavior, this module only report "failed" - if the module itself fails, not if a test fails. - To check the test results you need to subscribe to the result and assert - the returned value. - An experimental Callback_Plugin for junos_jsnapy is available to provide - additional information about tests that failed. - To enable it, you need to add "callback_whitelist = jsnapy" in your ansible - configuration file. -requirements: - - junos-eznc >= 1.2.2 -options: - host: - description: - - Set to {{ inventory_hostname }} - required: true - user: - description: - - Login username - required: false - default: $USER - passwd: - description: - - Login password - required: false - default: assumes ssh-key active - port: - description: - - port number to use when connecting to the device - required: false - default: 830 - ssh_private_key_file: - description: - - This can be used if you need to provide a private key rather than - loading the key into the ssh-key-ring/environment. if your - ssh-key requires a password, then you must provide it via - **passwd** - required: false - default: None - mode: - description: - - mode of console connection (telnet/serial). If mode is not - provided SSH connection is used. - required: false - default: None - logfile: - description: - - Path on the local server where the progress status is logged - for debugging purposes - required: false - default: None - dir: - description: - - Path for the JSNAPy yaml testfiles/configuration file - required: false - default: '/etc/jsnapy/testfiles' - action: - description: - Possible actions available - - jsnapcheck - - check - - snap_pre - - snap_post - required: false - default: None - test_files: - description: - - Test files which need to executed - required: False - type: list - default: None - config_file: - description: - - The YAML configuration file for the JSNAPy tests - required: false - default: None -''' - -EXAMPLES = ''' - - name: JUNOS Post Checklist - junos_jsnapy: - host: "{{ inventory_hostname}}" - passwd: "{{ tm1_password }}" - action: "snap_post" - config_file: "first_test.yml" - logfile: "migration_post.log" - register: test1 - - - name: Check JSNAPy tests results - assert: - that: - - "test1.passPercentage == 100" - - - name: Debug jsnapy - debug: msg=test1 - ---------- - - name: Test based on a test_file directly - junos_jsnapy: - host: "{{ junos_host }}" - port: "{{ netconf_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: tests/test_junos_interface.yaml - action: snapcheck - register: test1 - - - name: Check JSNAPy tests results - assert: - that: - - "test1.passPercentage == 100" ---------- - - name: "Collect Pre Snapshot" - junos_jsnapy: - host: "{{ junos_host }}" - port: "{{ netconf_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: tests/test_loopback.yml - action: snap_pre - register: test_pre - ---------- - - name: "Collect Post Snapshot" - junos_jsnapy: - host: "{{ junos_host }}" - port: "{{ netconf_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: tests/test_loopback.yml - action: snap_post - register: test_post - ---------- - - name: "Check after PRE - POST check" - junos_jsnapy: - host: "{{ junos_host }}" - port: "{{ netconf_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: tests/test_loopback.yml - action: check - register: test_check - - - name: Check Results - assert: - that: - - test_check|succeeded - - test_check.passPercentage == 100 - -''' -from distutils.version import LooseVersion -import logging -from lxml.builder import E -import os.path -import os -import time - -import_err_message = None - -try: - from jnpr.junos import Device - from jnpr.junos.version import VERSION - from jnpr.junos.exception import RpcError - from jnpr.jsnapy import SnapAdmin - if not LooseVersion(VERSION) >= LooseVersion('1.2.2'): - import_err_message = 'junos-eznc >= 1.2.2 is required for this module' -except ImportError as ex: - import_err_message = 'ImportError: %s' % ex.message - -def jsnap_selection(dev, module): - - args = module.params - action = args['action'] - config_file = args.get('config_file') - if config_file: - config_dir = args['dir'] - config_data = config_file - # in case config file given is not full path - if not os.path.isfile(config_file): - config_data = os.path.join(config_dir, config_file) - if not os.path.isfile(config_data): - msg = 'unable to find the config file {0} (test directory:{1})'.format(config_file,args['dir']) - logging.error(msg) - module.fail_json(msg=msg) - - else: - test_files = args.get('test_files') - - test_files_validated = [] - ## Check if file is present without and with dir - for test_file in test_files: - if not os.path.isfile(test_file): - test_file_full = os.path.join(args['dir'], test_file) - if not os.path.isfile(test_file_full): - msg = 'unable to find the test file {0} (test directory:{1})'.format(test_file,args['dir']) - logging.error(msg) - module.fail_json(msg=msg) - else: - test_files_validated.append(test_file_full) - else: - test_files_validated.append(test_file) - - ## Check that we have at least one test file - if len(test_files_validated) == 0: - msg = 'The list of test file is empty, please check your input' - logging.error(msg) - module.fail_json(msg=msg) - - config_data = {'tests': test_files_validated} - - results = {'action': action} - js = SnapAdmin() - - if action == 'snapcheck': - snapValue = js.snapcheck(data=config_data, dev=dev) - elif action == 'snap_pre': - snapValue = js.snap(data=config_data, dev=dev, file_name='PRE') - elif action == 'snap_post': - snapValue = js.snap(data=config_data, dev=dev, file_name='POST') - elif action == 'check': - snapValue = js.check(data=config_data, dev=dev, pre_file='PRE', post_file='POST') - - percentagePassed = 0 - if isinstance(snapValue, (list)): - if action in ['snapcheck', 'check']: - for snapCheck in snapValue: - router = snapCheck.device - results['router'] = router - results['final_result'] = snapCheck.result - results['total_passed'] = snapCheck.no_passed - results['total_failed'] = snapCheck.no_failed - results['test_results'] = snapCheck.test_results - total_test = int(snapCheck.no_passed) + int(snapCheck.no_failed) - results['total_tests'] = total_test - if total_test != 0: - percentagePassed = (int(results['total_passed']) * 100 ) / (results['total_tests']) - results['passPercentage'] = percentagePassed - elif action in ['snap_pre', 'snap_post']: - results['changed'] = True - return results - -def main(): - - module = AnsibleModule( - argument_spec=dict(host=dict(required=True, default=None), # host or ipaddr - user=dict(required=False, default=os.getenv('USER')), - passwd=dict(required=False, default=None, no_log=True), - port=dict(required=False, default=830), - ssh_private_key_file=dict(required=False, default=None), - mode=dict(required=False, default=None), - logfile=dict(required=False, default=None), - test_files=dict(required=False, type='list', default=None), - config_file=dict(required=False, default=None), - dir=dict(required=False, default='/etc/jsnapy/testfiles'), - action=dict(required=True, choices=['check', 'snapcheck', 'snap_pre', 'snap_post'], default=None) - ), - mutually_exclusive=[['test_files', 'config_file']], - required_one_of=[['test_files', 'config_file']], - supports_check_mode=False) - - args = module.params - results = {} - - if import_err_message is not None: - module.fail_json(msg=import_err_message) - - if args['mode'] is not None and LooseVersion(VERSION) < LooseVersion('2.0.0'): - module.fail_json(msg='junos-eznc >= 2.0.0 is required for console connection.') - - logfile = args['logfile'] - if logfile is not None: - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = 'JSNAPy:' + args['host'] - - logging.info("connecting to host: {0}@{1}:{2}".format(args['user'], args['host'], args['port'])) - - try: - dev = Device(args['host'], user=args['user'], password=args['passwd'], - port=args['port'], ssh_private_key_file=args['ssh_private_key_file'], - mode=args['mode'], gather_facts=False).open() - except Exception as err: - msg = 'unable to connect to {0}: {1}'.format(args['host'], str(err)) - logging.error(msg) - module.fail_json(msg=msg) - # --- UNREACHABLE --- - - try: - logging.info("Main program: ") - data = jsnap_selection(dev, module) - - if data['action'] in ['check', 'snapcheck'] and data['final_result']=='Failed': - msg = 'Test Failed: Passed {0}, Failed {1}'.format( - data['total_passed'], data['total_failed'] - ) - logging.error(msg) - dev.close() - module.exit_json(msg=msg, **data) - - except Exception as err: - msg = 'Uncaught exception - please report: {0}'.format(str(err)) - logging.error(msg) - dev.close() - module.fail_json(msg=msg) - - dev.close() - module.exit_json(**data) - -from ansible.module_utils.basic import * - -if __name__ == '__main__': - main() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 7c24c26c..c4f4aae6 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -83,6 +83,12 @@ except ImportError: HAS_PYEZ_EXCEPTIONS = False +try: + import jnpr.jsnapy + HAS_JSNAPY_VERSION = jnpr.jsnapy.__version__ +except ImportError: + HAS_JSNAPY_VERSION = None + try: from lxml import etree HAS_LXML_ETREE_VERSION = '.'.join(map(str, etree.LXML_VERSION)) @@ -118,6 +124,10 @@ MIN_LXML_ETREE_VERSION = "3.2.4" # Installation URL for LXML. LXML_ETREE_INSTALLATION_URL = "http://lxml.de/installation.html" +# Minimum JSNAPy version required by shared code. +MIN_JSNAPY_VERSION = "1.1.0" +# Installation URL for JSNAPy. +JSNAPY_INSTALLATION_URL = "https://github.com/Juniper/jsnapy#installation" # Minimum jxmlease version required by shared code. MIN_JXMLEASE_VERSION = "1.0.1" # Installation URL for jxmlease. @@ -515,6 +525,7 @@ def __init__(self, mutually_exclusive=[], min_pyez_version=MIN_PYEZ_VERSION, min_lxml_etree_version=MIN_LXML_ETREE_VERSION, + min_jsnapy_version=MIN_JSNAPY_VERSION, min_jxmlease_version=MIN_JXMLEASE_VERSION, min_yaml_version=MIN_YAML_VERSION, **kwargs): @@ -587,6 +598,9 @@ def __init__(self, # Check LXML Etree self.check_lxml_etree(min_lxml_etree_version) self.etree = etree + # Check jsnapy + self.check_jsnapy(min_jsnapy_version) + self.jsnapy = jnpr.jsnapy # Check jxmlease self.check_jxmlease(min_jxmlease_version) self.jxmlease = jxmlease @@ -831,6 +845,20 @@ def check_pyez(self, minimum=None, 'the jnpr.junos.exception module could not ' 'be imported.') + def check_jsnapy(self, minimum=None): + """Check jsnapy is available and version is >= minimum. + + Args: + minimum: The minimum jsnapy version required. + Default = None which means no version check. + + Failures: + - jsnapy not installed. + - jsnapy version < minimum. + """ + self._check_library('jsnapy', HAS_JSNAPY_VERSION, + JSNAPY_INSTALLATION_URL, minimum=minimum) + def check_jxmlease(self, minimum=None): """Check jxmlease is available and version is >= minimum. diff --git a/requirements.txt b/requirements.txt index 702a4553..1e730b55 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ junos-eznc>=2.1.7 +jsnapy>=1.1.0 jxmlease>=1.0.1 lxml>3.2.4 yaml>=3.08 \ No newline at end of file diff --git a/version.py b/version.py index 4d8989a3..20d20a2f 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev9" +VERSION = "2.0.0.dev10" DATE = "2017-Nov-05" From 8aaa436a8df0b603c50f75fbf7544cd49ab10e1f Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Fri, 10 Nov 2017 17:58:25 -0700 Subject: [PATCH 188/426] Only require libraries if they are actually needed by a module. Fixes #301. (#302) --- Dockerfile | 2 +- library/juniper_junos_command.py | 3 +- library/juniper_junos_config.py | 3 +- library/juniper_junos_facts.py | 3 +- library/juniper_junos_jsnapy.py | 3 +- library/juniper_junos_rpc.py | 3 +- library/juniper_junos_table.py | 3 +- module_utils/juniper_junos_common.py | 182 +++++++++++++++++---------- requirements.txt | 2 +- 9 files changed, 130 insertions(+), 74 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9fbcba55..32a232e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM juniper/pyez:latest MAINTAINER Stephen Steiner ARG ver_ansible=2.4.0.0 -ARG ver_jsnapy=1.1.0 +ARG ver_jsnapy=1.2.1 WORKDIR /tmp RUN mkdir /tmp/ansible-junos-stdlib &&\ diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index f76a44d8..27834080 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -397,7 +397,8 @@ def main(): # supported. Well, that's not completely true. It does depend on the # command executed. See the I(changed) key in the RETURN documentation # for more details. - supports_check_mode=True + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, ) # Check over commands diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index 86c141a9..13ca4f2a 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -853,7 +853,8 @@ def main(): # Required together options. required_together=[['template', 'vars']], # Check mode is implemented. - supports_check_mode=True + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, ) # Do additional argument verification. diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 66a7a921..c1f4623a 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -326,7 +326,8 @@ def main(): # Since this module doesn't change the device's configuration, there is # no additional work required to support check mode. It's inherently # supported. - supports_check_mode=True + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, ) junos_module.logger.debug("Gathering facts.") diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index ddbcbe54..4759868e 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -293,7 +293,8 @@ def main(): mutually_exclusive=[['test_files', 'config_file']], # One of test_files or config_file is required. required_one_of=[['test_files', 'config_file']], - supports_check_mode=True + supports_check_mode=True, + min_jsnapy_version=juniper_junos_common.MIN_JSNAPY_VERSION, ) # Straight from params diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index 75b90a06..841f21ba 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -469,7 +469,8 @@ def main(): # supported. Well, that's not completely true. It does depend on the # RPC executed. See the I(changed) key in the RETURN documentation # for more details. - supports_check_mode=True + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, ) # Check over rpcs diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 526e4fdf..c96b9a0d 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -399,7 +399,8 @@ def main(): default='list_of_dicts'), ), # Check mode is implemented. - supports_check_mode=True + supports_check_mode=True, + min_yaml_version=juniper_junos_common.MIN_YAML_VERSION, ) # Straight from params diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index c4f4aae6..bdef9621 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -125,7 +125,7 @@ # Installation URL for LXML. LXML_ETREE_INSTALLATION_URL = "http://lxml.de/installation.html" # Minimum JSNAPy version required by shared code. -MIN_JSNAPY_VERSION = "1.1.0" +MIN_JSNAPY_VERSION = "1.2.1" # Installation URL for JSNAPy. JSNAPY_INSTALLATION_URL = "https://github.com/Juniper/jsnapy#installation" # Minimum jxmlease version required by shared code. @@ -508,15 +508,32 @@ class JuniperJunosModule(AnsibleModule): Attributes: dev: An instance of a PyEZ Device() object. - Methods: + Public Methods: exit_json: Close self.dev and call parent's exit_json(). fail_json: Close self.dev and call parent's fail_json(). check_pyez: Verify the PyEZ library is present and functional. + check_jsnapy: Verify the JSNAPy library is present and functional. + check_jxmlease: Verify the Jxmlease library is present and functional. check_lxml_etree: Verify the lxml Etree library is present and functional. - check_jxmlease: Verify the Jxmlease library is present and functional. + check_yaml: Verify the YAML library is present and functional. + convert_to_bool: Try converting to bool using aliases for bool. + parse_arg_to_list_of_dicts: Parses string_val into a list of dicts. + parse_ignore_warning_option: Parses the ignore_warning option. + parse_rollback_option: Parses the rollback option. open: Open self.dev. close: Close self.dev. + add_sw: Add an instance of jnp.junos.utils.sw.SW() to self. + open_configuration: Open cand. conf. db in exclusive or private mode. + close_configuration: Close candidate configuration database. + get_configuration: Return the device config. in the specified format. + rollback_configuration: Rollback device config. to the specified id. + check_configuration: Check the candidate configuration. + diff_configuration: Diff the candidate and committed configurations. + load_configuration: Load the candidate configuration. + commit_configuration: Commit the candidate configuration. + ping: Execute a ping command from a Junos device. + save_text_output: Save text output into a file. """ # Method overrides @@ -525,9 +542,9 @@ def __init__(self, mutually_exclusive=[], min_pyez_version=MIN_PYEZ_VERSION, min_lxml_etree_version=MIN_LXML_ETREE_VERSION, - min_jsnapy_version=MIN_JSNAPY_VERSION, - min_jxmlease_version=MIN_JXMLEASE_VERSION, - min_yaml_version=MIN_YAML_VERSION, + min_jsnapy_version=None, + min_jxmlease_version=None, + min_yaml_version=None, **kwargs): """Initialize a new JuniperJunosModule instance. @@ -541,10 +558,24 @@ def __init__(self, mutually_exclusive: Module-specific mutually exclusive added to top_spec_mutually_exclusive. min_pyez_version: The minimum PyEZ version required by the module. + Since all modules require PyEZ this defaults to + MIN_PYEZ_VERSION. min_lxml_etree_version: The minimum lxml Etree version required by - the module. + the module. Since most modules require + lxml Etree this defaults to + MIN_LXML_ETREE_VERSION. + min_jsnapy_version: The minimum JSNAPy version required by the + module. If this is None, the default, it + means the module does not explicitly require + jsnapy. min_jxmlease_version: The minimum Jxmlease version required by the - module. + module. If this is None, the default, it + means the module does not explicitly require + jxmlease. + min_yanml_version: The minimum YAML version required by the + module. If this is None, the default, it + means the module does not explicitly require + yaml. **kwargs: All additional keyword arguments are passed to AnsibleModule.__init__(). @@ -584,7 +615,7 @@ def __init__(self, self.fail_json(msg="missing required arguments: host") if not self.params.get('user'): self.fail_json(msg="missing required arguments: user") - # Check PyEZ version + # Check PyEZ version and add attributes to reach PyEZ components. self.check_pyez(min_pyez_version, check_device=True, check_sw=True, @@ -595,18 +626,21 @@ def __init__(self, self.pyez_factory_table = jnpr.junos.factory.table self.pyez_op_table = jnpr.junos.op self.pyez_exception = pyez_exception - # Check LXML Etree + # Check LXML Etree. self.check_lxml_etree(min_lxml_etree_version) self.etree = etree - # Check jsnapy - self.check_jsnapy(min_jsnapy_version) - self.jsnapy = jnpr.jsnapy - # Check jxmlease - self.check_jxmlease(min_jxmlease_version) - self.jxmlease = jxmlease - # Check yaml - self.check_yaml(min_yaml_version) - self.yaml = yaml + # Check jsnapy if needed. + if min_jsnapy_version is not None: + self.check_jsnapy(min_jsnapy_version) + self.jsnapy = jnpr.jsnapy + # Check jxmlease if needed. + if min_jxmlease_version is not None: + self.check_jxmlease(min_jxmlease_version) + self.jxmlease = jxmlease + # Check yaml if needed. + if min_yaml_version is not None: + self.check_yaml(min_yaml_version) + self.yaml = yaml # Setup logging. self.logger = self._setup_logging() # Open the PyEZ connection @@ -873,20 +907,6 @@ def check_jxmlease(self, minimum=None): self._check_library('jxmlease', HAS_JXMLEASE_VERSION, JXMLEASE_INSTALLATION_URL, minimum=minimum) - def check_yaml(self, minimum=None): - """Check yaml is available and version is >= minimum. - - Args: - minimum: The minimum PyYAML version required. - Default = None which means no version check. - - Failures: - - yaml not installed. - - yaml version < minimum. - """ - self._check_library('yaml', HAS_YAML_VERSION, - YAML_INSTALLATION_URL, minimum=minimum) - def check_lxml_etree(self, minimum=None): """Check lxml etree is available and version is >= minimum. @@ -901,6 +921,20 @@ def check_lxml_etree(self, minimum=None): self._check_library('lxml Etree', HAS_LXML_ETREE_VERSION, LXML_ETREE_INSTALLATION_URL, minimum=minimum) + def check_yaml(self, minimum=None): + """Check yaml is available and version is >= minimum. + + Args: + minimum: The minimum PyYAML version required. + Default = None which means no version check. + + Failures: + - yaml not installed. + - yaml version < minimum. + """ + self._check_library('yaml', HAS_YAML_VERSION, + YAML_INSTALLATION_URL, minimum=minimum) + def convert_to_bool(self, arg): """Try converting arg to a bool value using Ansible's aliases for bool. @@ -1109,7 +1143,45 @@ def open(self): self.fail_json(msg='Unable to make a PyEZ connection: %s' % (str(ex))) + def close(self, raise_exceptions=False): + """Close the self.dev PyEZ Device instance. + """ + if self.dev is not None: + try: + # Because self.fail_json() calls self.close(), we must set + # self.dev = None BEFORE calling dev.close() in order to avoid + # the infinite recursion which would occur if dev.close() + # raised a ConnectError. + dev = self.dev + self.dev = None + dev.close() + self.logger.debug("Device closed.") + # Exceptions raised by close() are all sub-classes of + # ConnectError or RpcError, so this should catch all + # exceptions raised from PyEZ. + except (pyez_exception.ConnectError, + pyez_exception.RpcError) as ex: + if raise_exceptions is True: + raise ex + else: + # Ignore exceptions from closing. We're about to exit + # anyway and they will just mask the real error that + # happened. + pass + + def add_sw(self): + """Add an instance of jnp.junos.utils.sw.SW() to self. + """ + self.sw = jnpr.junos.utils.sw.SW(self.dev) + def open_configuration(self, mode): + """Open candidate configuration database in exclusive or private mode. + + Failures: + - ConnectError: When there's a problem with the PyEZ connection. + - RpcError: When there's a RPC problem including an already locked + config or an already opened private config. + """ # Already have an open configuration? if self.config is None: if mode not in CONFIG_MODE_CHOICES: @@ -1133,6 +1205,12 @@ def open_configuration(self, mode): self.logger.debug("Configuration opened in %s mode.", config.mode) def close_configuration(self): + """Close candidate configuration database. + + Failures: + - ConnectError: When there's a problem with the PyEZ connection. + - RpcError: When there's a RPC problem closing the config. + """ if self.config is not None: # Because self.fail_json() calls self.close_configuration(), we # must set self.config = None BEFORE closing the config in order to @@ -1151,37 +1229,6 @@ def close_configuration(self): self.fail_json(msg='Unable to close the configuration: %s' % (str(ex))) - def add_sw(self): - """Add an instance of jnp.junos.utils.sw.SW() to self. - """ - self.sw = jnpr.junos.utils.sw.SW(self.dev) - - def close(self, raise_exceptions=False): - """Close the self.dev PyEZ Device instance. - """ - if self.dev is not None: - try: - # Because self.fail_json() calls self.close(), we must set - # self.dev = None BEFORE calling dev.close() in order to avoid - # the infinite recursion which would occur if dev.close() - # raised a ConnectError. - dev = self.dev - self.dev = None - dev.close() - self.logger.debug("Device closed.") - # Exceptions raised by close() are all sub-classes of - # ConnectError or RpcError, so this should catch all - # exceptions raised from PyEZ. - except (pyez_exception.ConnectError, - pyez_exception.RpcError) as ex: - if raise_exceptions is True: - raise ex - else: - # Ignore exceptions from closing. We're about to exit - # anyway and they will just mask the real error that - # happened. - pass - def get_configuration(self, database='committed', format='text', options={}, filter=None): """Return the device configuration in the specified format. @@ -1275,7 +1322,7 @@ def get_configuration(self, database='committed', format='text', return return_val def rollback_configuration(self, id): - """Rolback the device configuration to the specified id. + """Rollback the device configuration to the specified id. Rolls back the configuration to the specified id. Assumes the configuration is already opened. Does NOT commit the configuration. @@ -1315,7 +1362,7 @@ def rollback_configuration(self, id): % (id)) def check_configuration(self): - """Check the device configuration. + """Check the candidate configuration. Check the configuration. Assumes the configuration is already opened. Performs the equivalent of a "commit check", but does NOT commit the @@ -1666,6 +1713,9 @@ class JuniperJunosActionModule(ActionNormal): All juniper_junos_* modules share common behavior which is implemented in this class. This includes specific option fallback/default behavior and passing the "hidden" _module_utils_path option to the module. + + Public Methods: + convert_to_bool: Try converting to bool using aliases for bool. """ def run(self, tmp=None, task_vars=None): # The new connection arguments based on fallback/defaults. diff --git a/requirements.txt b/requirements.txt index 1e730b55..fc1f4b0c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ junos-eznc>=2.1.7 -jsnapy>=1.1.0 +jsnapy>=1.2.1 jxmlease>=1.0.1 lxml>3.2.4 yaml>=3.08 \ No newline at end of file From b56e2ef4f6e3c03d8c8ca4d564f721103cfc6351 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 13 Nov 2017 15:29:25 -0700 Subject: [PATCH 189/426] Serial console support (#303) * Add support for mode='serial' in connection options. * Support serial console connections. * Check that the port option is an integer. --- module_utils/juniper_junos_common.py | 174 ++++++++++++++++++++++----- 1 file changed, 141 insertions(+), 33 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index bdef9621..66a3c443 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -39,6 +39,7 @@ from ansible.plugins.action.normal import ActionModule as ActionNormal # Standard library imports +from argparse import ArgumentParser from distutils.version import LooseVersion import json import logging @@ -88,6 +89,9 @@ HAS_JSNAPY_VERSION = jnpr.jsnapy.__version__ except ImportError: HAS_JSNAPY_VERSION = None +# Most likely JSNAPy 1.2.0 with https://github.com/Juniper/jsnapy/issues/263 +except TypeError: + HAS_JSNAPY_VERSION = 'possibly 1.2.0' try: from lxml import etree @@ -246,11 +250,12 @@ class ModuleDocFragment(object): A value of C(telnet) results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server - depending on the values of the C(host) and C(port) options. Mutually - exclusive with C(console). + depending on the values of the C(host) and C(port) options. A + value of C(serial) results in a NETCONF over serial console connection + to the Junos device. Mutually exclusive with C(console). required: False default: none - choices: [ none, "telnet" ] + choices: [ none, "telnet", "serial" ] console: description: - An alternate method of specifying a NETCONF over serial console @@ -261,16 +266,33 @@ class ModuleDocFragment(object): compatibility. The string value of this option is exactly equivalent to specifying C(host) with a value of I(), C(mode) with a value of I(telnet), and C(port) with a value of - I(). Mutually exclusive with C(mode) and C(port). + I(). Mutually exclusive with I(mode), I(port), + I(baud), and I(attempts). required: False default: none type: str port: description: - - The TCP port number used to establish the connection. Mutually - exclusive with C(console). + - The TCP port number or serial device port used to establish the + connection. Mutually exclusive with C(console). required: False - default: 830 + default: 830 if mode == none, 23 if mode == 'telnet', '/dev/ttyUSB0' if + mode == 'serial' + type: int or str + baud: + description: + - The serial baud rate used to connect to the Junos device when using + mode == 'serial'. Mutually exclusive with C(console). + required: False + default: 9600 + type: int + attempts: + description: + - The number of attempts to connect and log in to the Junos device when + using mode == 'telnet' or mode == 'serial'. Mutually exclusive with + C(console). + required: False + default: 10 type: int timeout: description: @@ -397,20 +419,35 @@ class ModuleDocFragment(object): # Default behavior coded in # JuniperJunosActionModule.run() default=None), - 'mode': dict(choices=[None, 'telnet'], + 'mode': dict(choices=[None, 'telnet', 'serial'], default=None), 'console': dict(type='str', required=False, default=None), - 'port': dict(type='int', + 'port': dict(type='str', required=False, - default=830), + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosModule.__init__() + default=None), + 'baud': dict(type='int', + required=False, + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosModule.__init__() + default=None), + 'attempts': dict(type='int', + required=False, + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosModule.__init__() + default=None), 'timeout': dict(type='int', required=False, default=30), } # Connection arguments which are mutually exclusive. -connection_spec_mutually_exclusive = [['mode', 'console'], ['port', 'console']] +connection_spec_mutually_exclusive = [['mode', 'console'], + ['port', 'console'], + ['baud', 'console'], + ['attempts','console']] # Keys are connection options. Values are a list of task_vars to use as the # default value. connection_spec_fallbacks = { @@ -610,6 +647,41 @@ def __init__(self, self.params.pop('provider') # Parse the console option self._parse_console_options() + # Default port based on mode. + if self.params.get('port') is None: + if self.params.get('mode') == 'telnet': + self.params['port'] = 23 + elif self.params.get('mode') == 'serial': + self.params['port'] = '/dev/ttyUSB0' + else: + self.params['port'] = 830 + else: + if self.params.get('mode') != 'serial': + try: + self.params['port'] = int(self.params['port']) + except ValueError: + self.fail_json(msg="The port option (%s) must be an " + "integer value." % + (self.params['port'])) + # Default baud if serial or telnet mode + if self.params.get('baud') is None: + if (self.params.get('mode') == 'telnet' or + self.params.get('mode') == 'serial'): + self.params['baud'] = 9600 + # Default attempts if serial or telnet mode + if self.params.get('attemps') is None: + if (self.params.get('mode') == 'telnet' or + self.params.get('mode') == 'serial'): + self.params['attempts'] = 10 + # baud and attempts are only valid if mode != None + if (self.params.get('baud') is not None and + self.params.get('mode') is None): + self.fail_json(msg="The baud option (%s) is not valid when " + "mode == none." % (self.params.get('baud'))) + if (self.params.get('attempts') is not None and + self.params.get('mode') is None): + self.fail_json(msg="The attempts option (%s) is not valid when " + "mode == none." % (self.params.get('attempts'))) # Check that we have a user and host if not self.params.get('host'): self.fail_json(msg="missing required arguments: host") @@ -632,7 +704,10 @@ def __init__(self, # Check jsnapy if needed. if min_jsnapy_version is not None: self.check_jsnapy(min_jsnapy_version) - self.jsnapy = jnpr.jsnapy + if hasattr(jnpr, 'jsnapy'): + self.jsnapy = jnpr.jsnapy + else: + self.fail_json("JSNAPy not available.") # Check jxmlease if needed. if min_jxmlease_version is not None: self.check_jxmlease(min_jxmlease_version) @@ -681,32 +756,65 @@ def _parse_console_options(self): """Parse the console option value. Parse the console option value and turn it into the equivalent: - host, mode, and port options. + host, mode, baud, attempts, and port options. """ if self.params.get('console') is not None: try: console_string = self.params.get('console') - # We only care about the value after --telnet - (_, _, after) = console_string.partition('--telnet') - # Split on , - host_port = after.split(',', 1) - # Strip any leading/trailing whitespace or equal sign - # from host - host = host_port[0].strip('= ') - # Try to convert port to an int. - port = int(host_port[1]) - # Successfully parsed. Set params values - self.params['mode'] = 'telnet' - self.params['host'] = host - self.params['port'] = port + + # Subclass ArgumentParser to simply raise a ValueError + # rather than printing to stderr and calling sys.exit() + class QuiteArgumentParser(ArgumentParser): + def error(self, message): + raise ValueError(message) + + # Parse the console_string. + parser = QuiteArgumentParser(add_help=False) + parser.add_argument('-t', '--telnet', default=None) + parser.add_argument('-p', '--port', default=None) + parser.add_argument('-b', '--baud', default=None) + parser.add_argument('-a', '--attempts', default=None) + parser.add_argument('--timeout', default=None) + con_params = vars(parser.parse_args(console_string.split())) + + telnet_params = con_params.get('telnet', None) + # mode == 'telnet' + if telnet_params is not None: + # Split on , + host_port = telnet_params.split(',', 1) + # Strip any leading/trailing whitespace or equal sign + # from host + host = host_port[0].strip(' ') + # Try to convert port to an int. + port = int(host_port[1]) + # Successfully parsed. Set params values + self.params['mode'] = 'telnet' + self.params['host'] = host + self.params['port'] = port + # mode == serial + else: + port = con_params.get('port', None) + baud = con_params.get('baud', None) + attempts = con_params.get('attempts', None) + timeout = con_params.get('timeout', None) + self.params['mode'] = 'serial' + if port is not None: + self.params['port'] = port + if baud is not None: + self.params['baud'] = baud + + # Remove the console option. self.params.pop('console') - except Exception: - self.fail_json(msg="Unable to parse the console value: '%s'. " - "The value of the console argument should " - "be in the format '--telnet " - "," - "'." % - (console_string)) + + except ValueError as ex: + self.fail_json(msg="Unable to parse the console value (%s). " + "Error: %s" % (console_string, str(ex))) + except Exception as ex: + self.fail_json(msg="Unable to parse the console value (%s). " + "The value of the console argument is " + "typically in the format '--telnet " + ",'." + % (console_string)) def _setup_logging(self): """Setup logging for the module. From 9c346c7ee2fb9060d23cc8315fc627b2f45677fa Mon Sep 17 00:00:00 2001 From: David Gethings Date: Tue, 14 Nov 2017 13:09:59 +0000 Subject: [PATCH 190/426] update examples to use /project rather than /playbooks --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5bde4489..9e1f0518 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ $ docker run -it --rm juniper/pyez-ansible ash ``` Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. ``` -$ docker run -it --rm -v $PWD:/playbooks ash +$ docker run -it --rm -v $PWD:/project ash ``` You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: ``` @@ -127,11 +127,11 @@ example ``` We can move to the example directory and run the playbook with the following command: ``` -$ docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml +$ docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook -i hosts playbook.yml ``` You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. ``` -$ alias pb-ansible="docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook" +$ alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" $ pb-ansible -i hosts playbook.yml ``` From 9bfe9f172e8fd92fce87beaaf8e7eef122e80710 Mon Sep 17 00:00:00 2001 From: jorgebonilla Date: Tue, 14 Nov 2017 15:55:19 -0800 Subject: [PATCH 191/426] Include openssh-client library (#217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to be able to install keys to connect using "ssh-add ˜/.ssh/key" Added apk add openssh-client &&\ --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 32a232e6..8e843189 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ ADD version.py /tmp/ansible-junos-stdlib/version.py RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ apk update && apk add ca-certificates &&\ + apk add openssh-client &&\ apk add build-base gcc g++ make python-dev &&\ pip install --upgrade pip setuptools &&\ pip install jxmlease &&\ From a5cad7a9784acc47121f847c6846e9528eeb91bc Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Tue, 9 Jan 2018 16:10:20 -0700 Subject: [PATCH 192/426] Documentation updates (#312) * Initial implementation of docs building infrastructure. Only builds the juniper_junos_facts module documentation currently. * Bump the version. * Add documentation for the juniper_junos_command module. * Add doc cleanup for a few more modules. * Doc cleanup for additional modules. * Doc cleanup for more modules. * Finish doc cleanup. * README cleanup, version increment, copyright date updates. --- .gitignore | 2 + COPYRIGHT | 2 +- README.md | 150 ++-- action_plugins/_junos_commit.py | 2 +- action_plugins/_junos_get_config.py | 2 +- action_plugins/_junos_install_config.py | 2 +- action_plugins/_junos_rollback.py | 2 +- action_plugins/_junos_shutdown.py | 2 +- action_plugins/_junos_zeroize.py | 2 +- action_plugins/juniper_junos_common_action.py | 2 +- docs/Makefile | 234 ++++++ docs/_static/juniper-junos-modules.css | 7 + docs/{ => _static}/juniper.png | Bin docs/ansible2rst.py | 311 ++++++-- docs/conf.py | 19 +- docs/rst.j2 | 535 ++++++++++++- library/juniper_junos_command.py | 177 ++--- library/juniper_junos_config.py | 734 +++++++++--------- library/juniper_junos_facts.py | 78 +- library/juniper_junos_jsnapy.py | 104 ++- library/juniper_junos_ping.py | 278 ++++--- library/juniper_junos_pmtud.py | 135 ++-- library/juniper_junos_rpc.py | 271 ++++--- library/juniper_junos_software.py | 428 +++++----- library/juniper_junos_srx_cluster.py | 56 +- library/juniper_junos_system.py | 157 ++-- library/juniper_junos_table.py | 280 +++---- module_utils/juniper_junos_common.py | 435 ++++++----- version.py | 4 +- 29 files changed, 2664 insertions(+), 1747 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/_static/juniper-junos-modules.css rename docs/{ => _static}/juniper.png (100%) diff --git a/.gitignore b/.gitignore index ca8ba219..d9913d81 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ nosetests.xml docs/build docs/*.rst docs/_build/ +docs/_rst/ +docs/sphinx.log tests/.vagrant/* diff --git a/COPYRIGHT b/COPYRIGHT index a56915bb..63dd8797 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,5 +1,5 @@ - Copyright (c) 1999-2017, Juniper Networks Inc. + Copyright (c) 1999-2018, Juniper Networks Inc. 2014, Jeremy Schulman All rights reserved. diff --git a/README.md b/README.md index 9e1f0518..9861bbc8 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,51 @@ -## ABOUT - -Juniper Networks provides support for using Ansible to deploy devices running -the Junos operating system (Junos OS). The Juniper Networks Ansible library, -which is hosted on the Ansible Galaxy website under the role [Juniper.junos] -(https://galaxy.ansible.com/list#/roles/1116), enables you to use Ansible to perform specific operational and configuration tasks on devices running Junos OS, including installing and upgrading Junos OS, deploying specific devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to [INSTALLATION](#installation) section for setup. - -In addition to these modules, Since 2.1, Ansible natively include some [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). Both Core and Galaxy modules can cohexist on the same platform. - -## OVERVIEW OF MODULES - -- **juniper_junos_facts** — Retrieve device-specific information from a Junos device. -- **junos_commit** — Commit candidate configuration on device. -- **junos_get_config** — Retrieve configuration of device. -- **junos_install_config** — Modify the configuration of a device running Junos OS. -- **junos_install_os** — Install a Junos OS software package. -- **junos_rollback** — Rollback configuration of device. -- **junos_shutdown** — Shut down or reboot a device running Junos OS. -- **junos_srx_cluster** — Enable/Disable cluster mode for SRX devices -- **junos_zeroize** — Remove all configuration information on the Routing Engines and reset all key values on a device. -- **junos_get_table** - Retrieve data from a Junos device using Tables/Views -- **junos_ping** - execute ping on junos devices -- **junos_pmtud** - execute path MTU discovery on junos devices -- **junos_jsnapy** - Integrate JSNAPy to ansible which helps audit network devices -- **junos_rpc** — To execute RPC on device and save output locally -- **junos_cli** — To execute CLI on device and save output locally - -### OVERVIEW OF PLUGINS - -In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `junos_jsnapy`. - -The callback_plugin `jsnapy` helps to print on the screen additional information regarding - jsnapy failed tests. -For each failed test, a log will be printed after the RECAP of the playbook. - -> The plugin `jsnapy` is currently in **Experimental** stage, please provide feedback. +## About + +Juniper Networks supports Ansible for managing devices running +the Junos operating system (Junos OS). This role is hosted on the Ansible Galaxy website under +the role [Juniper.junos](https://galaxy.ansible.com/Juniper/junos/). The Juniper.junos role includes a set of Ansible +modules that perform specific operational and configuration tasks on devices running Junos OS. These tasks include: +installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, +retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to the +[INSTALLATION](#installation) section for instructions on installing this role. + +## Two Sets of Ansible Modules for Junos devices +Since Ansible version >= 2.1, Ansible also natively includes +[core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included +in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.junos role +have names which begin with the prefix `juniper_junos_`. These two sets of Junos modules can coexist on the same +Ansible control machine, and an Ansible play may invoke a module from either (or both) sets. Juniper Networks recommends +using the modules in this role when writing new playbooks that manage Junos devices. + +## Overview of Modules +This Juniper.junos role includes the following modules: +- **juniper_junos_command** — Execute one or more CLI commands on a Junos device. +- **juniper_junos_config** — Manipulate the configuration of a Junos device. +- **juniper_junos_facts** — Retrieve facts from a Junos device. +- **juniper_junos_jsnapy** — Execute JSNAPy tests on a Junos device. +- **juniper_junos_ping** — Execute ping from a Junos device. +- **juniper_junos_pmtud** — Perform path MTU discovery from a Junos device to a destination. +- **juniper_junos_rpc** — Execute one or more NETCONF RPCs on a Junos device. +- **juniper_junos_software** — Install software on a Junos device. +- **juniper_junos_srx_cluster** — Add or remove SRX chassis cluster configuration. +- **juniper_junos_system** — Initiate operational actions on the Junos system. +- **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. + +### Important Changes +Significant changes to the modules in the Juniper.junos role were made between versions 1.4.3 and 2.0.0. +In versions <= 1.4.3 of the Juniper.junos role, the modules used different module and argument names. Versions >= 2.0.0 +of the Juniper.junos role provide backwards compatibility with playbooks written to prior versions of the Juniper.junos +role. If a playbook worked with a prior version of the Juniper.junos role, it should +continue to work on the current version without requiring modifications to the playbook. However, these older module and +argument names are no longer present in the current documentation. You may reference previous module and argument names +by referring directly to the +[1.4.3 version of the Juniper.junos role documentation](http://junos-ansible-modules.readthedocs.io/en/1.4.3/). + +### Overview of Plugins + +In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `juniper_junos_jsnapy`. + +The callback_plugin `jsnapy` helps to print on the screen additional information regarding jsnapy failed tests. +For each failed test, a log will be printed after the RECAP of the playbook as shown in this example: ``` PLAY RECAP ********************************************************************* @@ -50,22 +62,25 @@ JSNAPy Results for: qfx10002-02 ************************************************ Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} ``` -Callback plugins are not activated by default and needs to be manually added to the -configuration file under `[defaults]` with the variable `callback_whitelist` +The `jsnapy` plugin is currently in **Experimental** stage, please provide feedback. + +Callback plugins are not activated by default. They must be manually added to the Ansible +configuration file under the `[defaults]` section using the variable `callback_whitelist`. Specifically, these lines +should be added to the Ansible configuration file in order to allow the jsnapy callback plugin: ``` [defaults] callback_whitelist = jsnapy ``` + ## DOCUMENTATION -[Official documentation](http://www.juniper.net/techpubs/en_US/release-independent/junos-ansible/information-products/pathway-pages/index.html) (detailed information, includes examples) +[Official Juniper documentation](http://www.juniper.net/techpubs/en_US/release-independent/junos-ansible/information-products/pathway-pages/index.html) (detailed information, including examples) [Ansible style documentation](http://junos-ansible-modules.readthedocs.org) ## INSTALLATION - -This repo assumes you have the [DEPENDENCIES](#dependencies) installed on your system. +You must have the [DEPENDENCIES](#dependencies) installed on your system. ### Ansible Galaxy Role To download the latest released version of the junos role to the Ansible @@ -78,6 +93,7 @@ server, execute the ansible-galaxy install command, and specify **Juniper.junos* - extracting Juniper.junos to /usr/local/etc/ansible/roles/Juniper.junos - Juniper.junos was installed successfully ``` + You can also use the ansible-galaxy install command to install the latest development version of the junos role directly from GitHub. ``` @@ -89,30 +105,20 @@ For testing you can `git clone` this repo and run the `env-setup` script in the ``` user@ansible-junos-stdlib> source env-setup ``` -This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: - +This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: ``` [jeremy@ansible-junos-stdlib]$ echo $ANSIBLE_LIBRARY /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible ``` -An alternative to the above is installing the role from GitHub using ansible-galaxy. The first step is creating a yaml file for -input data to ansible-galaxy. We'll use install_role.yml. - -```yaml ---- -- src: https://github.com/Juniper/ansible-junos-stdlib - name: Juniper.junos -``` - -Now run `sudo ansible-galaxy install -r install_role.yml` to install the role. - ### Docker -To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. +To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. +The following will pull the latest image and run it in an interactive ash shell. ``` $ docker run -it --rm juniper/pyez-ansible ash ``` -Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. +Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and +associated files). The following will bind mount the current working directory and start the ash shell. ``` $ docker run -it --rm -v $PWD:/project ash ``` @@ -157,8 +163,7 @@ This example outlines how to use Ansible to install or upgrade the software imag - name: Checking NETCONF connectivity wait_for: host={{ inventory_hostname }} port=830 timeout=5 - name: Install Junos OS package - junos_install_os: - host: "{{ inventory_hostname }}" + juniper_junos_software: reboot: yes version: "{{ OS_version }}" package: "{{ pkg_dir }}/{{ OS_package }}" @@ -174,25 +179,34 @@ This example outlines how to use Ansible to install or upgrade the software imag ``` ## DEPENDENCIES - -Thes modules require the following to be installed on the Ansible server: - +This modules requires the following to be installed on the Ansible control machine: * Python 2.7 * [Ansible](http://www.ansible.com) 2.1 or later * Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.1.7 or later +* [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later ## LICENSE - Apache 2.0 -## CONTRIBUTORS +## SUPPORT +Support for this Juniper.junos role is provided by the community and Juniper Networks. If you have an +issue with a module in the Juniper.junos role, you may: +- Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). +- Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) +- Email [jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) +- Open a [JTAC Case](https://www.juniper.net/casemanager/#/create) -Juniper Networks is actively contributing to and maintaining this repo. Please contact jnpr-community-netdev@juniper.net for any queries. +Support for the Junos modules included in Ansible core is provided by Ansible. If you have an issue with an Ansible +core module you should open a [Github issue against the Ansible project](https://github.com/ansible/ansible/issues). -*Contributors:* +## CONTRIBUTORS +Juniper Networks is actively contributing to and maintaining this repo. Please contact +[jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) for any queries. -[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr), [Damien Garros](https://github.com/dgarros) +*Contributors:* +[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), +[David Gethings](https://github.com/dgjnpr) *Former Contributors:* - -[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog) +[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), +[Damien Garros](https://github.com/dgarros) diff --git a/action_plugins/_junos_commit.py b/action_plugins/_junos_commit.py index e07073be..347912de 100755 --- a/action_plugins/_junos_commit.py +++ b/action_plugins/_junos_commit.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/action_plugins/_junos_get_config.py b/action_plugins/_junos_get_config.py index dabf9aa2..f874063c 100755 --- a/action_plugins/_junos_get_config.py +++ b/action_plugins/_junos_get_config.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/action_plugins/_junos_install_config.py b/action_plugins/_junos_install_config.py index 9fd2672f..89abc61d 100755 --- a/action_plugins/_junos_install_config.py +++ b/action_plugins/_junos_install_config.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/action_plugins/_junos_rollback.py b/action_plugins/_junos_rollback.py index 352120b0..cd12f48c 100755 --- a/action_plugins/_junos_rollback.py +++ b/action_plugins/_junos_rollback.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/action_plugins/_junos_shutdown.py b/action_plugins/_junos_shutdown.py index 6e294150..36106396 100755 --- a/action_plugins/_junos_shutdown.py +++ b/action_plugins/_junos_shutdown.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/action_plugins/_junos_zeroize.py b/action_plugins/_junos_zeroize.py index 49fde22b..bfcfcea8 100755 --- a/action_plugins/_junos_zeroize.py +++ b/action_plugins/_junos_zeroize.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py index a8611213..37e2fbd5 100755 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..4fe6d3e5 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,234 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +LOGFILE = sphinx.log +FULL_TRACEBACKS = -T +CPUS ?= 4 +VERBOSITY ?= -v +FORCE_REBUILD = -a -E +CONFIG_DIR = -c . +NITPICK ?= -n +SPHINXOPTS = -j $(CPUS) -w $(LOGFILE) $(FULL_TRACEBACKS) $(FORCE_REBUILD) $(NITPICK) $(VERBOSITY) $(CONFIG_DIR) +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build +RSTDIR = _rst +MODULES_PATH = ../library +EXCLUDE_PATHS = ../library/_junos* +DOC_PROJECTS = "Ansible API" + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + rm -rf $(RSTDIR)/*.rst + rm -rf $(LOGFILE) + +.PHONY: apidoc +apidoc: + sphinx-apidoc --module-first --doc-project $(DOC_PROJECT) --force --maxdepth 7 -o $(RSTDIR) $(MODULES_PATH) $(EXCLUDE_PATHS) + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(RSTDIR) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: docs +docs: clean apidoc html + +.PHONY: webdocs +webdocs: clean apidoc html + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Ansible.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Ansible.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Ansible" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Ansible" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/_static/juniper-junos-modules.css b/docs/_static/juniper-junos-modules.css new file mode 100644 index 00000000..2723c307 --- /dev/null +++ b/docs/_static/juniper-junos-modules.css @@ -0,0 +1,7 @@ +td, th { + padding: 20px; +} +code { + color: #3a87ad; + background-color: #d9edf7; +} \ No newline at end of file diff --git a/docs/juniper.png b/docs/_static/juniper.png similarity index 100% rename from docs/juniper.png rename to docs/_static/juniper.png diff --git a/docs/ansible2rst.py b/docs/ansible2rst.py index 74c1de68..c1afab2b 100755 --- a/docs/ansible2rst.py +++ b/docs/ansible2rst.py @@ -4,7 +4,7 @@ # This file is part of Ansible # # Modified to support stand-alone Galaxy documentation -# Copyright (c) 2014, Juniper Networks Inc. +# Copyright (c) 2014, 2017-2018 Juniper Networks Inc. # 2014, Rick Sherman # # Ansible is free software: you can redistribute it and/or modify @@ -28,49 +28,96 @@ import cgi from distutils.version import LooseVersion from jinja2 import Environment, FileSystemLoader +import yaml + +from collections import MutableMapping, MutableSet, MutableSequence + +from ansible.module_utils.six import iteritems, string_types +from ansible.parsing.plugin_docs import read_docstring +from ansible.parsing.yaml.loader import AnsibleLoader +from ansible.plugins.loader import fragment_loader +from ansible.module_utils._text import to_bytes try: - from ansible.utils import module_docs + from html import escape as html_escape except ImportError: - from ansible.utils import plugin_docs as module_docs + # Python-3.2 or later + import cgi + + def html_escape(text, quote=True): + return cgi.escape(text, quote) + from ansible import __version__ as ansible_version ##################################################################################### # constants and paths +# if a module is added in a version of Ansible older than this, don't print the version added information +# in the module documentation because everyone is assumed to be running something newer than this already. +TO_OLD_TO_BE_NOTABLE = 1.3 + _ITALIC = re.compile(r"I\(([^)]+)\)") _BOLD = re.compile(r"B\(([^)]+)\)") _MODULE = re.compile(r"M\(([^)]+)\)") -_URL = re.compile(r"U\(([^)]+)\)") +_URL_W_TEXT = re.compile(r"U\(([^)^|]+)\|([^)]+)\)") +_URL = re.compile(r"U\(([^)^|]+)\)") _CONST = re.compile(r"C\(([^)]+)\)") +_UNDERSCORE = re.compile(r"_") +DEPRECATED = b" (D)" +MODULE_NAME_STARTS_WITH = "juniper_junos_" MODULEDIR = "../library/" -OUTPUTDIR = "." +OUTPUTDIR = "./_rst" ##################################################################################### +def too_old(added): + if not added: + return False + try: + added_tokens = str(added).split(".") + readded = added_tokens[0] + "." + added_tokens[1] + added_float = float(readded) + except ValueError as e: + warnings.warn("Could not parse %s: %s" % (added, str(e))) + return False + return added_float < TO_OLD_TO_BE_NOTABLE + +##################################################################################### def rst_ify(text): ''' convert symbols like I(this is in italics) to valid restructured text ''' - t = _ITALIC.sub(r'*' + r"\1" + r"*", text) - t = _BOLD.sub(r'**' + r"\1" + r"**", t) - t = _MODULE.sub(r'``' + r"\1" + r"``", t) - t = _URL.sub(r"\1", t) - t = _CONST.sub(r'``' + r"\1" + r"``", t) + try: + t = _ITALIC.sub(r'*' + r"\1" + r"*", text) + t = _BOLD.sub(r'**' + r"\1" + r"**", t) + t = _MODULE.sub(r':ref:`' + r"\1 <\1>" + r"`", t) + t = _URL_W_TEXT.sub(r'`' + r"\1" + r" <" + r"\2" + r">`_", t) + t = _URL.sub(r'`' + r"\1" + r" <" + r"\1" + r">`_", t) + t = _CONST.sub(r'``' + r"\1" + r"``", t) + except Exception as e: + raise AnsibleError("Could not process (%s) : %s" % (str(text), str(e))) return t ##################################################################################### +def module_to_html(matchobj): + if matchobj.group(1) is not None: + module_name = matchobj.group(1) + module_href = _UNDERSCORE.sub('-', module_name) + return '' + \ + module_name + '' + return '' def html_ify(text): ''' convert symbols like I(this is in italics) to valid HTML ''' - t = cgi.escape(text) + t = html_escape(text) t = _ITALIC.sub("" + r"\1" + "", t) t = _BOLD.sub("" + r"\1" + "", t) - t = _MODULE.sub("" + r"\1" + "", t) + t = _MODULE.sub(module_to_html, t) + t = _URL_W_TEXT.sub("" + r"\1" + "", t) t = _URL.sub("" + r"\1" + "", t) t = _CONST.sub("" + r"\1" + "", t) @@ -79,6 +126,7 @@ def html_ify(text): ##################################################################################### + def rst_fmt(text, fmt): ''' helper for Jinja2 to do format strings ''' @@ -99,16 +147,18 @@ def write_data(text, outputname, module, output_dir=None): ''' dumps module output to a file or the screen, as requested ''' if output_dir is not None: - f = open(os.path.join(output_dir, outputname % module), 'w') - f.write(text.encode('utf-8')) - f.close() + if not os.path.exists(output_dir): + os.makedirs(output_dir) + fname = os.path.join(output_dir, outputname % (module)) + with open(fname, 'wb') as f: + f.write(to_bytes(text)) else: - print text + print(text) ##################################################################################### -def jinja2_environment(template_dir, typ): +def jinja2_environment(template_dir, template_type): env = Environment(loader=FileSystemLoader(template_dir), variable_start_string="@{", @@ -117,7 +167,7 @@ def jinja2_environment(template_dir, typ): ) env.globals['xline'] = rst_xline - if typ == 'rst': + if template_type == 'rst': env.filters['convert_symbols_to_format'] = rst_ify env.filters['html_ify'] = html_ify env.filters['fmt'] = rst_fmt @@ -125,53 +175,224 @@ def jinja2_environment(template_dir, typ): template = env.get_template('rst.j2') outputname = "%s.rst" else: - raise Exception("unknown module format type: %s" % typ) + raise Exception("unknown module format type: %s" % template_type) return env, template, outputname ##################################################################################### +def add_fragments(doc, filename): + + fragments = doc.get('extends_documentation_fragment', []) + + if isinstance(fragments, string_types): + fragments = [fragments] + + # Allow the module to specify a var other than DOCUMENTATION + # to pull the fragment from, using dot notation as a separator + for fragment_slug in fragments: + fragment_slug = fragment_slug.lower() + if '.' in fragment_slug: + fragment_name, fragment_var = fragment_slug.split('.', 1) + fragment_var = fragment_var.upper() + else: + fragment_name, fragment_var = fragment_slug, 'DOCUMENTATION' + + fragment_loader.add_directory('../module_utils/') + fragment_class = fragment_loader.get(fragment_name) + assert fragment_class is not None + + fragment_yaml = getattr(fragment_class, fragment_var, '{}') + fragment = AnsibleLoader(fragment_yaml, file_name=filename).get_single_data() + + if 'notes' in fragment: + notes = fragment.pop('notes') + if notes: + if 'notes' not in doc: + doc['notes'] = [] + doc['notes'].extend(notes) + + if 'options' not in fragment and 'logging_options' not in fragment and 'connection_options' not in fragment: + raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename)) + + for key, value in fragment.items(): + if key in doc: + # assumes both structures have same type + if isinstance(doc[key], MutableMapping): + value.update(doc[key]) + elif isinstance(doc[key], MutableSet): + value.add(doc[key]) + elif isinstance(doc[key], MutableSequence): + value = sorted(frozenset(value + doc[key])) + else: + raise Exception("Attempt to extend a documentation fragement (%s) of unknown type: %s" % (fragment_name, filename)) + doc[key] = value -def process_module(fname, template, outputname): - print MODULEDIR + fname - if LooseVersion(ansible_version) >= LooseVersion('2.3.0'): - doc, examples, returndocs, _metadata = module_docs.get_docstring(MODULEDIR + fname) - else: - doc, examples, returndocs = module_docs.get_docstring(MODULEDIR + fname) - all_keys = [] +def get_docstring(filename, verbose=False): + """ + DOCUMENTATION can be extended using documentation fragments loaded by the PluginLoader from the module_docs_fragments directory. + """ - if 'version_added' not in doc: - sys.stderr.write("*** ERROR: missing version_added in: %s ***\n".format(fname)) - sys.exit(1) + data = read_docstring(filename, verbose=verbose) + # add fragments to documentation + if data.get('doc', False): + add_fragments(data['doc'], filename) + + return data['doc'], data['plainexamples'], data['returndocs'], data['metadata'] + +def process_module(fname, template, outputname, aliases=None): + + module_name = fname.replace(".py", "") + + print("Processing module %s" % (MODULEDIR + fname)) + doc, examples, returndocs, metadata = get_docstring(MODULEDIR + fname, + verbose=True) + + # add some defaults for plugins that dont have most of the info + doc['module'] = doc.get('module', module_name) + doc['version_added'] = doc.get('version_added', 'historical') + doc['plugin_type'] = 'module' + + required_fields = ('short_description',) + for field in required_fields: + if field not in doc: + print("%s: WARNING: MODULE MISSING field '%s'" % (fname, field)) + + not_nullable_fields = ('short_description',) + for field in not_nullable_fields: + if field in doc and doc[field] in (None, ''): + print("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field])) + + # + # The present template gets everything from doc so we spend most of this + # function moving data into doc for the template to reference + # + + if aliases: + doc['aliases'] = aliases + + # don't show version added information if it's too old to be called out added = 0 if doc['version_added'] == 'historical': del doc['version_added'] else: added = doc['version_added'] - # don't show version added information if it's too old to be called out - if added: - added_tokens = str(added).split(".") - added = added_tokens[0] + "." + added_tokens[1] - added_float = float(added) + # Strip old version_added for the module + if too_old(added): + del doc['version_added'] - for (k, v) in doc['options'].iteritems(): - all_keys.append(k) - all_keys = sorted(all_keys) - doc['option_keys'] = all_keys + option_names = [] + if 'options' in doc and doc['options']: + for (k, v) in iteritems(doc['options']): + # Error out if there's no description + if 'description' not in doc['options'][k]: + raise AnsibleError("Missing required description for option %s in %s " % (k, module)) + + # Error out if required isn't a boolean (people have been putting + # information on when something is required in here. Those need + # to go in the description instead). + required_value = doc['options'][k].get('required', False) + if not isinstance(required_value, bool): + raise AnsibleError("Invalid required value '%s' for option '%s' in '%s' (must be truthy)" % ( + required_value, k, module)) + + # Strip old version_added information for options + if 'version_added' in doc['options'][k] and too_old(doc['options'][k]['version_added']): + del doc['options'][k]['version_added'] + + # Make sure description is a list of lines for later formatting + if not isinstance(doc['options'][k]['description'], list): + doc['options'][k]['description'] = [doc['options'][k]['description']] + option_names.append(k) + option_names.sort() + doc['option_keys'] = option_names + + connection_option_names = [] + if 'connection_options' in doc and doc['connection_options']: + for (k, v) in iteritems(doc['connection_options']): + # Error out if there's no description + if 'description' not in doc['connection_options'][k]: + raise AnsibleError("Missing required description for connection_option %s in %s " % (k, module)) + + # Error out if required isn't a boolean (people have been putting + # information on when something is required in here. Those need + # to go in the description instead). + required_value = doc['connection_options'][k].get('required', False) + if not isinstance(required_value, bool): + raise AnsibleError("Invalid required value '%s' for connection_option '%s' in '%s' (must be truthy)" % + (required_value, k, module)) + + # Strip old version_added information for options + if ('version_added' in doc['connection_options'][k] and + too_old(doc['connection_options'][k]['version_added'])): + del doc['connection_options'][k]['version_added'] + + # Make sure description is a list of lines for later formatting + if not isinstance(doc['connection_options'][k]['description'], list): + doc['connection_options'][k]['description'] = [doc['connection_options'][k]['description']] + connection_option_names.append(k) + connection_option_names.sort() + doc['connection_option_keys'] = connection_option_names + + logging_option_names = [] + if 'logging_options' in doc and doc['logging_options']: + for (k, v) in iteritems(doc['logging_options']): + # Error out if there's no description + if 'description' not in doc['logging_options'][k]: + raise AnsibleError("Missing required description for logging_option %s in %s " % (k, module)) + + # Error out if required isn't a boolean (people have been putting + # information on when something is required in here. Those need + # to go in the description instead). + required_value = doc['logging_options'][k].get('required', False) + if not isinstance(required_value, bool): + raise AnsibleError("Invalid required value '%s' for logging_option '%s' in '%s' (must be truthy)" % + (required_value, k, module)) + + # Strip old version_added information for options + if ('version_added' in doc['logging_options'][k] and + too_old(doc['logging_options'][k]['version_added'])): + del doc['logging_options'][k]['version_added'] + + # Make sure description is a list of lines for later formatting + if not isinstance(doc['logging_options'][k]['description'], list): + doc['logging_options'][k]['description'] = [doc['logging_options'][k]['description']] + logging_option_names.append(k) + logging_option_names.sort() + doc['logging_option_keys'] = logging_option_names doc['filename'] = fname doc['docuri'] = doc['module'].replace('_', '-') doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') + doc['ansible_version'] = ansible_version doc['plainexamples'] = examples # plain text + doc['metadata'] = metadata + + if returndocs: + try: + doc['returndocs'] = yaml.safe_load(returndocs) + returndocs_keys = doc['returndocs'].keys() + returndocs_keys.sort() + doc['returndocs_keys'] = returndocs_keys + except Exception as e: + print("%s:%s:yaml error:%s:returndocs=%s" % (fname, module_name, e, returndocs)) + doc['returndocs'] = None + doc['returndocs_keys'] = None + else: + doc['returndocs'] = None + doc['returndocs_keys'] = None - # here is where we build the table of contents... + doc['author'] = doc.get('author', ['UNKNOWN']) + if isinstance(doc['author'], string_types): + doc['author'] = [doc['author']] + # here is where we build the table of contents... text = template.render(doc) - write_data(text, outputname, fname, OUTPUTDIR) + write_data(text, outputname, module_name, OUTPUTDIR) ##################################################################################### @@ -179,12 +400,12 @@ def process_module(fname, template, outputname): def main(): env, template, outputname = jinja2_environment('.', 'rst') - modules = [] + module_names = [] for module in os.listdir(MODULEDIR): - if module.startswith("junos_"): + if module.startswith(MODULE_NAME_STARTS_WITH): process_module(module, template, outputname) - modules.append(module) + module_names.append(module.replace(".py", "")) index_file_path = os.path.join(OUTPUTDIR, "index.rst") index_file = open(index_file_path, "w") @@ -197,8 +418,8 @@ def main(): index_file.write(' :maxdepth: 1\n') index_file.write('\n') - for module in modules: - index_file.write(' %s\n' % module) + for module_name in module_names: + index_file.write(' %s\n' % module_name) if __name__ == '__main__': main() diff --git a/docs/conf.py b/docs/conf.py index 49d0d69c..3371c88b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,14 +16,20 @@ import os import sphinx_bootstrap_theme + +def setup(app): + app.add_stylesheet("juniper-junos-modules.css") + + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('.')) sys.path.insert(1, os.path.abspath('..')) -# Import ansible2rst so that RST files are generated. +# Import ansible2rst so that RST files can be generated. import ansible2rst +# Call ansible2rst.main() to generate RST files. ansible2rst.main() # -- General configuration ------------------------------------------------ @@ -37,7 +43,7 @@ extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +#templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' @@ -133,7 +139,7 @@ # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = 'juniper.png' +html_logo = '_static/juniper.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 @@ -217,7 +223,7 @@ # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'JunosAnsibleModules.tex', u'Junos Ansible Modules Documentation', - u'Jeremy Schulman - Juniper Networks, Inc.', 'manual'), + u'Juniper Networks, Inc.', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -247,7 +253,7 @@ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'junosansiblemodules', u'Junos Ansible Modules Documentation', - [u'Jeremy Schulman - Juniper Networks, Inc.'], 1) + [u'Juniper Networks, Inc.'], 1) ] # If true, show URL addresses after external links. @@ -261,7 +267,8 @@ # dir menu entry, description, category) texinfo_documents = [ ('index', 'JunosAnsibleModules', u'Junos Ansible Modules Documentation', - u'Jeremy Schulman - Juniper Networks, Inc.', 'JunosAnsibleModules', 'One line description of project.', + u'Juniper Networks, Inc.', 'JunosAnsibleModules', 'Ansible Modules for ' + 'Junos', 'Miscellaneous'), ] diff --git a/docs/rst.j2 b/docs/rst.j2 index 5c393144..e20f93c7 100644 --- a/docs/rst.j2 +++ b/docs/rst.j2 @@ -1,22 +1,23 @@ .. _@{ module }@: -{% if short_description %} -{% set title = module + ' - ' + short_description|convert_symbols_to_format %} -{% else %} {% set title = module %} -{% endif %} {% set title_len = title|length %} - @{ title }@ @{ '+' * title_len }@ +{% if short_description %} +@{ short_description|convert_symbols_to_format }@ +{% endif %} + +{% if version_added is defined and version_added != '' -%} +.. versionadded:: @{ version_added | default('') }@ + -{% if author %} -:Author: @{ author }@ {% endif %} + .. contents:: :local: - :depth: 1 + :depth: 2 {# ------------------------------------------ # @@ -24,84 +25,536 @@ # but it isn't one. # --------------------------------------------#} +{% if deprecated is defined -%} + + +DEPRECATED +---------- + +{# use unknown here? skip the fields? #} +:In: version: @{ deprecated['version'] | default('') | string | convert_symbols_to_format }@ +:Why: @{ deprecated['why'] | default('') | convert_symbols_to_format }@ +:Alternative: @{ deprecated['alternative'] | default('')| convert_symbols_to_format }@ + + +{% endif %} Synopsis -------- -{% if version_added is defined -%} -.. versionadded:: @{ version_added }@ -{% endif %} +{% if description %} {% for desc in description -%} -@{ desc | convert_symbols_to_format }@ +* @{ desc | convert_symbols_to_format }@ +{% endfor %} + + +{% endif %} +{% if aliases is defined -%} + +Aliases: @{ ','.join(aliases) }@ + + +{% endif %} +{% if requirements %} + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +{% for req in requirements %} +* @{ req | convert_symbols_to_format }@ {% endfor %} + + +{% endif %} {% if options -%} -Options -------- + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: .. raw:: html + + - {% for k in option_keys %} - {% set v = options[k] %} +{% for k in option_keys -%} +{% set v = options[k] -%} +{% if not v['suboptions'] %} + - - - - {% if v.get('type', 'not_bool') == 'bool' %} + + + + +{% if v.get('type', 'not_bool') == 'bool' %} - {% else %} - - {% endif %} - +{% else %} + +{% endif %} + + + + + + + - {% endfor %} + + + + +{% endif %} + + +{% endfor %} +
parametertype required default choices comments
@{ k }@{% if v.get('required', False) %}yes{% else %}no{% endif %}{% if v['default'] %}@{ v['default'] }@{% endif %}@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% for desc in v.description -%}@{ desc | html_ify }@{% endfor -%}{% if v['version_added'] %} (added in Ansible @{v['version_added']}@){% endif %}{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v.description is string %} +
@{ v.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +{% else %} + +
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %}
+ + + + + + + + + + + +{% for k2 in v['suboptions'] %} +{% set v2 = v['suboptions'] [k2] %} + + + + + + +{% if v2.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + +{% endfor %} + +
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v2.description is string %} +
@{ v2.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v2.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v2.aliases %} +
aliases: @{ v2.aliases|join(', ') }@
+{% endif %} +
+ +
+
+ {% endif %} +{% if connection_options -%} -{% if requirements %} -{% for req in requirements %} -.. note:: Requires @{ req | convert_symbols_to_format }@ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + +{% for k in connection_option_keys -%} +{% set v = connection_options[k] -%} +{% if not v['suboptions'] %} + + + + + +{% if v.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + + + + + + + + + + +{% endif %} + + +{% endfor %} + +
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v.description is string %} +
@{ v.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
{% endfor %} {% endif %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +{% else %} + +
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +
+ + + + + + + + + + + +{% for k2 in v['suboptions'] %} +{% set v2 = v['suboptions'] [k2] %} + + + + + + +{% if v2.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + +{% endfor %} + +
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v2.description is string %} +
@{ v2.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v2.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v2.aliases %} +
aliases: @{ v2.aliases|join(', ') }@
+{% endif %} +
+ +
+
+ +{% endif %} +{% if logging_options -%} + + +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + +{% for k in logging_option_keys -%} +{% set v = logging_options[k] -%} +{% if not v['suboptions'] %} + + + + + + +{% if v.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + + + + + + + + + + +{% endif %} + + +{% endfor %} + +
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v.description is string %} +
@{ v.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +{% else %} + +
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +
+ + + + + + + + + + + +{% for k2 in v['suboptions'] %} +{% set v2 = v['suboptions'] [k2] %} + + + + + + +{% if v2.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + +{% endfor %} + +
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v2.description is string %} +
@{ v2.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v2.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v2.aliases %} +
aliases: @{ v2.aliases|join(', ') }@
+{% endif %} +
+ +
+
+ +{% endif %} +{% if examples or plainexamples -%} +.. _examples-label: -{% if examples or plainexamples %} Examples -------- -.. raw:: html +:: {% for example in examples %} - {% if example['description'] %}

@{ example['description'] | html_ify }@

{% endif %} -

-

+{% if example['description'] %}
+@{ example['description'] }@
+{% endif %}
 @{ example['code'] | escape | indent(4, True) }@
-    
-

{% endfor %} -
- {% if plainexamples %} +@{ plainexamples | indent(4, True) }@ +{% endif %} +{% endif %} -:: -@{ plainexamples | indent(4, True) }@ +{% if returndocs -%} + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + +{% for entry in returndocs_keys %} + + + + + + + + +{% if returndocs[entry].type == 'complex' %} + + + + + +{% endif %} +{% endfor %} + +
namedescriptionreturnedtypesample
@{ entry }@ +{% if returndocs[entry].description is string %} +
@{ returndocs[entry].description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in returndocs[entry].description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} {% endif %} +
@{ returndocs[entry].returned | html_ify }@@{ returndocs[entry].type | html_ify }@@{ returndocs[entry].sample | replace('\n', '\n ') | html_ify }@
contains: + + + + + + + + + +{% for sub in returndocs[entry].contains %} + + + + + + + + +{% endfor %} -{% if notes %} +
namedescriptionreturnedtypesample
@{ sub }@ +{% if returndocs[entry].contains[sub].description is string %} +
@{ returndocs[entry].contains[sub].description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in returndocs[entry].contains[sub].description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} {% endif %} +
@{ returndocs[entry].contains[sub].returned | html_ify }@@{ returndocs[entry].contains[sub].type | html_ify }@@{ returndocs[entry].contains[sub].sample }@
+
+
+
+{% endif %} + + +{% if notes -%} + + +Notes +----- + +.. note:: {% for note in notes %} -.. note:: @{ note | convert_symbols_to_format }@ + - @{ note | convert_symbols_to_format }@ {% endfor %} + + {% endif %} +{% if author is defined -%} + + +Author +~~~~~~ +{% for author_name in author %} +* @{ author_name }@ +{% endfor %} + + +{% endif %} +{% if not deprecated %} +{% set support = { 'core': 'The Ansible Core Team', 'network': 'The Ansible Network Team', 'certified': 'an Ansible Partner', 'community': 'The Ansible Community', 'curated': 'A Third Party'} %} +{% set module_states = { 'preview': 'it is not guaranteed to have a backwards compatible interface', 'stableinterface': 'the maintainers for this module guarantee that no backward incompatible interface changes will be made'} %} +{% if metadata %} +{% if metadata.status %} + + +Status +~~~~~~ + +{% for cur_state in metadata.status %} +This module is flagged as **@{cur_state}@** which means that @{module_states[cur_state]}@. +{% endfor %} + + +{% endif %} +{% endif %} +{% endif %} diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index 27834080..d19d010d 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2014, Jeremy Schulman # # All rights reserved. @@ -42,68 +42,48 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_command version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Execute one or more CLI commands on a Junos device description: - Execute one or more CLI commands on a Junos device. - NOTE: This module does NOT use the Junos CLI to execute the CLI command. - Instead, it uses the RPC over a NETCONF channel. The - RPC takes a CLI command as it's input and is very similar to executing the - command on the CLI, but you can NOT include any pipe modifies - (i.e. '| match', '| count', etc.) with the CLI commands executed by this + - This module does NOT use the Junos CLI to execute the CLI command. + Instead, it uses the C() RPC over a NETCONF channel. The + C() RPC takes a CLI command as it's input and is very similar to + executing the command on the CLI, but you can NOT include any pipe modifies + (i.e. C(| match), C(| count), etc.) with the CLI commands executed by this module. -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common options: commands: description: - A list of one or more CLI commands to execute on the Junos device. required: true default: none - type: 'list' + type: list aliases: - cli - command - cmd - cmds - formats: - description: - - The format of the reply for the CLI command(s) specified by the - I(commands) option. The specified format(s) must be supported by the - target Junos device. The value of this option can either be a single - format, or a list of formats. If a single format is specified, it - applies to all command(s) specified by the I(commands) option. If a - list of formats are specified, there must be one value in the list for - each command specified by the I(commands) option. Specifying the value - C(xml) for the I(formats) option is similar to appending - '| display xml' to a CLI command, and specifying the value C(json) - for the I(formats) option is similar to appending '| display json' to - a CLI command. - required: false - default: 'text' - type: 'str or list of str' - choices: ['text', 'xml', 'json'] - aliases: - - format - - display - - output dest: description: - The path to a file, on the Ansible control machine, where the output of the cli command will be saved. - The file must be writeable. If the file already exists, it is overwritten. - - NOTE: When tasks are executed against more than one target host, + - When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See - U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including - {{ inventory_hostname }} in the I(dest) value. It is the user's - responsibility to ensure this value is unique per target host. + C({{ inventory_hostname }}) in the value of the I(dest) option. It is + the user's responsibility to ensure this value is unique per target + host. - For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the I(dest_dir) option in new playbooks. The I(dest) and I(dest_dir) options are mutually exclusive. @@ -116,8 +96,8 @@ description: - The path to a directory, on the Ansible control machine, where the output of the cli command will be saved. The output will be logged - to a file named {{ inventory_hostname }}_C(cmd).C(format) - in the I(dest_dir) directory. + to a file named C({{ inventory_hostname }}_)I(command)C(.)I(format) + in the directory specified by the value of the I(dest_dir) option. - The destination file must be writeable. If the file already exists, it is overwritten. It is the users responsibility to ensure a unique I(dest_dir) value is provided for each execution of this module @@ -130,6 +110,30 @@ aliases: - destination_dir - destdir + formats: + description: + - The format of the reply for the CLI command(s) specified by the + I(commands) option. The specified format(s) must be supported by the + target Junos device. The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all command(s) specified by the I(commands) option. If a + list of formats are specified, there must be one value in the list for + each command specified by the I(commands) option. Specifying the value + C(xml) for the I(formats) option is similar to appending + C(| display xml) to a CLI command, and specifying the value C(json) + for the I(formats) option is similar to appending C(| display json) to + a CLI command. + required: false + default: text + type: str or list of str + choices: + - text + - xml + - json + aliases: + - format + - display + - output return_output: description: - Indicates if the output of the command should be returned in the @@ -144,15 +148,6 @@ EXAMPLES = ''' --- -# -# MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the -# juniper_junos_command module. These examples use the default connection, -# authtentication and logging parameters. See the examples labeled -# CONNECTION_EXAMPLES for details on connection parameters. See the examples -# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. -# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. -# - name: Examples of juniper_junos_command hosts: junos-all connection: local @@ -228,84 +223,64 @@ - "json" dest_dir: "/tmp/outputs/" return_output: false - -# -# CONNECTION_EXAMPLES -# - -# -# AUTHENTICATION_EXAMPLES -# - -# -# LOGGING_EXAMPLES -# ''' RETURN = ''' -msg: +changed: description: - - A human-readable message indicating the result. - returned: always - type: str + - Indicates if the device's state has changed. Since this module does not + change the operational or configuration state of the device, the value + is always set to false. + - You could use this module to execute a command which + changes the operational state of the the device. For example, + C(clear ospf neighbors). Beware, this module is unable to detect + this situation, and will still return the value C(false) for I(changed) + in this case. + returned: success + type: bool + sample: false command: description: - The CLI command which was executed. returned: always type: str +failed: + description: + - Indicates if the task failed. See the I(results) key for additional + details. + returned: always + type: bool format: description: - The format of the command response. returned: always type: str - choices: ['text', 'xml', 'json'] -stdout: +msg: description: - - The command reply from the Junos device as a single multi-line string. - returned: when command executed successfully and I(return_output) is true. + - A human-readable message indicating the result. + returned: always type: str -stdout_lines: - description: - - The command reply from the Junos device as a list of single-line strings. - returned: when command executed successfully and I(return_output) is true. - type: list of str parsed_output: description: - - The command reply from the Junos device parsed into a JSON datastructure. - For XML replies, the response is parsed into JSON using the jxmlease - library. For JSON the response is parsed using the Python json library. - - NOTE: When Ansible converts the jxmlease or native Python data structure + - The command reply from the Junos device parsed into a JSON data structure. + For XML replies, the response is parsed into JSON using the + U(jxmlease|https://github.com/Juniper/jxmlease) + library. For JSON the response is parsed using the Python + U(json|https://docs.python.org/2/library/json.html) library. + - When Ansible converts the jxmlease or native Python data structure into JSON, it does not guarantee that the order of dictionary/object keys are maintained. returned: when command executed successfully, I(return_output) is true, - and the command format is xml or json. + and the value of the I(formats) option is C(xml) or C(json). type: dict -changed: - description: - - Indicates if the device's state has changed. Since this module doesn't - change the operational or configuration state of the device, the value - is always set to false. - - NOTE: You could use this module to execute a command which - changes the operational state of the the device. For example, - 'clear ospf neighbors'. Beware, this module is unable to detect - this situation, and will still return a I(changed) value - C(False) in this case. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. See I(results) option below for additional - details. - returned: always - type: bool results: description: - - The above keys are returned when a single command is specified for the + - The other keys are returned when a single command is specified for the I(commands) option. When the value of the I(commands) option is a list of commands, this key is returned instead. The value of this key is a list of dictionaries. Each element in the list corresponds to the commands in the I(commands) option. The keys for each element in the list - include all of the keys listed above. The I(failed) key indicates if the + include all of the other keys listed. The I(failed) key indicates if the individual command failed. In this case, there is also a top-level I(failed) key. The top-level I(failed) key will have a value of C(false) if ANY of the commands ran successfully. In this case, check the value @@ -313,6 +288,16 @@ results of individual commands. returned: when the I(commands) option is a list value. type: list of dict +stdout: + description: + - The command reply from the Junos device as a single multi-line string. + returned: when command executed successfully and I(return_output) is C(true). + type: str +stdout_lines: + description: + - The command reply from the Junos device as a list of single-line strings. + returned: when command executed successfully and I(return_output) is C(true). + type: list of str ''' import sys diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index 13ca4f2a..6a58794a 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2014, Jeremy Schulman # 2015, Rick Sherman # @@ -43,316 +43,214 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_config version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Manipulate the configuration of a Junos device. +short_description: Manipulate the configuration of a Junos device description: - - Manipulate the configuration of a Junos device. This module allows a + - > + Manipulate the configuration of a Junos device. This module allows a combination of loading or rolling back, checking, diffing, retrieving, and committing the configuration of a Junos device. It performs the following steps in order: - 1) Open a candidate configuration database. - - If the I(config_mode) option has a value of C(exclusive), the default, + + #. Open a candidate configuration database. + + * If the I(config_mode) option has a value of C(exclusive), the default, take a lock on the candidate configuration database. If the lock fails the module fails and reports an error. - - If the I(config_mode) option has a value of C(private), open a private + * If the I(config_mode) option has a value of C(private), open a private candidate configuration database. If opening the private configuration database fails the module fails and reports an error. - 2) Load configuration data into the candidate configuration database. - - Configuration data may be loaded using the I(load) or I(rollback) + #. Load configuration data into the candidate configuration database. + + * Configuration data may be loaded using the I(load) or I(rollback) options. If either of these options are specified, new configuration data is loaded. If neither option is specified, this step is skipped. - - If the I(rollback) option is specified, replace the candidate + * If the I(rollback) option is specified, replace the candidate configuration with the previous configuration specified by the value of the I(rollback) option. - - If the I(load) option is specified, load new configuration data. - - The value of the I(load) option defines the type of load which is - performed. - - The source of the new configuration data is one of the following: - - I(src) - A file path on the local Ansible control machine. - - I(lines) - A list of strings containing the configuration data. - - I(template) - A file path to a Jinja2 template on the local - Ansible control machine. This template is rendered - with the variables specified by the I(vars) option. - If the I(template) option is specified, the I(vars) - option must also be specified. - - I(url) - A URL reachable from the target Junos device. - - If the I(format) option is specified, the configuration file being - loaded is in the specified format, rather than the format being - determined from the file name. - 3) Check the validity of the candidate configuration database. - - If the I(check) option is C(true), the default, check the validity + * If the I(load) option is specified, load new configuration data. + * The value of the I(load) option defines the type of load which is + performed. + * The source of the new configuration data is one of the following: + + * I(src) - A file path on the local Ansible control machine. + * I(lines) - A list of strings containing the configuration data. + * I(template) - A file path to a Jinja2 template on the local + Ansible control machine. This template is rendered with the variables + specified by the I(vars) option. If the I(template) option is + specified, the I(vars) option must also be specified. + * I(url) - A URL reachable from the target Junos device. + * If the I(format) option is specified, the configuration file being + loaded is in the specified format, rather than the format determined + from the file name. + #. Check the validity of the candidate configuration database. + + * If the I(check) option is C(true), the default, check the validity of the configuration by performing a "commit check" operation. - - This option may be specified with I(diff) C(false) and I(commit) + * This option may be specified with I(diff) C(false) and I(commit) C(false) to confirm a previous "commit confirmed " operation without actually performing an additional commit. - - If the configuration check fails, further processing stops, the module + * If the configuration check fails, further processing stops, the module fails, and an error is reported. - 4) Determine differences between the candidate and committed configuration + #. Determine differences between the candidate and committed configuration databases. - - If step 2 was not skipped, and the I(diff) option is C(true), + + * If step 2 was not skipped, and the I(diff) option is C(true), the default, perform a diff between the candidate and committed configuration databases. - - If the I(diffs_file) or I(dest_dir) option is specified, save the + * If the I(diffs_file) or I(dest_dir) option is specified, save the generated configuration differences. - - If the I(return_output) option is C(true), the default, include the + * If the I(return_output) option is C(true), the default, include the generated configuration difference in the I(diff) and I(diff_lines) keys of the module's response. - 5) Retrieve the configuration database from the Junos device. - - If the I(retrieve) option is specified, retrieve the configuration + #. Retrieve the configuration database from the Junos device. + + * If the I(retrieve) option is specified, retrieve the configuration database specified by the I(retrieve) value from the target Junos device to the local Ansible control machine. - - The format in which the configuration is retrieved is specified by the + * The format in which the configuration is retrieved is specified by the value of the I(format) option. - - The optional I(filter) controls which portions of the configuration + * The optional I(filter) controls which portions of the configuration are retrieved. - - If I(options) are specified, they control the content of the + * If I(options) are specified, they control the content of the configuration retrieved. - - If the I(dest) or I(dest_dir) option is specified, save the + * If the I(dest) or I(dest_dir) option is specified, save the retrieved configuration to a file on the local Ansible control machine. - - If the I(return_output) option is C(true), the default, include the + * If the I(return_output) option is C(true), the default, include the retrieved configuration in the I(config), I(config_lines), and I(config_parsed) keys of the module's response. - 6) Commit the configuration changes. - - If the I(commit) option is C(true), the default, commit the + #. Commit the configuration changes. + + * If the I(commit) option is C(true), the default, commit the configuration changes. - - This option may be specified with I(diff) C(false) and I(check) + * This option may be specified with I(diff) C(false) and I(check) C(false) to confirm a previous "commit confirmed " operation. - - If the I(comment) option is specified, add the comment to the commit. - - If the I(confirmed) option is specified, perform a - "commit confirmed " operation where is the value of the + * If the I(comment) option is specified, add the comment to the commit. + * If the I(confirmed) option is specified, perform a + C(commit confirmed) I(min) operation where I(min) is the value of the I(confirmed) option. - - If the I(check) option is C(true) and the I(check_commit_wait) + * If the I(check) option is C(true) and the I(check_commit_wait) option is specified, wait I(check_commit_wait) seconds before performing the commit. - 7) Close the candidate configuration database. - - Close and discard the candidate configuration database. - - If the I(config_mode) option has a value of C(exclusive), the default, + #. Close the candidate configuration database. + + * Close and discard the candidate configuration database. + * If the I(config_mode) option has a value of C(exclusive), the default, unlock the candidate configuration database. - -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common options: - ignore_warning: + check: description: - - A boolean, string or list of strings. If the value is C(true), - ignore all warnings regardless of the warning message. If the value - is a string, it will ignore warning(s) if the message of each warning - matches the string. If the value is a list of strings, ignore - warning(s) if the message of each warning matches at least one of the - strings in the list. The C(ignore_warning) value is applied to the - load and commit operations performed by this module. - load and commit operations performed by this module. - required: false - default: none - type: bool, str, or list of str - config_mode: - description - - The mode used to access the candidate configuration database. + - Perform a commit check operation. required: false - default: 'exclusive' - choices: ['exclusive', 'private'] - type: 'str' + default: true (false if retrieve is set and load and rollback are not set) + type: bool aliases: - - config_access - - edit_mode - - edit_access - rollback: + - check_commit + - commit_check + check_commit_wait: description: - - Populate the candidate configuration from a previously committed - configuration. This value be a configuration number between 0 and 49, - or the keyword C(rescue) to load the previously saved rescue - configuration. - - By default, some Junos platforms store fewer than 50 previous - configurations. Specifying a value greater than the number - of previous configurations available, or specifying C(rescue) when no - rescue configuration has been saved, will result in an error when the - module attempts to perform the rollback. - - The I(rollback) and I(load) options are mutually exclusive. + - The number of seconds to wait between check and commit operations. + - This option is only valid if I(check) is C(true) and I(commit) is + C(true). + - This option should not normally be needed. It works around an issue in + some versions of Junos. required: false default: none - choices: [0-49, 'rescue'] - type: 'int' or 'str' - load: + type: int + comment: description: - - Specifies the type of load operation to be performed. - - The I(load) and I(rollback) options are mutually exclusive. - - The choices have the following meanings: - none - Do not perform a load operation. - merge - Combine the new configuration with the existing configuration. - If statements in the new configuration conflict with statements - in the existing configuration, the statements in the new - configuration replace those in the existing configuration. - replace - This option is a superset of the 'merge' option. It combines - the new configuration with the existing configuration. If the - new configuration is in text format and a hierarchy level in - the new configuration is prefixed with the string - 'replace:', then the hierarchy level in the new configuration - replaces the entire correpsonding hierarhcy level in the - existing configuration, regardless of the existence or - content of that hierarchy level in the existing - configuration. If the configuration is in XML format, the - XML attribute replace="replace" is equivalent to the text - format's 'replace:' prefix. If a configuration hierarhcy in - the new configuration is not prefixed with 'replace:', then - the 'merge' behavior is used. Specifically, for any - statements in the new configuration which conflict with - statements in the existing configuration, the statements in - the new configuration replace those in the existing - configuration. - override - Discard the entire existing configuration and replace it - with the new configuration. When the configuration is - later committed, all system processes are notified and the - entire new configuration is marked as 'changed' even if - some statements previously existing in the configuration. - The value 'overwrite' is a synonym for 'override'. - update - This option is similar to the 'override' option. The new - configuration completely replaces the existing configuration. - The difference comes when the configuration is later - committed. This option performs a 'diff' between the new - candidate configuration and the existing committed - configuration. It then only notifies system processes - responsible for the changed portions of the configuration, and - only marks the actual configuration changes as 'changed'. - set - This option is used when the new configuraiton data is in set - format (a series of configuration mode commands). The new - configuration data is loaded line by line and may contain any - configuration mode commands, such as set, delete, edit, - or deactivate. This value must be specified if the new - configuration is in set format. + - Provide a comment to be used with the commit operation. + - This option is only valid if the I(commit) option is true. required: false default: none - choices: [none, 'set', 'merge', 'update', - 'replace', 'override', 'overwrite'] type: str - src: - description: - - Used with the I(load) option. Specifies the path to a file, on the - local Ansible control machine, containing the configuration to be - loaded. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - By default, the format of the configuration data is determined by the - file extension of this path name. If the file has a C(.conf) - extension, the content is treated as text format. If the file has a - C(.xml) extension, the content is treated as XML format. If the file - has a C(.set) extension, the content is treated as Junos B(set) - commands. - - If the I(format) option is specified, the I(format) value overrides the - file-extension based format detection. - required: false - default: none - type: 'path' - aliases: - - source - - file - lines: + commit: description: - - Used with the I(load) option. Specifies a list of list of - configuration strings containing the configuration to be loaded. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - By default, the format of the configuration data is auto-dectected by - the content of the first line in the I(lines) list. - - If the I(format) option is specified, the I(format) value overrides the - format auto-detection. + - Perform a commit operation. required: false - default: none - type: 'list' - template: + default: true (false if retrieve is set and load and rollback are not set) + type: bool + commit_empty_changes: description: - - The path to a Jinja2 template file, on the local Ansible control - machine, used to generate, along with the I(vars) option, the - configuration to be loaded on the target Junos device. - - The I(template) and I(vars) options are required together. If one is - specified, the other must be specified. + - Perform a commit operation, even if there are no changes between the + candidate configuration and the committed configuration. required: false - default: none - type: 'path' - aliases: - - template_path - vars: + default: false + type: bool + config_mode: description: - - A dictionary of keys and values used to render the Jinja2 template - specified by the I(template) option. - - The I(template) and I(vars) options are required together. If one is - specified, the other must be specified. + - The mode used to access the candidate configuration database. required: false - default: none - type: 'dict' + default: exclusive + type: str + choices: + - exclusive + - private aliases: - - template_vars - url: + - config_access + - edit_mode + - edit_access + confirmed: description: - - A URL, as documented at the 'url' section of: - U(https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html) - - Specifies the configuration data to load on the target Junos device. + - Provide a confirmed timeout, in minutes, to be used with the commit + operation. + - This option is only valid if the I(commit) option is C(true). + - The value of this option is the number of minutes to wait for another + commit operation before automatically rolling back the configuration + change performed by this task. In other words, this option causes the + module to perform a C(commit confirmed )I(min) where I(min) is the + value of the I(confirmed) option. This option DOES NOT confirm a + previous C(commit confirmed )I(min) operation. To confirm a previous + commit operation, invoke this module with the I(check) or I(commit) + option set to C(true). required: false default: none - type: 'str' - format: - description: - - The format of the configuration retrieved, if I(retrieve) is not none, - and/or loaded, if I(load) is not none. The specified format must be - supported by the target Junos device. - required: false - default: none (auto-detect on load, 'text' on retrieve) - choices: ['xml', 'set', 'text', 'json'] - check: - - Perform a commit check operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - aliases: - - check_commit - - commit_check - diff: - - Perform a configuration compare (diff) operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool + type: int aliases: - - compare - - diffs - diffs_file: + - confirm + dest: description: - - The path to a file, on the Ansible control machine, where the - configuration differences will be saved if the I(diff) option is - specified. + - The path to a file, on the local Ansible control machine, where the + configuration will be saved if the I(retrieve) option is specified. - The file must be writeable. If the file already exists, it is overwritten. - - This option is only valid if the I(diff) option is C(true). - - NOTE: When tasks are executed against more than one target host, + - This option is only valid if the I(retrieve) option is not C(none). + - When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See - U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including - {{ inventory_hostname }} in the I(diffs_file) value. It is the user's + C({{ inventory_hostname }}) in the I(dest) value. It is the user's responsibility to ensure this value is unique per target host. - For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(diffs_file) and I(dest_dir) options are mutually exclusive. + The I(dest) and I(dest_dir) options are mutually exclusive. required: false - default: None + default: none type: path + aliases: + - destination dest_dir: description: - The path to a directory, on the Ansible control machine. This is the directory where the configuration will be saved if the I(retrieve) option is specified. It is also the directory where the configuration diff will be specified if the I(diff) option is C(true). - - This option is only valid if the I(retrieve) option is not none or the - I(diff) option is I(true). + - This option is only valid if the I(retrieve) option is not C(none) or + the I(diff) option is C(true). - The retrieved configuration will be saved to a file named - {{ inventory_hostname }}.C(format_extension) in the I(dest_dir) - directory. Where C(format_extension) is C(conf) for text format, C(xml) + C({{ inventory_hostname }}.)I(format_extension) in the I(dest_dir) + directory. Where I(format_extension) is C(conf) for text format, C(xml) for XML format, C(json) for JSON format, and C(set) for set format. - - If the I(diff) option is true, the configuration diff will be save to - a file named {{ inventory_hostname }}.diff in the I(dest_dir) + - If the I(diff) option is C(true), the configuration diff will be saved + to a file named C({{ inventory_hostname }}.diff) in the I(dest_dir) directory. - The destination file must be writeable. If the file already exists, it is overwritten. It is the users responsibility to ensure a unique @@ -370,117 +268,250 @@ - destdir - savedir - save_dir - return_output: + diff: description: - - Indicates if the output of the I(diff) and I(retreive) options should - be returned in the module's response. You might want to set this option - to C(false), and set the I(dest_dir) option, if the configuration or - diff output is very large and you only need to save the output rather - than using it's content in subsequent tasks/plays of your playbook. + - Perform a configuration compare (aka diff) operation. required: false - default: true + default: true (false if retrieve is set and load and rollback are not set) type: bool - retrieve: + aliases: + - compare + - diffs + diffs_file: description: - - The configuration database to be retrieved. + - The path to a file, on the Ansible control machine, where the + configuration differences will be saved if the I(diff) option is + specified. + - The file must be writeable. If the file already exists, it is + overwritten. + - This option is only valid if the I(diff) option is C(true). + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(diffs_file) value. It is the + user's responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + - The I(diffs_file) and I(dest_dir) options are mutually exclusive. required: false - default: none - choices: [none, 'candidate', 'committed'] - options: + default: None + type: path + format: description: - - Additional options, specified as a dictionary of key/value pairs, used - when retrieving the configuration. See - U(https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) - for information on available options. + - Specifies the format of the configuration retrieved, if I(retrieve) + is not C(none). + - Specifies the format of the configuration to be loaded, if I(load) is + not C(none). + - The specified format must be supported by the target Junos device. required: false - default: None - type: dict + default: none (auto-detect on load, text on retrieve) + type: str + choices: + - xml + - set + - text + - json filter: description: - A string of XML, or '/'-separated configuration hierarchies, which specifies a filter used to restrict the portions of the configuration which are retrieved. See - U(http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + U(PyEZ's get_config method documentation|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) for details on the value of this option. required: false default: none type: 'str' aliases: - filter_xml - dest: + ignore_warning: description: - - The path to a file, on the local Ansible control machine, where the - configuration will be saved if the I(retrieve) option is specified. - - The file must be writeable. If the file already exists, it is - overwritten. - - This option is only valid if the I(retrieve) option is not none. - - NOTE: When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - {{ inventory_hostname }} in the I(dest) value. It is the user's - responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(dest) and I(dest_dir) options are mutually exclusive. + - A boolean, string or list of strings. If the value is C(true), + ignore all warnings regardless of the warning message. If the value + is a string, it will ignore warning(s) if the message of each warning + matches the string. If the value is a list of strings, ignore + warning(s) if the message of each warning matches at least one of the + strings in the list. The value of the I(ignore_warning) option is + applied to the load and commit operations performed by this module. + required: false + default: none + type: bool, str, or list of str + lines: + description: + - Used with the I(load) option. Specifies a list of list of + configuration strings containing the configuration to be loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is auto-dectected by + the content of the first line in the I(lines) list. + - If the I(format) option is specified, the I(format) value overrides the + format auto-detection. + required: false + default: none + type: list + load: + description: + - Specifies the type of load operation to be performed. + - The I(load) and I(rollback) options are mutually exclusive. + - > + The choices have the following meanings: + - B(none) - Do not perform a load operation. + - B(merge) - Combine the new configuration with the existing + configuration. If statements in the new configuration conflict with + statements in the existing configuration, the statements in + the new configuration replace those in the existing + configuration. + - B(replace) - This option is a superset of the B(merge) option. It + combines the new configuration with the existing configuration. If the + new configuration is in text format and a hierarchy level in the new + configuartion is prefixed with the string C(replace:), then the + hierarchy level in the new configuration replaces the entire + corresponding hierarchy level in the existing configuration, regardles + of the existence or content of that hierarchy level in the existing + configuration. If the configuration is in XML format, the XML attribute + C(replace = "replace") is equivalent to the text format's C(replace:) + prefix. If a configuration hierarchy in the new configuration is not + prefixed with C(replace:), then the B(merge) behavior is used. + Specifically, for any statements in the new configuration which + conflict with statements in the existing configuration, the statements + in the new configuration replace those in the existing configuration. + - B(override) - Discard the entire existing configuration and replace it + with the new configuration. When the configuration is later committed, + all system processes are notified and the entire new configuration is + marked as 'changed' even if some statements previously existed in the + configuration. The value B(overwrite) is a synonym for B(override). + - B(update) - This option is similar to the B(override) option. The new + configuration completely replaces the existing configuration. The + difference comes when the configuration is later committed. This option + performs a 'diff' between the new candidate configuration and the + existing committed configuration. It then only notifies system + processes repsonsible for the changed portions of the configuration, + and only marks the actual configuration changes as 'changed'. + - B(set) - This option is used when the new configuration data is in set + format (a series of configuration mode commands). The new configuration + data is loaded line by line and may contain any configuration mode + commands, such as set, delete, edit, or deactivate. This value must be + specified if the new configuration is in set format. + required: false + default: none + choices: + - none + - set + - merge + - update + - replace + - override + - overwrite + type: str + options: + description: + - Additional options, specified as a dictionary of key/value pairs, used + when retrieving the configuration. See the + U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) + for information on available options. required: false default: None - type: path - aliases: - - destination - commit: + type: dict + retrieve: description: - - Perform a commit operation. + - The configuration database to be retrieved. required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - commit_empty_changes: + default: none + choices: + - none + - candidate + - committed + type: str + return_output: description: - - Perform a commit operation, even if there are no changes between the - candidate configuration and the committed configuration. + - Indicates if the output of the I(diff) and I(retreive) options should + be returned in the module's response. You might want to set this option + to C(false), and set the I(dest_dir) option, if the configuration or + diff output is very large and you only need to save the output rather + than using it's content in subsequent tasks/plays of your playbook. required: false - default: false + default: true type: bool - confirmed: + rollback: description: - - Provide a confirmed timeout, in minutes, to be used with the commit - operation. This option is only valid if the I(commit) option is true. - The value of this option is the number of minutes to wait for another - commit operation before automatically rolling back the configuration - change performed by this task. In other words, this option causes the - module to perform a "commit confirmed " where is the value - of the I(confirmed) option. This option DOES NOT confirm a previous - "commit confirmed " operation. To confirm a previous commit - operation, invoke this module with the I(check) or I(commit) - option set to true. + - Populate the candidate configuration from a previously committed + configuration. This value can be a configuration number between 0 and + 49, or the keyword C(rescue) to load the previously saved rescue + configuration. + - By default, some Junos platforms store fewer than 50 previous + configurations. Specifying a value greater than the number + of previous configurations available, or specifying C(rescue) when no + rescue configuration has been saved, will result in an error when the + module attempts to perform the rollback. + - The I(rollback) and I(load) options are mutually exclusive. required: false default: none - type: int + choices: + - 0-49 + - rescue + type: int or str + src: + description: + - Used with the I(load) option. Specifies the path to a file, on the + local Ansible control machine, containing the configuration to be + loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is determined by the + file extension of this path name. If the file has a C(.conf) + extension, the content is treated as text format. If the file has a + C(.xml) extension, the content is treated as XML format. If the file + has a C(.set) extension, the content is treated as Junos B(set) + commands. + - If the I(format) option is specified, the I(format) value overrides the + file-extension based format detection. + required: false + default: none + type: 'path' aliases: - - confirm - comment: + - source + - file + template: description: - - Provide a comment to be used with the commit operation. This option is - only valid if the I(commit) option is true. + - The path to a Jinja2 template file, on the local Ansible control + machine. This template file, along with the I(vars) option, is used to + generate the configuration to be loaded on the target Junos device. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. + required: false + default: none + type: path + aliases: + - template_path + url: + description: + - A URL which specifies the configuration data to load on the target + Junos device. + - The Junos device uses this URL to load the configuration, therefore + this URL must be reachable by the target Junos device. + - The possible formats of this value are documented in the 'url' section + of the + U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. required: false default: none type: str - check_commit_wait: + vars: description: - - The number of seconds to wait between check and commit operations. - - This option is only valid if I(check) is C(true) and I(commit) is - C(true). - - This option should not normally be needed. It works around an issue in - some versions of Junos. + - A dictionary of keys and values used to render the Jinja2 template + specified by the I(template) option. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. required: false default: none - type: int - -# Options to load from other sources -# Templates, url, variable, argument -# + type: dict + aliases: + - template_vars ''' EXAMPLES = ''' @@ -630,67 +661,72 @@ - name: Print the complete response debug: var: response - -# Document connection examples -# Document authentication examples -# Document logging examples -# extends_documentation_fragment: juniper_junos_common ''' RETURN = ''' -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -file: +changed: description: - - The value of the I(src) option. - returned: when I(load) is not None and I(src) is not None - type: str + - Indicates if the device's configuration has changed, or would have + changed when in check mode. + returned: success + type: bool config: - description: The retrieved configuration. The value is a single multi-line - string in the format specified by the I(format) option. - returned: when I(retrieved) is not None and I(return_output) is true. + description: + - The retrieved configuration. The value is a single multi-line + string in the format specified by the I(format) option. + returned: when I(retrieved) is not C(none) and I(return_output) is C(true). type: str -config_lines: The retrieved configuration. The value is a list of single-line - strings in the format specified by the I(format) option. - returned: when I(retrieved) is not None and I(return_output) is true. +config_lines: + description: + - The retrieved configuration. The value is a list of single-line + strings in the format specified by the I(format) option. + returned: when I(retrieved) is not C(none) and I(return_output) is C(true). type: list -config_parsed: The retrieved configuration parsed into a JSON datastructure. - For XML replies, the response is parsed into JSON using the - jxmlease library. For JSON the response is parsed using the - Python json library. - NOTE: When Ansible converts the jxmlease or native Python data - structure into JSON, it does not guarantee that the order of - dictionary/object keys are maintained. - returned: when I(retrieved) is not None, the I(format) option is 'xml' or - 'json' and I(return_output) is true. - type: complex +config_parsed: + description: + - The retrieved configuration parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the + jxmlease library. For JSON the response is parsed using the + Python json library. + - When Ansible converts the jxmlease or native Python data + structure into JSON, it does not guarantee that the order of + dictionary/object keys are maintained. + returned: when I(retrieved) is not C(none), the I(format) option is C(xml) or + C(json) and I(return_output) is C(true). + type: dict diff: - description: The configuration differences between the previous and new - configurations. The value is a single multi-line string in - "diff" format. - returned: when I(load) or I(rollback) is specified, I(diff) is true, and - I(return_output) is true. + description: + - The configuration differences between the previous and new + configurations. The value is a single multi-line string in "diff" format. + returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and + I(return_output) is C(true). type: str -diff_lines: The configuration differences between the previous and new - configurations. The value is a list of single-line strings in - "diff" format. - returned: when I(load) or I(rollback) is specified, I(diff) is true, and - I(return_output) is true. +diff_lines: + description: + - The configuration differences between the previous and new + configurations. The value is a list of single-line strings in "diff" + format. + returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and + I(return_output) is C(true). type: list -changed: - description: Indicates if the device's configuration has changed, or would - have changed when in check mode. - returned: success - type: bool failed: - description: Indicates if the task failed. + description: + - Indicates if the task failed. returned: always type: bool +file: + description: + - The value of the I(src) option. + returned: when I(load) is not C(none) and I(src) is not C(none) + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str ''' + # Standard library imports import time diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index c1f4623a..87d93ebf 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2014, Jeremy Schulman # # All rights reserved. @@ -42,19 +42,18 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_facts version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Retrieve facts from a Junos device description: - - Retrieve facts from a Junos device using the PyEZ fact gathering system. - The specific facts returned are documented at: - U(http://junos-pyez.readthedocs.io/en/latest/jnpr.junos.facts.html) - Also returns the committed configuration of the Junos device if the + - Retrieve facts from a Junos device using the + U(PyEZ fact gathering system|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html). + - Also returns the committed configuration of the Junos device if the I(config_format) option has a value other than C(none). -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common options: config_format: description: @@ -62,17 +61,24 @@ supported by the target Junos device. required: false default: none - choices: [none, 'xml', 'set', 'text', 'json'] + choices: + - none + - xml + - set + - text + - json savedir: description: - A path to a directory, on the Ansible control machine, where facts - will be stored in a JSON file. The resulting JSON file is saved in: - C(savedir/hostname-facts.json). The directory is the value of - I(savedir). The filename begins with the value of the hostname fact - returned from the Junos device, which might be different than the - value of the I(host) option passed to the module. If the value of the - I(savedir) option is C(none), the default, then facts are NOT saved to - a file. + will be stored in a JSON file. + - The resulting JSON file is saved in + I(savedir)C(/)I(hostname)C(-facts.json). + - The I(savedir) directory is the value of the I(savedir) option. + - The I(hostname)C(-facts.json) filename begins with the value of the + C(hostname) fact returned from the Junos device, which might be + different than the value of the I(host) option passed to the module. + - If the value of the I(savedir) option is C(none), the default, then + facts are NOT saved to a file. required: false default: none type: path @@ -99,11 +105,6 @@ # Using savedir option # Print the saved JSON file - -# Document connection examples -# Document authentication examples -# Document logging examples -# extends_documentation_fragment: juniper_junos_common ''' RETURN = ''' @@ -112,8 +113,8 @@ - Facts collected from the Junos device. This dictionary contains the keys listed in the I(contains) section of this documentation PLUS all of the keys returned from PyEZ's fact gathering system. See - U(http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) - for a complete list of these keys and thier meaning. + U(PyEZ facts|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) + for a complete list of these keys and their meaning. returned: success type: complex contains: @@ -121,11 +122,13 @@ description: - The device's committed configuration, in the format specified by I(config_format), as a single multi-line string. - returned: when I(config_format) is not none. + returned: when I(config_format) is not C(none). type: str has_2RE: description: - Indicates if the device has more than one Routing Engine installed. + Because Ansible does not allow keys to begin with a number, this fact + is returned in place of PyEZ's C(2RE) fact. returned: success type: bool re_name: @@ -136,25 +139,30 @@ master_state: description: - The mastership state of the Routing Engine to which Ansible is - connected. True if the RE is the master Routing Engine. False if - the RE is not the master Routing Engine. + connected. C(true) if the RE is the master Routing Engine. C(false) + if the RE is not the master Routing Engine. returned: success type: bool -facts: - description: Returned for backwards compatibility. Returns the same keys and - values which are returned under I(ansible_facts.junos). - returned: success - type: dict changed: - description: Indicates if the device's state has changed. Since this module - doesn't change the operational or configuration state of the - device, the value is always set to false. + description: + - Indicates if the device's state has changed. Since this module does not + change the operational or configuration state of the device, the value is + always set to C(false). returned: success type: bool + sample: false +facts: + description: + - Returned for backwards compatibility. Returns the same keys and values + which are returned under I(ansible_facts.junos). + returned: success + type: dict failed: - description: Indicates if the task failed. + description: + - Indicates if the task failed. returned: always type: bool + sample: false ''' # Standard library imports diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index 4759868e..3c862c7e 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2016, Roslan Zaki # # All rights reserved. @@ -42,43 +42,42 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_jsnapy version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Roslan Zaki, Damien Garros, & Stacy Smith (@stacywsmith)" +author: + - Juniper Networks + - Roslan Zaki + - Damien Garros + - Stacy Smith (@stacywsmith)" short_description: Execute JSNAPy tests on a Junos device description: - Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. - JSNAPy is documented at: U(https://github.com/Juniper/jsnapy) and - U(https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) - - This module only reports "failed" if the module encounters an error and - fails to execute the JSNAPy tests. If does NOT report "failed" if one or + JSNAPy is documented on U(Github|https://github.com/Juniper/jsnapy) and + this + U(Day One Book|https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) + - This module only reports C(failed) if the module encounters an error and + fails to execute the JSNAPy tests. If does NOT report C(failed) if one or more of the JSNAPy tests fail. To check the test results, register the module's response and use the assert module to verify the expected result - in the response. (See MODULE_EXAMPLES.) + in the response. (See :ref:`examples-label`.) - A callback plugin which formats and prints JSNAPy test results for human consumption is also available. This callback plugin is enabled by adding - "callback_whitelist = jsnapy" to the Ansible configuration file. - -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common + C(callback_whitelist = jsnapy) to the Ansible configuration file. options: action: description: - The JSNAPy action to perform. required: true default: none - type: 'str' - choices: ['check', 'snapcheck', 'snap_pre', 'snap_post'] - test_files: - description: - - The filename of file(s) in the I(dir) directory. Each file contains - JSNAPy test case definitions. The I(test_files) option and the - I(config_file) option are mutually exclusive. Either the I(test_files) - option or the I(config_file) option is required. - required: false - type: 'list of path' - default: none + type: str + choices: + - check + - snapcheck + - snap_pre + - snap_post config_file: description: - The filename of a JSNAPy configuration file (in YAML format). The @@ -86,7 +85,7 @@ exclusive. Either the I(test_files) option or the I(config_file) option is required. required: false - type: 'path' + type: path default: none dir: description: @@ -94,23 +93,24 @@ by the I(test_files) option or the JSNAPy configuration file specified by the I(config_file) option. required: false - type: 'path' - default: '/etc/jsnapy/testfiles' + type: path + default: /etc/jsnapy/testfiles aliases: - directory + test_files: + description: + - The filename of file(s) in the I(dir) directory. Each file contains + JSNAPy test case definitions. The I(test_files) option and the + I(config_file) option are mutually exclusive. Either the I(test_files) + option or the I(config_file) option is required. + required: false + type: list of path + default: none ''' + EXAMPLES = ''' --- -# -# MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the -# juniper_junos_jsnapy module. These examples use the default connection, -# authentication and logging parameters. See the examples labeled -# CONNECTION_EXAMPLES for details on connection parameters. See the examples -# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. -# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. -# - name: Examples of juniper_junos_jsnapy hosts: junos-all connection: local @@ -169,43 +169,19 @@ - name: Print the full test response debug: var: test3 - -# -# CONNECTION_EXAMPLES -# - -# -# AUTHENTICATION_EXAMPLES -# - -# -# LOGGING_EXAMPLES -# ''' RETURN = ''' -msg: - description: - - A human-readable message indicating the result of the JSNAPy tests. - returned: always - type: str action: description: - The JSNAPy action performed as specified by the I(action) option. returned: success type: str - - -final_result: -total_passed: -total_failed: - - changed: description: - Indicates if the device's state has changed. Since this module doesn't change the operational or configuration state of the device, the value - is always set to false. + is always set to C(false). returned: success type: bool failed: @@ -213,6 +189,14 @@ - Indicates if the task failed. returned: always type: bool +# final_result: +msg: + description: + - A human-readable message indicating the result of the JSNAPy tests. + returned: always + type: str +# total_passed: +# total_failed: ''' # Standard Library imports diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index 1cba8743..6758c90c 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2016, Damien Garros # # All rights reserved. @@ -42,17 +42,32 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_ping version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Stacy Smith (@stacywsmith)" +author: Juniper Networks - Stacy Smith (@stacywsmith) short_description: Execute ping from a Junos device description: - - Execute the ping command from a Junos device in order to test network - reachability from the Junos device to a specified destination. -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common + - Execute the ping command from a Junos device to a specified destination in + order to test network reachability from the Junos device . options: + acceptable_percent_loss: + description: + - Maximum percentage of packets that may be lost and still consider the + task not to have failed. + required: false + default: 0 + type: int + aliases: + - acceptable_packet_loss + count: + description: + - Number of packets to send. + required: false + default: 5 + type: int dest: description: - The IP address, or hostname if DNS is configured on the Junos device, @@ -66,48 +81,42 @@ - destination - destination_ip - destination_host - acceptable_percent_loss: + do_not_fragment: description: - - Maximum percentage of packets that may be lost and still consider the - task not to have failed. + - Set Do Not Fragment bit on ping packets. required: false - default: 0 - type: int - aliases: - - acceptable_packet_loss - count: + default: false + type: bool + interface: description: - - Number of packets to send. + - The source interface from which the the ping is sent. If not + specified, the default Junos algorithm for determining the source + interface is used. required: false - default: 5 - type: int + default: none + type: str rapid: description: - - Send ping requests rapidly + - Send ping requests rapidly required: false default: true type: bool - ttl: + routing_instance: description: - - Maximum number of IP routers (hops) allowed between source and - destination. + - Name of the source routing instance from which the ping is + originated. If not specified, the default routing instance is used. required: false - default: None (default ttl for device) - type: int + default: none + type: str size: description: - - The size of the ICMP payload of the ping. NOTE: Total size of the IP - packet is I(size) + the 20 byte IP header + the 8 byte ICMP header. - Therefore, I(size) of 1472 generates an IP packet of size 1500. + - The size of the ICMP payload of the ping. + - Total size of the IP packet is I(size) + the 20 byte IP header + + the 8 byte ICMP header. Therefore, I(size) of C(1472) generates an IP + packet of size 1500. required: false - default: None (default size for device) + default: none (default size for device) type: int - do_not_fragment: - description: - - Set Do Not Fragment bit on ping packets. - required: false - default: false - type:bool source: description: - The IP address, or hostname if DNS is configured on the Junos device, @@ -122,34 +131,17 @@ - src - src_ip - src_host - interface: - description: - - The source interface from which the the ping is sent. If not - specified, the default Junos algorithm for determining the source - interface is used. - required: false - default: none - type: str - routing_instance: + ttl: description: - - Name of the source routing instance from which the ping is - originated. If not specified, the default routing instance is used. + - Maximum number of IP routers (hops) allowed between source and + destination. required: false - default: none - type: str + default: none (default ttl for device) + type: int ''' EXAMPLES = ''' --- -# -# MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the juniper_junos_ping -# module. These examples use the default connection, authtentication and -# logging parameters. See the examples labeled CONNECTION_EXAMPLES for details -# on connection parameters. See the examples labeled AUTHENTICATION_EXAMPLES -# for details on authentication parameters. See the examples labeled -# LOGGING_EXAMPLES for details on logging parameters. -# - name: Examples of juniper_junos_ping hosts: junos-all connection: local @@ -235,78 +227,34 @@ juniper_junos_ping: dest: "224.0.0.1" interface: "ge-0/0/0.0" - -# -# CONNECTION_EXAMPLES -# - -# -# AUTHENTICATION_EXAMPLES -# - -# -# LOGGING_EXAMPLES -# ''' RETURN = ''' -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -packet_loss: +acceptable_percent_loss: description: - - The percentage of packets lost. + - The acceptable packet loss (as a percentage) for this task as specified + by the I(acceptable_percent_loss) option. returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: str -packets_sent: +changed: description: - - The number of packets sent. + - Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to C(false). returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. - type: str -packets_received: + type: bool +count: description: - - The number of packets received. + - The number of pings sent, as specified by the I(count) option. returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: str -rtt_minimum: - description: - - The minimum round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and packet_loss < 100%. - type: str -rtt_maximum: - description: - - The maximum round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and packet_loss < 100%. - type: str -rtt_average: - description: - - The average round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and packet_loss < 100%. - type: str -rtt_stddev: - description: - - The standard deviation of round-trip-time, in microseconds, of all ping - responses received. - returned: when ping successfully executed, and packet_loss < 100%. - type: str -warnings: - description: - - A list of warning strings, if any, produced from the ping. - returned: when warnings are present - type: list -changed: +do_not_fragment: description: - - Indicates if the device's state has changed. Since this module - doesn't change the operational or configuration state of the - device, the value is always set to false. + - Whether or not the do not fragment bit was set on the pings sent, as + specified by the I(do_not_fragment) option. returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: bool @@ -318,27 +266,39 @@ host: description: - The destination IP/host of the pings sent as specified by the I(dest) - option. NOTE: Key I(dest) and I(dest_ip) is also returned for backwards + option. + - Keys I(dest) and I(dest_ip) are also returned for backwards compatibility. returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: str -acceptable_percent_loss: +interface: description: - - The acceptable packet loss (as a percentage) for this task as specified - by the I(acceptable_percent_loss) option. + - The source interface of the pings sent as specified by the + I(interface) option. + returned: when ping successfully executed and the I(interface) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +packet_loss: + description: + - The percentage of packets lost. returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: str -timeout: +packets_sent: description: - - The number of seconds to wait for a response from the ping RPC. + - The number of packets sent. returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: str -count: +packets_received: description: - - The number of pings sent, as specified by the I(count) option. + - The number of packets received. returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: str @@ -349,52 +309,74 @@ returned: when ping successfully executed, even if the I(acceptable_percent_loss) was exceeded. type: bool -ttl: +routing_instance: description: - - The time-to-live set on the pings sent as specified by the - I(ttl) option. - returned: when ping successfully executed and the I(ttl) option was - specified, even if the I(acceptable_percent_loss) was exceeded. + - The routing-instance from which the pings were sent as specified by + the I(routing_instance) option. + returned: when ping successfully executed and the I(routing_instance) + option was specified, even if the I(acceptable_percent_loss) was + exceeded. + type: str +rtt_average: + description: + - The average round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_maximum: + description: + - The maximum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_minimum: + description: + - The minimum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_stddev: + description: + - The standard deviation of round-trip-time, in microseconds, of all ping + responses received. + returned: when ping successfully executed, and I(packet_loss) < 100%. type: str size: description: - The size in bytes of the ICMP payload on the pings sent as specified - by the I(size) option. NOTE: Total size of the IP packet is I(size) + - the 20 byte IP header + the 8 byte ICMP header. Therefore, I(size) - of 1472 generates an IP packet of size 1500. + by the I(size) option. + - Total size of the IP packet is I(size) + the 20 byte IP header + the 8 + byte ICMP header. Therefore, I(size) of 1472 generates an IP packet of + size 1500. returned: when ping successfully executed and the I(size) option was specified, even if the I(acceptable_percent_loss) was exceeded. type: str -do_not_fragment: - description: - - Whether or not the do not fragment bit was set on the pings sent, as - specified by the I(do_not_fragment) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: bool source: description: - The source IP/host of the pings sent as specified by the I(source) option. - NOTE: Key I(source_ip) is also returned for backwards compatibility. + - Key I(source_ip) is also returned for backwards compatibility. returned: when ping successfully executed and the I(source) option was specified, even if the I(acceptable_percent_loss) was exceeded. type: str -interface: +timeout: description: - - The source interface of the pings sent as specified by the - I(interface) option. - returned: when ping successfully executed and the I(interface) option was - specified, even if the I(acceptable_percent_loss) was exceeded. + - The number of seconds to wait for a response from the ping RPC. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. type: str -routing_instance: +ttl: description: - - The routing-instance from which the pings were sent as specified by - the I(routing_instance) option. - returned: when ping successfully executed and the I(routing_instance) - option was specified, even if the I(acceptable_percent_loss) was - exceeded. + - The time-to-live set on the pings sent as specified by the + I(ttl) option. + returned: when ping successfully executed and the I(ttl) option was + specified, even if the I(acceptable_percent_loss) was exceeded. type: str +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list ''' diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index 02c7fdee..de135063 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2017, Martin Komon # # All rights reserved. @@ -42,21 +42,24 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_pmtud version_added: "2.0.0" # of Juniper.junos role -author: "Martin Komon (@mkomon)" # Updates to use common code by @stacywsmith -short_description: Perform path MTU discovery from a Junos device to a dest +author: + - Martin Komon (@mkomon) + - Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Perform path MTU discovery from a Junos device to a + destination description: - Determine the maximum IP MTU supported along a path from a Junos device to a user-specified destination by performing path MTU discovery (PMTUD) using the ping command. The reported MTU will be between min_test_size and - I(max_size) where min_test_size = (I(max_size) - I(max_range) + 1). + I(max_size) where I(min_test_size) = (I(max_size) - I(max_range) + 1). If the actual path MTU is greater than I(max_size), then I(max_size) will - be reported. If the actual path MTU is less than min_test_size, then a + be reported. If the actual path MTU is less than I(min_test_size), then a failure will be reported. -# connection arguments will be automatically added -# logging arguments will be automatically added -extends_documentation_fragment: juniper_junos_common options: dest: description: @@ -71,25 +74,42 @@ - destination - destination_ip - destination_host - max_size: + interface: description: - - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU - discovery. The value returned for I(inet_mtu) will be no more - than this value even if the path actually supports a higher MTU. This - value must be between 68 and 65496. + - The source interface from which the the PMTUD is performed. If not + specified, the default Junos algorithm for determining the source + interface is used. required: false - default: 1500 - type: int + default: none + type: str max_range: description: - - The maximum range of MTU values, in bytes, which will be searched - when performing path MTU discovery. This value must be 0 or - a power of 2 (2^n) between 2 and 65536. The minimum IPv4 MTU value - attempted when performing path MTU discovery is: - min_test_size = (I(max_size) - I(max_range) + 1) + - The maximum range of MTU values, in bytes, which will be searched + when performing path MTU discovery. This value must be C(0) or + a power of 2 (2^n) between C(2) and C(65536). The minimum IPv4 MTU + value attempted when performing path MTU discovery is + I(min_test_size) = (I(max_size) - I(max_range) + 1) required: false default: 512 type: int + max_size: + description: + - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU + discovery. + - The value returned for I(inet_mtu) will be no more + than this value even if the path actually supports a higher MTU. + - This value must be between 68 and 65496. + required: false + default: 1500 + type: int + routing_instance: + description: + - Name of the source routing instance from which the ping is + originated. + - If not specified, the default routing instance is used. + required: false + default: none + type: str source: description: - The IPv4 address, or hostname if DNS is configured on the Junos device, @@ -104,34 +124,10 @@ - src - src_ip - src_host - interface: - description: - - The source interface from which the the PMTUD is performed. If not - specified, the default Junos algorithm for determining the source - interface is used. - required: false - default: none - type: str - routing_instance: - description: - - Name of the source routing instance from which the ping is - originated. If not specified, the default routing instance is used. - required: false - default: none - type: str ''' EXAMPLES = ''' --- -# -# MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the juniper_junos_ping -# module. These examples use the default connection, authtentication and -# logging parameters. See the examples labeled CONNECTION_EXAMPLES for details -# on connection parameters. See the examples labeled AUTHENTICATION_EXAMPLES -# for details on authentication parameters. See the examples labeled -# LOGGING_EXAMPLES for details on logging parameters. -# - name: Examples of juniper_junos_mtud hosts: junos-all connection: local @@ -188,32 +184,14 @@ - name: Print the discovered MTU. debug: var: response.inet_mtu - -# -# CONNECTION_EXAMPLES -# - -# -# AUTHENTICATION_EXAMPLES -# - -# -# LOGGING_EXAMPLES -# ''' RETURN = ''' -inet_mtu: - description: - - The IPv4 path MTU size in bytes to the I(dest). This is the lesser of - I(max_size) and the actual path MTU to I(dest). If the actual path - MTU is less than min_test_size, then a failure is reported. Where - min_test_size = (I(max_size) - I(max_range) + 1) changed: description: - Indicates if the device's state has changed. Since this module doesn't change the operational or configuration state of the - device, the value is always set to false. + device, the value is always set to C(false). returned: when PMTUD successfully executed. type: bool failed: @@ -221,24 +199,21 @@ - Indicates if the task failed. returned: always type: bool -warnings: - description: - - A list of warning strings, if any, produced from the ping. - returned: when warnings are present - type: list host: description: - The destination IP/host of the PMTUD as specified by the I(dest) - option. NOTE: Keys I(dest) and I(dest_ip) are also returned for backwards + option. + - Keys I(dest) and I(dest_ip) are also returned for backwards compatibility. returned: when PMTUD successfully executed. type: str -source: +inet_mtu: description: - - The source IP/host of the PMTUD as specified by the I(source) - option. - NOTE: Key I(source_ip) is also returned for backwards compatibility. - returned: when the I(source) option was specified. + - The IPv4 path MTU size in bytes to the I(dest). This is the lesser of + I(max_size) and the actual path MTU to I(dest). If the actual path + MTU is less than I(min_test_size), then a failure is reported. Where + I(min_test_size) = (I(max_size) - I(max_range) + 1) + returned: when PMTUD successfully executed. type: str interface: description: @@ -252,6 +227,18 @@ the I(routing_instance) option. returned: when the I(routing_instance) option was specified. type: str +source: + description: + - The source IP/host of the PMTUD as specified by the I(source) + option. + - Key I(source_ip) is also returned for backwards compatibility. + returned: when the I(source) option was specified. + type: str +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list ''' diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index 841f21ba..76e27adc 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2016, Nitin Kumar # # All rights reserved. @@ -42,102 +42,47 @@ DOCUMENTATION = ''' --- -module: juniper_junos_command +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_rpc version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Execute one or more NETCONF RPCs on a Junos device description: - Execute one or more NETCONF RPCs on a Junos device. - NOTE: Use the '| display xml rpc' modifier to determine the equivalent RPC + - Use the C(| display xml rpc) modifier to determine the equivalent RPC name for a Junos CLI command. For example, - 'show version | display xml rpc' reveals the equivalent RPC name is - 'get-software-information'. -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common + C(show version | display xml rpc) reveals the equivalent RPC name is + C(get-software-information). options: - rpcs: - description: - - A list of one or more NETCONF RPCs to execute on the Junos device. - required: true - default: none - type: 'list' - aliases: - - rpc - formats: - description: - - The format of the reply for the RPC(s) specified by the - I(rpcs) option. The specified format(s) must be supported by the - target Junos device. The value of this option can either be a single - format, or a list of formats. If a single format is specified, it - applies to all RPC(s) specified by the I(rpcs) option. If a - list of formats are specified, there must be one value in the list for - each RPC specified by the I(rpcs) option. - required: false - default: 'xml' - type: 'str or list of str' - choices: ['text', 'xml', 'json'] - aliases: - - format - - display - - output - kwargs: - description: - - The keyword arguments and values to the RPC(s) specified by the - I(rpcs) option. The value of this option can either be a single - dictionary of keywords and values, or a list of dictionaries - containing keywords and values. There is a one-to-one correspondence - between the elements in the I(kwargs) list and the RPCs in the I(rpcs) - list. In other words, the two lists must always contain the same - number of elements. For RPC arguments which do not require a value, - specify the value of True as shown in the EXAMPLES. - required: false - default: none - type: 'dict or list of dict' - aliases: - - kwarg - - args - - arg attrs: description: - - The attributes and values to the RPC(s) specified by the + - The attributes and values to the RPCs specified by the I(rpcs) option. The value of this option can either be a single dictionary of keywords and values, or a list of dictionaries - containing keywords and values. There is a one-to-one correspondence - between the elements in the I(kwargs) list and the RPCs in the I(rpcs) - list. In other words, the two lists must always contain the same - number of elements. + containing keywords and values. + - There is a one-to-one correspondence between the elements in the + I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the + two lists must always contain the same number of elements. required: false default: none - type: 'dict or list of dict' + type: dict or list of dict aliases: - attr - filter: - description: - - This argument only applies if the I(rpcs) option contains a single - RPC with the value 'get-config'. When used, this value specifies an - XML filter used to restrict the portions of the configuration which are - retrieved. See - U(http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) - for details on the value of this option. - required: false - default: none - type: 'str' - aliases: - - filter_xml dest: description: - The path to a file, on the Ansible control machine, where the output of the RPC will be saved. - The file must be writeable. If the file already exists, it is overwritten. - - NOTE: When tasks are executed against more than one target host, + - When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See - U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including - {{ inventory_hostname }} in the I(dest) value. It is the user's + C({{ inventory_hostname }}) in the I(dest) value. It is the user's responsibility to ensure this value is unique per target host. - For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the I(dest_dir) option in new playbooks. @@ -151,7 +96,7 @@ description: - The path to a directory, on the Ansible control machine, where the output of the RPC will be saved. The output will be logged - to a file named {{ inventory_hostname }}_C(rpc).C(format) + to a file named C({{ inventory_hostname }}_)I(rpc).I(format) in the I(dest_dir) directory. - The destination file must be writeable. If the file already exists, it is overwritten. It is the users responsibility to ensure a unique @@ -165,6 +110,59 @@ aliases: - destination_dir - destdir + filter: + description: + - This argument only applies if the I(rpcs) option contains a single + RPC with the value C(get-config). When used, this value specifies an + XML filter used to restrict the portions of the configuration which are + retrieved. See the PyEZ + U(get_config method|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + for details on the value of this option. + required: false + default: none + type: str + aliases: + - filter_xml + formats: + description: + - The format of the reply for the RPCs specified by the + I(rpcs) option. + - The specified format(s) must be supported by the + target Junos device. + - The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all RPCs specified by the I(rpcs) option. If a + list of formats are specified, there must be one value in the list for + each RPC specified by the I(rpcs) option. + required: false + default: xml + type: str or list of str + choices: + - text + - xml + - json + aliases: + - format + - display + - output + kwargs: + description: + - The keyword arguments and values to the RPCs specified by the + I(rpcs) option. The value of this option can either be a single + dictionary of keywords and values, or a list of dictionaries + containing keywords and values. + - There must be a one-to-one correspondence between the elements in the + I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the + two lists must always contain the same number of elements. For RPC + arguments which do not require a value, specify the value of True as + shown in the :ref:`examples-label`. + required: false + default: none + type: dict or list of dict + aliases: + - kwarg + - args + - arg return_output: description: - Indicates if the output of the RPC should be returned in the @@ -175,19 +173,18 @@ required: false default: true type: bool + rpcs: + description: + - A list of one or more NETCONF RPCs to execute on the Junos device. + required: true + default: none + type: list + aliases: + - rpc ''' EXAMPLES = ''' --- -# -# MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the -# juniper_junos_rpc module. These examples use the default connection, -# authtentication and logging parameters. See the examples labeled -# CONNECTION_EXAMPLES for details on connection parameters. See the examples -# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. -# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. -# - name: Examples of juniper_junos_rpc hosts: junos-all connection: local @@ -267,97 +264,76 @@ - name: Print configuration debug: msg="{{ junos.rpc_reply }}" ###### OLD EXAMPLES ########## - -# -# CONNECTION_EXAMPLES -# - -# -# AUTHENTICATION_EXAMPLES -# - -# -# LOGGING_EXAMPLES -# ''' RETURN = ''' -msg: +attrs: description: - - A human-readable message indicating the result. + - The RPC attributes and values from the list of dictionaries in the + I(attrs) option. This will be none if no attributes are applied to the + RPC. returned: always - type: str -rpc: + type: dict +changed: description: - - The RPC which was executed from the list of RPCs in the I(rpcs) option. + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to C(false). + - You could use this module to execute an RPC which + changes the operational state of the the device. For example, + C(clear-ospf-neighbor-information). Beware, this module is unable to + detect this situation, and will still return a I(changed) value of + C(false) in this case. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. See the I(results) key for additional + details. returned: always - type: str + type: bool format: description: - The format of the RPC response from the list of formats in the I(formats) option. returned: always type: str - choices: ['text', 'xml', 'json'] + choices: + - text + - xml + - json kwargs: description: - The keyword arguments from the list of dictionaries in the I(kwargs) - option. This will be none if no kwargs are applied to the RPC. + option. This will be C(none) if no kwargs are applied to the RPC. returned: always type: dict -attrs: +msg: description: - - The RPC attributes and values from the list of dictionaries in the - I(attrs) option. This will be none if no attributes are applied to the RPC. + - A human-readable message indicating the result. returned: always - type: dict -stdout: - description: - - The RPC reply from the Junos device as a single multi-line string. - returned: when RPC executed successfully and I(return_output) is true. type: str -stdout_lines: - description: - - The RPC reply from the Junos device as a list of single-line strings. - returned: when RPC executed successfully and I(return_output) is true. - type: list of str parsed_output: description: - The RPC reply from the Junos device parsed into a JSON datastructure. - For XML replies, the response is parsed into JSON using the jxmlease - library. For JSON the response is parsed using the Python json library. - - NOTE: When Ansible converts the jxmlease or native Python data structure + For XML replies, the response is parsed into JSON using the + U(jxmlease|https://github.com/Juniper/jxmlease) + library. For JSON the response is parsed using the Python + U(json|https://docs.python.org/2/library/json.html) library. + - When Ansible converts the jxmlease or native Python data structure into JSON, it does not guarantee that the order of dictionary/object keys are maintained. - returned: when RPC executed successfully, I(return_output) is true, - and the RPC format is xml or json. + returned: when RPC executed successfully, I(return_output) is C(true), + and the RPC format is C(xml) or C(json). type: dict -changed: - description: - - Indicates if the device's state has changed. Since this module doesn't - change the operational or configuration state of the device, the value - is always set to false. - - NOTE: You could use this module to execute a RPC which - changes the operational state of the the device. For example, - 'clear-ospf-neighbor-information'. Beware, this module is unable to - detect this situation, and will still return a I(changed) value - C(False) in this case. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. See I(results) option below for additional - details. - returned: always - type: bool results: description: - - The above keys are returned when a single RPC is specified for the + - The other keys are returned when a single RPC is specified for the I(rpcs) option. When the value of the I(rpcs) option is a list of RPCs, this key is returned instead. The value of this key is a list of dictionaries. Each element in the list corresponds to the RPCs in the I(rpcs) option. The keys for each element in the list - include all of the keys listed above. The I(failed) key indicates if the + include all of the other keys listed. The I(failed) key indicates if the individual RPC failed. In this case, there is also a top-level I(failed) key. The top-level I(failed) key will have a value of C(false) if ANY of the RPCs ran successfully. In this case, check the value @@ -365,6 +341,21 @@ results of individual RPCs. returned: when the I(rpcs) option is a list value. type: list of dict +rpc: + description: + - The RPC which was executed from the list of RPCs in the I(rpcs) option. + returned: always + type: str +stdout: + description: + - The RPC reply from the Junos device as a single multi-line string. + returned: when RPC executed successfully and I(return_output) is C(true). + type: str +stdout_lines: + description: + - The RPC reply from the Junos device as a list of single-line strings. + returned: when RPC executed successfully and I(return_output) is C(true). + type: list of str ''' import os.path diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 466eadd3..4cd6219e 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2014, Jeremy Schulman # # All rights reserved. @@ -42,147 +42,106 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_software version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Jeremy Schulman, Stacy Smith (@stacywsmith)" +author: + - Jeremy Schulman + - "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Install software on a Junos device description: - - Install a Junos OS image, or other software package, on a Junos device. + - > + Install a Junos OS image, or other software package, on a Junos device. This action is generally equivalent to the C(request system software add) - operational-mode CLI command. - - The steps performed are: - 1) Compare the currently installed Junos version to the desired version - specified by the I(version) option. If the current and desired versions - are the same, stop and return I(changed) with a value of C(false). If - running in check mode, and the current and desired versions differ, - stop and return I(changed) with a value of C(true). Otherwise, proceed. - 2) If the I(local_package) option is specified, compute the MD5 checksum + operational-mode CLI command. It performs the following + steps in order: + + #. Compare the currently installed Junos version to the desired version + specified by the I(version) option. + + * If the current and desired versions are the same, stop and return + I(changed) with a value of C(false). + * If running in check mode, and the current and desired versions differ, + stop and return I(changed) with a value of C(true). + * Otherwise, proceed. + #. If the I(local_package) option is specified, compute the MD5 checksum of the I(local_package) file on the local Ansible control machine. - 3) Check if the file exists at the I(remote_package) location on the target + #. Check if the file exists at the I(remote_package) location on the target Junos device. If so, compute the MD5 checksum of the file on the target Junos device. - 4) If the checksums computed in steps 2 and 3 differ, or if the + #. If the I(cleanfs) option is C(true), the default, then perform the + equivalent of the C(request system storage cleanup) CLI command. + #. If the checksums computed in steps 2 and 3 differ, or if the I(remote_package) file does not exist on the target Junos device, then copy the package from I(local_package) on the local Ansible control machine to I(remote_package) on the target Junos device. - 5) Install the software pacakge from the I(remote_package) location on the - target Junos device using the specified options. - 6) If the I(reboot) option is true, the default, initiate a reboot of the - target Junos device. - - NOTE: This module does support connecting to the console of a Junos - device, but does not support copying the software package from the - local Ansible control machine to the target Junos device while - connected via the console. In this situation, the I(remote_package) - option must be specified, and the specified software package must - already exist on the target Junos device. - - NOTE: This module returns after installing the software and, optionally, - initiating a reboot of the target Junos device. It does not wait for - the reboot to complete, and it does not verify that the desired - version of software specified by the I(version) option is actually - activated on the target Junos device. It is the user's responsibility - to confirm the software installation using additional follow on - tasks in their playbook. - -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common + #. Install the software pacakge from the I(remote_package) location on the + target Junos device using the options specified. + #. If the I(reboot) option is C(true), the default, initiate a reboot of + the target Junos device. options: - local_package: + all_re: description: - - The path, on the local Ansible control machine, to the file - containing the software package to be installed on the target Junos - device. If this option is not specified, it is assumed that the - software package already exists on the target Junos device. In this - case, the I(remote_package) option must be specified. If this option - is specified, and a file with the same MD5 checksum doesn't already - exist at the I(remote_package) location on the target Junos device, - then the file is copied from the local Ansible control machine to the - target Junos device. - required: if I(remote_package) is not specified + - Whether or not to install the software on all Routing Engines of the + target Junos device. If C(true), and the device has multiple Routing + Engines, the software is installed on all Routing Engines. If C(false), + the software is only installed on the current Routing Engine. + required: false + default: true + type: bool + checksum: + description: + - The pre-calculated checksum, using the I(checksum_algorithm) of the + file specified by the I(local_package) option. Specifying this option + is simply an optimization to avoid repeatedly computing the checksum of + the I(local_package) file once for each target Junos host. + required: false default: none - type: 'path' - aliases: - - package - remote_package: + type: str + checksum_algorithm: description: - - This option may take one of two formats. It is either: - 1) A URL, from the perspective of the target Junos device, from which - the device retrieves the software package to be installed. The - acceptable formats for a URL value may be found at: - U(https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-software-formats-filenames-urls.html) - - The I(local_package) and I(no_copy) options must not be specified. - 2) A file path, on the taget Junos device, to the software package. - - If the I(local_package) option is also specified, and the - I(no_copy) option is C(false), the software package will be copied - from I(local_package) to I(remote_package), if necessary. - - If the I(no_copy) option is C(true) or the I(local_package) option - is not specified, then the file specified by this option must - already exist on the target Junos device. - - If this option is not specified, it is assumed that the software - package will be copied into the C(/var/tmp) directory on the - target Junos device using the filename portion of the - I(local_package) option. In this case, the I(local_package) option - must be specified. - - Specifying the I(remote_package) option and not specifying the - I(local_package) option is equivalent to specifying the - I(local_package) option and the I(no_copy) option. In this case, - you no longer have to explicitly specify the I(no_copy) option. - - If the I(remote_package) value is a directory (ends with /), then - the filename portion of I(local_package) will be appended to the - I(remote_package) value. - - If the I(remote_package) value is a file (does not end with /), - then the filename portion of I(remote_package) must be the same as - the filename portion of I(local_package). - required: if I(local_package) is not specified - default: '/var/tmp/' + filename portion of I(local_package) - type: 'path' - version: + - The algorithm to use when calculating the checksum of the local and + remote software packages. + required: false + default: md5 + type: str + checksum_timeout: description: - - The version of software contained in the file specified by the - I(local_package) and/or I(remote_package) options. This value should - match the Junos version which will be reported by the device once the - new software is installed. If the device is already running a version - of software which matches the I(version) option value, the software - install is not necessary. In this case the module returns a I(changed) - value of C(false) and an I(failed) value of C(false) and does not - attempt to perform the software install. + - The number of seconds to wait for the calculation of the checksum to + complete on the target Junos device. required: false - default: Attempt to extract the version from the file name specified by - the I(local_package) or I(remote_package) option values IF the - package appears to be a Junos software package. Otherwise, None. - type: 'str' - aliases: - - target_version - - new_version - - desired_version - no_copy: + default: 300 (5 minutes) + type: int + cleanfs: description: - - Indicates if the file containing the software package should be copied - from the I(local_package) location on the local Ansible control - machine to the I(remote_package) location on the target Junos device. - If the value is C(true), or if the I(local_package) option is not - specified, then the copy is skipped and the file must already exist - at the I(remote_package) location on the target Junos device. + - Whether or not to perform a C(request system storage cleanup) prior to + copying or installing the software. required: false - default: false - type: 'bool' - reboot: + default: true (unless I(no_copy) is C(true), then C(false)) + type: bool + cleanfs_timeout: description: - - Indicates if the target Junos device should be rebooted after - performing the software install. + - The number of seconds to wait for the + C(request system storage cleanup) to complete on the target Junos + device. required: false - default: true - type: 'bool' - reboot_pause: + default: 300 (5 minutes) + type: int + force_host: description: - - The amount of time, in seconds, to wait after the reboot is issued - before the module returns. This gives time for the reboot to begin. The - default value of 10 seconds is designed to ensure the device is no - longer reachable (because the reboot has begun) when the next task - begins. The value must be an integer greater than or equal to 0. + - Forces the upgrade of the Host Software package on QFX-series devices. required: false - default: 10 - type: 'int' + default: false + type: bool + install_timeout: + description: + - The number of seconds to wait for the software installation to + complete on the target Junos device. + required: false + default: 1800 (30 minutes) + type: int issu: description: - Indicates if a unified in-service software upgrade (ISSU) should be @@ -196,7 +155,47 @@ - The I(issu) and I(nssu) options are mutually exclusive. required: false default: false - type: 'bool' + type: bool + kwargs: + description: + - Additional keyword arguments and values which are passed to the + C() RPC used to install the software package. The + value of this option is a dictionary of keywords and values. + required: false + default: none + type: dict + aliases: + - kwarg + - args + - arg + local_package: + description: + - The path, on the local Ansible control machine, of a Junos software + package. This Junos software package will be installed on the target + Junos device. + - If this option is specified, and a file with the same MD5 checksum + doesn't already exist at the I(remote_package) location on the target + Junos device, then the file is copied from the local Ansible control + machine to the target Junos device. + - If this option is not specified, it is assumed that the + software package already exists on the target Junos device. In this + case, the I(remote_package) option must be specified. + required: false + default: none + type: path + aliases: + - package + no_copy: + description: + - Indicates if the file containing the software package should be copied + from the I(local_package) location on the local Ansible control + machine to the I(remote_package) location on the target Junos device. + - If the value is C(true), or if the I(local_package) option is not + specified, then the copy is skipped and the file must already exist + at the I(remote_package) location on the target Junos device. + required: false + default: false + type: bool nssu: description: - Indicates if a non-stop software upgrade (NSSU) should be @@ -211,104 +210,106 @@ - The I(nssu) and I(issu) options are mutually exclusive. required: false default: false - type: 'bool' - force_host: - description: - - Forces the upgrade of the Host Software package on QFX-series devices. - required: false - default: false - type: 'bool' - validate: - description: - - Whether or not to have the target Junos device valide the current - configuration against the new software package. - required: false - default: false - type: 'bool' - cleanfs: - description: - - Whether or not to perform a "request system storage cleanup" prior to - copying or installing the software. - required: false - default: true (unless no_copy is true, then false) - type: 'bool' - all_re: + type: bool + reboot: description: - - Whether or not to install the software on all Routing Engines of the - target Junos device. If true, and the device has multiple Routing - Engines, the software is installed on all Routing Engines. If false, - the software is only installed on the current Routing Engine. + - Indicates if the target Junos device should be rebooted after + performing the software install. required: false default: true - type: 'bool' - vmhost: - description: - - Whether or not this is a vmhost software installation. - required: false - default: false - type: 'bool' - checksum: - description: - - The pre-calculated checksum, using the I(checksum_algorithm) of the - file specified by the I(local_package) option. Specifying this option - is simply an optimization to avoid repeatedly computing the checksum of - the I(local_package) file once for each target Junos host. - required: false - default: none - type: 'str' - checksum_algorithm: + type: bool + reboot_pause: description: - - The algorithm to use when calculating the checksum of the local and - remote software packages. + - The amount of time, in seconds, to wait after the reboot is issued + before the module returns. This gives time for the reboot to begin. The + default value of 10 seconds is designed to ensure the device is no + longer reachable (because the reboot has begun) when the next task + begins. The value must be an integer greater than or equal to 0. required: false - default: 'md5' - type: 'str' - checksum_timeout: + default: 10 + type: int + remote_package: description: - - The number of seconds to wait for the calculation of the checksum to - complete on the target Junos device. + - This option may take one of two formats. + - The first format is a URL, from the perspective of the target Junos + device, from which the device retrieves the software package to be + installed. The acceptable formats for the URL value may be found + U(here|https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-software-formats-filenames-urls.html). + - When using the URL format, the I(local_package) and I(no_copy) options + must not be specified. + - The second format is a file path, on the taget Junos device, to the + software package. + - If the I(local_package) option is also specified, and the + I(no_copy) option is C(false), the software package will be copied + from I(local_package) to I(remote_package), if necessary. + - If the I(no_copy) option is C(true) or the I(local_package) option + is not specified, then the file specified by this option must already + exist on the target Junos device. + - If this option is not specified, it is assumed that the software + package will be copied into the C(/var/tmp) directory on the target + Junos device using the filename portion of the I(local_package) option. + In this case, the I(local_package) option must be specified. + - Specifying the I(remote_package) option and not specifying the + I(local_package) option is equivalent to specifying the + I(local_package) option and the I(no_copy) option. In this case, + you no longer have to explicitly specify the I(no_copy) option. + - If the I(remote_package) value is a directory (ends with /), then + the filename portion of I(local_package) will be appended to the + I(remote_package) value. + - If the I(remote_package) value is a file (does not end with /), + then the filename portion of I(remote_package) must be the same as + the filename portion of I(local_package). required: false - default: 300 (5 minutes) - type: 'int' - cleanfs_timeout: + default: C(/var/tmp/) + filename portion of I(local_package) + type: path + validate: description: - - The number of seconds to wait for the "request system storage cleanup" - to complete on the target Junos device. + - Whether or not to have the target Junos device should validate the + current configuration against the new software package. required: false - default: 300 (5 minutes) - type: 'int' - install_timeout: + default: false + type: bool + version: description: - - The number of seconds to wait for the software installation to - complete on the target Junos device. + - The version of software contained in the file specified by the + I(local_package) and/or I(remote_package) options. This value should + match the Junos version which will be reported by the device once the + new software is installed. If the device is already running a version + of software which matches the I(version) option value, the software + install is not necessary. In this case the module returns a I(changed) + value of C(false) and an I(failed) value of C(false) and does not + attempt to perform the software install. required: false - default: 1800 (30 minutes) - type: 'int' - kwargs: + default: Attempt to extract the version from the file name specified by + the I(local_package) or I(remote_package) option values IF the + package appears to be a Junos software package. Otherwise, C(none). + type: str + aliases: + - target_version + - new_version + - desired_version + vmhost: description: - - Additional keyword arguments and values which are passed to the - RPC used to install the software package. The - value of this option is a dictionary of keywords and values. + - Whether or not this is a vmhost software installation. required: false - default: none - type: 'dict' - aliases: - - kwarg - - args - - arg + default: false + type: bool +notes: + - This module does support connecting to the console of a Junos device, but + does not support copying the software package from the local Ansible + control machine to the target Junos device while connected via the console. + In this situation, the I(remote_package) option must be specified, and the + specified software package must already exist on the target Junos device. + - This module returns after installing the software and, optionally, + initiating a reboot of the target Junos device. It does not wait for + the reboot to complete, and it does not verify that the desired version of + software specified by the I(version) option is actually activated on the + target Junos device. It is the user's responsibility to confirm the + software installation using additional follow on tasks in their playbook. ''' EXAMPLES = ''' --- -# -# MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the -# juniper_junos_software module. These examples use the default connection, -# authentication and logging parameters. See the examples labeled -# CONNECTION_EXAMPLES for details on connection parameters. See the examples -# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. -# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. -# - name: Examples of juniper_junos_software hosts: junos-all connection: local @@ -332,27 +333,10 @@ package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz logfile=/usr/local/junos/log/software.log ###### OLD EXAMPLES ########## - -# -# CONNECTION_EXAMPLES -# - -# -# AUTHENTICATION_EXAMPLES -# - -# -# LOGGING_EXAMPLES -# ''' + RETURN = ''' -msg: - description: - - A human-readable message indicating the result of the software - installation. - returned: always - type: str changed: description: - Indicates if the device's state has changed, or if the state would have @@ -373,6 +357,12 @@ - Indicates if the task failed. returned: always type: bool +msg: + description: + - A human-readable message indicating the result of the software + installation. + returned: always + type: str ''' # Standard Library imports diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py index e16ade15..28811df3 100644 --- a/library/juniper_junos_srx_cluster.py +++ b/library/juniper_junos_srx_cluster.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2014, Patrik Bok # 2015, Rick Sherman # @@ -43,22 +43,22 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_srx_cluster version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Add or remove SRX chassis cluster configuration. +short_description: Add or remove SRX chassis cluster configuration description: - Add an SRX chassis cluster configuration and reboot the device. Assuming the device is capable of forming an SRX cluster and has the correct cables connected, this will form an SRX cluster. - If an SRX chassis cluster is already present, setting I(cluster_enable) to + - If an SRX chassis cluster is already present, setting I(cluster_enable) to C(false) will remove the SRX chassis cluster configuration and reboot the device causing the SRX cluster to be broken and the device to return to stand-alone mode. - -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common +options: enable: description: - Enable or disable cluster mode. When C(true) cluster mode is enabled @@ -66,23 +66,25 @@ cluster mode is disabled and the device returns to stand-alone mode. required: true default: none - type: 'bool' + type: bool aliases: - cluster_enable cluster_id: description: - The cluster ID to configure. - required: when I(enable)=C(true) + - Required when I(enable) is C(true). + required: false default: none - type: 'int' + type: int aliases: - cluster node_id: description: - - The node ID to configure. (0 or 1) - required: when I(enable)=C(true) + - The node ID to configure. (C(0) or C(1)) + - Required when I(enable) is C(true). + required: false default: none - type: 'int' + type: int aliases: - node ''' @@ -113,32 +115,30 @@ - name: Print the response. debug: var: response.config_lines - -# Document connection examples -# Document authentication examples -# Document logging examples -# extends_documentation_fragment: juniper_junos_common ''' RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed, or would have + changed when in check mode. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool msg: description: - A human-readable message indicating the result. returned: always type: str reboot: - description: Indicates if a reboot of the device has been initiated. - returned: success - type: bool -changed: - description: Indicates if the device's configuration has changed, or would - have changed when in check mode. + description: + - Indicates if a reboot of the device has been initiated. returned: success type: bool -failed: - description: Indicates if the task failed. - returned: always - type: bool ''' # Standard library imports diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index 0bb49005..d9367bb6 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2017, Juniper Networks Inc. +# Copyright (c) 1999-2018, Juniper Networks Inc. # 2014, Jeremy Schulman # # All rights reserved. @@ -42,59 +42,59 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_system version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Initiate operational actions on the Junos system. +short_description: Initiate operational actions on the Junos system description: - Initiate an operational action (shutdown, reboot, halt or zeroize) on a Junos system. The particular action to execute is defined by the mandatory - I(action) option. This module only INITIATES the action. It does NOT wait - for the action to complete. - - NOTE: Some Junos devices are effected by a Junos bug which causes this - Ansible module to hang indefinitely when connected to the Junos device via - the console. This problem is not seen when connecting to the Junos device - using the normal NETCONF over SSH transport connection. Therefore, it is - recommended to use this module only with a NETCONF over SSH transport - connection. However, this module does still permit connecting to Junos - devices via the console port and this functionality may still be used for - Junos devices running Junos versions less than 15.1. -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common + I(action) option. options: action: description: - - The action performed by the module. The choices include: - C(shutdown) - Power off the Junos devices. The values: C(off), - C(power-off), and C(power_off) are aliases for this value. - This is the equivalent of the 'request system power-off' - CLI command. - C(halt) - Stop the Junos OS running on the RE, but do not power off the - system. Once the system is halted, it will reboot if a - keystroke is entered on the console. This is the equivalent - of the 'request system halt' CLI command. - C(reboot) - Reboot the system. This is the equivalent of the - 'request system reboot' CLI command. - C(zeroize) - Restore the system (configuration, log files, etc.) to a - factory default state. This is the equivalent of - the 'request system zeroize' CLI command. + - The action performed by the module. + - > + The following actions are supported: + - B(shutdown) - Power off the Junos devices. The values C(off), + C(power-off), and C(power_off) are aliases for this value. + This is the equivalent of the C(request system power-off) CLI + command. + - B(halt) - Stop the Junos OS running on the RE, but do not power off + the system. Once the system is halted, it will reboot if a + keystroke is entered on the console. This is the equivalent + of the C(request system halt) CLI command. + - B(reboot) - Reboot the system. This is the equivalent of the + C(request system reboot) CLI command. + - B(zeroize) - Restore the system (configuration, log files, etc.) to a + factory default state. This is the equivalent of the + C(request system zeroize) CLI command. required: true default: none type: str - choices: ['shutdown', 'halt', 'reboot', 'zeroize, - 'off', 'power-off', 'power_off'] + choices: + - shutdown + - halt + - reboot + - zeroize + - 'off' + - power-off + - power_off at: description: - - The time at which to shutdown, halt, or reboot the system. The value - may be specified in one of the following ways: - C(now) - The action takes effect immediately. - C(+minutes) — The action takes effect in C(minutes) minutes from now. - C(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute time, - specified as year, month, day, hour, and minute. - C(hh:mm) — The action takes effect at C(hh:mm) absolute time on the - current day, specified in 24-hour time. - The I(at) option can not be used when the I(action) option has a + - The time at which to shutdown, halt, or reboot the system. + - > + The value may be specified in one of the following ways: + - B(now) - The action takes effect immediately. + - B(+minutes) — The action takes effect in C(minutes) minutes from now. + - B(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute + time, specified as year, month, day, hour, and minute. + - B(hh:mm) — The action takes effect at C(hh:mm) absolute time on the + current day, specified in 24-hour time. + - The I(at) option can not be used when the I(action) option has a value of C(zeroize). The I(at) option is mutually exclusive with the I(in_min) option. required: false @@ -102,8 +102,8 @@ type: str in_min: description: - - Specify a delay, in minutes, before the shutdown, halt, or reboot. The - I(in_min) option can not be used when the I(action) option has a + - Specify a delay, in minutes, before the shutdown, halt, or reboot. + - The I(in_min) option can not be used when the I(action) option has a value of C(zeroize). The I(in_min) option is mutually exclusive with the I(at) option. required: false @@ -111,22 +111,23 @@ type: int all_re: description: - - If the system has multiple Routing Engines and this option is true, + - If the system has multiple Routing Engines and this option is C(true), then the action is performed on all REs in the system. If the system does not have multiple Routing Engines, then this option has no effect. - This option applies to all I(action) values. The I(all_re) option is - mutually exclusive with the I(other_re) option. + - This option applies to all I(action) values. + - The I(all_re) option is mutually exclusive with the I(other_re) option. required: false default: true type: bool other_re: description: - - If the system has dual Routing Engines and this option is true, + - If the system has dual Routing Engines and this option is C(true), then the action is performed on the other REs in the system. If the system does not have dual Routing Engines, then this option has no - effect. The I(other_re) option can not be used when the I(action) - option has a value of C(zeroize).The I(other_re) option is mutually - exclusive with the I(all_re) option. + effect. + - The I(other_re) option can not be used when the I(action) option has a + value of C(zeroize). + - The I(other_re) option is mutually exclusive with the I(all_re) option. required: false default: false type: bool @@ -137,19 +138,21 @@ required: false default: false type: bool +notes: + - This module only B(INITIATES) the action. It does B(NOT) wait for the + action to complete. + - Some Junos devices are effected by a Junos defect which causes this Ansible + module to hang indefinitely when connected to the Junos device via + the console. This problem is not seen when connecting to the Junos device + using the normal NETCONF over SSH transport connection. Therefore, it is + recommended to use this module only with a NETCONF over SSH transport + connection. However, this module does still permit connecting to Junos + devices via the console port and this functionality may still be used for + Junos devices running Junos versions less than 15.1. ''' EXAMPLES = ''' --- -# -# MODULE_EXAMPLES -# This playbook demonstrate the parameters supported by the -# juniper_junos_system module. These examples use the default connection, -# authtentication and logging parameters. See the examples labeled -# CONNECTION_EXAMPLES for details on connection parameters. See the examples -# labeled AUTHENTICATION_EXAMPLES for details on authentication parameters. -# See the examples labeled LOGGING_EXAMPLES for details on logging parameters. -# - name: Examples of juniper_junos_system hosts: junos-all connection: local @@ -197,32 +200,25 @@ juniper_junos_system: action: "zeroize" media: True - -# -# CONNECTION_EXAMPLES -# - -# -# AUTHENTICATION_EXAMPLES -# - -# -# LOGGING_EXAMPLES -# ''' RETURN = ''' -msg: +action: description: - - A human-readable message indicating the result. + - The value of the I(action) option. + returned: always + type: str +all_re: + description: + - The value of the I(all_re) option. returned: always type: str changed: description: - Indicates if the device's state has changed. If the action is performed (or if it would have been performed when in check mode) then the value - will be true. If there was an error before the action, then the value - will be false. + will be C(true). If there was an error before the action, then the value + will be C(false). returned: always type: bool failed: @@ -230,14 +226,14 @@ - Indicates if the task failed. returned: always type: bool -action: +media: description: - - The value of the I(action) option. + - The value of the I(media) option. returned: always type: str -all_re: +msg: description: - - The value of the I(all_re) option. + - A human-readable message indicating the result. returned: always type: str other_re: @@ -245,11 +241,6 @@ - The value of the I(other_re) option. returned: always type: str -media: - description: - - The value of the I(media) option. - returned: always - type: str ''' diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index c96b9a0d..03cebc80 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -4,7 +4,7 @@ # Copyright 2016 Jason Edelman # Network to Code, LLC # -# Copyright (c) 2017, Juniper Networks Inc. +# Copyright (c) 2017-2018, Juniper Networks Inc. # # All rights reserved. # @@ -44,51 +44,28 @@ DOCUMENTATION = ''' --- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation module: juniper_junos_table version_added: "2.0.0" # of Juniper.junos role -author: "Jason Edelman (@jedelman8) updated by Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Retrieve data from a Junos device using table/views. +author: + - Jason Edelman (@jedelman8) + - Updated by Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Retrieve data from a Junos device using a PyEZ table/view description: - Retrieve data from a Junos device using PyEZ's operational table/views. This module may be used with the tables/views which are included in the PyEZ distribution or it may be used with user-defined tables/views. - NOTE: The module only works with operational tables/views; it does not - work with configuration tables/views. - -# Document connection arguments -# Document logging arguments -extends_documentation_fragment: juniper_junos_common +options: file: description: - Name of the YAML file, relative to the I(path) option, that contains - the table/view definition. The file name must end with the '.yml' or - '.yaml' extension. + the table/view definition. The file name must end with the C(.yml) or + C(.yaml) extension. required: true default: none - type: 'path' - table: - description: - - Name of the PyEZ table used to retrieve data. If not specified, - defaults to the name of the table defined in the I(file) option. Any - table names in I(file) which begin with '_' are ignored. If more than - one table is defined in I(file), the module fails with an error - message. In this case, you must manually specify the name of the table - by setting this option. - required: false - default: The name of the table defined in the I(file) option. - type: 'str' - path: - description: - - The directory containing the YAML table/view definition file as - specified by the I(file) option. The default value is the op directory - in jnpr.junos.op. This is the directory containing the table/view - definitions which are included in the PyEZ distribution. - required: false - default: op directory in jnpr.junos.op - type: 'path' - aliases: - - directory - - dir + type: path kwargs: description: - Optional keyword arguments and values to the table's get() method. The @@ -98,11 +75,23 @@ table's definition and the underlying RPC which the table invokes. required: false default: none - type: 'dict' + type: dict aliases: - kwarg - args - arg + path: + description: + - The directory containing the YAML table/view definition file as + specified by the I(file) option. The default value is the C(op) + directory in C(jnpr.junos.op). This is the directory containing the + table/view definitions which are included in the PyEZ distribution. + required: false + default: C(op) directory in C(jnpr.junos.op) + type: path + aliases: + - directory + - dir response_type: description: - Defines the format of data returned by the module. See RETURN. @@ -112,9 +101,25 @@ data, PyEZ's native return format C(juniper_items) is translated into a list of lists. required: false - default: 'list_of_dicts' - choices: ['list_of_dicts', 'juniper_items'] - type: 'str' + default: list_of_dicts + choices: + - list_of_dicts + - juniper_items + type: str + table: + description: + - Name of the PyEZ table used to retrieve data. If not specified, + defaults to the name of the table defined in the I(file) option. Any + table names in I(file) which begin with C(_) are ignored. If more than + one table is defined in I(file), the module fails with an error + message. In this case, you must manually specify the name of the table + by setting this option. + required: false + default: The name of the table defined in the I(file) option. + type: str +notes: + - This module only works with operational tables/views; it does not work with + configuration tables/views. ''' EXAMPLES = ''' @@ -155,14 +160,21 @@ - name: Print response debug: var: response - -# Document connection examples -# Document authentication examples -# Document logging examples -# extends_documentation_fragment: juniper_junos_common ''' RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed. Since this + module does not change the operational or configuration state of the + device, the value is always set to C(false). + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool msg: description: - A human-readable message indicating a summary of the result. @@ -171,110 +183,100 @@ resource: description: - The items retrieved by the table/view. - returned: success - type: list of dicts if I(response_type) is C(list_of_dicts) or list of - lists if I(respsonse_type) is C(juniper_items). - sample: - # when response_type == 'list_of_dicts' - [ - { - "local_int": "ge-0/0/3", - "local_parent": "-", - "remote_chassis_id": "00:05:86:08:d4:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/0", - "remote_sysname": "r5", - "remote_type": "Mac address" - }, - { - "local_int": "ge-0/0/0", - "local_parent": "-", - "remote_chassis_id": "00:05:86:18:f3:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/2", - "remote_sysname": "r4", - "remote_type": "Mac address" - } - ] - # when response_type == 'juniper_items' + returned: success + type: list of dicts if I(response_type) is C(list_of_dicts) or list of + lists if I(respsonse_type) is C(juniper_items). + sample: | + # when response_type == 'list_of_dicts' + [ + { + "local_int": "ge-0/0/3", + "local_parent": "-", + "remote_chassis_id": "00:05:86:08:d4:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/0", + "remote_sysname": "r5", + "remote_type": "Mac address" + }, + { + "local_int": "ge-0/0/0", + "local_parent": "-", + "remote_chassis_id": "00:05:86:18:f3:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/2", + "remote_sysname": "r4", + "remote_type": "Mac address" + } + ] + # when response_type == 'juniper_items' + [ [ + "ge-0/0/3", [ - "ge-0/0/3", [ - [ - "local_parent", - "-" - ], - [ - "remote_port_id", - "ge-0/0/0" - ], - [ - "remote_chassis_id", - "00:05:86:08:d4:c0" - ], - [ - "remote_port_desc", - null - ], - [ - "remote_type", - "Mac address" - ], - [ - "local_int", - "ge-0/0/3" - ], - [ - "remote_sysname", - "r5" - ] + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/0" + ], + [ + "remote_chassis_id", + "00:05:86:08:d4:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/3" + ], + [ + "remote_sysname", + "r5" ] - ], + ] + ], + [ + "ge-0/0/0", [ - "ge-0/0/0", [ - [ - "local_parent", - "-" - ], - [ - "remote_port_id", - "ge-0/0/2" - ], - [ - "remote_chassis_id", - "00:05:86:18:f3:c0" - ], - [ - "remote_port_desc", - null - ], - [ - "remote_type", - "Mac address" - ], - [ - "local_int", - "ge-0/0/0" - ], - [ - "remote_sysname", - "r4" - ] + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/2" + ], + [ + "remote_chassis_id", + "00:05:86:18:f3:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/0" + ], + [ + "remote_sysname", + "r4" ] ] ] -changed: - description: Indicates if the device's configuration has changed. Since this - module doesn't change the operational or configuration state - of the device, the value is always set to false. - returned: success - type: bool -failed: - description: Indicates if the task failed. - returned: always - type: bool + ] ''' # Standard library imports diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 66a3c443..aebdf683 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # @@ -169,223 +169,246 @@ class ModuleDocFragment(object): which are documented in this class. Attributes: - DOCUMENTATION: The documentation string defining the connection-related - parameters for juniper_junos_* modules. + CONNECTION_DOCUMENTATION: The documentation string defining the + connection-related parameters for the + juniper_junos_* modules. + LOGGING_DOCUMENTATION: The documentation string defining the + logging-related parameters for the + juniper_junos_* modules. """ # The connection-specific options. Defined here so it can be re-used as # suboptions in provider. - CONNECTION_DOCUMENTATION = ''' - host: - description: - - The hostname or IP address of the Junos device to which the connection - should be established. This is normally the Junos device itself, but - is the hostname or IP address of a console server when connecting - to the console of the device by setting the I(mode) option to the value - C(telnet). This option is required, but does not have to be specified - explicitly by the user because it defaults to - C({{ inventory_hostname }}). - required: True - default: {{ inventory_hostname }} - type: str - aliases: - - hostname - - ip - user: - description: - - The username used to authenticate with the Junos device. This option - is required, but does not have to be specified explicitly by the user - due to the below algorithm for determining the default value. - required: True - default: The first defined value from the following list: - 1) The ANSIBLE_NET_USERNAME environment variable. - (used by Ansible Tower) - 2) The remote_user as defined by Ansible. Ansible sets this - value via several methods including: - a) -u or --user command line option. - b) ANSIBLE_REMOTE_USER environment variable. - c) remote_user configuration setting. - See the Ansible documentation for the precedence used to set - the remote_user value. - 3) The USER environment variable. - type: str - aliases: - - username - passwd: - description: - - The password, or ssh key's passphrase, used to authenticate with the - Junos device. If this option is not specified, authentication is - attempted using an empty password, or ssh key passphrase. The below - algorithm is used to determine the default value. - required: False - default: The first defined value from the following list: - 1) The ANSIBLE_NET_PASSWORD environment variable. - (used by Ansible Tower) - 2) The value specified using the -k or --ask-pass command line - argument. - 3) none - type: str - aliases: - - password - ssh_private_key_file: - description: - - The path to the SSH private key file used to authenticate with the - Junos device. If this option is not specified, and no default value is - found using the algorithm below, then the SSH private key file - specified in the user's SSH configuration, or the - operating-system-specific default is used. - default: The first defined value from the following list: - 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. - (used by Ansible Tower) - 2) The value specified using the --private-key or --key-file - command line argument. - 3) none - type: path - aliases: - - ssh_keyfile - mode: - description: - - The PyEZ mode used to establish a NETCONF connection to the Junos - device. A value of C(none) uses the default NETCONF over SSH mode. - A value of C(telnet) results in either a direct NETCONF over Telnet - connection to the Junos device, or a NETCONF over serial console - connection to the Junos device using Telnet to a console server - depending on the values of the C(host) and C(port) options. A - value of C(serial) results in a NETCONF over serial console connection - to the Junos device. Mutually exclusive with C(console). - required: False - default: none - choices: [ none, "telnet", "serial" ] - console: - description: - - An alternate method of specifying a NETCONF over serial console - connection to the Junos device using Telnet to a console server. - Its value must be a string in the format - '--telnet ,'. - This option is deprecated. It is present only for backwards - compatibility. The string value of this option is exactly equivalent to - specifying C(host) with a value of I(), C(mode) with - a value of I(telnet), and C(port) with a value of - I(). Mutually exclusive with I(mode), I(port), - I(baud), and I(attempts). - required: False - default: none - type: str - port: - description: - - The TCP port number or serial device port used to establish the - connection. Mutually exclusive with C(console). - required: False - default: 830 if mode == none, 23 if mode == 'telnet', '/dev/ttyUSB0' if - mode == 'serial' - type: int or str - baud: - description: - - The serial baud rate used to connect to the Junos device when using - mode == 'serial'. Mutually exclusive with C(console). - required: False - default: 9600 - type: int - attempts: - description: - - The number of attempts to connect and log in to the Junos device when - using mode == 'telnet' or mode == 'serial'. Mutually exclusive with - C(console). - required: False - default: 10 - type: int - timeout: - description: - - Maximum number of seconds to wait for RPC responses from the Junos - device. This option does NOT control the initial connection timeout - value. - required: False - default: 30 + _CONNECT_DOCUMENTATION = ''' + attempts: + description: + - The number of times to try connecting and logging in to the Junos + device. This option is only applicable when using C(mode = 'telnet') + or C(mode = 'serial'). Mutually exclusive with the I(console) + option. + required: false + default: 10 + type: int + baud: + description: + - The serial baud rate, in bits per second, used to connect to the + Junos device. This option is only applicable when using + C(mode = 'serial'). Mutually exclusive with the I(console) option. + required: false + default: 9600 + type: int + console: + description: + - An alternate method of specifying a NETCONF over serial console + connection to the Junos device using Telnet to a console server. + The value of this option must be a string in the format + C(--telnet ,). + This option is deprecated. It is present only for backwards + compatibility. The string value of this option is exactly equivalent + to specifying I(host) with a value of C(), + I(mode) with a value of C(telnet), and I(port) with a value of + C(). Mutually exclusive with the I(mode), + I(port), I(baud), and I(attempts) options. + required: false + default: none + type: str + host: + description: + - The hostname or IP address of the Junos device to which the + connection should be established. This is normally the Junos device + itself, but is the hostname or IP address of a console server when + connecting to the console of the device by setting the I(mode) + option to the value C(telnet). This option is required, but does not + have to be specified explicitly by the user because it defaults to + C({{ inventory_hostname }}). + required: true + default: C({{ inventory_hostname }}) + type: str + aliases: + - hostname + - ip + mode: + description: + - The PyEZ mode used to establish a NETCONF connection to the Junos + device. A value of C(none) uses the default NETCONF over SSH mode. + Depending on the values of the I(host) and I(port) options, a value + of C(telnet) results in either a direct NETCONF over Telnet + connection to the Junos device, or a NETCONF over serial console + connection to the Junos device using Telnet to a console server. + A value of C(serial) results in a NETCONF over serial console + connection to the Junos device. Mutually exclusive with the + I(console) option. + required: false + default: none + type: str + choices: + - none + - telnet + - serial + passwd: + description: + - The password, or ssh key's passphrase, used to authenticate with the + Junos device. If this option is not specified, authentication is + attempted using an empty password, or ssh key passphrase. + required: false + default: The first defined value from the following list + 1) The C(ANSIBLE_NET_PASSWORD) environment variable. + (used by Ansible Tower) + 2) The value specified using the C(-k) or C(--ask-pass) + command line arguments to the C(ansible) or + C(ansible-playbook) command. + 3) none (An empty password/passphrase) + type: str + aliases: + - password + port: + description: + - The TCP port number or serial device port used to establish the + connection. Mutually exclusive with the I(console) option. + required: false + default: C(830) if C(mode = none), C(23) if C(mode = 'telnet'), + C('/dev/ttyUSB0') if (mode = 'serial') + type: int or str + ssh_private_key_file: + description: + - The path to the SSH private key file used to authenticate with the + Junos device. If this option is not specified, and no default value + is found using the algorithm below, then the SSH private key file + specified in the user's SSH configuration, or the + operating-system-specific default is used. + required: false + default: The first defined value from the following list + 1) The C(ANSIBLE_NET_SSH_KEYFILE) environment variable. + (used by Ansible Tower) + 2) The value specified using the C(--private-key) or + C(--key-file) command line arguments to the C(ansible) or + C(ansible-playbook) command. + 3) none (the file specified in the user's SSH configuration, + or the operating-system-specific default) + type: path + aliases: + - ssh_keyfile + timeout: + description: + - The maximum number of seconds to wait for RPC responses from the + Junos device. This option does NOT control the initial connection + timeout value. + required: false + default: 30 + type: int + user: + description: + - The username used to authenticate with the Junos device. This option + is required, but does not have to be specified explicitly by the + user due to the algorithm for determining the default value. + required: true + default: The first defined value from the following list + 1) The C(ANSIBLE_NET_USERNAME) environment variable. + (used by Ansible Tower) + 2) The C(remote_user) as defined by Ansible. Ansible sets this + value via several methods including + a) C(-u) or C(--user) command line arguments to the + C(ansible) or C(ansible-playbook) command. + b) C(ANSIBLE_REMOTE_USER) environment variable. + c) C(remote_user) configuration setting. + See the Ansible documentation for the precedence used to set + the C(remote_user) value. + 3) The C(USER) environment variable. + type: str + aliases: + - username ''' LOGGING_DOCUMENTATION = ''' - logfile: - description: - - The path to a file, on the Ansible control machine, where debugging - information for the particular task is logged. - - The log file must be writeable. If the file already exists, it is - appended. It is the users responsibility to delete/rotate log files. - - The level of information logged in this file is controlled by Ansible's - verbosity and debug options: - 1) By default, messages at level WARNING or higher are logged. - 2) If the -v or --verbose command-line options to ansible-playbook are - specified, messages at level INFO or higher are logged. - 3) If the -vv (or more verbose) command-line option to ansible-playbook - is specified, or the ANSIBLE_DEBUG environment variable is set, - then messages at level DEBUG or higher are logged. - - NOTE: When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - {{ inventory_hostname }} in the C(logfile) value. It is the user's - responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the C(logdir) option in new playbooks. The - C(logfile) and C(logdir) options are mutually exclusive. - required: false - default: None - type: path - aliases: - - log_file - logdir: - description: - - The path to a directory, on the Ansible control machine, where - debugging information for the particular task is logged. Debugging - information will be logged to a file named {{ inventory_hostname }}.log - in the C(logdir) directory. - - The log file must be writeable. If the file already exists, it is - appended. It is the users responsibility to delete/rotate log files. - - The level of information logged in this file is controlled by Ansible's - verbosity and debug options: - 1) By default, messages at level WARNING or higher are logged. - 2) If the -v or --verbose command-line options to ansible-playbook are - specified, messages at level INFO or higher are logged. - 3) If the -vv (or more verbose) command-line option to ansible-playbook - is specified, or the ANSIBLE_DEBUG environment variable is set, - then messages at level DEBUG or higher are logged. - - The C(logfile) and C(logdir) options are mutually exclusive. The - C(logdir) option is recommended for all new playbooks. - required: false - default: None - type: path - aliases: - - log_dir + logging_options: + logdir: + description: + - The path to a directory, on the Ansible control machine, where + debugging information for the particular task is logged. + - If this option is specified, debugging information is logged to a + file named C({{ inventory_hostname }}.log) in the directory + specified by the I(logdir) option. + - The log file must be writeable. If the file already exists, it is + appended. It is the users responsibility to delete/rotate log files. + - The level of information logged in this file is controlled by + Ansible's verbosity and debug options + - 1) By default, messages at level C(WARNING) or higher are logged. + - 2) If the C(-v) or C(--verbose) command-line options to the + C(ansible-playbook) command are specified, messages at level + C(INFO) or higher are logged. + - 3) If the C(-vv) (or more verbose) command-line option to the + C(ansible-playbook) command is specified, or the C(ANSIBLE_DEBUG) + environment variable is set, then messages at level C(DEBUG) or + higher are logged. + - The I(logfile) and I(logdir) options are mutually exclusive. The + I(logdir) option is recommended for all new playbooks. + required: false + default: none + type: path + aliases: + - log_dir + logfile: + description: + - The path to a file, on the Ansible control machine, where debugging + information for the particular task is logged. + - The log file must be writeable. If the file already exists, it is + appended. It is the users responsibility to delete/rotate log files. + - The level of information logged in this file is controlled by + Ansible's verbosity and debug options + - 1) By default, messages at level C(WARNING) or higher are logged. + - 2) If the C(-v) or C(--verbose) command-line options to the + C(ansible-playbook) command are specified, messages at level + C(INFO) or higher are logged. + - 3) If the C(-vv) (or more verbose) command-line option to the + C(ansible-playbook) command is specified, or the C(ANSIBLE_DEBUG) + environment variable is set, then messages at level C(DEBUG) or + higher are logged. + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be + unique per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(logfile) value. It is the + user's responsibility to ensure this value is unique per target + host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(logdir) option in new playbooks. + The I(logfile) and I(logdir) options are mutually exclusive. + required: false + default: none + type: path + aliases: + - log_file ''' - # SUB_CONNECTION_DOCUMENTATION is just CONNECTION_DOCUMENTATION with each + # _SUB_CONNECT_DOCUMENTATION is just _CONNECT_DOCUMENTATION with each # line indented. - SUB_CONNECTION_DOCUMENTATION = '' - for line in CONNECTION_DOCUMENTATION.splitlines(True): - SUB_CONNECTION_DOCUMENTATION += ' ' + line + _SUB_CONNECT_DOCUMENTATION = '' + for line in _CONNECT_DOCUMENTATION.splitlines(True): + _SUB_CONNECT_DOCUMENTATION += ' ' + line # Build actual DOCUMENTATION string by putting the pieces together. - DOCUMENTATION = ''' -options:''' + CONNECTION_DOCUMENTATION + ''' - provider: - description: - - An alternative syntax for specifying the connection options. Rather - than specifying each connection-related top-level option, the - connection-related options may be specified as a dictionary of - suboptions using this option. All connection-related options must - either be specified as top-level options or as suboptions of the - C(provider) option. You can not combine the two methods of specifying - connection-related options. - required: False - default: None - type: dict - suboptions:''' + SUB_CONNECTION_DOCUMENTATION + ''' -requirements: - - junos-eznc >= ''' + MIN_PYEZ_VERSION + ''' - - Python >= 2.7 -notes: - - The NETCONF system service must be enabled on the target Junos device. + CONNECTION_DOCUMENTATION = ''' + connection_options:''' + _CONNECT_DOCUMENTATION + ''' + provider: + description: + - An alternative syntax for specifying the connection options. Rather + than specifying each connection-related top-level option, the + connection-related options may be specified as a dictionary of + suboptions to the I(provider) option. All connection-related options + must either be specified as top-level options or as suboptions of + the I(provider) option. You can not combine the two methods of + specifying connection-related options. + required: false + default: none + type: dict + suboptions:''' + _SUB_CONNECT_DOCUMENTATION + ''' + requirements: + - U(junos-eznc|https://github.com/Juniper/py-junos-eznc) >= ''' + MIN_PYEZ_VERSION + ''' + - Python >= 2.7 + notes: + - The NETCONF system service must be enabled on the target Junos device. ''' diff --git a/version.py b/version.py index 20d20a2f..8da36641 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev10" -DATE = "2017-Nov-05" +VERSION = "2.0.0.dev11" +DATE = "2018-Jan-08" From b105b491e2fa186895d2790a0baf2049856e325f Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Wed, 10 Jan 2018 08:43:34 -0700 Subject: [PATCH 193/426] Update version to trigger CI/CD. (#313) --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index 8da36641..3e53ec16 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev11" -DATE = "2018-Jan-08" +VERSION = "2.0.0.dev12" +DATE = "2018-Jan-09" From eb53da7dd16bea9ed342f5c8532f39fce712d848 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Wed, 10 Jan 2018 10:48:06 -0700 Subject: [PATCH 194/426] Fixes to allow the documentation to build correctly on readthedocs. (#315) --- .gitignore | 1 - docs/Makefile | 2 +- docs/ansible2rst.py | 2 +- docs/rst.j2 | 2 +- library/juniper_junos_config.py | 1 + library/juniper_junos_jsnapy.py | 2 +- library/juniper_junos_rpc.py | 2 +- library/juniper_junos_software.py | 3 ++- version.py | 4 ++-- 9 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index d9913d81..7f6e6ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,6 @@ nosetests.xml docs/build docs/*.rst docs/_build/ -docs/_rst/ docs/sphinx.log tests/.vagrant/* diff --git a/docs/Makefile b/docs/Makefile index 4fe6d3e5..7b3aac77 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -13,7 +13,7 @@ SPHINXOPTS = -j $(CPUS) -w $(LOGFILE) $(FULL_TRACEBACKS) $(FORCE_REBUILD) $(N SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build -RSTDIR = _rst +RSTDIR = . MODULES_PATH = ../library EXCLUDE_PATHS = ../library/_junos* DOC_PROJECTS = "Ansible API" diff --git a/docs/ansible2rst.py b/docs/ansible2rst.py index c1afab2b..efbefec2 100755 --- a/docs/ansible2rst.py +++ b/docs/ansible2rst.py @@ -67,7 +67,7 @@ def html_escape(text, quote=True): MODULE_NAME_STARTS_WITH = "juniper_junos_" MODULEDIR = "../library/" -OUTPUTDIR = "./_rst" +OUTPUTDIR = "./" ##################################################################################### diff --git a/docs/rst.j2 b/docs/rst.j2 index e20f93c7..3b7338f0 100644 --- a/docs/rst.j2 +++ b/docs/rst.j2 @@ -416,7 +416,7 @@ In addition to the :ref:`module-specific-options-label`, the following logging-r {% endif %} {% if examples or plainexamples -%} -.. _examples-label: +.. _@{ title }@-examples-label: Examples -------- diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index 6a58794a..fee56851 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -57,6 +57,7 @@ committing the configuration of a Junos device. It performs the following steps in order: + #. Open a candidate configuration database. * If the I(config_mode) option has a value of C(exclusive), the default, diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index 3c862c7e..812725b8 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -62,7 +62,7 @@ fails to execute the JSNAPy tests. If does NOT report C(failed) if one or more of the JSNAPy tests fail. To check the test results, register the module's response and use the assert module to verify the expected result - in the response. (See :ref:`examples-label`.) + in the response. (See :ref:`juniper_junos_jsnapy-examples-label`.) - A callback plugin which formats and prints JSNAPy test results for human consumption is also available. This callback plugin is enabled by adding C(callback_whitelist = jsnapy) to the Ansible configuration file. diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index 76e27adc..c873be31 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -155,7 +155,7 @@ I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the two lists must always contain the same number of elements. For RPC arguments which do not require a value, specify the value of True as - shown in the :ref:`examples-label`. + shown in the :ref:`juniper_junos_rpc-examples-label`. required: false default: none type: dict or list of dict diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 4cd6219e..daf0e6e2 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -57,7 +57,8 @@ This action is generally equivalent to the C(request system software add) operational-mode CLI command. It performs the following steps in order: - + + #. Compare the currently installed Junos version to the desired version specified by the I(version) option. diff --git a/version.py b/version.py index 3e53ec16..aa1af15e 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev12" -DATE = "2018-Jan-09" +VERSION = "2.0.0.dev13" +DATE = "2018-Jan-10" From aea2d4c15c1c78c11d2110535083ffa2b595ef28 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 11 Jan 2018 09:29:03 -0700 Subject: [PATCH 195/426] Correctly handle in_min and at arguments to juniper_junos_system. Fixes #310. (#317) The `in_min` and `at` arguments to `juniper_junos_system` were not handled correctly. The generated RPC argument looked like ``, when it should have been `5`. --- library/juniper_junos_system.py | 7 ++++--- version.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index d9367bb6..03ea725a 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -407,10 +407,11 @@ def main(): junos_module.etree.SubElement(rpc, 'media') else: if params['in_min'] is not None: - junos_module.etree.SubElement(rpc, 'in', - text=str(params['in_min'])) + junos_module.etree.SubElement(rpc, + 'in').text = str(params['in_min']) elif params['at'] is not None: - junos_module.etree.SubElement(rpc, 'at', text=params['at']) + junos_module.etree.SubElement(rpc, + 'at').text = params['at'] if params['other_re'] is True: if junos_module.dev.facts['2RE']: junos_module.etree.SubElement(rpc, 'other-routing-engine') diff --git a/version.py b/version.py index aa1af15e..9e93f547 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev13" -DATE = "2018-Jan-10" +VERSION = "2.0.0.dev14" +DATE = "2018-Jan-11" From 8e485b1936de62a6950fadc8ffa6ec491fc3bf6f Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 11 Jan 2018 17:04:15 -0700 Subject: [PATCH 196/426] Write configuration file using UTF-8 encoding rather than ASCII. Fixes #316. (#318) * Write configuration file using UTF-8 encoding rather than ASCII. Fixes #316. --- library/juniper_junos_facts.py | 7 +++++-- module_utils/juniper_junos_common.py | 6 ++++-- version.py | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 87d93ebf..419eb10d 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -166,8 +166,11 @@ ''' # Standard library imports -import os.path +# Python 2.x and 3.x portable open +from io import open import json +import os.path + def import_juniper_junos_common(): @@ -308,7 +311,7 @@ def save_inventory(junos_module, inventory): file_path = os.path.normpath(os.path.join(save_dir, file_name)) junos_module.logger.debug("Saving inventory to: %s.", file_path) try: - with open(file_path, 'w') as fact_file: + with open(file_path, 'wt', encoding='utf-8') as fact_file: fact_file.write(inventory) junos_module.logger.debug("Inventory saved to: %s.", file_path) except IOError: diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index aebdf683..689992d7 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -41,6 +41,8 @@ # Standard library imports from argparse import ArgumentParser from distutils.version import LooseVersion +# Python 2.x and 3.x portable open +from io import open import json import logging import os @@ -1804,7 +1806,7 @@ def save_text_output(self, name, format, text): - If the destination file is not writable. """ file_path = None - mode = 'w' + mode = 'wt' if name == 'diff': if self.params.get('diffs_file') is not None: file_path = os.path.normpath(self.params.get('diffs_file')) @@ -1830,7 +1832,7 @@ def save_text_output(self, name, format, text): file_path = os.path.normpath(os.path.join(dest_dir, file_name)) if file_path is not None: try: - with open(file_path, mode) as save_file: + with open(file_path, mode, encoding='utf-8') as save_file: save_file.write(text) self.logger.debug("Output saved to: %s.", file_path) except IOError: diff --git a/version.py b/version.py index 9e93f547..eb058a90 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev14" +VERSION = "2.0.0.dev15" DATE = "2018-Jan-11" From cfd171a1f988aa38e61b2cb33defadbc30fd10f4 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 11 Jan 2018 21:23:10 -0700 Subject: [PATCH 197/426] Prepare the 2.0.0 release. (#319) --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index eb058a90..4ea12c76 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0.dev15" +VERSION = "2.0.0" DATE = "2018-Jan-11" From 0e9b54fd72ff3139cfe2c32c93b905ce094cb6e0 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Fri, 12 Jan 2018 11:26:22 -0700 Subject: [PATCH 198/426] Prepare for 2.0.1 development. (#320) --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 4ea12c76..42f02e37 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.0" +VERSION = "2.0.1.dev0" DATE = "2018-Jan-11" From d4685f5ef93263132c77c7b2ee8d7f7179396361 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 15 Jan 2018 14:26:44 -0700 Subject: [PATCH 199/426] Unicode fix2 (#324) * Handle both ASCII and Unicode content when writing inventory and config files. Fixes #323. * Increment version. --- library/juniper_junos_facts.py | 6 ++---- module_utils/juniper_junos_common.py | 8 +++----- version.py | 4 ++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 419eb10d..4c88c3b8 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -166,8 +166,6 @@ ''' # Standard library imports -# Python 2.x and 3.x portable open -from io import open import json import os.path @@ -311,8 +309,8 @@ def save_inventory(junos_module, inventory): file_path = os.path.normpath(os.path.join(save_dir, file_name)) junos_module.logger.debug("Saving inventory to: %s.", file_path) try: - with open(file_path, 'wt', encoding='utf-8') as fact_file: - fact_file.write(inventory) + with open(file_path, 'wb') as fact_file: + fact_file.write(inventory.encode(encoding='utf-8')) junos_module.logger.debug("Inventory saved to: %s.", file_path) except IOError: junos_module.fail_json(msg="Unable to save inventory. Failed to " diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 689992d7..6bd6db0c 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -41,8 +41,6 @@ # Standard library imports from argparse import ArgumentParser from distutils.version import LooseVersion -# Python 2.x and 3.x portable open -from io import open import json import logging import os @@ -1806,7 +1804,7 @@ def save_text_output(self, name, format, text): - If the destination file is not writable. """ file_path = None - mode = 'wt' + mode = 'wb' if name == 'diff': if self.params.get('diffs_file') is not None: file_path = os.path.normpath(self.params.get('diffs_file')) @@ -1832,8 +1830,8 @@ def save_text_output(self, name, format, text): file_path = os.path.normpath(os.path.join(dest_dir, file_name)) if file_path is not None: try: - with open(file_path, mode, encoding='utf-8') as save_file: - save_file.write(text) + with open(file_path, mode) as save_file: + save_file.write(text.encode(encoding='utf-8')) self.logger.debug("Output saved to: %s.", file_path) except IOError: self.fail_json(msg="Unable to save output. Failed to " diff --git a/version.py b/version.py index 42f02e37..43f94069 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.1.dev0" -DATE = "2018-Jan-11" +VERSION = "2.0.1" +DATE = "2018-Jan-15" From a56cdaa3f55a4d0659e46c726898efcc47831d98 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Mon, 15 Jan 2018 14:35:53 -0700 Subject: [PATCH 200/426] Prepare for 2.0.2 development. (#325) --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 43f94069..51bd4731 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.1" +VERSION = "2.0.2dev0" DATE = "2018-Jan-15" From 7cc8e9b0dc8875d2bc856c7c1de556af01d89cbb Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Wed, 17 Jan 2018 09:25:54 -0700 Subject: [PATCH 201/426] Mimic old behavior for junos_install_config with check_commit = False. (#327) There was a backwards compatibility issue with junos_install_config. junos_install_config: check_commit: false Before the above used to perform a diff, but not perform a "commit check" or a "commit". Now, it performs the diff, but it also does a "commit". This change restores the previous behavior where: junos_install_config: check_commit: false will only perform the diff. --- action_plugins/_junos_install_config.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/action_plugins/_junos_install_config.py b/action_plugins/_junos_install_config.py index 89abc61d..55fe8ef9 100755 --- a/action_plugins/_junos_install_config.py +++ b/action_plugins/_junos_install_config.py @@ -78,6 +78,19 @@ def run(self, tmp=None, task_vars=None): # Always commit changes to mimic the previous behavior self._task.args['commit_empty_changes'] = True + # If check_commit is False, then also bypass the commit. + check = True + # Check for the 'check_commit' option which was an optional boolean + # argument for the junos_install_config module. + if 'check_commit' in self._task.args: + check = self._task.args.pop('check_commit') + if check is not None and self.convert_to_bool(check) is False: + # Translate to check_commit = False, commit = False, and + # commit_empty_changes = False + self._task.args['check_commit'] = False + self._task.args['commit'] = False + self._task.args['commit_empty_changes'] = False + # Remaining arguments can be passed through transparently. # Call the parent action module. From efb8f8beaa2029507736be9e64143de264657b62 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Wed, 17 Jan 2018 12:56:10 -0700 Subject: [PATCH 202/426] Translate | in output filenames to _. (#328) --- module_utils/juniper_junos_common.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 6bd6db0c..76a43b2a 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -1825,6 +1825,8 @@ def save_text_output(self, name, format, text): hostname = self.params.get('host') # Substitute underscore for spaces. name = name.replace(' ', '_') + # Substitute underscore for pipe + name = name.replace('|', '_') name = '' if name == 'config' else '_' + name file_name = '%s%s.%s' % (hostname, name, format) file_path = os.path.normpath(os.path.join(dest_dir, file_name)) From 1fa16d889ef20b81c419b087febc429e6839b2d0 Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Sun, 21 Jan 2018 21:21:11 -0700 Subject: [PATCH 203/426] Fix acceptable_percent_loss parameter to juniper_junos_ping. (#330) --- library/juniper_junos_ping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index 6758c90c..fa2607b0 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -521,7 +521,7 @@ def main(): # Results should include all the ping params in argument_spec_keys. for key in argument_spec_keys: results[key] = params.get(key) - # Overrite to be a string in the results + # Overwrite to be a string in the results results['acceptable_percent_loss'] = str( params.get('acceptable_percent_loss')) # Add timeout to the response even though it's a connect parameter. @@ -534,7 +534,7 @@ def main(): # Execute the ping. results = junos_module.ping( ping_params, - acceptable_percent_loss=['acceptable_percent_loss'], + acceptable_percent_loss=params['acceptable_percent_loss'], results=results) # Return results. From 11bda805374b057cc4ddf32d30cbb1d6a91160bb Mon Sep 17 00:00:00 2001 From: vnitinv Date: Mon, 19 Feb 2018 20:46:00 +0530 Subject: [PATCH 204/426] getting ready for 2.0.2 release --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index 51bd4731..b623a57a 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.2dev0" -DATE = "2018-Jan-15" +VERSION = "2.0.2" +DATE = "2018-Feb-19" From c24f3f3141dc479e563fc42f014e2e1dfbe659e8 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Thu, 1 Mar 2018 12:46:23 +0530 Subject: [PATCH 205/426] Resolved logger issue and removed unused logger --- module_utils/juniper_junos_common.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 76a43b2a..7a237a7e 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -901,10 +901,9 @@ def process(self, msg, kwargs): '%(asctime)s - %(name)s - %(levelname)s - %(message)s') # add formatter to handler handler.setFormatter(formatter) - # Handler should log anything from the 'jnpr' namespace to + # Handler should log anything from the 'jnpr.ansible_module.' namespace to # catch PyEZ, JSNAPY, etc. logs. - jnpr_logger = logging.getLogger('jnpr') - jnpr_logger.addHandler(handler) + logger.addHandler(handler) for name in additional_logger_names: logging.getLogger(name).addHandler(handler) except IOError as ex: From 1b5a5a9efb598f10470c45d041104e4df581f826 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 7 Mar 2018 10:51:46 +0530 Subject: [PATCH 206/426] Docker typo fixed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9861bbc8..4c1a79d3 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ $ docker run -it --rm juniper/pyez-ansible ash Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. ``` -$ docker run -it --rm -v $PWD:/project ash +$ docker run -it --rm -v $PWD:/project juniper/pyez-ansible ash ``` You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: ``` From 03dbf3644cb74ce94b303c0bb384fb6f807179cf Mon Sep 17 00:00:00 2001 From: rsmekala <35719774+rsmekala@users.noreply.github.com> Date: Mon, 26 Mar 2018 15:17:43 +0530 Subject: [PATCH 207/426] Update .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6ff52a58..478c458f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - 2.7 + - 3.6 sudo: required dist: trusty From d8392c95202a576a696d4d963f1f06d8fe7e03b2 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Tue, 27 Mar 2018 15:35:38 +0530 Subject: [PATCH 208/426] Added Python3 support --- callback_plugins/jsnapy.py | 11 ++++++----- docs/ansible2rst.py | 13 +++++++------ library/juniper_junos_ping.py | 2 +- library/juniper_junos_rpc.py | 5 +++-- library/juniper_junos_software.py | 2 +- library/juniper_junos_table.py | 5 +++-- module_utils/juniper_junos_common.py | 3 ++- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/callback_plugins/jsnapy.py b/callback_plugins/jsnapy.py index 3525f6b5..a5187e89 100644 --- a/callback_plugins/jsnapy.py +++ b/callback_plugins/jsnapy.py @@ -7,6 +7,7 @@ import time import pprint import json +from six import iteritems from ansible.plugins.callback import CallbackBase from ansible import constants as C @@ -44,7 +45,7 @@ def v2_runner_on_ok(self, result): ## Check if dic return has all valid information if module_name == '' or module_args == {}: return None - elif not module_args.has_key('action'): + elif 'action' not in module_args: return None if module_name == 'junos_jsnapy' and \ @@ -52,7 +53,7 @@ def v2_runner_on_ok(self, result): ## Check if dict entry already exist for this host host = result._host.name - if not host in self._results.keys(): + if not host in list(self._results.keys()): self._results[host] = [] self._results[host].append(result) @@ -60,13 +61,13 @@ def v2_runner_on_ok(self, result): def v2_playbook_on_stats(self, stats): ## Go over all results for all hosts - for host, results in self._results.iteritems(): + for host, results in iteritems(self._results): has_printed_banner = False for result in results: # self._pp.pprint(result.__dict__) res = result._result if res['final_result'] == "Failed": - for test_name, test_results in res['test_results'].iteritems(): + for test_name, test_results in iteritems(res['test_results']): for testlet in test_results: if testlet['count']['fail'] != 0: @@ -78,7 +79,7 @@ def v2_playbook_on_stats(self, stats): # Check if POST exist in the response data = '' - if test.has_key('post'): + if 'post' in test: data = test['post'] else: data = test diff --git a/docs/ansible2rst.py b/docs/ansible2rst.py index efbefec2..5591662c 100755 --- a/docs/ansible2rst.py +++ b/docs/ansible2rst.py @@ -29,6 +29,7 @@ from distutils.version import LooseVersion from jinja2 import Environment, FileSystemLoader import yaml +from six import print_ from collections import MutableMapping, MutableSet, MutableSequence @@ -215,7 +216,7 @@ def add_fragments(doc, filename): if 'options' not in fragment and 'logging_options' not in fragment and 'connection_options' not in fragment: raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename)) - for key, value in fragment.items(): + for key, value in iteritems(fragment.items()): if key in doc: # assumes both structures have same type if isinstance(doc[key], MutableMapping): @@ -247,7 +248,7 @@ def process_module(fname, template, outputname, aliases=None): module_name = fname.replace(".py", "") - print("Processing module %s" % (MODULEDIR + fname)) + print_("Processing module %s" % (MODULEDIR + fname)) doc, examples, returndocs, metadata = get_docstring(MODULEDIR + fname, verbose=True) @@ -259,12 +260,12 @@ def process_module(fname, template, outputname, aliases=None): required_fields = ('short_description',) for field in required_fields: if field not in doc: - print("%s: WARNING: MODULE MISSING field '%s'" % (fname, field)) + print_("%s: WARNING: MODULE MISSING field '%s'" % (fname, field)) not_nullable_fields = ('short_description',) for field in not_nullable_fields: if field in doc and doc[field] in (None, ''): - print("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field])) + print_("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field])) # # The present template gets everything from doc so we spend most of this @@ -375,11 +376,11 @@ def process_module(fname, template, outputname, aliases=None): if returndocs: try: doc['returndocs'] = yaml.safe_load(returndocs) - returndocs_keys = doc['returndocs'].keys() + returndocs_keys = list(doc['returndocs'].keys()) returndocs_keys.sort() doc['returndocs_keys'] = returndocs_keys except Exception as e: - print("%s:%s:yaml error:%s:returndocs=%s" % (fname, module_name, e, returndocs)) + print_("%s:%s:yaml error:%s:returndocs=%s" % (fname, module_name, e, returndocs)) doc['returndocs'] = None doc['returndocs_keys'] = None else: diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index fa2607b0..1ca920f2 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -478,7 +478,7 @@ def main(): # Add the ping RPC parameter argument spec fo the full argument_spec. argument_spec.update(ping_argument_spec) - argument_spec_keys = argument_spec.keys() + argument_spec_keys = list(argument_spec.keys()) # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index c873be31..fcc5fb9c 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -35,6 +35,7 @@ # from __future__ import absolute_import, division, print_function +from six import iteritems ANSIBLE_METADATA = {'metadata_version': '1.1', 'supported_by': 'community', @@ -563,7 +564,7 @@ def main(): rpc = junos_module.etree.Element(rpc_string, format=format) if kwarg is not None: # Add kwarg - for (key, value) in kwarg.items(): + for (key, value) in iteritems(kwarg): # Replace underscores with dashes in key name. key = key.replace('_', '-') sub_element = junos_module.etree.SubElement(rpc, key) @@ -571,7 +572,7 @@ def main(): sub_element.text = value if attr is not None: # Add attr - for (key, value) in attr.items(): + for (key, value) in iteritems(attr): # Replace underscores with dashes in key name. key = key.replace('_', '-') rpc.set(key, value) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index daf0e6e2..ef3f1463 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -550,7 +550,7 @@ def main(): ) # Save keys for later. Must do because software_argument_spec gets # modified. - option_keys = software_argument_spec.keys() + option_keys = list(software_argument_spec.keys()) # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 03cebc80..13d81129 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -37,6 +37,7 @@ # from __future__ import absolute_import, division, print_function +from six import iteritems ANSIBLE_METADATA = {'metadata_version': '1.1', 'supported_by': 'community', @@ -338,7 +339,7 @@ def expand_items(module, data): """ resources = [] # data.items() is a list of tuples - for table_key, table_fields in data.items(): + for table_key, table_fields in iteritems(data): # sample: # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), # ('neighbor', 'vmx2')] @@ -359,7 +360,7 @@ def juniper_items_to_list_of_dicts(module, data): """ resources = [] # data.items() is a list of tuples - for table_key, table_fields in data.items(): + for table_key, table_fields in iteritems(data): # sample: # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), # ('neighbor', 'vmx2')] diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 7a237a7e..08354f1a 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -32,6 +32,7 @@ # from __future__ import absolute_import, division, print_function +from six import iteritems # Ansible imports from ansible.module_utils.basic import AnsibleModule @@ -663,7 +664,7 @@ def __init__(self, self.params.pop(arg_name) # Promote any provider arg_name into params if 'provider' in self.params and self.params['provider'] is not None: - for arg_name, arg_value in self.params['provider'].items(): + for arg_name, arg_value in iteritems(self.params['provider']): if arg_name in self.aliases: arg_name = self.aliases[arg_name] self.params[arg_name] = arg_value From c2ae694700a8331847cb36b7067f7f9045a56a83 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Fri, 6 Apr 2018 12:56:55 +0530 Subject: [PATCH 209/426] Correcting wrong usage of iteritems() --- callback_plugins/jsnapy.py | 2 +- docs/ansible2rst.py | 2 +- library/juniper_junos_table.py | 5 ++--- module_utils/juniper_junos_common.py | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/callback_plugins/jsnapy.py b/callback_plugins/jsnapy.py index a5187e89..a611209d 100644 --- a/callback_plugins/jsnapy.py +++ b/callback_plugins/jsnapy.py @@ -53,7 +53,7 @@ def v2_runner_on_ok(self, result): ## Check if dict entry already exist for this host host = result._host.name - if not host in list(self._results.keys()): + if not host in self._results.keys(): self._results[host] = [] self._results[host].append(result) diff --git a/docs/ansible2rst.py b/docs/ansible2rst.py index 5591662c..a5809813 100755 --- a/docs/ansible2rst.py +++ b/docs/ansible2rst.py @@ -216,7 +216,7 @@ def add_fragments(doc, filename): if 'options' not in fragment and 'logging_options' not in fragment and 'connection_options' not in fragment: raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename)) - for key, value in iteritems(fragment.items()): + for key, value in iteritems(fragment): if key in doc: # assumes both structures have same type if isinstance(doc[key], MutableMapping): diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 13d81129..03cebc80 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -37,7 +37,6 @@ # from __future__ import absolute_import, division, print_function -from six import iteritems ANSIBLE_METADATA = {'metadata_version': '1.1', 'supported_by': 'community', @@ -339,7 +338,7 @@ def expand_items(module, data): """ resources = [] # data.items() is a list of tuples - for table_key, table_fields in iteritems(data): + for table_key, table_fields in data.items(): # sample: # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), # ('neighbor', 'vmx2')] @@ -360,7 +359,7 @@ def juniper_items_to_list_of_dicts(module, data): """ resources = [] # data.items() is a list of tuples - for table_key, table_fields in iteritems(data): + for table_key, table_fields in data.items(): # sample: # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), # ('neighbor', 'vmx2')] diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 08354f1a..8c4b99b4 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -664,7 +664,7 @@ def __init__(self, self.params.pop(arg_name) # Promote any provider arg_name into params if 'provider' in self.params and self.params['provider'] is not None: - for arg_name, arg_value in iteritems(self.params['provider']): + for arg_name, arg_value in self.params['provider'].items(): if arg_name in self.aliases: arg_name = self.aliases[arg_name] self.params[arg_name] = arg_value From 21b51d3acd6ebed2807888e0e0c8a7d0674e1182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20Ristim=C3=A4ki?= Date: Tue, 17 Apr 2018 20:58:12 +0300 Subject: [PATCH 210/426] Add support for defining ssh_config path --- module_utils/juniper_junos_common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 8c4b99b4..bcf69035 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -443,6 +443,9 @@ class ModuleDocFragment(object): # Default behavior coded in # JuniperJunosActionModule.run() default=None), + 'ssh_config': dict(type='path', + required=False, + default=None), 'mode': dict(choices=[None, 'telnet', 'serial'], default=None), 'console': dict(type='str', From 3fff9b8efdd5c63e5801d499cfdae2b5405cc566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20Ristim=C3=A4ki?= Date: Wed, 18 Apr 2018 08:57:12 +0300 Subject: [PATCH 211/426] added description of ssh_config option to docstring --- module_utils/juniper_junos_common.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index bcf69035..e518ab33 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -289,6 +289,13 @@ class ModuleDocFragment(object): type: path aliases: - ssh_keyfile + ssh_config: + description: + - The path to the SSH client configuration file. If this option is not + specified, then the PyEZ Device instance by default queries file + ~/.ssh/config. + required: false + type: path timeout: description: - The maximum number of seconds to wait for RPC responses from the From 9804d2b136194d3054951359fea55055524b9448 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 18 Apr 2018 16:29:19 +0530 Subject: [PATCH 212/426] Adding supports_check_mode to import_juniper_junos_common function --- library/juniper_junos_command.py | 3 ++- library/juniper_junos_config.py | 3 ++- library/juniper_junos_facts.py | 3 ++- library/juniper_junos_jsnapy.py | 3 ++- library/juniper_junos_ping.py | 3 ++- library/juniper_junos_pmtud.py | 3 ++- library/juniper_junos_rpc.py | 3 ++- library/juniper_junos_software.py | 3 ++- library/juniper_junos_srx_cluster.py | 3 ++- library/juniper_junos_system.py | 3 ++- library/juniper_junos_table.py | 3 ++- 11 files changed, 22 insertions(+), 11 deletions(-) diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index d19d010d..b65339ef 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -340,7 +340,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index fee56851..4b970a22 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -769,7 +769,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 4c88c3b8..64b17d9d 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -208,7 +208,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index 812725b8..1ea34668 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -240,7 +240,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index 1ca920f2..c3cc4caa 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -417,7 +417,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index de135063..b11cf45f 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -279,7 +279,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index fcc5fb9c..d8b39303 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -407,7 +407,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index ef3f1463..25a48cdb 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -415,7 +415,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py index 28811df3..6151a4ef 100644 --- a/library/juniper_junos_srx_cluster.py +++ b/library/juniper_junos_srx_cluster.py @@ -181,7 +181,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index 03ea725a..0a4a4766 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -281,7 +281,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 03cebc80..0d866e5a 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -323,7 +323,8 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True + bypass_checks=True, + supports_check_mode=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: From 635578f24bdc2abedf7651a53d2024ff5dfe17ad Mon Sep 17 00:00:00 2001 From: rsmekala Date: Thu, 19 Apr 2018 16:36:21 +0530 Subject: [PATCH 213/426] Changes to use Ansiballz Framework for importing module_utils. --- library/juniper_junos_command.py | 54 +++------------------------ library/juniper_junos_config.py | 54 +++------------------------ library/juniper_junos_facts.py | 54 +++------------------------ library/juniper_junos_jsnapy.py | 55 +++------------------------ library/juniper_junos_ping.py | 56 ++++------------------------ library/juniper_junos_pmtud.py | 56 ++++------------------------ library/juniper_junos_rpc.py | 56 ++++------------------------ library/juniper_junos_software.py | 54 +++------------------------ library/juniper_junos_srx_cluster.py | 54 +++------------------------ library/juniper_junos_system.py | 56 ++++------------------------ library/juniper_junos_table.py | 56 ++++------------------------ module_utils/__init__.py | 0 12 files changed, 71 insertions(+), 534 deletions(-) create mode 100644 module_utils/__init__.py diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index b65339ef..9a2b66a6 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -303,58 +303,16 @@ import sys -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index 4b970a22..c7f13868 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -732,58 +732,16 @@ import time -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - # Choices which are defined in the common module. config_format_choices = juniper_junos_common.CONFIG_FORMAT_CHOICES config_database_choices = [None] + \ diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 64b17d9d..b004afca 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -171,52 +171,13 @@ -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def get_facts_dict(junos_module): @@ -319,9 +280,6 @@ def save_inventory(junos_module, inventory): def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - config_format_choices = [None] config_format_choices += juniper_junos_common.CONFIG_FORMAT_CHOICES diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index 1ea34668..a87539f7 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -203,60 +203,17 @@ import os.path -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index c3cc4caa..19f6e80f 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -380,58 +380,16 @@ ''' -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common -def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() +def main(): # The argument spec for the module. argument_spec = dict( dest=dict(type='str', diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index b11cf45f..d0e7edae 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -242,52 +242,13 @@ ''' -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def main(): @@ -309,9 +270,6 @@ def main(): # Choices for max_size MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2 ** x, range(1, 17))) - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index d8b39303..a8f738b1 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -370,58 +370,16 @@ basestring = str -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common -def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() +def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 25a48cdb..97df8bf3 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -378,52 +378,13 @@ from urlparse import urlparse -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def parse_version_from_filename(filename): @@ -477,9 +438,6 @@ def myprogress(_, report): def main(): CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - #Define the argument spec. software_argument_spec=dict( local_package=dict(required=False, diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py index 6151a4ef..4fb7b71a 100644 --- a/library/juniper_junos_srx_cluster.py +++ b/library/juniper_junos_srx_cluster.py @@ -144,58 +144,16 @@ # Standard library imports -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index 0a4a4766..d05c7311 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -244,58 +244,16 @@ ''' -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common -def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() +def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 0d866e5a..008c5a89 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -286,52 +286,13 @@ RESPONSE_CHOICES = ['list_of_dicts', 'juniper_items'] -def import_juniper_junos_common(): - """Imports the juniper_junos_common module from _module_utils_path. - - Ansible versions < 2.4 do not provide a way to package common code in a - role. This function solves that problem for juniper_junos_* modules by - reading the module arguments passed on stdin and interpreting the special - option _module_utils_path as a path to the the directory where the - juniper_junos_common module resides. It temporarily inserts this path at - the head of sys.path, imports the juniper_junos_common module, and removes - the path from sys.path. It then returns the imported juniper_junos_common - module object. All juniper_junos_* modules must include this boilerplate - function in order to import the shared juniper_junos_common module. - - Args: - None. - - Returns: - The juniper_junos_common module object. - - Raises: - ImportError: If the juniper_junos_common object can not be imported - from the path specified by the module_utils_path argument. - """ - from ansible.module_utils.basic import AnsibleModule - import sys - - juniper_junos_common = None - module = AnsibleModule( - argument_spec={ - '_module_utils_path': dict(type='path', default=None), - # Avoids a warning about not specifying no_log for passwd. - 'passwd': dict(no_log=True) - }, - # Doesn't really work due to Ansible bug. Keeping it here for when - # Ansible bug is fixed. - no_log=True, - check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True - ) - import_path = module.params.get('_module_utils_path') - if import_path is not None: - sys.path.insert(0, import_path) - import juniper_junos_common - del sys.path[0] - return juniper_junos_common +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common def expand_items(module, data): @@ -376,9 +337,6 @@ def juniper_items_to_list_of_dicts(module, data): def main(): - # Import juniper_junos_common - juniper_junos_common = import_juniper_junos_common() - # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( diff --git a/module_utils/__init__.py b/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b From e6c9d4700b976407b15adefe02b416b6afac0b85 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Thu, 19 Apr 2018 16:37:58 +0530 Subject: [PATCH 214/426] Update .travis.yml to remove Ansible 2.1 & 2.2 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 478c458f..482d79d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,6 @@ dist: trusty env: - ANSIBLE_VERSION=2.4.0.0 - ANSIBLE_VERSION=2.3.0.0 - - ANSIBLE_VERSION=2.2.0.0 - - ANSIBLE_VERSION=2.1.3.0 install: ## Create Docker with Ansible modules and all dependancies From 2b0e95909cbdaa6f96dd98e42102cb5066a9a244 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Tue, 24 Apr 2018 16:42:26 +0530 Subject: [PATCH 215/426] Added 'level' option to logging --- module_utils/juniper_junos_common.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 8c4b99b4..6684ba83 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -381,6 +381,20 @@ class ModuleDocFragment(object): type: path aliases: - log_file + level: + description: + - The level of information to be logged can be modified using this option + - 1) By default, messages at level C(WARNING) or higher are logged. + - 2) If the C(-v) or C(--verbose) command-line options to the + C(ansible-playbook) command are specified, messages at level + C(INFO) or higher are logged. + - 3) If the C(-vv) (or more verbose) command-line option to the + C(ansible-playbook) command is specified, or the C(ANSIBLE_DEBUG) + environment variable is set, then messages at level C(DEBUG) or + higher are logged. + - 4) If I(level) is mentioned then messages at level I(level) or more are + logged. + ''' # _SUB_CONNECT_DOCUMENTATION is just _CONNECT_DOCUMENTATION with each @@ -497,12 +511,15 @@ class ModuleDocFragment(object): # Specify the logging spec. logging_spec = { 'logfile': dict(type='path', required=False, default=None), - 'logdir': dict(type='path', required=False, default=None) + 'logdir': dict(type='path', required=False, default=None), + 'level': dict(choices=[None, 'INFO', 'DEBUG'], required=False, default=None) } # The logdir and logfile options are mutually exclusive. logging_spec_mutually_exclusive = ['logfile', 'logdir'] + + # Other logging names which should be logged to the logfile additional_logger_names = ['ncclient', 'paramiko'] @@ -652,7 +669,7 @@ def __init__(self, # Update argument_spec with the top_spec argument_spec.update(top_spec) # Extend mutually_exclusive with connection_mutually_exclusive - mutually_exclusive += top_spec_mutually_exclusive + mutually_exclusive = top_spec_mutually_exclusive # Call parent's __init__() super(JuniperJunosModule, self).__init__( argument_spec=argument_spec, @@ -874,6 +891,9 @@ def process(self, msg, kwargs): level = logging.INFO elif self._verbosity > 1: level = logging.DEBUG + # Set level as mentioned in task + elif self.params.get('level') is not None: + level = self.params.get('level') # Get the logger object to be used for our logging. logger = logging.getLogger('jnpr.ansible_module.' + self.module_name) # Attach the NullHandler to avoid any errors if no logging is needed. From 2e8122cce3d065ac1a0d937c306aec7a4ad443a0 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Thu, 26 Apr 2018 11:21:24 +0530 Subject: [PATCH 216/426] Removed unused AnsibleModule import --- library/juniper_junos_command.py | 1 - library/juniper_junos_config.py | 1 - library/juniper_junos_facts.py | 1 - library/juniper_junos_jsnapy.py | 1 - library/juniper_junos_ping.py | 1 - library/juniper_junos_pmtud.py | 1 - library/juniper_junos_rpc.py | 1 - library/juniper_junos_software.py | 1 - library/juniper_junos_srx_cluster.py | 1 - library/juniper_junos_system.py | 1 - library/juniper_junos_table.py | 1 - module_utils/juniper_junos_common.py | 4 +++- 12 files changed, 3 insertions(+), 12 deletions(-) diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index 9a2b66a6..51545117 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -308,7 +308,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index c7f13868..b37d612d 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -737,7 +737,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index b004afca..47a187b1 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -176,7 +176,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index a87539f7..e60b5e37 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -208,7 +208,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common def main(): diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index 19f6e80f..43a0daec 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -385,7 +385,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index d0e7edae..add1d600 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -247,7 +247,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index a8f738b1..42bc879a 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -375,7 +375,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 97df8bf3..2905f63c 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -383,7 +383,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py index 4fb7b71a..c0758f46 100644 --- a/library/juniper_junos_srx_cluster.py +++ b/library/juniper_junos_srx_cluster.py @@ -149,7 +149,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index d05c7311..dc7cf163 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -249,7 +249,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 008c5a89..e113f196 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -291,7 +291,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 8c4b99b4..c5b96410 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -501,7 +501,7 @@ class ModuleDocFragment(object): } # The logdir and logfile options are mutually exclusive. -logging_spec_mutually_exclusive = ['logfile', 'logdir'] +logging_spec_mutually_exclusive = [['logfile', 'logdir']] # Other logging names which should be logged to the logfile additional_logger_names = ['ncclient', 'paramiko'] @@ -653,6 +653,8 @@ def __init__(self, argument_spec.update(top_spec) # Extend mutually_exclusive with connection_mutually_exclusive mutually_exclusive += top_spec_mutually_exclusive + from ansible.errors import AnsibleError + #raise AnsibleError(mutually_exclusive) # Call parent's __init__() super(JuniperJunosModule, self).__init__( argument_spec=argument_spec, From 0e535b8b0a3d124f3212e3f37d17ad9fb635afdb Mon Sep 17 00:00:00 2001 From: rsmekala Date: Thu, 26 Apr 2018 11:42:40 +0530 Subject: [PATCH 217/426] Revert "Removed unused AnsibleModule import" This reverts commit 2e8122cce3d065ac1a0d937c306aec7a4ad443a0. --- library/juniper_junos_command.py | 1 + library/juniper_junos_config.py | 1 + library/juniper_junos_facts.py | 1 + library/juniper_junos_jsnapy.py | 1 + library/juniper_junos_ping.py | 1 + library/juniper_junos_pmtud.py | 1 + library/juniper_junos_rpc.py | 1 + library/juniper_junos_software.py | 1 + library/juniper_junos_srx_cluster.py | 1 + library/juniper_junos_system.py | 1 + library/juniper_junos_table.py | 1 + module_utils/juniper_junos_common.py | 4 +--- 12 files changed, 12 insertions(+), 3 deletions(-) diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index 51545117..9a2b66a6 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -308,6 +308,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index b37d612d..c7f13868 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -737,6 +737,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 47a187b1..b004afca 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -176,6 +176,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index e60b5e37..a87539f7 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -208,6 +208,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common def main(): diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index 43a0daec..19f6e80f 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -385,6 +385,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index add1d600..d0e7edae 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -247,6 +247,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index 42bc879a..a8f738b1 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -375,6 +375,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 2905f63c..97df8bf3 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -383,6 +383,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py index c0758f46..4fb7b71a 100644 --- a/library/juniper_junos_srx_cluster.py +++ b/library/juniper_junos_srx_cluster.py @@ -149,6 +149,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index dc7cf163..d05c7311 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -249,6 +249,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index e113f196..008c5a89 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -291,6 +291,7 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index c5b96410..8c4b99b4 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -501,7 +501,7 @@ class ModuleDocFragment(object): } # The logdir and logfile options are mutually exclusive. -logging_spec_mutually_exclusive = [['logfile', 'logdir']] +logging_spec_mutually_exclusive = ['logfile', 'logdir'] # Other logging names which should be logged to the logfile additional_logger_names = ['ncclient', 'paramiko'] @@ -653,8 +653,6 @@ def __init__(self, argument_spec.update(top_spec) # Extend mutually_exclusive with connection_mutually_exclusive mutually_exclusive += top_spec_mutually_exclusive - from ansible.errors import AnsibleError - #raise AnsibleError(mutually_exclusive) # Call parent's __init__() super(JuniperJunosModule, self).__init__( argument_spec=argument_spec, From d9cd8f41fa935b32c454ae2366ef48e804d96a6e Mon Sep 17 00:00:00 2001 From: "Stacy W. Smith" Date: Thu, 26 Apr 2018 16:39:19 -0600 Subject: [PATCH 218/426] Handle empty 'version_info' and 'junos_info' facts more gracefully. Addresses #360. When PyEZ is unable to gather a fact from a Junos device, the key still exists in the dev.facts attribute, but has a value of None. The checks for converting the `version_info` and `junos_info` facts from their custom types to a dict did not properly account for this situation. IN that case, they would attempt to convert `None` to a `dict` and throw an exception. This PR more gracefully handles that situation. However, it does not fix the underlying Junos or PyEZ issue which might lead to being unable to collect the `version_info` and `junos_info` facts from a device. That would need to be addresses separately in PyEZ. --- library/juniper_junos_facts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 4c88c3b8..b23864ea 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -246,11 +246,11 @@ def get_facts_dict(junos_module): del facts['2RE'] # The value of the 'version_info' key is a custom junos.version_info # object. Convert this value to a dict. - if 'version_info' in facts: + if 'version_info' in facts and facts['version_info'] is not None: facts['version_info'] = dict(facts['version_info']) # The values of the ['junos_info'][re_name]['object'] keys are # custom junos.version_info objects. Convert all of these to dicts. - if 'junos_info' in facts: + if 'junos_info' in facts and facts['junos_info'] is not None: for key in facts['junos_info']: facts['junos_info'][key]['object'] = dict( facts['junos_info'][key]['object']) From 12eabecaa6e6b681b25954ea7ef8cae8c077b4f4 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 2 May 2018 11:15:17 +0530 Subject: [PATCH 219/426] PEP8 fixes --- module_utils/juniper_junos_common.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 6684ba83..aa268e24 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -518,8 +518,6 @@ class ModuleDocFragment(object): # The logdir and logfile options are mutually exclusive. logging_spec_mutually_exclusive = ['logfile', 'logdir'] - - # Other logging names which should be logged to the logfile additional_logger_names = ['ncclient', 'paramiko'] @@ -669,7 +667,7 @@ def __init__(self, # Update argument_spec with the top_spec argument_spec.update(top_spec) # Extend mutually_exclusive with connection_mutually_exclusive - mutually_exclusive = top_spec_mutually_exclusive + mutually_exclusive += top_spec_mutually_exclusive # Call parent's __init__() super(JuniperJunosModule, self).__init__( argument_spec=argument_spec, From c7f056bd90018d5b84c0a0f463b92dbc798150ff Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 2 May 2018 16:18:07 +0530 Subject: [PATCH 220/426] Revert "Adding supports_check_mode to import_juniper_junos_common function" This reverts commit 9804d2b136194d3054951359fea55055524b9448. --- library/juniper_junos_command.py | 3 +-- library/juniper_junos_config.py | 3 +-- library/juniper_junos_facts.py | 3 +-- library/juniper_junos_jsnapy.py | 3 +-- library/juniper_junos_ping.py | 3 +-- library/juniper_junos_pmtud.py | 3 +-- library/juniper_junos_rpc.py | 3 +-- library/juniper_junos_software.py | 3 +-- library/juniper_junos_srx_cluster.py | 3 +-- library/juniper_junos_system.py | 3 +-- library/juniper_junos_table.py | 3 +-- 11 files changed, 11 insertions(+), 22 deletions(-) diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py index b65339ef..d19d010d 100644 --- a/library/juniper_junos_command.py +++ b/library/juniper_junos_command.py @@ -340,8 +340,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index 4b970a22..fee56851 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -769,8 +769,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index 64b17d9d..4c88c3b8 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -208,8 +208,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py index 1ea34668..812725b8 100644 --- a/library/juniper_junos_jsnapy.py +++ b/library/juniper_junos_jsnapy.py @@ -240,8 +240,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index c3cc4caa..1ca920f2 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -417,8 +417,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index b11cf45f..de135063 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -279,8 +279,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index d8b39303..fcc5fb9c 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -407,8 +407,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 25a48cdb..ef3f1463 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -415,8 +415,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py index 6151a4ef..28811df3 100644 --- a/library/juniper_junos_srx_cluster.py +++ b/library/juniper_junos_srx_cluster.py @@ -181,8 +181,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index 0a4a4766..03ea725a 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -281,8 +281,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 0d866e5a..03cebc80 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -323,8 +323,7 @@ def import_juniper_junos_common(): # Ansible bug is fixed. no_log=True, check_invalid_arguments=False, - bypass_checks=True, - supports_check_mode=True + bypass_checks=True ) import_path = module.params.get('_module_utils_path') if import_path is not None: From fc399c38c2e5347df92a1caf17fc0e4705ad8f11 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Thu, 3 May 2018 12:25:33 +0530 Subject: [PATCH 221/426] Documentation fixes --- module_utils/juniper_junos_common.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index aa268e24..97a24913 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -332,7 +332,7 @@ class ModuleDocFragment(object): - The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files. - The level of information logged in this file is controlled by - Ansible's verbosity and debug options + Ansible's verbosity, debug options and level option in task - 1) By default, messages at level C(WARNING) or higher are logged. - 2) If the C(-v) or C(--verbose) command-line options to the C(ansible-playbook) command are specified, messages at level @@ -341,6 +341,8 @@ class ModuleDocFragment(object): C(ansible-playbook) command is specified, or the C(ANSIBLE_DEBUG) environment variable is set, then messages at level C(DEBUG) or higher are logged. + - 4) If C(level) is mentioned then messages at level C(level) or more are + logged. - The I(logfile) and I(logdir) options are mutually exclusive. The I(logdir) option is recommended for all new playbooks. required: false @@ -355,7 +357,7 @@ class ModuleDocFragment(object): - The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files. - The level of information logged in this file is controlled by - Ansible's verbosity and debug options + Ansible's verbosity, debug options and level option in task - 1) By default, messages at level C(WARNING) or higher are logged. - 2) If the C(-v) or C(--verbose) command-line options to the C(ansible-playbook) command are specified, messages at level @@ -364,6 +366,8 @@ class ModuleDocFragment(object): C(ansible-playbook) command is specified, or the C(ANSIBLE_DEBUG) environment variable is set, then messages at level C(DEBUG) or higher are logged. + - 4) If C(level) is mentioned then messages at level C(level) or more are + logged. - When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See @@ -392,8 +396,15 @@ class ModuleDocFragment(object): C(ansible-playbook) command is specified, or the C(ANSIBLE_DEBUG) environment variable is set, then messages at level C(DEBUG) or higher are logged. - - 4) If I(level) is mentioned then messages at level I(level) or more are + - 4) If C(level) is mentioned then messages at level C(level) or more are logged. + required: false + default: WARNING + type: str + choices: + - INFO + - DEBUG + ''' From ed651eac3bf8d68857bd5e5fac7284c687b8e2b0 Mon Sep 17 00:00:00 2001 From: rsmekala <35719774+rsmekala@users.noreply.github.com> Date: Thu, 3 May 2018 14:44:33 +0530 Subject: [PATCH 222/426] Create ISSUE_TEMPLATE.md - To help user mention all the required details needed for contributor to respond to the issue. - To help contributor get the details straight right from the start. --- ISSUE_TEMPLATE.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..255b3d93 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,55 @@ + + +Issue Type +------ + + - Bug Report + - Feature Idea + - Documentation Report + +Module Name +------ + + +Juniper.Junos role and Python libraries version + +``` + +``` + +OS / Environment +------ + + +Summary +------ + + +Steps to reproduce +------ + + + +```yaml + +``` + + + +Expected results + +``` + +``` +Actual results +------ + + + +``` + +``` From d7d5ad8bdec2030aab07922dc4b368e18ab7e897 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 14 May 2018 16:36:32 +0530 Subject: [PATCH 223/426] Fixed junos_jsnapy callback plugin - From Ansible 2.4, module_name is not passed as part of result to v2_runner_on_ok(). So, the current code return a Null object without adding the hosts. - Corrected by removing usage of module_name to add hosts in junos_jsnapy callback plugin. --- callback_plugins/jsnapy.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/callback_plugins/jsnapy.py b/callback_plugins/jsnapy.py index a611209d..826192c8 100644 --- a/callback_plugins/jsnapy.py +++ b/callback_plugins/jsnapy.py @@ -31,25 +31,20 @@ def __init__(self): def v2_runner_on_ok(self, result): """ - Collect test results for all tests executed if module is junos_jsnapy + Collect test results for all tests executed if action is snapcheck or check """ ## Extract module name - module_name = '' module_args = {} if 'invocation' in result._result: - if 'module_name' in result._result['invocation']: - module_name = result._result['invocation']['module_name'] + if 'module_args' in result._result['invocation']: module_args = result._result['invocation']['module_args'] ## Check if dic return has all valid information - if module_name == '' or module_args == {}: - return None - elif 'action' not in module_args: + if 'action' not in module_args: return None - if module_name == 'junos_jsnapy' and \ - ( module_args['action'] == 'snapcheck' or module_args['action'] == 'check' ): + if module_args['action'] == 'snapcheck' or module_args['action'] == 'check': ## Check if dict entry already exist for this host host = result._host.name From 37a7d5e0920013e2a4b3657024557d01783f8702 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 14 May 2018 16:39:31 +0530 Subject: [PATCH 224/426] Updated Dockerfile to remove pip error. - Include python2-dev installation. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 8e843189..2de8ac01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ ADD version.py /tmp/ansible-junos-stdlib/version.py RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ + apk add python2-dev &&\ apk update && apk add ca-certificates &&\ apk add openssh-client &&\ apk add build-base gcc g++ make python-dev &&\ From c6e5a0615cf2a94c850ebb55dd7d55e952dc3bf2 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 14 May 2018 17:07:26 +0530 Subject: [PATCH 225/426] Updated Dockerfile to include: - apk update to avoid pip installation errors. - Install pip from py-pip --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2de8ac01..8a399ccb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,10 +21,10 @@ ADD version.py /tmp/ansible-junos-stdlib/version.py RUN tar -czf Juniper.junos ansible-junos-stdlib &&\ - apk add python2-dev &&\ apk update && apk add ca-certificates &&\ apk add openssh-client &&\ apk add build-base gcc g++ make python-dev &&\ + apk update && apk add py-pip &&\ pip install --upgrade pip setuptools &&\ pip install jxmlease &&\ pip install ansible==$ver_ansible &&\ From 766a6a27c3968f285150b03db7b93f084b2e5f37 Mon Sep 17 00:00:00 2001 From: rsmekala <35719774+rsmekala@users.noreply.github.com> Date: Fri, 1 Jun 2018 12:17:01 +0530 Subject: [PATCH 226/426] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c1a79d3..a6ba5212 100644 --- a/README.md +++ b/README.md @@ -180,8 +180,8 @@ This example outlines how to use Ansible to install or upgrade the software imag ## DEPENDENCIES This modules requires the following to be installed on the Ansible control machine: -* Python 2.7 -* [Ansible](http://www.ansible.com) 2.1 or later +* Python >= 2.7 +* [Ansible](http://www.ansible.com) 2.3 or later * Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.1.7 or later * [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later @@ -207,6 +207,8 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c [Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr) +* v2.1.0: [Raja Shekar](https://github.com/rsmekala), [Stacy W Smith](https://github.com/stacywsmith) + *Former Contributors:* [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros) From 5325bdbde37ce6cb14e6dfccf8f8c564bb825844 Mon Sep 17 00:00:00 2001 From: rsmekala <35719774+rsmekala@users.noreply.github.com> Date: Fri, 1 Jun 2018 12:19:05 +0530 Subject: [PATCH 227/426] Update version.py --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index b623a57a..5302ae7d 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.0.2" -DATE = "2018-Feb-19" +VERSION = "2.1.0" +DATE = "2018-May-1" From a8bb15df9da27457a9942443df2ee77ca0a923a2 Mon Sep 17 00:00:00 2001 From: rsmekala <35719774+rsmekala@users.noreply.github.com> Date: Fri, 1 Jun 2018 14:28:22 +0530 Subject: [PATCH 228/426] Update version.py --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 5302ae7d..13f67261 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ VERSION = "2.1.0" -DATE = "2018-May-1" +DATE = "2018-June-1" From 05c727d35cfb0f9c31a4820cb3cee5464aa255ff Mon Sep 17 00:00:00 2001 From: rsmekala <35719774+rsmekala@users.noreply.github.com> Date: Fri, 1 Jun 2018 15:31:19 +0530 Subject: [PATCH 229/426] Update version.py --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 13f67261..88d19ddd 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.1.0" +VERSION = "2.1.1.dev0" DATE = "2018-June-1" From f2e53a2be8410692187bf26767cadae399d25ae2 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Tue, 28 Aug 2018 12:37:29 +0530 Subject: [PATCH 230/426] Updated sample juniper_junos_ping playbooks to remove 10.0.x.x --- library/juniper_junos_ping.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py index 19f6e80f..ce6c81ec 100644 --- a/library/juniper_junos_ping.py +++ b/library/juniper_junos_ping.py @@ -150,31 +150,31 @@ - Juniper.junos tasks: - - name: Ping 10.0.0.1 with default parameters. Fails if any packets lost. + - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. juniper_junos_ping: - dest: "10.0.0.1" + dest: "192.68.1.1" - - name: Ping 10.0.0.1. Allow 50% packet loss. Register response. + - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. juniper_junos_ping: - dest: "10.0.0.1" + dest: "192.68.1.1" acceptable_percent_loss: 50 register: response - name: Print all keys in the response. debug: var: response - - name: Ping 10.0.0.1. Send 20 packets. Register response. + - name: Ping 192.68.1.1. Send 20 packets. Register response. juniper_junos_ping: - dest: "10.0.0.1" + dest: "192.68.1.1" count: 20 register: response - name: Print packet sent from the response. debug: var: response.packets_sent - - name: Ping 10.0.0.1. Send 10 packets wihtout rapid. Register response. + - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. juniper_junos_ping: - dest: "10.0.0.1" + dest: "192.68.1.1" count: 10 rapid: false register: response @@ -191,28 +191,28 @@ debug: var: response.packet_loss - - name: Ping 10.0.0.1 with IP packet size of 1500. Register response. + - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. juniper_junos_ping: - dest: "10.0.0.1" + dest: "192.68.1.1" size: 1472 register: response - name: Print the packets_received from the response. debug: var: response.packets_received - - name: Ping 10.0.0.1 with do-not-fragment bit set. Register response. + - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. juniper_junos_ping: - dest: "10.0.0.1" + dest: "192.68.1.1" do_not_fragment: true register: response - name: Print the maximum round-trip-time from the response. debug: var: response.rtt_maximum - - name: Ping 10.0.0.1 with source set to 10.0.0.2. Register response. + - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. juniper_junos_ping: - dest: "10.0.0.1" - source: "10.0.0.2" + dest: "192.68.1.1" + source: "192.68.1.2" register: response - name: Print the source from the response. debug: From 51e034e62d397bdd37732b542058c0857258b9ba Mon Sep 17 00:00:00 2001 From: rsmekala Date: Tue, 28 Aug 2018 12:41:20 +0530 Subject: [PATCH 231/426] Updated sample juniper_junos_mtud playbooks to remove 10.0.x.x --- library/juniper_junos_pmtud.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py index d0e7edae..7947b7bf 100644 --- a/library/juniper_junos_pmtud.py +++ b/library/juniper_junos_pmtud.py @@ -136,21 +136,21 @@ - Juniper.junos tasks: - - name: Perform PMTUD to 10.0.0.1 with default parameters. + - name: Perform PMTUD to 192.68.1.1 with default parameters. juniper_junos_pmtud: - dest: "10.0.0.1" + dest: "192.68.1.1" - - name: Perform PMTUD to 10.0.0.1. Register response. + - name: Perform PMTUD to 192.68.1.1. Register response. juniper_junos_pmtud: - dest: "10.0.0.1" + dest: "192.68.1.1" register: response - name: Print the discovered MTU. debug: var: response.inet_mtu - - name: Perform PMTUD to 10.0.0.1. Search all possible MTU values. + - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. juniper_junos_pmtud: - dest: "10.0.0.1" + dest: "192.68.1.1" max_size: 65496 max_range: 65536 register: response @@ -158,27 +158,27 @@ debug: var: response.inet_mtu - - name: Perform PMTUD to 10.0.0.1. Source from ge-0/0/0.0 interface. + - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. juniper_junos_pmtud: - dest: "10.0.0.1" + dest: "192.68.1.1" interface: "ge-0/0/0.0" register: response - name: Print the discovered MTU. debug: var: response.inet_mtu - - name: Perform PMTUD to 10.0.0.1. Source from 192.168.1.1. + - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. juniper_junos_pmtud: - dest: "10.0.0.1" - source: "192.168.1.1" + dest: "192.68.1.1" + source: "192.168.1.2" register: response - name: Print the discovered MTU. debug: var: response.inet_mtu - - name: Perform PMTUD to 10.0.0.1. Source from the red routing-instance. + - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. juniper_junos_pmtud: - dest: "10.0.0.1" + dest: "192.68.1.1" routing_instance: "red" register: response - name: Print the discovered MTU. From 6e28bf4ede83a2bc6600fd7d5e5cbfff0a4ce027 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Tue, 28 Aug 2018 12:43:06 +0530 Subject: [PATCH 232/426] Updated sample juniper_junos_table playbooks to remove 10.0.x.x --- library/juniper_junos_table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 008c5a89..73916673 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -140,12 +140,12 @@ debug: var: response - - name: Retrieve routes within 10.0.0/8 + - name: Retrieve routes within 192.68.1/8 juniper_junos_table: file: "routes.yml" table: "RouteTable" kwargs: - destination: "10.0.0.0/8" + destination: "192.68.1.0/8" response_type: "juniper_items" register: response - name: Print response From 2518e1c62ff67dfe439bb87cbd6ba7170d35b1c9 Mon Sep 17 00:00:00 2001 From: panks21 Date: Tue, 28 Aug 2018 14:30:01 +0530 Subject: [PATCH 233/426] added support for ansible_port and ansible_host To support ansible defined variables --- module_utils/juniper_junos_common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 40513948..07cee449 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -510,9 +510,10 @@ class ModuleDocFragment(object): # Keys are connection options. Values are a list of task_vars to use as the # default value. connection_spec_fallbacks = { - 'host': ['inventory_hostname'], + 'host': ['inventory_hostname', 'ansible_host'], 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], 'ssh_private_key_file': ['ansible_ssh_private_key_file', 'ansible_private_key_file'] } From 51c0bc901a597ab0b0c905c4423de743b85be1ff Mon Sep 17 00:00:00 2001 From: rsmekala Date: Thu, 20 Sep 2018 15:46:04 +0530 Subject: [PATCH 234/426] Updated reboot, shutdown and halt rpc calls for WR-Linux HW. --- library/juniper_junos_software.py | 10 ++++++++-- library/juniper_junos_system.py | 20 ++++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 97df8bf3..4a6d80a2 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -680,7 +680,10 @@ def main(): "to 5 seconds.") junos_module.dev.timeout = 5 junos_module.logger.debug('Initiating reboot.') - rpc = junos_module.etree.Element('request-reboot') + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-reboot') + else: + rpc = junos_module.etree.Element('request-reboot') xpath_list = ['.//request-reboot-status'] if all_re is True: if (junos_module.sw._multi_RE is True and @@ -699,7 +702,10 @@ def main(): junos_module.logger.debug("Reboot RPC executed cleanly.") if len(xpath_list) > 0: for xpath in xpath_list: - got = resp.findtext(xpath) + if junos_module.dev.facts['_is_linux']: + got = resp.text + else: + got = resp.findtext(xpath) if got is not None: results['msg'] += ' Reboot successfully initiated.' break diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py index d05c7311..b186f037 100644 --- a/library/juniper_junos_system.py +++ b/library/juniper_junos_system.py @@ -343,13 +343,22 @@ def main(): rpc = None xpath_list = [] if action == 'reboot': - rpc = junos_module.etree.Element('request-reboot') + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-reboot') + else: + rpc = junos_module.etree.Element('request-reboot') xpath_list.append('.//request-reboot-status') elif action == 'shutdown': - rpc = junos_module.etree.Element('request-power-off') + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-power-off') + else: + rpc = junos_module.etree.Element('request-power-off') xpath_list.append('.//request-reboot-status') elif action == 'halt': - rpc = junos_module.etree.Element('request-halt') + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-halt') + else: + rpc = junos_module.etree.Element('request-halt') xpath_list.append('.//request-reboot-status') elif action == 'zeroize': rpc = junos_module.etree.Element('request-system-zeroize') @@ -420,7 +429,10 @@ def main(): junos_module.logger.debug("RPC executed cleanly.") if len(xpath_list) > 0: for xpath in xpath_list: - got = resp.findtext(xpath) + if junos_module.dev.facts['_is_linux']: + got = resp.text + else: + got = resp.findtext(xpath) if got is not None: results['msg'] = '%s successfully initiated.' % \ (action) From 014ff18de8939345600009fdda49fc9e9be6e2d0 Mon Sep 17 00:00:00 2001 From: chientingchen <43360612+chientingchen@users.noreply.github.com> Date: Tue, 25 Sep 2018 16:36:06 +0800 Subject: [PATCH 235/426] Update README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index a6ba5212..5b55c31d 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,28 @@ callback_whitelist = jsnapy ## INSTALLATION You must have the [DEPENDENCIES](#dependencies) installed on your system. +If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy install Juniper.junos -c` + +``` +[WARNING]: - Juniper.junos was NOT installed successfully: Failed to get data +from the API server (https://galaxy.ansible.com/api/): Failed to validate the +SSL certificate for galaxy.ansible.com:443. Make sure your managed systems have +a valid CA certificate installed. If the website serving the url uses SNI you +need python >= 2.7.9 on your managed machine (the python executable used +(/usr/bin/python) is version: 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC +4.8.4]) or you can install the `urllib3`, `pyOpenSSL`, `ndg-httpsclient`, and +`pyasn1` python modules to perform SNI verification in python >= 2.6. You can +use validate_certs=False if you do not need to confirm the servers identity but +this is unsafe and not recommended. Paths checked for this platform: +/etc/ssl/certs, /etc/pki/ca-trust/extracted/pem, /etc/pki/tls/certs, /usr/share +/ca-certificates/cacert.org, /etc/ansible. The exception msg was: hostname +u'galaxy.ansible.com' doesn't match either of +'*.c1e4.galaxy.openshiftapps.com', 'c1e4.galaxy.openshiftapps.com'. + +ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list. +``` + + ### Ansible Galaxy Role To download the latest released version of the junos role to the Ansible server, execute the ansible-galaxy install command, and specify **Juniper.junos**. From 2f34b56b492b0b5e55a0d861e74d8e7568a962fe Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Feb 2019 12:31:31 +0530 Subject: [PATCH 236/426] Added anible-junos-awx project. Refer README for additional details --- ansible-junos-awx/Makefile | 85 ++++ ansible-junos-awx/Makefile.variable | 6 + ansible-junos-awx/README.md | 612 ++++++++++++++++++++++++++++ 3 files changed, 703 insertions(+) create mode 100644 ansible-junos-awx/Makefile create mode 100644 ansible-junos-awx/Makefile.variable create mode 100644 ansible-junos-awx/README.md diff --git a/ansible-junos-awx/Makefile b/ansible-junos-awx/Makefile new file mode 100644 index 00000000..203297a5 --- /dev/null +++ b/ansible-junos-awx/Makefile @@ -0,0 +1,85 @@ +PWD = $(shell pwd) +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Darwin) + SED := sed -i '' +else + SED := sed -i +endif + +include Makefile.variable + +all:prequisite virtual-env ansible-awx docker-start docker-exec ## install ansible-junos-awx + + +.PHONY: prequisite +prequisite: + pip install virtualenv + rm -rf ./awx ./awx-env + +.PHONY: virtual-env +virtual-env: + virtualenv awx-env --no-site-packages + . awx-env/bin/activate && \ + pip install ansible docker-py + +.PHONY: ansible-awx +ansible-awx: + . awx-env/bin/activate && \ + git clone https://github.com/ansible/awx.git --single-branch --depth 1 + +.PHONY: docker-start +docker-start: +ifneq '$(PROJECT_DATA_DIR)' '' + mkdir -p $(PROJECT_DATA_DIR) + @${SED} '/project_data_dir/s/^#//g' $(PWD)/awx/installer/inventory + @${SED} 's|project_data_dir=.*|project_data_dir=$(PROJECT_DATA_DIR)|g' $(PWD)/awx/installer/inventory +endif +ifneq '$(AWX_TASK_TAG)' '' + @${SED} 's/dockerhub_version=.*/dockerhub_version=$(AWX_TASK_TAG)/g' $(PWD)/awx/installer/inventory +endif +ifneq '$(POSTGRES_DATA_DIR)' '' + @${SED} 's|postgres_data_dir=.*|postgres_data_dir=$(POSTGRES_DATA_DIR)|g' $(PWD)/awx/installer/inventory + @mkdir -p ${POSTGRES_DATA_DIR}/pg_snapshots && touch ${POSTGRES_DATA_DIR}/pg_snapshots/.keep + @mkdir -p ${POSTGRES_DATA_DIR}/pg_replslot && touch ${POSTGRES_DATA_DIR}/pg_replslot/.keep + @mkdir -p ${POSTGRES_DATA_DIR}/pg_stat_tmp && touch ${POSTGRES_DATA_DIR}/pg_stat_tmp/.keep + @mkdir -p ${POSTGRES_DATA_DIR}/pg_stat && touch ${POSTGRES_DATA_DIR}/pg_stat/.keep + @mkdir -p ${POSTGRES_DATA_DIR}/pg_twophase && touch ${POSTGRES_DATA_DIR}/pg_twophase/.keep + @mkdir -p ${POSTGRES_DATA_DIR}/pg_tblspc && touch ${POSTGRES_DATA_DIR}/pg_tblspc/.keep +endif +ifneq '$(HOST_FILE)' '' + cp $(HOST_FILE) $(strip $(PROJECT_DATA_DIR))/hosts +endif + + . awx-env/bin/activate && \ + ansible-playbook -i $(PWD)/awx/installer/inventory $(PWD)/awx/installer/install.yml + sleep 120 + +.PHONY: docker-exec +docker-exec: + docker exec -it awx_task pip install jsnapy jxmlease junos-eznc + docker exec -it awx_task ansible-galaxy install Juniper.junos,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles + docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' +ifneq '$(HOST_FILE)' '' + curl -u admin:password --noproxy '*' http://localhost/api/v2/inventories/ --header "Content-Type: application/json" -x POST -d '{"name":"$(INVENTORY_NAME)" , "organization": 1}' + docker exec -it awx_task /bin/bash -c 'awx-manage inventory_import --source=/var/lib/awx/projects/hosts --inventory-name=$(INVENTORY_NAME) --overwrite' +endif + +.PHONY: docker-stop +docker-stop: ## stop the docker + docker stop awx_task + docker stop awx_web + docker stop memcached + docker stop rabbitmq + docker stop postgres + +.PHONY: docker-remove +docker-remove: docker-stop ##clean the docker + docker rm awx_task + docker rm awx_web + docker rm memcached + docker rm rabbitmq + docker rm postgres + +clean: prequisite ## clean the project + docker system prune -f diff --git a/ansible-junos-awx/Makefile.variable b/ansible-junos-awx/Makefile.variable new file mode 100644 index 00000000..90ab278d --- /dev/null +++ b/ansible-junos-awx/Makefile.variable @@ -0,0 +1,6 @@ +PROJECT_DATA_DIR = +AWX_TASK_TAG = +POSTGRES_DATA_DIR = +ANSIBLE_JUNOS_VERSION = +HOST_FILE = +INVENTORY_NAME = diff --git a/ansible-junos-awx/README.md b/ansible-junos-awx/README.md new file mode 100644 index 00000000..e1f08d94 --- /dev/null +++ b/ansible-junos-awx/README.md @@ -0,0 +1,612 @@ +## About + + Ansible-junos-awx provides a web-based user interface and task engine built on top of [Ansible](https://github.com/ansible/ansible.git) which helps to perform specific +operational and configuration tasks on devices running Junos OS using [ansible-junos-stdlib](https://github.com/Juniper/ansible-junos-stdlib.git). + +## Requirements + Before you can run a deployment, you'll need the following installed in your local environment: + +- [Docker](https://www.docker.com) +- pip module +- [GNU Make](https://ftp.gnu.org/gnu/make/) +- [Git](https://git-scm.com/downloads) Requires Version 1.8.4+ + +## Installation + +Clone repo and run make inside Juniper-awx folder + +``` +$ git clone https://github.com/Juniper/ansible-junos-awx +$ cd ansible-junos-awx +$ make or make all +``` +This will do the following operations: +- Creates virtual environment Juniper-awx. +- Install python modules required for the project in the virtualenv: Ansible,docker-py. +- Clone AWX repository into the Juniper-awx/awx folder +- Change AWX inventory file to include user specifications.Refer [Makefile.variable](#makefilevariable). +- Launch AWX conatiners. +- Install Juniper.junos role with user specified version.Refer [Makefile.variable](#makefilevariable). +- Install python modules required for Juniper.junos role in awx_task container: jxmlease,junos-eznc,jsnappy. +- Change roles_path in ansible.cfg for awx_task container. +- If HOST_FILE is mentioned, an inventory with name INVENTORY_NAME is created and host's loaded into it.Refer [Makefile.variable](#makefilevariable). + +# Example make + +``` + +$ make +pip install virtualenv +Requirement already satisfied: virtualenv in /Library/Python/2.7/site-packages +rm -rf ./awx ./awx-env /private/tmp/pgdocker +virtualenv awx-env --no-site-packages +New python executable in /private/tmp/ansible-junos-awx/awx-env/bin/python +Installing setuptools, pip, wheel...done. +. awx-env/bin/activate && \ + pip install ansible docker-py +Collecting ansible +Collecting docker-py + Using cached docker_py-1.10.6-py2.py3-none-any.whl +Collecting jinja2 (from ansible) + Using cached Jinja2-2.10-py2.py3-none-any.whl +Collecting PyYAML (from ansible) +Collecting cryptography (from ansible) + Using cached cryptography-2.1.4-cp27-cp27m-macosx_10_6_intel.whl +Collecting paramiko (from ansible) + Using cached paramiko-2.4.0-py2.py3-none-any.whl +Requirement already satisfied: setuptools in ./awx-env/lib/python2.7/site-packages (from ansible) +Collecting websocket-client>=0.32.0 (from docker-py) + Using cached websocket_client-0.47.0-py2.py3-none-any.whl +Collecting backports.ssl-match-hostname>=3.5; python_version < "3.5" (from docker-py) +Collecting ipaddress>=1.0.16; python_version < "3.3" (from docker-py) +Collecting six>=1.4.0 (from docker-py) + Using cached six-1.11.0-py2.py3-none-any.whl +Collecting requests!=2.11.0,>=2.5.2 (from docker-py) + Using cached requests-2.18.4-py2.py3-none-any.whl +Collecting docker-pycreds>=0.2.1 (from docker-py) + Using cached docker_pycreds-0.2.2-py2.py3-none-any.whl +Collecting MarkupSafe>=0.23 (from jinja2->ansible) +Collecting cffi>=1.7; platform_python_implementation != "PyPy" (from cryptography->ansible) + Using cached cffi-1.11.5-cp27-cp27m-macosx_10_6_intel.whl +Collecting enum34; python_version < "3" (from cryptography->ansible) + Using cached enum34-1.1.6-py2-none-any.whl +Collecting idna>=2.1 (from cryptography->ansible) + Using cached idna-2.6-py2.py3-none-any.whl +Collecting asn1crypto>=0.21.0 (from cryptography->ansible) + Using cached asn1crypto-0.24.0-py2.py3-none-any.whl +Collecting pynacl>=1.0.1 (from paramiko->ansible) + Using cached PyNaCl-1.2.1-cp27-cp27m-macosx_10_6_intel.whl +Collecting bcrypt>=3.1.3 (from paramiko->ansible) + Using cached bcrypt-3.1.4-cp27-cp27m-macosx_10_6_intel.whl +Collecting pyasn1>=0.1.7 (from paramiko->ansible) + Using cached pyasn1-0.4.2-py2.py3-none-any.whl +Collecting urllib3<1.23,>=1.21.1 (from requests!=2.11.0,>=2.5.2->docker-py) + Using cached urllib3-1.22-py2.py3-none-any.whl +Collecting certifi>=2017.4.17 (from requests!=2.11.0,>=2.5.2->docker-py) + Using cached certifi-2018.1.18-py2.py3-none-any.whl +Collecting chardet<3.1.0,>=3.0.2 (from requests!=2.11.0,>=2.5.2->docker-py) + Using cached chardet-3.0.4-py2.py3-none-any.whl +Collecting pycparser (from cffi>=1.7; platform_python_implementation != "PyPy"->cryptography->ansible) +Installing collected packages: MarkupSafe, jinja2, PyYAML, pycparser, cffi, enum34, idna, asn1crypto, ipaddress, six, cryptography, pynacl, bcrypt, pyasn1, paramiko, ansible, websocket-client, backports.ssl-match-hostname, urllib3, certifi, chardet, requests, docker-pycreds, docker-py +Successfully installed MarkupSafe-1.0 PyYAML-3.12 ansible-2.4.3.0 asn1crypto-0.24.0 backports.ssl-match-hostname-3.5.0.1 bcrypt-3.1.4 certifi-2018.1.18 cffi-1.11.5 chardet-3.0.4 cryptography-2.1.4 docker-py-1.10.6 docker-pycreds-0.2.2 enum34-1.1.6 idna-2.6 ipaddress-1.0.19 jinja2-2.10 paramiko-2.4.0 pyasn1-0.4.2 pycparser-2.18 pynacl-1.2.1 requests-2.18.4 six-1.11.0 urllib3-1.22 websocket-client-0.47.0 +. awx-env/bin/activate && \ + git clone https://github.com/ansible/awx.git --single-branch --depth 1 +Cloning into 'awx'... +remote: Counting objects: 2596, done. +remote: Compressing objects: 100% (2295/2295), done. +remote: Total 2596 (delta 603), reused 1034 (delta 237), pack-reused 0 +Receiving objects: 100% (2596/2596), 7.48 MiB | 482.00 KiB/s, done. +Resolving deltas: 100% (603/603), done. +Checking out files: 100% (2317/2317), done. +mkdir -p /private/tmp/pgdocker +. awx-env/bin/activate && \ + ansible-playbook -i /private/tmp/ansible-junos-awx/awx/installer/inventory /private/tmp/ansible-junos-awx/awx/installer/install.yml +[DEPRECATION WARNING]: DEFAULT_SUDO_USER option, In favor of become which is a generic framework . This feature will be removed in version 2.8. Deprecation +warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. + +PLAY [Build and deploy AWX] ************************************************************************************************************************************ + +TASK [check_vars : include_tasks] ****************************************************************************************************************************** +skipping: [localhost] + +TASK [check_vars : include_tasks] ****************************************************************************************************************************** +included: /private/tmp/ansible-junos-awx/awx/installer/check_vars/tasks/check_docker.yml for localhost + +TASK [check_vars : postgres_data_dir should be defined] ******************************************************************************************************** +ok: [localhost] => { + "changed": false, + "msg": "All assertions passed" +} + +TASK [check_vars : host_port should be defined] **************************************************************************************************************** +ok: [localhost] => { + "changed": false, + "msg": "All assertions passed" +} + +TASK [image_build : Get Version from checkout if not provided] ************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Set global version if not provided] ******************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Verify awx-logos directory exists for official install] ************************************************************************************ +skipping: [localhost] + +TASK [image_build : Copy logos for inclusion in sdist] ********************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Set sdist file name] *********************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : AWX Distribution] ************************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Stat distribution file] ******************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Clean distribution] ************************************************************************************************************************ +skipping: [localhost] + +TASK [image_build : Build sdist builder image] ***************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Build AWX distribution using container] **************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Build AWX distribution locally] ************************************************************************************************************ +skipping: [localhost] + +TASK [image_build : Set docker build base path] **************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Set awx_web image name] ******************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Set awx_task image name] ******************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Ensure directory exists] ******************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Stage sdist] ******************************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Template web Dockerfile] ******************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Template task Dockerfile] ****************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Stage launch_awx] ************************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Stage launch_awx_task] ********************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Stage nginx.conf] ************************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Stage supervisor.conf] ********************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Stage supervisor_task.conf] **************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Stage settings.py] ************************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Stage requirements] ************************************************************************************************************************ +skipping: [localhost] + +TASK [image_build : Stage config watcher] ********************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Stage Makefile] **************************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Stage ansible repo] ************************************************************************************************************************ +skipping: [localhost] + +TASK [image_build : Stage ansible repo key] ******************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Build base web image] ********************************************************************************************************************** +skipping: [localhost] + +TASK [image_build : Build base task image] ********************************************************************************************************************* +skipping: [localhost] + +TASK [image_build : Clean docker base directory] *************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Authenticate with OpenShift via user and password] ******************************************************************************************* +skipping: [localhost] + +TASK [openshift : Authenticate with OpenShift via token] ******************************************************************************************************* +skipping: [localhost] + +TASK [openshift : Get Project Detail] ************************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Get Postgres Service Detail] ***************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Create AWX Openshift Project] **************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Mark Openshift User as Admin] **************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Set docker registry password] **************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Set docker registry password] **************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Authenticate with Docker registry] *********************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Wait for Openshift] ************************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Tag and push web image to registry] ********************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Wait for the registry to settle] ************************************************************************************************************* +skipping: [localhost] + +TASK [openshift : Tag and push task image to registry] ********************************************************************************************************* +skipping: [localhost] + +TASK [openshift : Enable image stream lookups for awx images] ************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Set full web image path] ********************************************************************************************************************* +skipping: [localhost] + +TASK [openshift : Set full task image path] ******************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Set DockerHub Image Paths] ******************************************************************************************************************* +skipping: [localhost] + +TASK [openshift : Deploy and Activate Postgres] **************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Wait for Postgres to activate] *************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Set openshift base path] ********************************************************************************************************************* +skipping: [localhost] + +TASK [openshift : Ensure directory exists] ********************************************************************************************************************* +skipping: [localhost] + +TASK [openshift : Template Openshift AWX Config] *************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Template Openshift AWX Deployment] *********************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Template Openshift AWX etcd2] **************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Apply etcd deployment] *********************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Apply Configmap] ***************************************************************************************************************************** +skipping: [localhost] + +TASK [openshift : Apply Deployment] **************************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Set the Kubernetes Context] ***************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Get Namespace Detail] *********************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Get Postgres Service Detail] **************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Create AWX Kubernetes Project] ************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Authenticate with Docker registry] ********************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Wait for Openshift] ************************************************************************************************************************* +skipping: [localhost] + +TASK [kubernetes : Tag and push web image to registry] ********************************************************************************************************* +skipping: [localhost] + +TASK [kubernetes : Wait for the registry to settle] ************************************************************************************************************ +skipping: [localhost] + +TASK [kubernetes : Tag and push task image to registry] ******************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Set full web image path] ******************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Set full task image path] ******************************************************************************************************************* +skipping: [localhost] + +TASK [kubernetes : Set DockerHub Image Paths] ****************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Deploy and Activate Postgres] *************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Set postgresql hostname to helm package service] ******************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Wait for Postgres to activate] ************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Set kubernetes base path] ******************************************************************************************************************* +skipping: [localhost] + +TASK [kubernetes : Ensure directory exists] ******************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Template Kubernetes AWX etcd2] ************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Template Kubernetes AWX Config] ************************************************************************************************************* +skipping: [localhost] + +TASK [kubernetes : Template Kubernetes AWX Deployment] ********************************************************************************************************* +skipping: [localhost] + +TASK [kubernetes : Apply etcd deployment] ********************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Apply Configmap] **************************************************************************************************************************** +skipping: [localhost] + +TASK [kubernetes : Apply Deployment] *************************************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Export Docker web image if it isnt local and there isnt a registry defined] *************************************************************** +skipping: [localhost] + +TASK [local_docker : Export Docker task image if it isnt local and there isnt a registry defined] ************************************************************** +skipping: [localhost] + +TASK [local_docker : Authenticate with Docker registry if registry password given] ***************************************************************************** +skipping: [localhost] + +TASK [local_docker : Set docker base path] ********************************************************************************************************************* +skipping: [localhost] + +TASK [local_docker : Ensure directory exists] ****************************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Copy web image to docker execution] ******************************************************************************************************* +skipping: [localhost] + +TASK [local_docker : Copy task image to docker execution] ****************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Load web image] *************************************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Load task image] ************************************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : include_role] ***************************************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Set full image path for local install] **************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Set DockerHub Image Paths] **************************************************************************************************************** +ok: [localhost] + +TASK [local_docker : Activate postgres container] ************************************************************************************************************** +changed: [localhost] + +TASK [local_docker : Activate rabbitmq container] ************************************************************************************************************** +changed: [localhost] + +TASK [local_docker : Activate memcached container] ************************************************************************************************************* +changed: [localhost] + +TASK [local_docker : Wait for postgres and rabbitmq to activate] *********************************************************************************************** +Pausing for 15 seconds +(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort) +ok: [localhost] + +TASK [local_docker : Set properties without postgres for awx_web] ********************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Set properties with postgres for awx_web] ************************************************************************************************* +ok: [localhost] + +TASK [local_docker : Set properties without postgres for awx_task] ********************************************************************************************* +skipping: [localhost] + +TASK [local_docker : Set properties with postgres for awx_task] ************************************************************************************************ +ok: [localhost] + +TASK [local_docker : Activate AWX Web Container] *************************************************************************************************************** +changed: [localhost] + +TASK [local_docker : Activate AWX Task Container] ************************************************************************************************************** +changed: [localhost] + +TASK [local_docker : Create /var/lib/awx directory] ************************************************************************************************************ +skipping: [localhost] + +TASK [local_docker : Create docker-compose.yml file] *********************************************************************************************************** +skipping: [localhost] + +TASK [local_docker : Start the containers] ********************************************************************************************************************* +skipping: [localhost] + +PLAY RECAP ***************************************************************************************************************************************************** +localhost : ok=12 changed=5 unreachable=0 failed=0 + +sleep 120 +docker exec -it awx_task pip install jsnapy jxmlease junos-eznc +Collecting jsnapy + Downloading jsnapy-1.3.1.tar.gz (50kB) + 100% |################################| 51kB 399kB/s +Collecting jxmlease + Downloading jxmlease-1.0.1-py2.py3-none-any.whl +Collecting junos-eznc + Downloading junos_eznc-2.1.7-py2.py3-none-any.whl (150kB) + 100% |################################| 153kB 1.6MB/s +Collecting colorama (from jsnapy) + Downloading colorama-0.3.9-py2.py3-none-any.whl +Collecting configparser (from jsnapy) + Downloading configparser-3.5.0.tar.gz +Collecting pyparsing (from jsnapy) + Downloading pyparsing-2.2.0-py2.py3-none-any.whl (56kB) + 100% |################################| 61kB 5.5MB/s +Collecting icdiff (from jsnapy) + Downloading icdiff-1.9.1.tar.gz +Collecting future (from jsnapy) + Downloading future-0.16.0.tar.gz (824kB) + 100% |################################| 829kB 937kB/s +Collecting ncclient>=0.5.3 (from junos-eznc) + Downloading ncclient-0.5.3.tar.gz (63kB) + 100% |################################| 71kB 6.5MB/s +Requirement already satisfied (use --upgrade to upgrade): paramiko>=1.15.2 in /usr/lib/python2.7/site-packages (from junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): six in /usr/lib/python2.7/site-packages (from junos-eznc) +Collecting scp>=0.7.0 (from junos-eznc) + Downloading scp-0.10.2-py2.py3-none-any.whl +Requirement already satisfied (use --upgrade to upgrade): jinja2>=2.7.1 in /usr/lib/python2.7/site-packages (from junos-eznc) +Collecting lxml>=3.2.4 (from junos-eznc) + Downloading lxml-4.1.1-cp27-cp27mu-manylinux1_x86_64.whl (5.6MB) + 100% |################################| 5.6MB 229kB/s +Collecting pyserial (from junos-eznc) + Downloading pyserial-3.4-py2.py3-none-any.whl (193kB) + 100% |################################| 194kB 4.3MB/s +Requirement already satisfied (use --upgrade to upgrade): PyYAML>=3.10 in /usr/lib64/python2.7/site-packages (from junos-eznc) +Collecting netaddr (from junos-eznc) + Downloading netaddr-0.7.19-py2.py3-none-any.whl (1.6MB) + 100% |################################| 1.6MB 815kB/s +Requirement already satisfied (use --upgrade to upgrade): setuptools>0.6 in /usr/lib/python2.7/site-packages (from ncclient>=0.5.3->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): cryptography>=1.1 in /usr/lib64/python2.7/site-packages (from paramiko>=1.15.2->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): pyasn1>=0.1.7 in /usr/lib/python2.7/site-packages (from paramiko>=1.15.2->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): MarkupSafe in /usr/lib64/python2.7/site-packages (from jinja2>=2.7.1->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): idna>=2.0 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko>=1.15.2->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): enum34 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko>=1.15.2->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): ipaddress in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko>=1.15.2->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): cffi>=1.4.1 in /usr/lib64/python2.7/site-packages (from cryptography>=1.1->paramiko>=1.15.2->junos-eznc) +Requirement already satisfied (use --upgrade to upgrade): pycparser in /usr/lib/python2.7/site-packages (from cffi>=1.4.1->cryptography>=1.1->paramiko>=1.15.2->junos-eznc) +Installing collected packages: lxml, ncclient, scp, pyserial, netaddr, junos-eznc, colorama, configparser, pyparsing, icdiff, future, jsnapy, jxmlease + Running setup.py install for ncclient ... done + Running setup.py install for configparser ... done + Running setup.py install for icdiff ... done + Running setup.py install for future ... done + Running setup.py install for jsnapy ... done +Successfully installed colorama-0.3.9 configparser-3.5.0 future-0.16.0 icdiff-1.9.1 jsnapy-1.3.1 junos-eznc-2.1.7 jxmlease-1.0.1 lxml-4.1.1 ncclient-0.5.3 netaddr-0.7.19 pyparsing-2.2.0 pyserial-3.4 scp-0.10.2 +You are using pip version 8.1.2, however version 9.0.1 is available. +You should consider upgrading via the 'pip install --upgrade pip' command. +docker exec -it awx_task ansible-galaxy install Juniper.junos, -p /etc/ansible/roles +- downloading role 'junos', owned by Juniper +- downloading role from https://github.com/Juniper/ansible-junos-stdlib/archive/2.0.2.tar.gz +- extracting Juniper.junos to /etc/ansible/roles/Juniper.junos +- Juniper.junos (2.0.2) was installed successfully +docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' + + +``` + +After it has finished executing, check whether all containers are up. + +``` +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +ee36bb9312bc ansible/awx_task:latest "/tini -- /bin/sh -c…" About an hour ago Up About an hour 8052/tcp awx_task +bc8652bcf6ea ansible/awx_web:latest "/tini -- /bin/sh -c…" About an hour ago Up About an hour 0.0.0.0:80->8052/tcp awx_web +fb820f201e0c memcached:alpine "docker-entrypoint.s…" About an hour ago Up About an hour 11211/tcp memcached +c0b5bfd1bd85 rabbitmq:3 "docker-entrypoint.s…" About an hour ago Up About an hour 4369/tcp, 5671-5672/tcp, 25672/tcp rabbitmq +4411bd57f8d3 postgres:9.6 "docker-entrypoint.s…" About an hour ago Up About an hour 5432/tcp postgres + +``` +Log into 0.0.0.0:80 or localhost to acess the AWX web UI. + +``` +make docker-remove + This command will stop and remove the docker container + +$ make docker-remove +docker stop awx_task +awx_task +docker stop awx_web +awx_web +docker stop memcached +memcached +docker stop rabbitmq +rabbitmq +docker stop postgres +postgres +docker rm awx_task +awx_task +docker rm awx_web +awx_web +docker rm memcached +memcached +docker rm rabbitmq +rabbitmq +docker rm postgres +postgres + +``` + +## Makefile.variable + +This file helps to pass arguments to make file.User can specific the path, name of the project and postgres data +directory.Docker hub version and ansible junos version helps to control the version of the docker and juniper +ansible-galaxy respectively. + +``` +Example: + +PROJECT_DATA_DIR = +AWX_TASK_TAG = +POSTGRES_DATA_DIR = +ANSIBLE_JUNOS_VERSION = +HOST_FILE = /etc/ansible/hosts +INVENTORY_NAME = Junos + +``` +1. `PROJECT_DATA_DIR` : Provide absolute path to directory where the ansible projects reside.If the directory is not present Makefile will create the path. +2. `AWX_TASK_TAG`: Mention the awx_task tag to be installed.For available versions refer [Dockerhub](https://hub.docker.com/r/ansible/awx_task/tags/). +3. `POSTGRES_DATA_DIR`: Provide absolute path to postgres directory.If the directory is not present Makefile will create the path and create folders required for postgres to run. +4. `ANSIBLE_JUNOS_VERSION`: Mention the Juniper.junos version to be installed.By default, it installs the latest version. +5. `HOST_FILE`: Provide the absolute path to the host file.This option can be only used if PROJECT_DATA_DIR is mentioned. +By default, it doesnot load any host file.Please ensure that a unique INVENTORY_NAME is mentioned to avoid errors e.g Hosts. +6. `INVENTORY_NAME`: The name of the inventory to which HOST_FILE is to be loaded. + +Note: +- `PROJECT_DATA_DIR` is the location where the Ansible projects will be manually sourced from. But it doesn't directly contain any playbook, AWX expects Ansible project folders at this location. +For more information about Ansible project structure, please refer [here](http://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#content-organization)
+
A sample directory structure is explained below. +``` +XYZ/ +└── Ansible_Project_1 + └── getFacts.yml +``` +In the above case, the `PROJECT_DATA_DIR = XYZ` +- If a variable is left blank, it is considered to be built with default values. +- If postgres container keeps on restarting, source the `POSTGRES_DATA_DIR` into any other location other than /tmp. +- Ensure docker has permission to bind the location mentioned in Makefile.variable + +## LICENSE + +Apache 2.0 + +## CONTRIBUTORS + +Juniper Networks is actively contributing to and maintaining this repo. Please contact jnpr-community-netdev@juniper.net for any queries. + +*Contributors:* + +- v0.0.1: [Raja Shekar M](https://github.com/rsmekala),[Dinesh Babu R](https://github.com/dineshbaburam91), [Jasminderpal Sidhu](https://github.com/sidhujasminder) From 18cc7c6b380eaf9277b23f041a863bea25ddb1c7 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Thu, 18 Oct 2018 11:52:27 -0700 Subject: [PATCH 237/426] Commit first changes for console over ssh --- module_utils/juniper_junos_common.py | 14 +++++++++++++- requirements.txt | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 07cee449..10e3243b 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -122,7 +122,7 @@ # Constants # Minimum PyEZ version required by shared code. -MIN_PYEZ_VERSION = "2.1.7" +MIN_PYEZ_VERSION = "2.2.0" # Installation URL for PyEZ. PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" # Minimum lxml version required by shared code. @@ -468,6 +468,15 @@ class ModuleDocFragment(object): # Default behavior coded in JuniperJunosActionModule.run() default=None, no_log=True), + 'cs_user': dict(type='str', + aliases=['console_username'], + required=False, + default=None), + 'cs_passwd': dict(type='str', + aliases=['console_password'], + required=False, + default=None, + no_log=True), 'ssh_private_key_file': dict(type='path', required=False, aliases=['ssh_keyfile'], @@ -1297,6 +1306,9 @@ def open(self): self.close() log_connect_args = dict(connect_args) log_connect_args['passwd'] = 'NOT_LOGGING_PARAMETER' + if 'cs_passwd' in log_connect_args: + log_connect_args['cs_passwd'] = 'NOT_LOGGING_PARAMETER' + self.logger.debug("Creating device parameters: %s", log_connect_args) timeout = connect_args.pop('timeout') diff --git a/requirements.txt b/requirements.txt index fc1f4b0c..50945cf9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -junos-eznc>=2.1.7 +junos-eznc>=2.2.0 jsnapy>=1.2.1 jxmlease>=1.0.1 lxml>3.2.4 From 291bb019ebc5ceff7e567e716ac83d9b38ed79b9 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 27 Feb 2019 17:23:13 -0800 Subject: [PATCH 238/426] Add documentation for new parameters --- module_utils/juniper_junos_common.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 10e3243b..bb9e10c9 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -325,6 +325,24 @@ class ModuleDocFragment(object): type: str aliases: - username + cs_user: + description: + - The username used to authenticate with the console server over SSH. + This option is only required if you want to connect to a device over console + using SSH as transport. Mutually exclusive with the I(console) option. + required: false + type: str + aliases: + - console_username + cs_passwd: + description: + - The password used to authenticate with the console server over SSH. + This option is only required if you want to connect to a device over console + using SSH as transport. Mutually exclusive with the I(console) option. + required: false + type: str + aliases: + - console_password ''' LOGGING_DOCUMENTATION = ''' @@ -515,7 +533,9 @@ class ModuleDocFragment(object): connection_spec_mutually_exclusive = [['mode', 'console'], ['port', 'console'], ['baud', 'console'], - ['attempts','console']] + ['attempts','console'] + ['cs_user', 'console'], + ['cs_passwd', 'console']] # Keys are connection options. Values are a list of task_vars to use as the # default value. connection_spec_fallbacks = { From e38e680910e48875e1c2f37c08c47f94a75fd0ec Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Wed, 27 Feb 2019 17:38:30 -0800 Subject: [PATCH 239/426] fix missing comma --- module_utils/juniper_junos_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index bb9e10c9..ed7509f0 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -533,7 +533,7 @@ class ModuleDocFragment(object): connection_spec_mutually_exclusive = [['mode', 'console'], ['port', 'console'], ['baud', 'console'], - ['attempts','console'] + ['attempts','console'], ['cs_user', 'console'], ['cs_passwd', 'console']] # Keys are connection options. Values are a list of task_vars to use as the From 1a400c5c84c29a82b784544563087e10b61ac96c Mon Sep 17 00:00:00 2001 From: rsmekala <35719774+rsmekala@users.noreply.github.com> Date: Thu, 7 Mar 2019 14:32:08 +0530 Subject: [PATCH 240/426] Update requirements.txt `yaml` is not a python package, the package name has to be `pyyaml`. Link: https://pyyaml.org/wiki/PyYAMLDocumentation --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fc1f4b0c..251c500e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ junos-eznc>=2.1.7 jsnapy>=1.2.1 jxmlease>=1.0.1 lxml>3.2.4 -yaml>=3.08 \ No newline at end of file +pyyaml>=3.08 From dc88e6d65e1fb90f8877863e692dc8a8eec74006 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 8 Mar 2017 12:38:24 -0500 Subject: [PATCH 241/426] Cleaned up markdown and changed contributors --- README.md | 166 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 93 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 5b55c31d..2175e669 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Juniper Ansible roles for Junos + ## About Juniper Networks supports Ansible for managing devices running @@ -9,6 +11,7 @@ retrieving information, and resetting, rebooting, or shutting down managed devic [INSTALLATION](#installation) section for instructions on installing this role. ## Two Sets of Ansible Modules for Junos devices + Since Ansible version >= 2.1, Ansible also natively includes [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.junos role @@ -17,7 +20,9 @@ Ansible control machine, and an Ansible play may invoke a module from either (or using the modules in this role when writing new playbooks that manage Junos devices. ## Overview of Modules + This Juniper.junos role includes the following modules: + - **juniper_junos_command** — Execute one or more CLI commands on a Junos device. - **juniper_junos_config** — Manipulate the configuration of a Junos device. - **juniper_junos_facts** — Retrieve facts from a Junos device. @@ -31,6 +36,7 @@ This Juniper.junos role includes the following modules: - **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. ### Important Changes + Significant changes to the modules in the Juniper.junos role were made between versions 1.4.3 and 2.0.0. In versions <= 1.4.3 of the Juniper.junos role, the modules used different module and argument names. Versions >= 2.0.0 of the Juniper.junos role provide backwards compatibility with playbooks written to prior versions of the Juniper.junos @@ -47,30 +53,27 @@ In addition to the modules listed above, a callback_plugin `jsnapy` is available The callback_plugin `jsnapy` helps to print on the screen additional information regarding jsnapy failed tests. For each failed test, a log will be printed after the RECAP of the playbook as shown in this example: -``` -PLAY RECAP ********************************************************************* -qfx10002-01 : ok=3 changed=0 unreachable=0 failed=1 -qfx10002-02 : ok=3 changed=0 unreachable=0 failed=1 -qfx5100-01 : ok=1 changed=0 unreachable=0 failed=1 - -JSNAPy Results for: qfx10002-01 ************************************************ -Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} -Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "60021", "peer-state": "Idle", "peer-address": "192.168.0.1"} -Value of 'oper-status' not 'is-equal' at '//interface-information/physical-interface[normalize-space(admin-status)='up' and logical-interface/address-family/address-family-name ]' with {"oper-status": "down", "name": "et-0/0/18"} - -JSNAPy Results for: qfx10002-02 ************************************************ -Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} -``` + PLAY RECAP ********************************************************************* + qfx10002-01 : ok=3 changed=0 unreachable=0 failed=1 + qfx10002-02 : ok=3 changed=0 unreachable=0 failed=1 + qfx5100-01 : ok=1 changed=0 unreachable=0 failed=1 + + JSNAPy Results for: qfx10002-01 ************************************************ + Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} + Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "60021", "peer-state": "Idle", "peer-address": "192.168.0.1"} + Value of 'oper-status' not 'is-equal' at '//interface-information/physical-interface[normalize-space(admin-status)='up' and logical-interface/address-family/address-family-name ]' with {"oper-status": "down", "name": "et-0/0/18"} + + JSNAPy Results for: qfx10002-02 ************************************************ + Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} The `jsnapy` plugin is currently in **Experimental** stage, please provide feedback. Callback plugins are not activated by default. They must be manually added to the Ansible configuration file under the `[defaults]` section using the variable `callback_whitelist`. Specifically, these lines should be added to the Ansible configuration file in order to allow the jsnapy callback plugin: -``` -[defaults] -callback_whitelist = jsnapy -``` + + [defaults] + callback_whitelist = jsnapy ## DOCUMENTATION @@ -78,37 +81,35 @@ callback_whitelist = jsnapy [Ansible style documentation](http://junos-ansible-modules.readthedocs.org) - ## INSTALLATION + You must have the [DEPENDENCIES](#dependencies) installed on your system. If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy install Juniper.junos -c` -``` -[WARNING]: - Juniper.junos was NOT installed successfully: Failed to get data -from the API server (https://galaxy.ansible.com/api/): Failed to validate the -SSL certificate for galaxy.ansible.com:443. Make sure your managed systems have -a valid CA certificate installed. If the website serving the url uses SNI you -need python >= 2.7.9 on your managed machine (the python executable used -(/usr/bin/python) is version: 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC -4.8.4]) or you can install the `urllib3`, `pyOpenSSL`, `ndg-httpsclient`, and -`pyasn1` python modules to perform SNI verification in python >= 2.6. You can -use validate_certs=False if you do not need to confirm the servers identity but -this is unsafe and not recommended. Paths checked for this platform: -/etc/ssl/certs, /etc/pki/ca-trust/extracted/pem, /etc/pki/tls/certs, /usr/share -/ca-certificates/cacert.org, /etc/ansible. The exception msg was: hostname -u'galaxy.ansible.com' doesn't match either of -'*.c1e4.galaxy.openshiftapps.com', 'c1e4.galaxy.openshiftapps.com'. - -ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list. -``` - + [WARNING]: - Juniper.junos was NOT installed successfully: Failed to get data + from the API server (https://galaxy.ansible.com/api/): Failed to validate the + SSL certificate for galaxy.ansible.com:443. Make sure your managed systems have + a valid CA certificate installed. If the website serving the url uses SNI you + need python >= 2.7.9 on your managed machine (the python executable used + (/usr/bin/python) is version: 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC + 4.8.4]) or you can install the `urllib3`, `pyOpenSSL`, `ndg-httpsclient`, and + `pyasn1` python modules to perform SNI verification in python >= 2.6. You can + use validate_certs=False if you do not need to confirm the servers identity but + this is unsafe and not recommended. Paths checked for this platform: + /etc/ssl/certs, /etc/pki/ca-trust/extracted/pem, /etc/pki/tls/certs, /usr/share + /ca-certificates/cacert.org, /etc/ansible. The exception msg was: hostname + u'galaxy.ansible.com' doesn't match either of + '*.c1e4.galaxy.openshiftapps.com', 'c1e4.galaxy.openshiftapps.com'. + + ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list. ### Ansible Galaxy Role + To download the latest released version of the junos role to the Ansible server, execute the ansible-galaxy install command, and specify **Juniper.junos**. -``` +```bash [root@ansible-cm]# ansible-galaxy install Juniper.junos - downloading role 'junos', owned by Juniper - downloading role from https://github.com/Juniper/ansible-junos-stdlib/archive/1.3.1.tar.gz @@ -118,52 +119,65 @@ server, execute the ansible-galaxy install command, and specify **Juniper.junos* You can also use the ansible-galaxy install command to install the latest development version of the junos role directly from GitHub. -``` + +```bash sudo ansible-galaxy install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos ``` ### Git clone + For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: -``` + +```bash user@ansible-junos-stdlib> source env-setup ``` -This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: -``` -[jeremy@ansible-junos-stdlib]$ echo $ANSIBLE_LIBRARY + +This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: + +```bash +$ echo $ANSIBLE_LIBRARY /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible ``` ### Docker -To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. -The following will pull the latest image and run it in an interactive ash shell. -``` -$ docker run -it --rm juniper/pyez-ansible ash -``` -Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and -associated files). The following will bind mount the current working directory and start the ash shell. + +To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. + +```bash +docker run -it --rm juniper/pyez-ansible ash ``` -$ docker run -it --rm -v $PWD:/project juniper/pyez-ansible ash + +Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. + +```bash +docker run -it --rm -v $PWD:/project juniper/pyez-ansible ash ``` + You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: -``` -example -|playbook.yml -|hosts -|-vars -|-templates -|-scripts -``` + + example + |playbook.yml + |hosts + |-vars + |-templates + |-scripts + We can move to the example directory and run the playbook with the following command: + +```bash +cd example/ +docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml ``` -$ docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook -i hosts playbook.yml -``` + You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. -``` -$ alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" -$ pb-ansible -i hosts playbook.yml + +```bash +alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" +pb-ansible -i hosts playbook.yml ``` ## Example Playbook + This example outlines how to use Ansible to install or upgrade the software image on a device running Junos OS. ```yaml @@ -198,21 +212,26 @@ This example outlines how to use Ansible to install or upgrade the software imag - name: wait_reboot wait_for: host={{ inventory_hostname }} port=830 timeout={{ wait_time }} when: not sw.check_mode -``` +``` ## DEPENDENCIES + This modules requires the following to be installed on the Ansible control machine: -* Python >= 2.7 -* [Ansible](http://www.ansible.com) 2.3 or later -* Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.1.7 or later -* [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later + +- Python >= 2.7 +- [Ansible](http://www.ansible.com) 2.3 or later +- Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.1.7 or later +- [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later ## LICENSE + Apache 2.0 ## SUPPORT + Support for this Juniper.junos role is provided by the community and Juniper Networks. If you have an issue with a module in the Juniper.junos role, you may: + - Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). - Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) - Email [jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) @@ -222,6 +241,7 @@ Support for the Junos modules included in Ansible core is provided by Ansible. I core module you should open a [Github issue against the Ansible project](https://github.com/ansible/ansible/issues). ## CONTRIBUTORS + Juniper Networks is actively contributing to and maintaining this repo. Please contact [jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) for any queries. @@ -229,8 +249,8 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c [Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [David Gethings](https://github.com/dgjnpr) -* v2.1.0: [Raja Shekar](https://github.com/rsmekala), [Stacy W Smith](https://github.com/stacywsmith) +[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [Stephen Steiner](https://github.com/ntwrkguru) *Former Contributors:* -[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), -[Damien Garros](https://github.com/dgarros) + +[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) From 2387919274dc8ef8f18753e8b5ef32f635100f46 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Sat, 16 Mar 2019 11:36:24 -0400 Subject: [PATCH 242/426] no need up upgrade setuptools and pip --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c6089295..7551fed7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ LABEL net.juniper.jsnapy.version=$ver_jsnapy LABEL net.juniper.ansible.module.version=$ver_ansible-stdlib RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make python-dev py-pip &&\ - pip install --upgrade pip setuptools jxmlease ansible==$ver_ansible jsnapy==$ver_jsnapy &&\ + pip install jxmlease ansible==$ver_ansible jsnapy==$ver_jsnapy &&\ apk del -r --purge gcc make g++ &&\ rm -rf /source/* &&\ rm -rf /var/cache/apk/* &&\ From df7e39a4b02634ee9ab7805978245b5bd62029c8 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Sat, 16 Mar 2019 11:38:38 -0400 Subject: [PATCH 243/426] added vscode meta to .dockerignore and .gitignore --- .dockerignore | 3 ++- .gitignore | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index e45e2b7d..31441a7d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,5 @@ docs/ test/ tools/ COPYRIGHT -env-setup \ No newline at end of file +env-setup +.vscode \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7f6e6ab8..ef97d417 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ desktop.ini # PyCharm .idea + +# VC Code +.vscode From 1f51721e0173a98f78a89ca039a816f2e0acddec Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Sat, 16 Mar 2019 11:54:17 -0400 Subject: [PATCH 244/426] switched to requirements.txt for pip --- Dockerfile | 13 +++++-------- requirements.txt | 5 +++++ 2 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile index 7551fed7..a69eeb92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,18 @@ FROM juniper/pyez:latest -ARG ver_ansible=2.7.9 -ARG ver_jsnapy=1.3.2 -ARG ver_ansible-stdlib=2.1.0 - LABEL net.juniper.image.maintainer="Stephen Steiner " -LABEL net.juniper.ansible.version=$ver_ansible -LABEL net.juniper.jsnapy.version=$ver_jsnapy -LABEL net.juniper.ansible.module.version=$ver_ansible-stdlib + +WORKDIR /tmp RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make python-dev py-pip &&\ - pip install jxmlease ansible==$ver_ansible jsnapy==$ver_jsnapy &&\ apk del -r --purge gcc make g++ &&\ rm -rf /source/* &&\ rm -rf /var/cache/apk/* &&\ rm -rf /tmp/* +COPY requirements.txt . +RUN pip install -r requirements.txt + WORKDIR /root/.ansible/roles/Juniper.junos COPY action_plugins action_plugins COPY callback_plugins callback_plugins diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..d466fa2f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +ansible==2.7.9 +jsnapy==1.3.2 +jxmlease +docker +junos-netconify From 97b592ff4c48ffb01b5b6e2c360d4e5233db10ae Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 3 Apr 2019 09:50:06 +0200 Subject: [PATCH 245/426] change role install dir Signed-off-by: Stephen Steiner --- Dockerfile | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a69eeb92..86ed50d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,16 +4,17 @@ LABEL net.juniper.image.maintainer="Stephen Steiner " WORKDIR /tmp -RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make python-dev py-pip &&\ - apk del -r --purge gcc make g++ &&\ - rm -rf /source/* &&\ - rm -rf /var/cache/apk/* &&\ - rm -rf /tmp/* +RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make python-dev py-pip COPY requirements.txt . RUN pip install -r requirements.txt -WORKDIR /root/.ansible/roles/Juniper.junos +RUN apk del -r --purge gcc make g++ &&\ + rm -rf /source/* &&\ + rm -rf /var/cache/apk/* &&\ + rm -rf /tmp/* + +WORKDIR /etc/ansible/roles/Juniper.junos COPY action_plugins action_plugins COPY callback_plugins callback_plugins COPY library library @@ -22,4 +23,4 @@ COPY module_utils module_utils WORKDIR /playbooks -VOLUME /playbooks \ No newline at end of file +VOLUME /playbooks From c61a018e9dc6aade2f1741046ba2c470e597b5af Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 3 Apr 2019 10:47:55 +0200 Subject: [PATCH 246/426] fixed some docstring typos Signed-off-by: Stephen Steiner --- module_utils/juniper_junos_common.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 07cee449..47924489 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -412,7 +412,6 @@ class ModuleDocFragment(object): - INFO - DEBUG - ''' # _SUB_CONNECT_DOCUMENTATION is just _CONNECT_DOCUMENTATION with each @@ -1019,7 +1018,7 @@ def check_pyez(self, minimum=None, - PyEZ not installed (unable to import). - PyEZ version < minimum. - check_device and PyEZ Device object can't be imported - - check_exception and PyEZ excepetions can't be imported + - check_exception and PyEZ exceptions can't be imported """ self._check_library('junos-eznc', HAS_PYEZ_VERSION, PYEZ_INSTALLATION_URL, minimum=minimum, @@ -1404,7 +1403,7 @@ def get_configuration(self, database='committed', format='text', options={}, filter=None): """Return the device configuration in the specified format. - Return the datbase device configuration datbase in the format format. + Return the database device configuration database in the format format. Pass the options specified in the options dict and the filter specified in the filter argument. From 68c5f68eb0bbb5605de681350446aa6ac5b1c55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Sch=C3=A4fer=20=7C=20indevis=20GmbH?= Date: Thu, 18 Apr 2019 18:06:16 +0200 Subject: [PATCH 247/426] changed handling for --diff --- library/juniper_junos_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index c7f13868..ac7e322d 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -1054,12 +1054,12 @@ def main(): junos_module.logger.debug("Step 4 - Determine differences between the " "candidate and committed configuration " "databases.") - if diff is True: + if diff is True or junos_module._diff: diff = junos_module.diff_configuration() if diff is not None: results['changed'] = True - if return_output is True: - results['diff'] = diff + if return_output is True or junos_module._diff: + results['diff'] = {'prepared': diff} results['diff_lines'] = diff.splitlines() # Save the diff output junos_module.save_text_output('diff', 'diff', diff) From c8672c85f8b70922fc2ee80bbd526c8b8e5ba874 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Tue, 30 Apr 2019 18:09:39 -0700 Subject: [PATCH 248/426] Add support for ignore_warning in open_configuration --- library/juniper_junos_config.py | 2 +- module_utils/juniper_junos_common.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index c7f13868..1d31aa16 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -1001,7 +1001,7 @@ def main(): junos_module.logger.debug("Step 1 - Open a candidate configuration " "database.") - junos_module.open_configuration(mode=config_mode) + junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning) results['msg'] += 'opened' junos_module.logger.debug("Step 2 - Load configuration data into the " diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 47924489..425cf596 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -1344,7 +1344,7 @@ def add_sw(self): """ self.sw = jnpr.junos.utils.sw.SW(self.dev) - def open_configuration(self, mode): + def open_configuration(self, mode, ignore_warning=None): """Open candidate configuration database in exclusive or private mode. Failures: @@ -1352,6 +1352,18 @@ def open_configuration(self, mode): - RpcError: When there's a RPC problem including an already locked config or an already opened private config. """ + + ignore_warn=['uncommitted changes will be discarded on exit'] + # if ignore_warning is a bool, pass the bool + # if ignore_warning is a string add to the list + # if ignore_warning is a list, merge them + if ignore_warning != None and isinstance(ignore_warning, bool): + ignore_warn = ignore_warning + elif ignore_warning != None and isinstance(ignore_warning, str): + ignore_warn.append(ignore_warning) + elif ignore_warning != None and isinstance(ignore_warning, list): + ignore_warn = ignore_warn + ignore_warning + # Already have an open configuration? if self.config is None: if mode not in CONFIG_MODE_CHOICES: @@ -1365,8 +1377,7 @@ def open_configuration(self, mode): elif config.mode == 'private': self.dev.rpc.open_configuration( private=True, - ignore_warning='uncommitted changes will be ' - 'discarded on exit') + ignore_warning=ignore_warn) except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: self.fail_json(msg='Unable to open the configuration in %s ' From d7290d26e48ef7cf8f922158d208319b68464760 Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Fri, 3 May 2019 15:57:30 +0530 Subject: [PATCH 249/426] open config model support in junipe_junos_config --- library/juniper_junos_config.py | 20 +++++++++++++++++++- module_utils/juniper_junos_common.py | 22 +++++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index c7f13868..44cbae0c 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -748,6 +748,7 @@ def main(): juniper_junos_common.CONFIG_DATABASE_CHOICES config_action_choices = [None] + juniper_junos_common.CONFIG_ACTION_CHOICES config_mode_choices = juniper_junos_common.CONFIG_MODE_CHOICES + config_model_choices = juniper_junos_common.CONFIG_MODEL_CHOICES # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( @@ -790,6 +791,16 @@ def main(): type='str', required=False, default=None), + model=dict(required=False, + choices=config_model_choices, + type='str', + default=None), + remove_ns=dict(required=False, + type='bool', + default=None), + namespace=dict(required=False, + type='str', + default=None), check=dict(required=False, type='bool', aliases=['check_commit', 'commit_check'], @@ -885,6 +896,10 @@ def main(): confirmed = junos_module.params.get('confirmed') comment = junos_module.params.get('comment') check_commit_wait = junos_module.params.get('check_commit_wait') + model = junos_module.params.get('model') + remove_ns = junos_module.params.get('remove_ns') + namespace = junos_module.params.get('namespace') + # If retrieve is set and load and rollback are not set, then # check, diff, and commit default to False. @@ -1076,7 +1091,10 @@ def main(): database=retrieve, format=format, options=options, - filter=filter) + filter=filter, + model=model, + namespace=namespace, + remove_ns=remove_ns) if return_output is True: if config is not None: results['config'] = config diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 47924489..d32d54ce 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -412,6 +412,7 @@ class ModuleDocFragment(object): - INFO - DEBUG + ''' # _SUB_CONNECT_DOCUMENTATION is just _CONNECT_DOCUMENTATION with each @@ -573,6 +574,8 @@ class ModuleDocFragment(object): 'replace', 'override', 'overwrite'] # Supported configuration modes CONFIG_MODE_CHOICES = ['exclusive', 'private'] +# Supported configuration models +CONFIG_MODEL_CHOICES = ['openconfig', 'custom', 'ietf', 'True'] def convert_to_bool_func(arg): @@ -1400,7 +1403,8 @@ def close_configuration(self): (str(ex))) def get_configuration(self, database='committed', format='text', - options={}, filter=None): + options={}, filter=None, model=None, + namespace=None, remove_ns=True): """Return the device configuration in the specified format. Return the database device configuration database in the format format. @@ -1437,6 +1441,11 @@ def get_configuration(self, database='committed', format='text', 'of recognized configuration formats: %s.' % (format, str(CONFIG_FORMAT_CHOICES))) + if model not in CONFIG_MODEL_CHOICES: + self.fail_json(msg='The configuration format % is not in the list ' + 'of recognized configuration formats: %s.' % + (model, str(CONFIG_MODEL_CHOICES))) + options.update({'database': database, 'format': format}) @@ -1448,7 +1457,10 @@ def get_configuration(self, database='committed', format='text', config = None try: config = self.dev.rpc.get_config(options=options, - filter_xml=filter) + filter_xml=filter, + model=model, + remove_ns=remove_ns, + namespace=namespace) self.logger.debug("Configuration retrieved.") except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: @@ -1460,7 +1472,7 @@ def get_configuration(self, database='committed', format='text', if not isinstance(config, self.etree._Element): self.fail_json(msg='Unexpected configuration type returned. ' 'Configuration is: %s' % (str(config))) - if config.tag != 'configuration-text': + if model is None and config.tag != 'configuration-text': self.fail_json(msg='Unexpected XML tag returned. ' 'Configuration is: %s' % (etree.tostring(config, pretty_print=True))) @@ -1469,7 +1481,7 @@ def get_configuration(self, database='committed', format='text', if not isinstance(config, self.etree._Element): self.fail_json(msg='Unexpected configuration type returned. ' 'Configuration is: %s' % (str(config))) - if config.tag != 'configuration-set': + if model is None and config.tag != 'configuration-set': self.fail_json(msg='Unexpected XML tag returned. ' 'Configuration is: %s' % (etree.tostring(config, pretty_print=True))) @@ -1478,7 +1490,7 @@ def get_configuration(self, database='committed', format='text', if not isinstance(config, self.etree._Element): self.fail_json(msg='Unexpected configuration type returned. ' 'Configuration is: %s' % (str(config))) - if config.tag != 'configuration': + if model is None and config.tag != 'configuration': self.fail_json(msg='Unexpected XML tag returned. ' 'Configuration is: %s' % (etree.tostring(config, pretty_print=True))) From 65b180c1ecb3ad288017aecd1dc7dd370dcdc560 Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Fri, 3 May 2019 16:00:33 +0530 Subject: [PATCH 250/426] open config support in juniper_junos_config --- module_utils/juniper_junos_common.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index d32d54ce..34ba0672 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -1441,11 +1441,6 @@ def get_configuration(self, database='committed', format='text', 'of recognized configuration formats: %s.' % (format, str(CONFIG_FORMAT_CHOICES))) - if model not in CONFIG_MODEL_CHOICES: - self.fail_json(msg='The configuration format % is not in the list ' - 'of recognized configuration formats: %s.' % - (model, str(CONFIG_MODEL_CHOICES))) - options.update({'database': database, 'format': format}) From 9cc4139a1f6d1913d69087d92742ec11695a60c0 Mon Sep 17 00:00:00 2001 From: Bryan Hundven Date: Mon, 20 May 2019 15:33:47 -0700 Subject: [PATCH 251/426] [meta] Remove trailing whitespace Signed-off-by: Bryan Hundven --- meta/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/main.yml b/meta/main.yml index 3b4daf4b..9327a515 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,7 +1,7 @@ --- galaxy_info: author: Juniper #Stacy W. Smith @stacywsmith - description: Network build automation of Junos devices. + description: Network build automation of Junos devices. company: Juniper Networks, Inc. license: Apache 2.0 min_ansible_version: 2.1 From 38b459b259e68172189cc151451b02eaf882bdff Mon Sep 17 00:00:00 2001 From: Bryan Hundven Date: Mon, 20 May 2019 15:34:13 -0700 Subject: [PATCH 252/426] [meta] Remove categories:, as it is replaced by galaxy_tags: Signed-off-by: Bryan Hundven --- meta/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/meta/main.yml b/meta/main.yml index 9327a515..3bb69c40 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -9,8 +9,6 @@ galaxy_info: - name: junos versions: - all - categories: - - networking galaxy_tags: - networking - junos From 753e23e7bb3f8252e44d00b2a69fbbffed1ed91d Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 22 May 2019 15:30:44 +0530 Subject: [PATCH 253/426] Fixed issue #434 --- library/juniper_junos_rpc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py index a8f738b1..8f848f35 100644 --- a/library/juniper_junos_rpc.py +++ b/library/juniper_junos_rpc.py @@ -510,6 +510,8 @@ def main(): attr = {} if kwarg is None: kwarg = {} + if format is not None: + attr['format'] = format junos_module.logger.debug('Executing "get-config" RPC. ' 'filter_xml=%s, options=%s, ' 'kwargs=%s', From 5788dd9817985449b697571ebc489591b0e5c9f9 Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Thu, 13 Jun 2019 10:31:44 +0530 Subject: [PATCH 254/426] added pkg_set params --- library/juniper_junos_software.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 4a6d80a2..27b6bcf7 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -263,6 +263,12 @@ required: false default: C(/var/tmp/) + filename portion of I(local_package) type: path + pkg_set: + description: + - install software on the members in a mixed Virtual Chassis + required: false + default: false + type: list validate: description: - Whether or not to have the target Junos device should validate the @@ -449,6 +455,9 @@ def main(): # Default is '/var/tmp/' + filename from the # local_package option, if set. default=None), + pkg_set=dict(required=False, + type='list', + default=None), version=dict(required=False, aliases=['target_version', 'new_version', 'desired_version'], @@ -524,6 +533,7 @@ def main(): # Straight from params local_package = junos_module.params.pop('local_package') remote_package = junos_module.params.pop('remote_package') + pkg_set = junos_module.params.pop('pkg_set') target_version = junos_module.params.pop('version') no_copy = junos_module.params.pop('no_copy') reboot = junos_module.params.pop('reboot') @@ -568,6 +578,9 @@ def main(): junos_module.fail_json(msg='There is no filename component to ' 'the local_package (%s).' % (local_package)) + if pkg_set is None: + junos_module.fail_json(msg='install() requires either ' + 'the package or pkg_set argument.') else: # Local package was not specified, so we must assume no_copy. no_copy = True @@ -593,7 +606,7 @@ def main(): if no_copy is True: cleanfs = False - if target_version is None: + if target_version is None and pkg_set is None: target_version = parse_version_from_filename(remote_filename) junos_module.logger.debug("New target version is: %s.", target_version) @@ -638,6 +651,8 @@ def main(): install_params['package'] = url elif local_package is not None: install_params['package'] = local_package + elif pkg_set is not None: + install_params['pkg_set'] = pkg_set else: install_params['package'] = remote_filename if remote_dir is not None: From 32ff3768b85abc33c586af0af0ef3a5b0ebda5dd Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Thu, 13 Jun 2019 11:07:31 +0530 Subject: [PATCH 255/426] pkg_set --- library/juniper_junos_software.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index 27b6bcf7..cd2218bc 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -526,7 +526,7 @@ def main(): # Mutually exclusive options. mutually_exclusive=[['issu', 'nssu']], # One of local_package and remote_package is required. - required_one_of=[['local_package', 'remote_package']], + required_one_of=[['local_package', 'remote_package', 'pkg_set']], supports_check_mode=True ) From 763a18ade1a131750ac90e89e1924a62e6076851 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Wed, 19 Jun 2019 10:16:13 -0400 Subject: [PATCH 256/426] adding clarification for ssh_private_key_usage Signed-off-by: Stephen Steiner --- README.md | 15 ++++++++++++--- module_utils/juniper_junos_common.py | 7 +++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2175e669..b3e92250 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ should be added to the Ansible configuration file in order to allow the jsnapy c You must have the [DEPENDENCIES](#dependencies) installed on your system. +### NOTICES + +#### Ubuntu 14.04 + If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy install Juniper.junos -c` [WARNING]: - Juniper.junos was NOT installed successfully: Failed to get data @@ -104,6 +108,14 @@ If you're dealing with Ubuntu 14.04 and faced following error during the install ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list. +### MacOS Mojave and newer + +In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keygen` are created using the newer 'OPENSSH' key format, even when specifying `-t rsa` during creation. This directly affects the usage of ssh keys, particularly when using the `ssh_private_key_file`. To create/convert/check keys, follow these steps: + +- Create a new RSA key: `ssh-keygen -m PEM -t rsa -b 4096` +- Check existing keys: `head -n1 ~/.ssh/some_private_key` RSA keys will be `-----BEGIN RSA PRIVATE KEY-----` and OPENSSH keys will be `-----BEGIN OPENSSH PRIVATE KEY-----` +- Convert an OPENSSH key to an RSA key: `ssh-keygen -p -m PEM -f ~/.ssh/some_key` + ### Ansible Galaxy Role To download the latest released version of the junos role to the Ansible @@ -246,9 +258,6 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c [jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) for any queries. *Contributors:* -[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), -[David Gethings](https://github.com/dgjnpr) - [Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [Stephen Steiner](https://github.com/ntwrkguru) *Former Contributors:* diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 47924489..a2a2b7d1 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -277,6 +277,13 @@ class ModuleDocFragment(object): is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used. + - This must be in the RSA PEM format, and not the newer OPENSSH + format. To check if the private key is in the correct format, issue + the command: `head -n1 ~/.ssh/some_private_key` and ensure that + it's RSA and not OPENSSH. To create a key in the RSA PEM format, + issue the command: `ssh-keygen -m PEM -t rsa -b 4096`. To convert + an OPENSSH key to an RSA key, issue the command: `ssh-keygen -p -m + PEM -f ~/.ssh/some_private_key` required: false default: The first defined value from the following list 1) The C(ANSIBLE_NET_SSH_KEYFILE) environment variable. From d42bea5f164ecce737c7074239f7a8df9fba07a2 Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Fri, 21 Jun 2019 09:28:39 +0530 Subject: [PATCH 257/426] fix pkg_set for mixed virtual chassis --- library/juniper_junos_software.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index cd2218bc..da2c6a5e 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -265,7 +265,8 @@ type: path pkg_set: description: - - install software on the members in a mixed Virtual Chassis + - install software on the members in a mixed Virtual Chassis. Currently + we are not doing target package check this option is provided. required: false default: false type: list @@ -570,6 +571,7 @@ def main(): (remote_package)) if url is None: + local_filename = None if local_package is not None: # Expand out the path. local_package = os.path.abspath(local_package) @@ -578,13 +580,9 @@ def main(): junos_module.fail_json(msg='There is no filename component to ' 'the local_package (%s).' % (local_package)) - if pkg_set is None: - junos_module.fail_json(msg='install() requires either ' - 'the package or pkg_set argument.') - else: - # Local package was not specified, so we must assume no_copy. + elif remote_package is not None: + # remote package was, so we must assume no_copy. no_copy = True - local_filename = None if no_copy is False and not os.path.isfile(local_package): junos_module.fail_json(msg='The local_package (%s) is not a valid ' @@ -679,7 +677,8 @@ def main(): results['msg'] = 'Package %s successfully installed.' % \ (install_params['package']) junos_module.logger.debug('Package %s successfully installed.', - install_params['package']) + install_params.get('package') or + install_params.get('pkg_set')) except (junos_module.pyez_exception.ConnectError, junos_module.pyez_exception.RpcError) as ex: results['msg'] = 'Installation failed. Error: %s' % str(ex) From 089f6da63f7985ff0ec0ca7038b2f428a3daabfe Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Mon, 24 Jun 2019 16:07:00 +0530 Subject: [PATCH 258/426] fix for reboot and mixed vc --- library/juniper_junos_software.py | 38 +++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index da2c6a5e..be96586f 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -441,6 +441,8 @@ def myprogress(_, report): junos_module.logger.info(report) return myprogress +# global variable to be used in progress function param to sw.install +progress_report = '' def main(): CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] @@ -666,19 +668,24 @@ def main(): install_params[key] = value if kwargs is not None: install_params.update(kwargs) + def progress(dev, report): + global progress_report + progress_report = progress_report + report + install_params['progress'] = progress try: junos_module.logger.debug("Install parameters are: %s", - str(install_params)) + str(install_params)) junos_module.add_sw() ok = junos_module.sw.install(**install_params) if ok is not True: - results['msg'] = 'Unable to install the software' + results['msg'] = 'Unable to install the software, ' \ + 'potential reason: %s' % progress_report junos_module.fail_json(**results) - results['msg'] = 'Package %s successfully installed.' % \ - (install_params['package']) - junos_module.logger.debug('Package %s successfully installed.', - install_params.get('package') or - install_params.get('pkg_set')) + msg = 'Package %s successfully installed.' % ( + install_params.get('package') or + install_params.get('pkg_set')) + results['msg'] = msg + junos_module.logger.debug(msg) except (junos_module.pyez_exception.ConnectError, junos_module.pyez_exception.RpcError) as ex: results['msg'] = 'Installation failed. Error: %s' % str(ex) @@ -719,7 +726,18 @@ def main(): if junos_module.dev.facts['_is_linux']: got = resp.text else: - got = resp.findtext(xpath) + # there are cases where rpc-reply will have multiple + # child element, hence lets work on parent. + # for ex: + # Rebooting fpc1 + # + # + # Shutdown at Mon Jun 24 10:16:35 2019. + # [pid 1949] + # + # + obj = resp.getparent() + got = obj.findtext(xpath) if got is not None: results['msg'] += ' Reboot successfully initiated.' break @@ -728,7 +746,9 @@ def main(): # It only gets executed if the loop finished without # hitting the break. results['msg'] += ' Did not find expected response ' \ - 'from reboot RPC.' + 'from reboot RPC. RPC response is ' \ + '%s' % \ + junos_module.etree.tostring(resp) junos_module.fail_json(**results) else: results['msg'] += ' Reboot successfully initiated.' From 813653b363ce7f6f80082dd3d55bddd2bada4ed2 Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Wed, 26 Jun 2019 16:00:24 +0530 Subject: [PATCH 259/426] added check for files existance --- library/juniper_junos_software.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index be96586f..a94d8a26 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -565,12 +565,12 @@ def main(): if url is not None and local_package is not None: junos_module.fail_json(msg='There remote_package (%s) is a URL. ' 'The local_package option is not allowed.' % - (remote_package)) + remote_package) if url is not None and no_copy is True: junos_module.fail_json(msg='There remote_package (%s) is a URL. ' 'The no_copy option is not allowed.' % - (remote_package)) + remote_package) if url is None: local_filename = None @@ -581,15 +581,23 @@ def main(): if local_filename == '': junos_module.fail_json(msg='There is no filename component to ' 'the local_package (%s).' % - (local_package)) + local_package) elif remote_package is not None: # remote package was, so we must assume no_copy. no_copy = True - if no_copy is False and not os.path.isfile(local_package): - junos_module.fail_json(msg='The local_package (%s) is not a valid ' - 'file on the local Ansible control ' - 'machine.' % (local_package)) + if no_copy is False: + if local_package is not None and not os.path.isfile(local_package): + junos_module.fail_json(msg='The local_package (%s) is not a ' + 'valid file on the local Ansible ' + 'control machine.' % local_package) + elif pkg_set is not None: + pkg_set = [os.path.abspath(item) for item in pkg_set] + for pkg_set_item in pkg_set: + if not os.path.isfile(pkg_set_item): + junos_module.fail_json( + msg='The pkg (%s) is not a valid file on the local' + ' Ansible control machine.' % pkg_set_item) if remote_filename == '': # Use the same name as local_filename From c36f9c67874361081a047ebd8289fbb9f961d3c2 Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Wed, 26 Jun 2019 22:22:02 +0530 Subject: [PATCH 260/426] vmhost reboot support --- library/juniper_junos_software.py | 39 ++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py index a94d8a26..ccfa5a72 100644 --- a/library/juniper_junos_software.py +++ b/library/juniper_junos_software.py @@ -441,8 +441,6 @@ def myprogress(_, report): junos_module.logger.info(report) return myprogress -# global variable to be used in progress function param to sw.install -progress_report = '' def main(): CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] @@ -636,15 +634,23 @@ def main(): current_version, current_re, target_version) results['changed'] = True + else: + results['msg'] += "Current version on %s: %s same as Targeted " \ + "version: %s.\n" % (current_version, current_re, + target_version) else: current_version = junos_module.dev.facts['version'] + re_name = junos_module.dev.re_name if target_version != current_version: - re_name = junos_module.dev.re_name junos_module.logger.debug("Current version on %s: %s. " "Target version: %s.", current_version, re_name, target_version) results['changed'] = True + else: + results['msg'] += "Current version on %s: %s same as Targeted " \ + "version: %s.\n" % (current_version, re_name, + target_version) else: # A non-Junos install. Always attempt to install. results['changed'] = True @@ -676,18 +682,13 @@ def main(): install_params[key] = value if kwargs is not None: install_params.update(kwargs) - def progress(dev, report): - global progress_report - progress_report = progress_report + report - install_params['progress'] = progress try: junos_module.logger.debug("Install parameters are: %s", str(install_params)) junos_module.add_sw() ok = junos_module.sw.install(**install_params) if ok is not True: - results['msg'] = 'Unable to install the software, ' \ - 'potential reason: %s' % progress_report + results['msg'] = 'Unable to install the software' junos_module.fail_json(**results) msg = 'Package %s successfully installed.' % ( install_params.get('package') or @@ -709,11 +710,16 @@ def progress(dev, report): "to 5 seconds.") junos_module.dev.timeout = 5 junos_module.logger.debug('Initiating reboot.') + xpath_list = ['.//request-reboot-status'] if junos_module.dev.facts['_is_linux']: rpc = junos_module.etree.Element('request-shutdown-reboot') + elif install_params.get('vmhost'): + rpc = junos_module.etree.Element('request-vmhost-reboot') + # RPC reply can contain multiple output tags + xpath_list.append('output') else: rpc = junos_module.etree.Element('request-reboot') - xpath_list = ['.//request-reboot-status'] + if all_re is True: if (junos_module.sw._multi_RE is True and junos_module.sw._multi_VC is False): @@ -722,7 +728,7 @@ def progress(dev, report): # At least on some platforms stopping/rebooting both # REs produces messages and # messages. - xpath_list.append('..//output') + xpath_list.append('output') elif junos_module.sw._mixed_VC is True: junos_module.etree.SubElement(rpc, 'all-members') resp = junos_module.dev.rpc(rpc, @@ -730,6 +736,7 @@ def progress(dev, report): normalize=True) junos_module.logger.debug("Reboot RPC executed cleanly.") if len(xpath_list) > 0: + obj = resp.getparent() for xpath in xpath_list: if junos_module.dev.facts['_is_linux']: got = resp.text @@ -744,10 +751,14 @@ def progress(dev, report): # [pid 1949] # # - obj = resp.getparent() - got = obj.findtext(xpath) + if xpath == 'output': + got = '\n'.join([i.text for i in obj.findall('output') + if i.text is not None]) + else: + got = obj.findtext(xpath) if got is not None: - results['msg'] += ' Reboot successfully initiated.' + results['msg'] += ' Reboot successfully initiated. ' \ + 'Reboot message: %s' % got break else: # This is the else clause of the for loop. From b6765a2eff22dea7247fbfc0ab93630801289862 Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Tue, 20 Aug 2019 12:48:27 +0530 Subject: [PATCH 261/426] remove requirements.txt file --- requirements.txt | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d466fa2f..00000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -ansible==2.7.9 -jsnapy==1.3.2 -jxmlease -docker -junos-netconify From e3928ae3ab8b9b9bd22b0a917b16da85e6f9c92e Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Tue, 20 Aug 2019 13:01:45 +0530 Subject: [PATCH 262/426] removed version of ansible --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..b5a8e9f9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +ansible +jsnapy==1.3.2 +jxmlease +docker +junos-netconify From 2c672a2599d377bc938f509064c8f6682f382812 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 4 Sep 2019 12:12:01 +0530 Subject: [PATCH 263/426] Diabled TravisCI, as Ravello subscription is revoked --- .travis.yml => tests/.travis.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .travis.yml => tests/.travis.yml (100%) diff --git a/.travis.yml b/tests/.travis.yml similarity index 100% rename from .travis.yml rename to tests/.travis.yml From 00963d4db600e55f98b5574f24d41da9397f845d Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 4 Sep 2019 12:31:22 +0530 Subject: [PATCH 264/426] Deleted unused import --- module_utils/juniper_junos_common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index a2a2b7d1..e053b01f 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -32,7 +32,6 @@ # from __future__ import absolute_import, division, print_function -from six import iteritems # Ansible imports from ansible.module_utils.basic import AnsibleModule From d08201b60ad082bc6c02034e2361556cee7586f4 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 4 Sep 2019 17:52:54 +0530 Subject: [PATCH 265/426] Fixed python3 incompatibilty using ansible module utility --- library/juniper_junos_facts.py | 4 +++- module_utils/juniper_junos_common.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py index ec1e820f..3c5fc811 100644 --- a/library/juniper_junos_facts.py +++ b/library/juniper_junos_facts.py @@ -178,6 +178,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule from ansible.module_utils import juniper_junos_common +from ansible.module_utils._text import to_bytes def get_facts_dict(junos_module): @@ -241,6 +242,7 @@ def save_facts(junos_module, facts): file_path = os.path.normpath(os.path.join(save_dir, file_name)) junos_module.logger.debug("Saving facts to: %s.", file_path) try: + # TODO: Verify does thsi work with Python3 with open(file_path, 'w') as fact_file: json.dump(facts, fact_file) junos_module.logger.debug("Facts saved to: %s.", file_path) @@ -272,7 +274,7 @@ def save_inventory(junos_module, inventory): junos_module.logger.debug("Saving inventory to: %s.", file_path) try: with open(file_path, 'wb') as fact_file: - fact_file.write(inventory.encode(encoding='utf-8')) + fact_file.write(to_bytes(inventory, encoding='utf-8')) junos_module.logger.debug("Inventory saved to: %s.", file_path) except IOError: junos_module.fail_json(msg="Unable to save inventory. Failed to " diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index e053b01f..ebbf0143 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -37,6 +37,7 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE from ansible.plugins.action.normal import ActionModule as ActionNormal +from ansible.module_utils._text import to_bytes # Standard library imports from argparse import ArgumentParser @@ -1877,8 +1878,10 @@ def save_text_output(self, name, format, text): file_path = os.path.normpath(os.path.join(dest_dir, file_name)) if file_path is not None: try: + # Use ansible utility to convert objects to bytes + # to achieve Python2/3 compatibility with open(file_path, mode) as save_file: - save_file.write(text.encode(encoding='utf-8')) + save_file.write(to_bytes(text, encoding='utf-8')) self.logger.debug("Output saved to: %s.", file_path) except IOError: self.fail_json(msg="Unable to save output. Failed to " From 2aef23849a4ee476903505c1acc10ea16d7ad49c Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 4 Sep 2019 17:53:33 +0530 Subject: [PATCH 266/426] Fix preference order for host: ansible_host>inventory_hostname --- module_utils/juniper_junos_common.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index ebbf0143..bccbd877 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -514,9 +514,11 @@ class ModuleDocFragment(object): ['baud', 'console'], ['attempts','console']] # Keys are connection options. Values are a list of task_vars to use as the -# default value. +# default value. Order of values specified against each key represents the +# preference order of options in the key. Has to be maintained consistent with +# ansible core modules connection_spec_fallbacks = { - 'host': ['inventory_hostname', 'ansible_host'], + 'host': ['ansible_host', 'inventory_hostname'], 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], 'passwd': ['ansible_ssh_pass', 'ansible_pass'], 'port': ['ansible_ssh_port', 'ansible_port'], From ebd88b39055fc2f051be4a803b95e8355e0d6ed1 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Fri, 6 Sep 2019 19:18:10 +0530 Subject: [PATCH 267/426] Prepare to release 2.2.0 --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index 88d19ddd..56cc5973 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.1.1.dev0" -DATE = "2018-June-1" +VERSION = "2.2.0" +DATE = "2019-Sept-6" From 69b0eff9a36b409db1f4f47f75a9d1e4ff911526 Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Fri, 6 Sep 2019 20:55:43 +0530 Subject: [PATCH 268/426] updated docs --- module_utils/juniper_junos_common.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index b3c1a738..8a1898be 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -279,10 +279,10 @@ class ModuleDocFragment(object): operating-system-specific default is used. - This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue - the command: `head -n1 ~/.ssh/some_private_key` and ensure that + the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, - issue the command: `ssh-keygen -m PEM -t rsa -b 4096`. To convert - an OPENSSH key to an RSA key, issue the command: `ssh-keygen -p -m + issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert + an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key` required: false default: The first defined value from the following list @@ -332,7 +332,7 @@ class ModuleDocFragment(object): type: str aliases: - username - cs_user: + cs_user: description: - The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console @@ -341,7 +341,7 @@ class ModuleDocFragment(object): type: str aliases: - console_username - cs_passwd: + cs_passwd: description: - The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console From 8d8809f7cf632b4cf47d9c25ac6d81b3722eb1c7 Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Fri, 6 Sep 2019 21:14:53 +0530 Subject: [PATCH 269/426] Version Upgrade --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index 56cc5973..9a605f25 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.2.0" -DATE = "2019-Sept-6" +VERSION = "2.2.1" +DATE = "2019-Sept-06" From 6359ece35af5d9b6803438b13b1f4481ac429bf3 Mon Sep 17 00:00:00 2001 From: Dinesh Babu Date: Fri, 6 Sep 2019 21:24:41 +0530 Subject: [PATCH 270/426] prepare to release 2.2.2 dev version --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 9a605f25..84734d57 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.2.1" +VERSION = "2.2.2.dev0" DATE = "2019-Sept-06" From 913c9b320d3acc0ca7e8cbe9f4c330a599ddaae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20I=C5=BCykowski?= Date: Mon, 23 Sep 2019 11:05:43 +0200 Subject: [PATCH 271/426] juniper_junos_config: update doc Change #427 modified behavior of juniper_junos_config (diff is not a string anymore - it's a dict now). Behavior is now inconsistent with the documentation. --- library/juniper_junos_config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py index ec94a46e..10101d42 100644 --- a/library/juniper_junos_config.py +++ b/library/juniper_junos_config.py @@ -698,10 +698,12 @@ diff: description: - The configuration differences between the previous and new - configurations. The value is a single multi-line string in "diff" format. + configurations. The value is a dict that contains a single key named + "prepared". Value associated with that key is a single multi-line string + in "diff" format. returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and I(return_output) is C(true). - type: str + type: dict diff_lines: description: - The configuration differences between the previous and new From 6b0c39bd7fd1d92e82082facc62d636db173a05b Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Nov 2019 11:12:33 +0530 Subject: [PATCH 272/426] Moved action plugin code to action_plugins/juniper_junos_common_action from module_utils/juniper_junos_common --- action_plugins/juniper_junos_common_action.py | 131 ++++++++++++++++-- module_utils/juniper_junos_common.py | 108 +-------------- 2 files changed, 117 insertions(+), 122 deletions(-) diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py index 37e2fbd5..66c43132 100755 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -32,22 +32,123 @@ # from __future__ import absolute_import, division, print_function +from ansible.plugins.action.network import ActionModule as ActionNetworkModule +from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE +import os -# Standard library imports -import os.path -import sys +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} -# Use the custom behavior of JuniperJunosActionModule as our ActionModule. -# The Ansible core engine will call ActionModule.run() -from juniper_junos_common import JuniperJunosActionModule as ActionModule +def convert_to_bool_func(arg): + """Try converting arg to a bool value using Ansible's aliases for bool. + + Args: + arg: The value to convert. + + Returns: + A boolean value if successfully converted, or None if not. + """ + if arg is None or type(arg) == bool: + return arg + if isinstance(arg, basestring): + arg = arg.lower() + if arg in BOOLEANS_TRUE: + return True + elif arg in BOOLEANS_FALSE: + return False + else: + return None + + +class ActionModule(ActionNetworkModule): + """A subclass of ActionNormal used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + Public Methods: + convert_to_bool: Try converting to bool using aliases for bool. + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(task_vars) + + def convert_to_bool(self, arg): + """Try converting arg to a bool value using Ansible's aliases for bool. + + Args: + arg: The value to convert. + + Returns: + A boolean value if successfully converted, or None if not. + """ + return convert_to_bool_func(arg) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 8a1898be..997298dc 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -36,7 +36,7 @@ # Ansible imports from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE -from ansible.plugins.action.normal import ActionModule as ActionNormal + from ansible.module_utils._text import to_bytes # Standard library imports @@ -112,14 +112,6 @@ except ImportError: HAS_YAML_VERSION = None -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str - - # Constants # Minimum PyEZ version required by shared code. MIN_PYEZ_VERSION = "2.2.0" @@ -142,26 +134,6 @@ MIN_YAML_VERSION = "3.08" YAML_INSTALLATION_URL = "http://pyyaml.org/wiki/PyYAMLDocumentation" -def convert_to_bool_func(arg): - """Try converting arg to a bool value using Ansible's aliases for bool. - - Args: - arg: The value to convert. - - Returns: - A boolean value if successfully converted, or None if not. - """ - if arg is None or type(arg) == bool: - return arg - if isinstance(arg, basestring): - arg = arg.lower() - if arg in BOOLEANS_TRUE: - return True - elif arg in BOOLEANS_FALSE: - return False - else: - return None - class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -1940,81 +1912,3 @@ def save_text_output(self, name, format, text): "open the %s file." % (file_path)) -class JuniperJunosActionModule(ActionNormal): - """A subclass of ActionNormal used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - Public Methods: - convert_to_bool: Try converting to bool using aliases for bool. - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(JuniperJunosActionModule, self).run(tmp, task_vars) - - def convert_to_bool(self, arg): - """Try converting arg to a bool value using Ansible's aliases for bool. - - Args: - arg: The value to convert. - - Returns: - A boolean value if successfully converted, or None if not. - """ - return convert_to_bool_func(arg) From dd656f9bd5348fed4a7b7c92fa56f0254be0a094 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Nov 2019 12:05:27 +0530 Subject: [PATCH 273/426] convert_to_bool_func has been replaced with ansible.module_utils.basic.boolean --- action_plugins/juniper_junos_common_action.py | 27 +++---------------- module_utils/juniper_junos_common.py | 26 ++---------------- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py index 66c43132..5eb6ada3 100755 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -33,7 +33,7 @@ from __future__ import absolute_import, division, print_function from ansible.plugins.action.network import ActionModule as ActionNetworkModule -from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE +from ansible.module_utils.basic import boolean as ansible_vars_convert_to_boolean import os try: @@ -52,28 +52,7 @@ 'ansible_private_key_file'] } - -def convert_to_bool_func(arg): - """Try converting arg to a bool value using Ansible's aliases for bool. - - Args: - arg: The value to convert. - - Returns: - A boolean value if successfully converted, or None if not. - """ - if arg is None or type(arg) == bool: - return arg - if isinstance(arg, basestring): - arg = arg.lower() - if arg in BOOLEANS_TRUE: - return True - elif arg in BOOLEANS_FALSE: - return False - else: - return None - - +# Moved the defintion class ActionModule(ActionNetworkModule): """A subclass of ActionNormal used by all juniper_junos_* modules. @@ -151,4 +130,4 @@ def convert_to_bool(self, arg): Returns: A boolean value if successfully converted, or None if not. """ - return convert_to_bool_func(arg) + return ansible_vars_convert_to_boolean(arg) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 997298dc..771f85e8 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -35,8 +35,7 @@ # Ansible imports from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE - +from ansible.module_utils.basic import boolean as ansible_vars_convert_to_boolean from ansible.module_utils._text import to_bytes # Standard library imports @@ -588,27 +587,6 @@ class ModuleDocFragment(object): CONFIG_MODEL_CHOICES = ['openconfig', 'custom', 'ietf', 'True'] -def convert_to_bool_func(arg): - """Try converting arg to a bool value using Ansible's aliases for bool. - - Args: - arg: The value to convert. - - Returns: - A boolean value if successfully converted, or None if not. - """ - if arg is None or type(arg) == bool: - return arg - if isinstance(arg, basestring): - arg = arg.lower() - if arg in BOOLEANS_TRUE: - return True - elif arg in BOOLEANS_FALSE: - return False - else: - return None - - class JuniperJunosModule(AnsibleModule): """A subclass of AnsibleModule used by all juniper_junos_* modules. @@ -1127,7 +1105,7 @@ def convert_to_bool(self, arg): Returns: A boolean value if successfully converted, or None if not. """ - return convert_to_bool_func(arg) + return ansible_vars_convert_to_boolean(arg) def parse_arg_to_list_of_dicts(self, option_name, From 8ddc5067c93964b61099bd8855aa0e67a96ac239 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Nov 2019 12:06:15 +0530 Subject: [PATCH 274/426] Refactored code to remove unused usages, and modified comments --- action_plugins/juniper_junos_common_action.py | 10 +++------- module_utils/juniper_junos_common.py | 15 +-------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py index 5eb6ada3..2daa0502 100755 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -36,12 +36,6 @@ from ansible.module_utils.basic import boolean as ansible_vars_convert_to_boolean import os -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str connection_spec_fallbacks = { 'host': ['ansible_host', 'inventory_hostname'], @@ -54,7 +48,7 @@ # Moved the defintion class ActionModule(ActionNetworkModule): - """A subclass of ActionNormal used by all juniper_junos_* modules. + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. All juniper_junos_* modules share common behavior which is implemented in this class. This includes specific option fallback/default behavior and @@ -64,6 +58,8 @@ class ActionModule(ActionNetworkModule): convert_to_bool: Try converting to bool using aliases for bool. """ def run(self, tmp=None, task_vars=None): + # This variable is no longer used in the run definition + del tmp # The new connection arguments based on fallback/defaults. new_connection_args = dict() diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 771f85e8..3860353c 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -507,6 +507,7 @@ class ModuleDocFragment(object): required=False, default=30), } + # Connection arguments which are mutually exclusive. connection_spec_mutually_exclusive = [['mode', 'console'], ['port', 'console'], @@ -514,18 +515,6 @@ class ModuleDocFragment(object): ['attempts','console'], ['cs_user', 'console'], ['cs_passwd', 'console']] -# Keys are connection options. Values are a list of task_vars to use as the -# default value. Order of values specified against each key represents the -# preference order of options in the key. Has to be maintained consistent with -# ansible core modules -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} # Specify the provider spec with options matching connection_spec. provider_spec = { @@ -1888,5 +1877,3 @@ def save_text_output(self, name, format, text): except IOError: self.fail_json(msg="Unable to save output. Failed to " "open the %s file." % (file_path)) - - From c220fc06cbb6e01fd48f8cf42e4d584157a3edef Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Nov 2019 15:44:36 +0530 Subject: [PATCH 275/426] Minor modifications - Added/Modified comments to explain the changes done - Replaced ansible.plugins.action.network.ActionModule with ansible.plugins.action.normal.ActionModule --- action_plugins/juniper_junos_common_action.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py index 2daa0502..25029e70 100755 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -32,7 +32,7 @@ # from __future__ import absolute_import, division, print_function -from ansible.plugins.action.network import ActionModule as ActionNetworkModule +from ansible.plugins.action.normal import ActionModule as ActionNormal from ansible.module_utils.basic import boolean as ansible_vars_convert_to_boolean import os @@ -46,8 +46,11 @@ 'ansible_private_key_file'] } -# Moved the defintion -class ActionModule(ActionNetworkModule): + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. All juniper_junos_* modules share common behavior which is implemented in @@ -58,8 +61,6 @@ class ActionModule(ActionNetworkModule): convert_to_bool: Try converting to bool using aliases for bool. """ def run(self, tmp=None, task_vars=None): - # This variable is no longer used in the run definition - del tmp # The new connection arguments based on fallback/defaults. new_connection_args = dict() @@ -115,15 +116,16 @@ def run(self, tmp=None, task_vars=None): self._task.args['_module_name'] = self._task.action # Call the parent action module. - return super(ActionModule, self).run(task_vars) + return super(ActionModule, self).run(tmp, task_vars) def convert_to_bool(self, arg): - """Try converting arg to a bool value using Ansible's aliases for bool. + """ + Try converting arg to a bool value using Ansible's aliases for bool. Args: arg: The value to convert. Returns: - A boolean value if successfully converted, or None if not. + A boolean value if successfully converted, or raises TypeError if not able to convert. """ return ansible_vars_convert_to_boolean(arg) From 8f42abc46165c2135f19a1e08d886edeea1d4f0c Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Nov 2019 15:45:06 +0530 Subject: [PATCH 276/426] Dropped backward compatibility for Juniper.junos 1.4.3 --- action_plugins/_junos_cli.py | 1 - action_plugins/_junos_commit.py | 72 ------------------ action_plugins/_junos_get_config.py | 72 ------------------ action_plugins/_junos_get_facts.py | 1 - action_plugins/_junos_get_table.py | 1 - action_plugins/_junos_install_config.py | 97 ------------------------- action_plugins/_junos_install_os.py | 1 - action_plugins/_junos_jsnapy.py | 1 - action_plugins/_junos_ping.py | 1 - action_plugins/_junos_pmtud.py | 1 - action_plugins/_junos_rollback.py | 73 ------------------- action_plugins/_junos_rpc.py | 1 - action_plugins/_junos_shutdown.py | 93 ------------------------ action_plugins/_junos_srx_cluster.py | 1 - action_plugins/_junos_zeroize.py | 75 ------------------- library/_junos_cli.py | 1 - library/_junos_commit.py | 1 - library/_junos_get_config.py | 1 - library/_junos_get_facts.py | 1 - library/_junos_get_table.py | 1 - library/_junos_install_config.py | 1 - library/_junos_install_os.py | 1 - library/_junos_jsnapy.py | 1 - library/_junos_ping.py | 1 - library/_junos_pmtud.py | 1 - library/_junos_rollback.py | 1 - library/_junos_rpc.py | 1 - library/_junos_shutdown.py | 1 - library/_junos_srx_cluster.py | 1 - library/_junos_zeroize.py | 1 - 30 files changed, 506 deletions(-) delete mode 120000 action_plugins/_junos_cli.py delete mode 100755 action_plugins/_junos_commit.py delete mode 100755 action_plugins/_junos_get_config.py delete mode 120000 action_plugins/_junos_get_facts.py delete mode 120000 action_plugins/_junos_get_table.py delete mode 100755 action_plugins/_junos_install_config.py delete mode 120000 action_plugins/_junos_install_os.py delete mode 120000 action_plugins/_junos_jsnapy.py delete mode 120000 action_plugins/_junos_ping.py delete mode 120000 action_plugins/_junos_pmtud.py delete mode 100755 action_plugins/_junos_rollback.py delete mode 120000 action_plugins/_junos_rpc.py delete mode 100755 action_plugins/_junos_shutdown.py delete mode 120000 action_plugins/_junos_srx_cluster.py delete mode 100755 action_plugins/_junos_zeroize.py delete mode 120000 library/_junos_cli.py delete mode 120000 library/_junos_commit.py delete mode 120000 library/_junos_get_config.py delete mode 120000 library/_junos_get_facts.py delete mode 120000 library/_junos_get_table.py delete mode 120000 library/_junos_install_config.py delete mode 120000 library/_junos_install_os.py delete mode 120000 library/_junos_jsnapy.py delete mode 120000 library/_junos_ping.py delete mode 120000 library/_junos_pmtud.py delete mode 120000 library/_junos_rollback.py delete mode 120000 library/_junos_rpc.py delete mode 120000 library/_junos_shutdown.py delete mode 120000 library/_junos_srx_cluster.py delete mode 120000 library/_junos_zeroize.py diff --git a/action_plugins/_junos_cli.py b/action_plugins/_junos_cli.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_cli.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_commit.py b/action_plugins/_junos_commit.py deleted file mode 100755 index 347912de..00000000 --- a/action_plugins/_junos_commit.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -# Standard library imports -import os.path -import sys - -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] - - -# Use the custom behavior of JuniperJunosActionModule as the superclass of -# our ActionModule. -class ActionModule(juniper_junos_common.JuniperJunosActionModule): - """Translates junos_commit args to juniper_junos_config args. - - This class is a subclass of JuniperJunosActionModule. It exists solely - for backwards compatibility. It translates the arguments from the old - junos_commit module into the arguments on the new - juniper_junos_config module. - """ - def run(self, tmp=None, task_vars=None): - check = self._task.args.get('check') - if check is True: - # In the old module, check and commit were mutually exclusive. - # In the new module, you can potentially do both. - # If check is set on the old module, then we need to not commit. - self._task.args['commit'] = False - - # Remaining arguments can be passed through transparently. - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_get_config.py b/action_plugins/_junos_get_config.py deleted file mode 100755 index f874063c..00000000 --- a/action_plugins/_junos_get_config.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -# Standard library imports -import os.path -import sys - -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] - - -# Use the custom behavior of JuniperJunosActionModule as the superclass of -# our ActionModule. -class ActionModule(juniper_junos_common.JuniperJunosActionModule): - """Translates junos_get_config args to juniper_junos_config args. - - This class is a subclass of JuniperJunosActionModule. It exists solely - for backwards compatibility. It translates the arguments from the old - junos_get_config module into the arguments on the new - juniper_junos_config module. - """ - def run(self, tmp=None, task_vars=None): - # No diff, check, or commit - self._task.args['diff'] = False - self._task.args['check'] = False - self._task.args['commit'] = False - # Retrieve candidate - self._task.args['retrieve'] = 'candidate' - - # Remaining arguments can be passed through transparently. - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_get_facts.py b/action_plugins/_junos_get_facts.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_get_facts.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_get_table.py b/action_plugins/_junos_get_table.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_get_table.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_install_config.py b/action_plugins/_junos_install_config.py deleted file mode 100755 index 55fe8ef9..00000000 --- a/action_plugins/_junos_install_config.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -# Standard library imports -import os.path -import sys - -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] - - -# Use the custom behavior of JuniperJunosActionModule as the superclass of -# our ActionModule. -class ActionModule(juniper_junos_common.JuniperJunosActionModule): - """Translates junos_install_config args to juniper_junos_config args. - - This class is a subclass of JuniperJunosActionModule. It exists solely - for backwards compatibility. It translates the arguments from the old - junos_install_config module into the arguments on the new - juniper_junos_config module. - """ - def run(self, tmp=None, task_vars=None): - # Pop the action arguments - update = self._task.args.pop('update', False) - overwrite = self._task.args.pop('overwrite', False) - replace = self._task.args.pop('replace', False) - action = '' - if update is True: - action += 'update' - if overwrite is True: - action += 'overwrite' - if replace is True: - action += 'replace' - if not action: - action = 'merge' - # Set the load argument based on action - self._task.args['load'] = action - - # Always commit changes to mimic the previous behavior - self._task.args['commit_empty_changes'] = True - - # If check_commit is False, then also bypass the commit. - check = True - # Check for the 'check_commit' option which was an optional boolean - # argument for the junos_install_config module. - if 'check_commit' in self._task.args: - check = self._task.args.pop('check_commit') - if check is not None and self.convert_to_bool(check) is False: - # Translate to check_commit = False, commit = False, and - # commit_empty_changes = False - self._task.args['check_commit'] = False - self._task.args['commit'] = False - self._task.args['commit_empty_changes'] = False - - # Remaining arguments can be passed through transparently. - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_install_os.py b/action_plugins/_junos_install_os.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_install_os.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_jsnapy.py b/action_plugins/_junos_jsnapy.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_jsnapy.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_ping.py b/action_plugins/_junos_ping.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_ping.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_pmtud.py b/action_plugins/_junos_pmtud.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_pmtud.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_rollback.py b/action_plugins/_junos_rollback.py deleted file mode 100755 index cd12f48c..00000000 --- a/action_plugins/_junos_rollback.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -# Standard library imports -import os.path -import sys - -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] - - -# Use the custom behavior of JuniperJunosActionModule as the superclass of -# our ActionModule. -class ActionModule(juniper_junos_common.JuniperJunosActionModule): - """Translates junos_rollback args to juniper_junos_config args. - - This class is a subclass of JuniperJunosActionModule. It exists solely - for backwards compatibility. It translates the arguments from the old - junos_rollback module into the arguments on the new - juniper_junos_config module. - """ - def run(self, tmp=None, task_vars=None): - rollback = self._task.args.get('rollback') - if rollback is None: - # rollback is mandatory when called as junos_rollback. - # Mimic this behavior be setting rollback to 'value not specified'. - self._task.args['rollback'] = 'value not specified' - # Always commit changes to mimic the previous behavior - self._task.args['commit_empty_changes'] = True - - # Remaining arguments can be passed through transparently. - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_rpc.py b/action_plugins/_junos_rpc.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_rpc.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_shutdown.py b/action_plugins/_junos_shutdown.py deleted file mode 100755 index 36106396..00000000 --- a/action_plugins/_junos_shutdown.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -# Standard library imports -import os.path -import sys - -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] - - -# Use the custom behavior of JuniperJunosActionModule as the superclass of -# our ActionModule. -class ActionModule(juniper_junos_common.JuniperJunosActionModule): - """Translates junos_shutdown args to juniper_junos_system args. - - This class is a subclass of JuniperJunosActionModule. It exists solely - for backwards compatibility. It translates the arguments from the old - junos_shutdown module into the arguments on the new juniper_junos_system - module. - """ - def run(self, tmp=None, task_vars=None): - # Check for the 'shutdown' option which was mandatory for - # the junos_shutdown module. - if 'shutdown' in self._task.args: - shutdown = self._task.args.pop('shutdown') - # 'shutdown' was the only valid value for the 'shutdown' option. - if shutdown == 'shutdown': - reboot = False - # Check for the 'reboot' option which was an optional boolean - # argument for the junos_shutdown module. - if 'reboot' in self._task.args: - reboot = self._task.args.pop('reboot') - if reboot is not None and self.convert_to_bool(reboot) is True: - # Translate to action="reboot" - self._task.args['action'] = 'reboot' - elif reboot is None or self.convert_to_bool(reboot) is False: - # Translate to action="shutdown" - self._task.args['action'] = 'shutdown' - else: - # This isn't a valid value for action/reboot - # We'll pass it through and the module will complain - # appropriately. - self._task.args['action'] = reboot - else: - # This isn't a valid value for action/shutdown - # We'll pass it through and the module will complain - # appropriately. - self._task.args['action'] = shutdown - - # Remaining arguments can be passed through transparently. - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/action_plugins/_junos_srx_cluster.py b/action_plugins/_junos_srx_cluster.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/_junos_srx_cluster.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/_junos_zeroize.py b/action_plugins/_junos_zeroize.py deleted file mode 100755 index bfcfcea8..00000000 --- a/action_plugins/_junos_zeroize.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -# Standard library imports -import os.path -import sys - -# The module_utils path must be added to sys.path in order to import -# juniper_junos_common. The module_utils path is relative to the path of this -# file. -module_utils_path = os.path.normpath(os.path.dirname(__file__) + - '/../module_utils') -if module_utils_path is not None: - sys.path.insert(0, module_utils_path) - import juniper_junos_common - del sys.path[0] - - -# Use the custom behavior of JuniperJunosActionModule as the superclass of -# our ActionModule. -class ActionModule(juniper_junos_common.JuniperJunosActionModule): - """Translates junos_zeroize args to juniper_junos_system args. - - This class is a subclass of JuniperJunosActionModule. It exists solely - for backwards compatibility. It translates the arguments from the old - junos_zeroize module into the arguments on the new juniper_junos_system - module. - """ - def run(self, tmp=None, task_vars=None): - # Check for the 'zeroize' option which was mandatory for - # the junos_zeroize module. - if 'zeroize' in self._task.args: - # Delete the zeroize option. - zeroize = self._task.args.pop('zeroize') - # Add the action option with the value from the zeroize option. - # This should normally be the value 'zeroize'. If it's not, then - # the juniper_junos_system module will throw an appropriate error. - self._task.args['action'] = zeroize - - # Remaining arguments can be passed through transparently. - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/library/_junos_cli.py b/library/_junos_cli.py deleted file mode 120000 index cc8aaf8f..00000000 --- a/library/_junos_cli.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_command.py \ No newline at end of file diff --git a/library/_junos_commit.py b/library/_junos_commit.py deleted file mode 120000 index 5383605d..00000000 --- a/library/_junos_commit.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_config.py \ No newline at end of file diff --git a/library/_junos_get_config.py b/library/_junos_get_config.py deleted file mode 120000 index 5383605d..00000000 --- a/library/_junos_get_config.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_config.py \ No newline at end of file diff --git a/library/_junos_get_facts.py b/library/_junos_get_facts.py deleted file mode 120000 index cbdd3b96..00000000 --- a/library/_junos_get_facts.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_facts.py \ No newline at end of file diff --git a/library/_junos_get_table.py b/library/_junos_get_table.py deleted file mode 120000 index 32df1a46..00000000 --- a/library/_junos_get_table.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_table.py \ No newline at end of file diff --git a/library/_junos_install_config.py b/library/_junos_install_config.py deleted file mode 120000 index 5383605d..00000000 --- a/library/_junos_install_config.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_config.py \ No newline at end of file diff --git a/library/_junos_install_os.py b/library/_junos_install_os.py deleted file mode 120000 index 0a63f359..00000000 --- a/library/_junos_install_os.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_software.py \ No newline at end of file diff --git a/library/_junos_jsnapy.py b/library/_junos_jsnapy.py deleted file mode 120000 index e14ff8d8..00000000 --- a/library/_junos_jsnapy.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_jsnapy.py \ No newline at end of file diff --git a/library/_junos_ping.py b/library/_junos_ping.py deleted file mode 120000 index 3ca2aa75..00000000 --- a/library/_junos_ping.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_ping.py \ No newline at end of file diff --git a/library/_junos_pmtud.py b/library/_junos_pmtud.py deleted file mode 120000 index b7bdfe9b..00000000 --- a/library/_junos_pmtud.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_pmtud.py \ No newline at end of file diff --git a/library/_junos_rollback.py b/library/_junos_rollback.py deleted file mode 120000 index 5383605d..00000000 --- a/library/_junos_rollback.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_config.py \ No newline at end of file diff --git a/library/_junos_rpc.py b/library/_junos_rpc.py deleted file mode 120000 index 9e5b8b3f..00000000 --- a/library/_junos_rpc.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_rpc.py \ No newline at end of file diff --git a/library/_junos_shutdown.py b/library/_junos_shutdown.py deleted file mode 120000 index 95f32974..00000000 --- a/library/_junos_shutdown.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_system.py \ No newline at end of file diff --git a/library/_junos_srx_cluster.py b/library/_junos_srx_cluster.py deleted file mode 120000 index 511a3de3..00000000 --- a/library/_junos_srx_cluster.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_srx_cluster.py \ No newline at end of file diff --git a/library/_junos_zeroize.py b/library/_junos_zeroize.py deleted file mode 120000 index 95f32974..00000000 --- a/library/_junos_zeroize.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_system.py \ No newline at end of file From f65633db82ad123b8172405d8f95acdd6a5c39c6 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Nov 2019 15:45:24 +0530 Subject: [PATCH 277/426] Updated requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bf81e3de..c9d1b985 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -ansible +ansible >= 2.4 +junos-eznc jsnapy==1.3.2 jxmlease docker From 75ba3ca7be24ad3393fdc7442698f3a03a6c2a5e Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 25 Nov 2019 15:56:44 +0530 Subject: [PATCH 278/426] Added back str compatibility for Python2/Python3 --- module_utils/juniper_junos_common.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 3860353c..200233d1 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -111,6 +111,13 @@ except ImportError: HAS_YAML_VERSION = None +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + # Constants # Minimum PyEZ version required by shared code. MIN_PYEZ_VERSION = "2.2.0" From 7572a89dbd42789f62ce2708e5d339341f959637 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Fri, 29 Nov 2019 15:42:08 +0530 Subject: [PATCH 279/426] Minor modifications to resolve review comments --- action_plugins/juniper_junos_common_action.py | 15 --------------- module_utils/juniper_junos_common.py | 18 +++--------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/action_plugins/juniper_junos_common_action.py b/action_plugins/juniper_junos_common_action.py index 25029e70..a6b8336f 100755 --- a/action_plugins/juniper_junos_common_action.py +++ b/action_plugins/juniper_junos_common_action.py @@ -33,7 +33,6 @@ from __future__ import absolute_import, division, print_function from ansible.plugins.action.normal import ActionModule as ActionNormal -from ansible.module_utils.basic import boolean as ansible_vars_convert_to_boolean import os @@ -57,8 +56,6 @@ class ActionModule(ActionNormal): this class. This includes specific option fallback/default behavior and passing the "hidden" _module_utils_path option to the module. - Public Methods: - convert_to_bool: Try converting to bool using aliases for bool. """ def run(self, tmp=None, task_vars=None): # The new connection arguments based on fallback/defaults. @@ -117,15 +114,3 @@ def run(self, tmp=None, task_vars=None): # Call the parent action module. return super(ActionModule, self).run(tmp, task_vars) - - def convert_to_bool(self, arg): - """ - Try converting arg to a bool value using Ansible's aliases for bool. - - Args: - arg: The value to convert. - - Returns: - A boolean value if successfully converted, or raises TypeError if not able to convert. - """ - return ansible_vars_convert_to_boolean(arg) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 200233d1..b97b6115 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -35,7 +35,7 @@ # Ansible imports from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.basic import boolean as ansible_vars_convert_to_boolean +from ansible.module_utils.basic import boolean from ansible.module_utils._text import to_bytes # Standard library imports @@ -601,7 +601,6 @@ class JuniperJunosModule(AnsibleModule): check_lxml_etree: Verify the lxml Etree library is present and functional. check_yaml: Verify the YAML library is present and functional. - convert_to_bool: Try converting to bool using aliases for bool. parse_arg_to_list_of_dicts: Parses string_val into a list of dicts. parse_ignore_warning_option: Parses the ignore_warning option. parse_rollback_option: Parses the rollback option. @@ -1092,17 +1091,6 @@ def check_yaml(self, minimum=None): self._check_library('yaml', HAS_YAML_VERSION, YAML_INSTALLATION_URL, minimum=minimum) - def convert_to_bool(self, arg): - """Try converting arg to a bool value using Ansible's aliases for bool. - - Args: - arg: The value to convert. - - Returns: - A boolean value if successfully converted, or None if not. - """ - return ansible_vars_convert_to_boolean(arg) - def parse_arg_to_list_of_dicts(self, option_name, string_val, @@ -1185,7 +1173,7 @@ def parse_arg_to_list_of_dicts(self, if allow_bool_values is True: # Try to convert it to a boolean value. Will be None if it # can't be converted. - bool_val = self.convert_to_bool(v) + bool_val = boolean(v) if bool_val is not None: v = bool_val return_item[k] = v @@ -1210,7 +1198,7 @@ def parse_ignore_warning_option(self): if ignore_warn_list is None: return ignore_warn_list if len(ignore_warn_list) == 1: - bool_val = self.convert_to_bool(ignore_warn_list[0]) + bool_val = boolean(ignore_warn_list[0]) if bool_val is not None: return bool_val elif isinstance(ignore_warn_list[0], basestring): From a9e763fc859a0eedf07227f0d633adacfc5a65da Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 9 Dec 2019 10:31:12 +0530 Subject: [PATCH 280/426] Modified tests to use latest module names --- ..._facts.yaml => pb.juniper_junos_facts.yml} | 2 +- ...napy.yaml => pb.juniper_junos_jsnapy.yaml} | 24 +++++++++---------- ...s_ping.yaml => pb.juniper_junos_ping.yaml} | 10 ++++---- ...pmtud.yaml => pb.juniper_junos_pmtud.yaml} | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) rename tests/{pb.junos_get_facts.yaml => pb.juniper_junos_facts.yml} (95%) rename tests/{pb.junos_jsnapy.yaml => pb.juniper_junos_jsnapy.yaml} (94%) rename tests/{pb.junos_ping.yaml => pb.juniper_junos_ping.yaml} (94%) rename tests/{pb.junos_pmtud.yaml => pb.juniper_junos_pmtud.yaml} (95%) diff --git a/tests/pb.junos_get_facts.yaml b/tests/pb.juniper_junos_facts.yml similarity index 95% rename from tests/pb.junos_get_facts.yaml rename to tests/pb.juniper_junos_facts.yml index a94f6d9b..6ce691e8 100644 --- a/tests/pb.junos_get_facts.yaml +++ b/tests/pb.juniper_junos_facts.yml @@ -7,7 +7,7 @@ - Juniper.junos tasks: - name: "TEST 1 - Gather Facts" - junos_get_facts: + juniper_junos_facts: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" diff --git a/tests/pb.junos_jsnapy.yaml b/tests/pb.juniper_junos_jsnapy.yaml similarity index 94% rename from tests/pb.junos_jsnapy.yaml rename to tests/pb.juniper_junos_jsnapy.yaml index 874ccd8a..d913a6f8 100644 --- a/tests/pb.junos_jsnapy.yaml +++ b/tests/pb.juniper_junos_jsnapy.yaml @@ -10,7 +10,7 @@ #### TEST 1 ## ################################################## - name: "TEST 1 - Execute SNAPCHECK with 1 test file / no dir" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -34,7 +34,7 @@ #### TEST 2 ## ################################################## - name: "TEST 2 - Execute SNAPCHECK with 2 test file & dir" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -61,7 +61,7 @@ #### TEST 3 ## ################################################## - name: "TEST 3 - Wrong test file" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -83,7 +83,7 @@ #### TEST 4 ## ################################################## - name: "TEST 4 - SNAP_PRE" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -106,7 +106,7 @@ #### TEST 5 ## ################################################## - name: "TEST 5 - SNAP_POST" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -129,7 +129,7 @@ #### TEST 6 ## ################################################## - name: "TEST 6 - CHECK" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -153,13 +153,13 @@ #### TEST 7 ## ################################################## - name: "PRE-TEST 7 - Add loopback address" - junos_install_config: + juniper_junos_config: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" passwd: "{{ ansible_ssh_pass }}" + load: 'merge' file: junos_jsnapy/add_loopback.set - overwrite: no register: test7_1 ignore_errors: True tags: [ test7 ] @@ -168,7 +168,7 @@ pause: seconds=15 - name: "TEST 7 - SNAP_POST with additional loopback" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -181,7 +181,7 @@ # - debug: var=pretest7 - name: "TEST 7 - CHECK" - junos_jsnapy: + juniper_junos_jsnapy: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -194,13 +194,13 @@ - debug: var=test7 - name: "TEST 7 - Cleanup" - junos_install_config: + juniper_junos_config: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" passwd: "{{ ansible_ssh_pass }}" file: junos_jsnapy/delete_loopback.set - overwrite: no + load: 'merge' register: test7_3 ignore_errors: True tags: [ test7 ] diff --git a/tests/pb.junos_ping.yaml b/tests/pb.juniper_junos_ping.yaml similarity index 94% rename from tests/pb.junos_ping.yaml rename to tests/pb.juniper_junos_ping.yaml index a12e6726..fc596117 100644 --- a/tests/pb.junos_ping.yaml +++ b/tests/pb.juniper_junos_ping.yaml @@ -7,7 +7,7 @@ - Juniper.junos tasks: - name: "TEST 1 - Ping Google DNS" - junos_ping: + juniper_junos_ping: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -25,7 +25,7 @@ ############ - name: "TEST 2 - Ping Wrong IP" - junos_ping: + juniper_junos_ping: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -42,7 +42,7 @@ ################# - name: "TEST 3 - Change nbr packets" - junos_ping: + juniper_junos_ping: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -61,7 +61,7 @@ ################# - name: "TEST 4 - Ping with DF-bit set" - junos_ping: + juniper_junos_ping: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" @@ -82,7 +82,7 @@ ################# - name: "TEST 5 - Ping with DF-bit set and size that well exceeds jumbo sizes" - junos_ping: + juniper_junos_ping: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" diff --git a/tests/pb.junos_pmtud.yaml b/tests/pb.juniper_junos_pmtud.yaml similarity index 95% rename from tests/pb.junos_pmtud.yaml rename to tests/pb.juniper_junos_pmtud.yaml index 7b390399..57bcac61 100644 --- a/tests/pb.junos_pmtud.yaml +++ b/tests/pb.juniper_junos_pmtud.yaml @@ -7,7 +7,7 @@ - Juniper.junos tasks: - name: "TEST 1 - Check path MTU to Google DNS" - junos_pmtud: + juniper_junos_pmtud: host: "{{ ansible_ssh_host }}" port: "{{ ansible_ssh_port }}" user: "{{ ansible_ssh_user }}" From 9a57110ad684043eace6e8e7ee8dc586f472e672 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 9 Dec 2019 10:43:26 +0530 Subject: [PATCH 281/426] Renamed extension .yaml to .yml --- .../{pb.juniper_junos_jsnapy.yaml => pb.juniper_junos_jsnapy.yml} | 0 tests/{pb.juniper_junos_ping.yaml => pb.juniper_junos_ping.yml} | 0 tests/{pb.juniper_junos_pmtud.yaml => pb.juniper_junos_pmtud.yml} | 0 tests/{pb.rav.token.app_stop.yaml => pb.rav.token.app_stop.yml} | 0 ...av.token.create-deploy.yaml => pb.rav.token.create-deploy.yml} | 0 tests/{pb.rav.token.fqdn_get.yaml => pb.rav.token.fqdn_get.yml} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/{pb.juniper_junos_jsnapy.yaml => pb.juniper_junos_jsnapy.yml} (100%) rename tests/{pb.juniper_junos_ping.yaml => pb.juniper_junos_ping.yml} (100%) rename tests/{pb.juniper_junos_pmtud.yaml => pb.juniper_junos_pmtud.yml} (100%) rename tests/{pb.rav.token.app_stop.yaml => pb.rav.token.app_stop.yml} (100%) rename tests/{pb.rav.token.create-deploy.yaml => pb.rav.token.create-deploy.yml} (100%) rename tests/{pb.rav.token.fqdn_get.yaml => pb.rav.token.fqdn_get.yml} (100%) diff --git a/tests/pb.juniper_junos_jsnapy.yaml b/tests/pb.juniper_junos_jsnapy.yml similarity index 100% rename from tests/pb.juniper_junos_jsnapy.yaml rename to tests/pb.juniper_junos_jsnapy.yml diff --git a/tests/pb.juniper_junos_ping.yaml b/tests/pb.juniper_junos_ping.yml similarity index 100% rename from tests/pb.juniper_junos_ping.yaml rename to tests/pb.juniper_junos_ping.yml diff --git a/tests/pb.juniper_junos_pmtud.yaml b/tests/pb.juniper_junos_pmtud.yml similarity index 100% rename from tests/pb.juniper_junos_pmtud.yaml rename to tests/pb.juniper_junos_pmtud.yml diff --git a/tests/pb.rav.token.app_stop.yaml b/tests/pb.rav.token.app_stop.yml similarity index 100% rename from tests/pb.rav.token.app_stop.yaml rename to tests/pb.rav.token.app_stop.yml diff --git a/tests/pb.rav.token.create-deploy.yaml b/tests/pb.rav.token.create-deploy.yml similarity index 100% rename from tests/pb.rav.token.create-deploy.yaml rename to tests/pb.rav.token.create-deploy.yml diff --git a/tests/pb.rav.token.fqdn_get.yaml b/tests/pb.rav.token.fqdn_get.yml similarity index 100% rename from tests/pb.rav.token.fqdn_get.yaml rename to tests/pb.rav.token.fqdn_get.yml From cfccc22b287a99a465ba18b4b36ad2ed74b0fda5 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 11 Dec 2019 10:29:18 +0530 Subject: [PATCH 282/426] Added tests for juniper_junos_rpc --- tests/pb.juniper_junos_rpc.yml | 182 +++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 tests/pb.juniper_junos_rpc.yml diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml new file mode 100644 index 00000000..52d6fb6d --- /dev/null +++ b/tests/pb.juniper_junos_rpc.yml @@ -0,0 +1,182 @@ +--- +- name: Test junos_rpc module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: +################################################## +#### TEST 1 ## +################################################## + - name: "Execute single RPC get-software-information without any kwargs" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + register: test1 + ignore_errors: True + tags: [ test1 ] + + - name: Check TEST 1 + assert: + that: + - test1|succeeded + tags: [ test1 ] + +################################################## +#### TEST 2 ## +################################################## + - name: "Get Device Configuration with dest" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpc: get-config + dest: get_config.conf + register: test2 + ignore_errors: True + tags: [ test2 ] + + - name: Check that the get_config.conf exists + stat: + path: get_config.conf + register: stat_result + + - name: Check TEST 2 + assert: + that: + - test2|succeeded + - stat_result.stat.exists == True + tags: [ test2 ] + + - name: Clean up TEST 2 + + +################# + + - name: "Get Device Configuration in text" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpc: get-interface-information + kwargs: "interface_name=em0" + format: text + register: test3 + ignore_errors: True + tags: [ test3 ] + + - name: Check TEST 3 + assert: + that: + - test3|succeeded + tags: [ test3 ] + +################# + + - name: "Execute multiple RPCs without any kwargs" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + - "get-interface-information" + register: test4 + ignore_errors: True + tags: [ test4 ] + + - name: Check TEST 4 + assert: + that: + - test4|succeeded + tags: [ test4 ] + +################# + + - name: "Execute multiple RPCs with multiple kwargs" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + - "get-interface-information" + kwargs: + - {} + - "interface_name=em0" + register: test5 + ignore_errors: True + tags: [ test5 ] + + - name: Check TEST 5 + assert: + that: + - test5|succeeded + tags: [ test5 ] + +################# + - name: Creates directory + file: + path: out + state: directory + + - name: "Execute multiple RPCs with multiple kwargs and dest-dir" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + - "get-interface-information" + kwargs: + - {} + - "interface_name=em0" + dest_dir: "out" + register: test6 + ignore_errors: True + tags: [ test6 ] + + - name: Check that the get_config.conf exists + stat: + path: out + register: stat_result_1 + + - name: Check TEST 6 + assert: + that: + - test6|succeeded + - stat_result_1.stat.exists == True + tags: [ test6 ] + +################# + + - name: Get Device Configuration for interface + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpc: get-config + filter_xml: "" + register: test7 + ignore_errors: True + tags: [ test7 ] + + - name: Check TEST 7 + assert: + that: + - test7|succeeded + tags: [ test7 ] + +################# \ No newline at end of file From ceab66bfb22fca69c501e113a15273053c0b6fc6 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 11 Dec 2019 10:48:58 +0530 Subject: [PATCH 283/426] Fixed tests for juniper_junos_rpc --- tests/pb.juniper_junos_rpc.yml | 38 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 52d6fb6d..ea352e02 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -7,9 +7,7 @@ - Juniper.junos tasks: -################################################## -#### TEST 1 ## -################################################## +################# - name: "Execute single RPC get-software-information without any kwargs" juniper_junos_rpc: host: "{{ ansible_ssh_host }}" @@ -28,9 +26,7 @@ - test1|succeeded tags: [ test1 ] -################################################## -#### TEST 2 ## -################################################## +################# - name: "Get Device Configuration with dest" juniper_junos_rpc: host: "{{ ansible_ssh_host }}" @@ -56,6 +52,9 @@ tags: [ test2 ] - name: Clean up TEST 2 + file: + path: get_config.conf + state: absent ################# @@ -159,8 +158,12 @@ - stat_result_1.stat.exists == True tags: [ test6 ] -################# + - name: Clean up TEST 6 + file: + path: get_config.conf + state: absent +################# - name: Get Device Configuration for interface juniper_junos_rpc: host: "{{ ansible_ssh_host }}" @@ -179,4 +182,23 @@ - test7|succeeded tags: [ test7 ] -################# \ No newline at end of file +################# + - name: "Execute wrong RPC to generate RPC error" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "wrong-rpc" + register: test8 + ignore_errors: True + tags: [ test5 ] + + - name: Check TEST 8 + assert: + that: + - test8|failed + tags: [ test8 ] + +################# From 50487994fde67e81198a336c03cd40ec2f7543a5 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 11 Dec 2019 11:34:22 +0530 Subject: [PATCH 284/426] Fixed tests for juniper_junos_rpc --- tests/pb.juniper_junos_rpc.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index ea352e02..41e16503 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -23,7 +23,7 @@ - name: Check TEST 1 assert: that: - - test1|succeeded + - test1.msg == "The RPC executed successfully." tags: [ test1 ] ################# @@ -47,7 +47,7 @@ - name: Check TEST 2 assert: that: - - test2|succeeded + - test2.msg == "The \"get-config\" RPC executed successfully." - stat_result.stat.exists == True tags: [ test2 ] @@ -75,7 +75,7 @@ - name: Check TEST 3 assert: that: - - test3|succeeded + - test3.msg == "The RPC executed successfully." tags: [ test3 ] ################# @@ -89,6 +89,7 @@ rpcs: - "get-software-information" - "get-interface-information" + dest: get_config.conf register: test4 ignore_errors: True tags: [ test4 ] @@ -96,7 +97,8 @@ - name: Check TEST 4 assert: that: - - test4|succeeded + - test4.results[0].msg == "The RPC executed successfully." + - test4.results[1].msg == "The RPC executed successfully." tags: [ test4 ] ################# @@ -120,7 +122,8 @@ - name: Check TEST 5 assert: that: - - test5|succeeded + - test5.results[0].msg == "The RPC executed successfully." + - test5.results[1].msg == "The RPC executed successfully." tags: [ test5 ] ################# @@ -154,7 +157,8 @@ - name: Check TEST 6 assert: that: - - test6|succeeded + - test6.results[0].msg == "The RPC executed successfully." + - test6.results[1].msg == "The RPC executed successfully." - stat_result_1.stat.exists == True tags: [ test6 ] @@ -179,7 +183,7 @@ - name: Check TEST 7 assert: that: - - test7|succeeded + - test7.msg == "The \"get-config\" RPC executed successfully." tags: [ test7 ] ################# @@ -193,12 +197,12 @@ - "wrong-rpc" register: test8 ignore_errors: True - tags: [ test5 ] + tags: [ test8 ] - name: Check TEST 8 assert: that: - - test8|failed + - '"Unable to execute the RPC" in test8.msg' tags: [ test8 ] ################# From 617bfb942a915a4a3f2b330b5fdd1c1307af3b19 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 11 Dec 2019 11:36:30 +0530 Subject: [PATCH 285/426] Minor fixes to module_utils. - Incase we are not able to convert it to bool, we should return None. Currently, it causes a TypeError. - The mode should be 'ab' instead of 'a' to allow python3 to write bytestrings --- module_utils/juniper_junos_common.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index b97b6115..6cc352d9 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -1173,7 +1173,11 @@ def parse_arg_to_list_of_dicts(self, if allow_bool_values is True: # Try to convert it to a boolean value. Will be None if it # can't be converted. - bool_val = boolean(v) + try: + bool_val = boolean(v) + except TypeError: + bool_val = None + pass if bool_val is not None: v = bool_val return_item[k] = v @@ -1851,7 +1855,7 @@ def save_text_output(self, name, format, text): if getattr(self, 'destfile', None) is None: self.destfile = self.params.get('dest') else: - mode = 'a' + mode = 'ab' elif self.params.get('dest_dir') is not None: dest_dir = self.params.get('dest_dir') hostname = self.params.get('host') From 24d8e457802e142d7db9484acd97af171f279934 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Wed, 11 Dec 2019 11:56:26 +0530 Subject: [PATCH 286/426] Stat the created files instead of the directory --- tests/pb.juniper_junos_rpc.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 41e16503..72fadb82 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -149,22 +149,28 @@ ignore_errors: True tags: [ test6 ] - - name: Check that the get_config.conf exists + - name: Check out/choc-qfx-a_get-interface-information.xml exists stat: - path: out + path: out/choc-qfx-a_get-interface-information.xml register: stat_result_1 + - name: Check out/choc-qfx-a_get-software-information.xml exists + stat: + path: out/choc-qfx-a_get-software-information.xml + register: stat_result_2 + - name: Check TEST 6 assert: that: - test6.results[0].msg == "The RPC executed successfully." - test6.results[1].msg == "The RPC executed successfully." - stat_result_1.stat.exists == True + - stat_result_2.stat.exists == True tags: [ test6 ] - name: Clean up TEST 6 file: - path: get_config.conf + path: out state: absent ################# From dce52513380a56624bec5b724c87f199dcd3bfb4 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Fri, 13 Dec 2019 14:23:52 +0530 Subject: [PATCH 287/426] Added UT for juniper_junos_config --- tests/pb.juniper_junos_config.yml | 118 ++++++++++++++++++++++++++++++ tests/pb.juniper_junos_facts.yml | 2 +- tests/pb.juniper_junos_jsnapy.yml | 2 +- tests/pb.juniper_junos_ping.yml | 2 +- tests/pb.juniper_junos_pmtud.yml | 2 +- tests/pb.juniper_junos_rpc.yml | 17 +++-- 6 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 tests/pb.juniper_junos_config.yml diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml new file mode 100644 index 00000000..5ecbc4cf --- /dev/null +++ b/tests/pb.juniper_junos_config.yml @@ -0,0 +1,118 @@ +--- +- name: Test juniper_junos_config module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: +################# + - name: Retrieve the committed configuration + juniper_junos_config: + retrieve: 'committed' + diff: false + check: false + commit: false + register: test1 + ignore_errors: True + tags: [ test1 ] + + - name: Check TEST 1 + assert: + that: + - test1.config + - "'host-name choc-qfx-a;' in test1.config" +################# + - name: Append .foo to the hostname using private config mode. + juniper_junos_config: + config_mode: 'private' + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + register: test2 + ignore_errors: True + tags: [ test2 ] + + - name: Check TEST 2 + assert: + that: + - test2.diff_lines + - "'+ host-name choc-qfx-a.foo;' in test2.diff_lines" +################# + - name: Rollback to the previous config. + juniper_junos_config: + config_mode: 'private' + rollback: "1" + register: test3 + ignore_errors: True + tags: [ test3 ] + + - name: Check TEST 3 + assert: + that: + - test3.diff_lines + - "'- host-name choc-qfx-a.foo;' in test3.diff_lines" +################# + - name: Creates directory + file: + path: out + state: directory + + - name: Configure LLDP + juniper_junos_config: + load: 'merge' + lines: + - "set protocols lldp advertisement-interval 30" + - "set protocols lldp transmit-delay 2" + - "set protocols lldp hold-multiplier 4" + - "set protocols lldp ptopo-configuration-trap-interval 30" + - "set protocols lldp ptopo-configuration-maximum-hold-time 300" + - "set protocols lldp lldp-configuration-notification-interval 30" + - "set protocols lldp interface all disable" + - "set protocols lldp interface ge-1/1/1" + format: 'set' + comment: 'Start LLDP with given options' + dest_dir: './out' + register: test4 + ignore_errors: True + tags: [ test4 ] + + - name: Rollback to the rescue config. + juniper_junos_config: + rollback: 'rescue' + + - name: Check out/choc-qfx-a.diff exists + stat: + path: out/choc-qfx-a.diff + register: stat_result_1 + + - name: Check TEST 4 + assert: + that: + - stat_result_1.stat.exists == True + - test4.diff_lines + - "'+ interface ge-1/1/1;' in test4.diff_lines" + + - name: Clean up TEST 4 + file: + path: out + state: absent +################# + - name: Retrieve [edit system services] of current committed config. + juniper_junos_config: + retrieve: 'committed' + filter: 'system/services' + diff: true + check: false + commit: false + register: test5 + ignore_errors: True + tags: [ test5 ] + + - name: Check TEST 5 + assert: + that: + - test5.failed == False + - "'system {' in test5.config_lines" +################# +#TODO: Add tests for commit check and commit confirmed workflows diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 6ce691e8..c585b025 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -1,5 +1,5 @@ --- -- name: Test junos_get_facts module +- name: Test juniper_junos_facts module hosts: all connection: local gather_facts: no diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index d913a6f8..4318c76f 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -1,5 +1,5 @@ --- -- name: Test junos_ping module +- name: Test juniper_junos_ping module hosts: all connection: local gather_facts: no diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index fc596117..c8fb77c2 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -1,5 +1,5 @@ --- -- name: Test junos_ping module +- name: Test juniper_junos_ping module hosts: all connection: local gather_facts: no diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index 57bcac61..cdf3fb41 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -1,5 +1,5 @@ --- -- name: Test junos_pmtud module +- name: Test juniper_junos_pmtud module hosts: all connection: local gather_facts: no diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 72fadb82..7df77e5d 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -1,5 +1,5 @@ --- -- name: Test junos_rpc module +- name: Test juniper_junos_rpc module hosts: all connection: local gather_facts: no @@ -55,8 +55,6 @@ file: path: get_config.conf state: absent - - ################# - name: "Get Device Configuration in text" @@ -101,6 +99,11 @@ - test4.results[1].msg == "The RPC executed successfully." tags: [ test4 ] + - name: Clean up TEST 4 + file: + path: get_config.conf + state: absent + ################# - name: "Execute multiple RPCs with multiple kwargs" @@ -149,14 +152,14 @@ ignore_errors: True tags: [ test6 ] - - name: Check out/choc-qfx-a_get-interface-information.xml exists + - name: Check get-interface-information.xml exists stat: - path: out/choc-qfx-a_get-interface-information.xml + path: "out/{{ inventory_hostname }}_get-interface-information.xml" register: stat_result_1 - - name: Check out/choc-qfx-a_get-software-information.xml exists + - name: Check get-software-information.xml exists stat: - path: out/choc-qfx-a_get-software-information.xml + path: "out/{{ inventory_hostname }}_get-software-information.xml" register: stat_result_2 - name: Check TEST 6 From abdffadc519c5d6b356ea9142e149adf2aa132b7 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Fri, 13 Dec 2019 16:19:16 +0530 Subject: [PATCH 288/426] Fixed review comments --- module_utils/juniper_junos_common.py | 1 - tests/pb.juniper_junos_rpc.yml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/module_utils/juniper_junos_common.py b/module_utils/juniper_junos_common.py index 6cc352d9..d7f0151e 100644 --- a/module_utils/juniper_junos_common.py +++ b/module_utils/juniper_junos_common.py @@ -1177,7 +1177,6 @@ def parse_arg_to_list_of_dicts(self, bool_val = boolean(v) except TypeError: bool_val = None - pass if bool_val is not None: v = bool_val return_item[k] = v diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 7df77e5d..22a195da 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -154,12 +154,12 @@ - name: Check get-interface-information.xml exists stat: - path: "out/{{ inventory_hostname }}_get-interface-information.xml" + path: "out/{{ ansible_ssh_host }}_get-interface-information.xml" register: stat_result_1 - name: Check get-software-information.xml exists stat: - path: "out/{{ inventory_hostname }}_get-software-information.xml" + path: "out/{{ ansible_ssh_host }}_get-software-information.xml" register: stat_result_2 - name: Check TEST 6 From 4850b521c6decd4831fb4102b590ff8efe8568fd Mon Sep 17 00:00:00 2001 From: rsmekala Date: Fri, 13 Dec 2019 16:31:40 +0530 Subject: [PATCH 289/426] prepare to release 2.3 --- version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index 84734d57..bc673ed3 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.2.2.dev0" -DATE = "2019-Sept-06" +VERSION = "2.3" +DATE = "2019-Dec-13" From 574c751ccd4b0254971321e87c92f58c7784d767 Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 16 Dec 2019 12:28:01 +0530 Subject: [PATCH 290/426] prepare to release 2.3 --- meta/main.yml | 1 + version.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/meta/main.yml b/meta/main.yml index 3bb69c40..735091fa 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,6 +1,7 @@ --- galaxy_info: author: Juniper #Stacy W. Smith @stacywsmith + role_name: junos description: Network build automation of Junos devices. company: Juniper Networks, Inc. license: Apache 2.0 diff --git a/version.py b/version.py index bc673ed3..dfff5cd8 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.3" +VERSION = "2.3.0" DATE = "2019-Dec-13" From aecddc035c05fdb18a6473b91bad3800acb2805b Mon Sep 17 00:00:00 2001 From: rsmekala Date: Mon, 16 Dec 2019 12:44:03 +0530 Subject: [PATCH 291/426] prepare to release 2.3 --- meta/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/main.yml b/meta/main.yml index 735091fa..068cffaa 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -5,7 +5,7 @@ galaxy_info: description: Network build automation of Junos devices. company: Juniper Networks, Inc. license: Apache 2.0 - min_ansible_version: 2.1 + min_ansible_version: 2.4 platforms: - name: junos versions: From ca54730ea295d4ea20f1acf7d698cb49db7f3007 Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Wed, 19 Feb 2020 13:48:16 +0100 Subject: [PATCH 292/426] Update juniper_junos_table.py Fixes #379 --- library/juniper_junos_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py index 73916673..5d74f70a 100644 --- a/library/juniper_junos_table.py +++ b/library/juniper_junos_table.py @@ -329,7 +329,7 @@ def juniper_items_to_list_of_dicts(module, data): # table_fields - element 1 is also a list of tuples temp = {} for key, value in table_fields: - if (value and isinstance(value, module.pyez_factory_table.Table)): + if isinstance(value, module.pyez_factory_table.Table): value = juniper_items_to_list_of_dicts(module, value) temp[key] = value resources.append(temp) From 1ec34725a04cabcf09a0b65b901528455a8f2110 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Tue, 25 Feb 2020 11:05:14 +0530 Subject: [PATCH 293/426] removed python2 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 86ed50d3..86b1c52f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,10 @@ LABEL net.juniper.image.maintainer="Stephen Steiner " WORKDIR /tmp -RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make python-dev py-pip +RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make COPY requirements.txt . -RUN pip install -r requirements.txt +RUN pip3 install -r requirements.txt RUN apk del -r --purge gcc make g++ &&\ rm -rf /source/* &&\ From 6688a91a470247dc871d8a9fb75972f667dbeca6 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 4 Mar 2020 12:38:37 +0530 Subject: [PATCH 294/426] Adding support for ansible-collections --- action_plugins/juniper_junos_command.py | 1 - action_plugins/juniper_junos_config.py | 1 - action_plugins/juniper_junos_facts.py | 1 - action_plugins/juniper_junos_jsnapy.py | 1 - action_plugins/juniper_junos_ping.py | 1 - action_plugins/juniper_junos_pmtud.py | 1 - action_plugins/juniper_junos_rpc.py | 1 - action_plugins/juniper_junos_software.py | 1 - action_plugins/juniper_junos_srx_cluster.py | 1 - action_plugins/juniper_junos_system.py | 1 - action_plugins/juniper_junos_table.py | 1 - ansible_collection/Juniper/junos/README.md | 3 + .../Juniper/junos/docs/Makefile | 234 ++++++++ .../docs/_static/juniper-junos-modules.css | 7 + .../Juniper/junos/docs/_static/juniper.png | Bin 0 -> 5044 bytes .../Juniper/junos/docs/ansible2rst.py | 426 +++++++++++++ ansible_collection/Juniper/junos/docs/conf.py | 285 +++++++++ .../Juniper/junos/docs/docreq.txt | 2 + ansible_collection/Juniper/junos/docs/rst.j2 | 560 ++++++++++++++++++ ansible_collection/Juniper/junos/galaxy.yml | 57 ++ .../Juniper/junos/plugins/README.md | 31 + .../plugins/action/juniper_junos_command.py | 0 .../action/juniper_junos_common_action.py | 116 ++++ .../plugins/action/juniper_junos_config.py | 116 ++++ .../plugins/action/juniper_junos_facts.py | 116 ++++ .../plugins/action/juniper_junos_jsnapy.py | 116 ++++ .../plugins/action/juniper_junos_ping.py | 116 ++++ .../plugins/action/juniper_junos_pmtud.py | 116 ++++ .../junos/plugins/action/juniper_junos_rpc.py | 116 ++++ .../plugins/action/juniper_junos_software.py | 116 ++++ .../action/juniper_junos_srx_cluster.py | 116 ++++ .../plugins/action/juniper_junos_system.py | 116 ++++ .../plugins/action/juniper_junos_table.py | 116 ++++ .../Juniper/junos/plugins/callback}/jsnapy.py | 22 + .../junos/plugins/module_utils}/__init__.py | 0 .../module_utils}/juniper_junos_common.py | 0 .../plugins/modules}/juniper_junos_command.py | 3 +- .../plugins/modules}/juniper_junos_config.py | 3 +- .../plugins/modules}/juniper_junos_facts.py | 3 +- .../plugins/modules}/juniper_junos_jsnapy.py | 3 +- .../plugins/modules}/juniper_junos_ping.py | 3 +- .../plugins/modules}/juniper_junos_pmtud.py | 3 +- .../plugins/modules}/juniper_junos_rpc.py | 3 +- .../modules}/juniper_junos_software.py | 3 +- .../modules}/juniper_junos_srx_cluster.py | 3 +- .../plugins/modules}/juniper_junos_system.py | 3 +- .../plugins/modules}/juniper_junos_table.py | 3 +- .../Juniper/junos/tests/.travis.yml | 30 + .../Juniper/junos/tests/ansible.cfg | 5 + .../junos/tests/junos_jsnapy/add_loopback.set | 3 + .../tests/junos_jsnapy/delete_loopback.set | 2 + .../tests/junos_jsnapy/test_junos_storage.yml | 11 + .../tests/junos_jsnapy/test_loopback.yml | 16 + .../junos/tests/junos_jsnapy/test_version.yml | 12 + .../junos/tests/pb.juniper_junos_config.yml | 118 ++++ .../junos/tests/pb.juniper_junos_facts.yml | 26 + .../junos/tests/pb.juniper_junos_jsnapy.yml | 217 +++++++ .../junos/tests/pb.juniper_junos_ping.yml | 102 ++++ .../junos/tests/pb.juniper_junos_pmtud.yml | 23 + .../junos/tests/pb.juniper_junos_rpc.yml | 217 +++++++ .../junos/tests/pb.rav.token.app_stop.yml | 34 ++ .../tests/pb.rav.token.create-deploy.yml | 85 +++ .../junos/tests/pb.rav.token.fqdn_get.yml | 54 ++ .../Juniper/junos/tests/ravello.ini | 18 + .../Juniper/junos/tests/test.yml | 27 + .../Juniper/junos/tests/test_case.yaml | 20 + .../Juniper/junos/tools/sw_upgrade | 129 ++++ 67 files changed, 4063 insertions(+), 33 deletions(-) delete mode 120000 action_plugins/juniper_junos_command.py delete mode 120000 action_plugins/juniper_junos_config.py delete mode 120000 action_plugins/juniper_junos_facts.py delete mode 120000 action_plugins/juniper_junos_jsnapy.py delete mode 120000 action_plugins/juniper_junos_ping.py delete mode 120000 action_plugins/juniper_junos_pmtud.py delete mode 120000 action_plugins/juniper_junos_rpc.py delete mode 120000 action_plugins/juniper_junos_software.py delete mode 120000 action_plugins/juniper_junos_srx_cluster.py delete mode 120000 action_plugins/juniper_junos_system.py delete mode 120000 action_plugins/juniper_junos_table.py create mode 100644 ansible_collection/Juniper/junos/README.md create mode 100644 ansible_collection/Juniper/junos/docs/Makefile create mode 100644 ansible_collection/Juniper/junos/docs/_static/juniper-junos-modules.css create mode 100755 ansible_collection/Juniper/junos/docs/_static/juniper.png create mode 100755 ansible_collection/Juniper/junos/docs/ansible2rst.py create mode 100644 ansible_collection/Juniper/junos/docs/conf.py create mode 100644 ansible_collection/Juniper/junos/docs/docreq.txt create mode 100644 ansible_collection/Juniper/junos/docs/rst.j2 create mode 100644 ansible_collection/Juniper/junos/galaxy.yml create mode 100644 ansible_collection/Juniper/junos/plugins/README.md rename action_plugins/juniper_junos_common_action.py => ansible_collection/Juniper/junos/plugins/action/juniper_junos_command.py (100%) create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_common_action.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_config.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_facts.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_jsnapy.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_ping.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_pmtud.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_rpc.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_software.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_system.py create mode 100755 ansible_collection/Juniper/junos/plugins/action/juniper_junos_table.py rename {callback_plugins => ansible_collection/Juniper/junos/plugins/callback}/jsnapy.py (76%) rename {module_utils => ansible_collection/Juniper/junos/plugins/module_utils}/__init__.py (100%) rename {module_utils => ansible_collection/Juniper/junos/plugins/module_utils}/juniper_junos_common.py (100%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_command.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_config.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_facts.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_jsnapy.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_ping.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_pmtud.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_rpc.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_software.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_srx_cluster.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_system.py (99%) rename {library => ansible_collection/Juniper/junos/plugins/modules}/juniper_junos_table.py (99%) create mode 100644 ansible_collection/Juniper/junos/tests/.travis.yml create mode 100644 ansible_collection/Juniper/junos/tests/ansible.cfg create mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set create mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set create mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml create mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml create mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml create mode 100644 ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml create mode 100644 ansible_collection/Juniper/junos/tests/ravello.ini create mode 100644 ansible_collection/Juniper/junos/tests/test.yml create mode 100644 ansible_collection/Juniper/junos/tests/test_case.yaml create mode 100755 ansible_collection/Juniper/junos/tools/sw_upgrade diff --git a/action_plugins/juniper_junos_command.py b/action_plugins/juniper_junos_command.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_command.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_config.py b/action_plugins/juniper_junos_config.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_config.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_facts.py b/action_plugins/juniper_junos_facts.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_facts.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_jsnapy.py b/action_plugins/juniper_junos_jsnapy.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_jsnapy.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_ping.py b/action_plugins/juniper_junos_ping.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_ping.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_pmtud.py b/action_plugins/juniper_junos_pmtud.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_pmtud.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_rpc.py b/action_plugins/juniper_junos_rpc.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_rpc.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_software.py b/action_plugins/juniper_junos_software.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_software.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_srx_cluster.py b/action_plugins/juniper_junos_srx_cluster.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_srx_cluster.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_system.py b/action_plugins/juniper_junos_system.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_system.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/action_plugins/juniper_junos_table.py b/action_plugins/juniper_junos_table.py deleted file mode 120000 index 7470277b..00000000 --- a/action_plugins/juniper_junos_table.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/README.md b/ansible_collection/Juniper/junos/README.md new file mode 100644 index 00000000..7ee43d2e --- /dev/null +++ b/ansible_collection/Juniper/junos/README.md @@ -0,0 +1,3 @@ +# Ansible Collection - juniper.junos + +Documentation for the collection. \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/docs/Makefile b/ansible_collection/Juniper/junos/docs/Makefile new file mode 100644 index 00000000..7b3aac77 --- /dev/null +++ b/ansible_collection/Juniper/junos/docs/Makefile @@ -0,0 +1,234 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +LOGFILE = sphinx.log +FULL_TRACEBACKS = -T +CPUS ?= 4 +VERBOSITY ?= -v +FORCE_REBUILD = -a -E +CONFIG_DIR = -c . +NITPICK ?= -n +SPHINXOPTS = -j $(CPUS) -w $(LOGFILE) $(FULL_TRACEBACKS) $(FORCE_REBUILD) $(NITPICK) $(VERBOSITY) $(CONFIG_DIR) +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build +RSTDIR = . +MODULES_PATH = ../library +EXCLUDE_PATHS = ../library/_junos* +DOC_PROJECTS = "Ansible API" + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + rm -rf $(RSTDIR)/*.rst + rm -rf $(LOGFILE) + +.PHONY: apidoc +apidoc: + sphinx-apidoc --module-first --doc-project $(DOC_PROJECT) --force --maxdepth 7 -o $(RSTDIR) $(MODULES_PATH) $(EXCLUDE_PATHS) + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(RSTDIR) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: docs +docs: clean apidoc html + +.PHONY: webdocs +webdocs: clean apidoc html + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Ansible.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Ansible.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Ansible" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Ansible" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/ansible_collection/Juniper/junos/docs/_static/juniper-junos-modules.css b/ansible_collection/Juniper/junos/docs/_static/juniper-junos-modules.css new file mode 100644 index 00000000..2723c307 --- /dev/null +++ b/ansible_collection/Juniper/junos/docs/_static/juniper-junos-modules.css @@ -0,0 +1,7 @@ +td, th { + padding: 20px; +} +code { + color: #3a87ad; + background-color: #d9edf7; +} \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/docs/_static/juniper.png b/ansible_collection/Juniper/junos/docs/_static/juniper.png new file mode 100755 index 0000000000000000000000000000000000000000..e3db7d8f91ca51d3e9c63fba27801d08875cb28f GIT binary patch literal 5044 zcmV;l6HDxgP)100004XF*Lt006O$ zeEU(80000WV@Og>004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ0#b(FyTAa_ zdy`&8VVD_UC<6{NG_fI~0ue<-nj%P0#DLLIBvwSR5EN9f2P6n6F&ITuEN@2Ei>|D^ z_ww@lRz|vC zuzLs)$;-`!o*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT!&C1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2h zoGcOF60t^#FqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTX za!E_i;d2ub1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqK zG_|(0G&D0Z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoDiKdLpOAxi2$L0#SX*@cY z_n(^h55xYX#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^b zXThc7C4-yrInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0fwx1%qj zZ=)yBuQ3=54Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK% z>{;v(b^`kbN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<) z0>40zCTJ7v2qAyk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01) zS~6}jY?%U?gEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j z*2tcg9i<^OEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYWVlfKTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu761 zjmyXF)a;mc^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQq zHZJR2&bcD49Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^ zTY0bZ?)4%01p8F`JoeS|<@=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK z8LKk71XR(_RKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS z<&CX#T35dwS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@ zqL5!WvekBL-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW z%ue3U;av{94wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J#oSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%o zZ=0JGnu?n~9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8N zo_-(u{qS+0<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-U zsyQuty7Ua;Ou?B?XLHZaol8GAb3Wnxcu!2v{R_`T4=x`(GvqLI{-*2AOSimk zUAw*F_TX^n@STz9kDQ z$NC=!KfXWC8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgU zAAWQEt$#LRcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6 z?<+s(e(3(_^YOu_)K8!O1p}D#{JO;G(*OVf32;bRa{vGr5dZ)e5dq33^FIIp02Xvb zSaefwW^{L9a%BK;VQFr3E^cLXAT%y8JLvUk000QXNklRjx*t{$T#NAgMw;47sXR}~fVfAw~IAluM z%oGK@o;X{nE19YYmJoBO9XfuHsRC$#wvfBDUoMuAUm@6iNWhoHJnZ&jTz7EO5}?iB zDN$V280dGXg7LkJ?$Tq+w-ag)xId$>KY{%h57_G%au}+uNB}-bwaqdiu#3Ze~?I(#Emk+KP`i!Ay7Uk z;FmEqD6s5j>RE9Vke zm=61YqI^!1jA@PG#vO>!7N{;1nyP*INzKokA%U6ak3p!VOlgdSKm_<$&id$?MN8++7GnW_$>Es&+uPuTyk1ft(7+Lx5SVoGDA z1PX;hCnBi_(e^J)K_Imo{e8sMVF7Jl$Jh?wRPVFn0u`Yefw12+vH!#ShE)40$q?@S z;TBqnXx|Sv`muRjpdt)cAo^XyZD@~}%QX2u0FEA+|4?m#n{k1H1i~!-)CyZaELZ5a z723_m*g9dqSH~yRR(7EQZvQ~v)nZ(rA{<@~6_+nC#*RoNGKpPiAgOq&le@V3KVeh_ zg7bC6AvsquRS}8=TFcbY6o_VjSJ{G?9HkO=SS`el(9^kT39t&^pxYe1++?b1jP44; z?Be9y$W%ojL3KN!9$@Ng`;ZU>^yTS3!{NZU@x#IB3Z&E>{y>A@D3=2UwN$r5d#1*e zK#r74bf1`@OjV6hUP0nA-?@5$sfqymZQ57SH;%C+5Z(IVXFuZDGn{vEK^QTC5bT}g zh<+C^WjKyA8A1OId_NL&1Ji>7erCh|-zc9UxhF6^rUW8j_1sJS!sqYnC)=|N$K zYzO0d*0$|1Q-cXq@e5-O1ly()D9me^(!gfw>JE@}BCu z0KW5BC=hab9c>$q z5RFU?4FsDZM`pi`d!#JL7KGPlfh2H+|!Erb9|H)rNeP8ha^B8La5h8{$3Bufs zzUX6|A0PEAq9?u()0r9^^f(FUAEUDajkUFb z{{x^^gWp3pAyMlw=wD(Wui^M6I>#?(`5W+?Q5Khbbfy11VDr6=-cB=B5$M|twR6Je zwM=Q)!u&CfhpQSCG{z9;>WG2#r$hj{Ozn>?xeDEEgB85*j~!ieO5= z!Kjn+qJbaw%Ap1wtko(@^*nRq_&;F+?H4p7>ZP29+fIyT0f~c|u#aXr+kNd3PxcmO zWF+y@q~SfxAp4(C@e@kenzw-t3Qjkwr)OdQ99Q^ z=PhlX8B2Tfqv>p`^#w zL$Yqt1>&@%ydUWEWcEcmx~bek5(n4x*V3EUK7B?s>3tYUB=qYZH|0H?fZ#ZKuvyxY z^e&5~vUj1L;OpJsZv_9NXv%xYP4&)e>G0;Fel3ZL{yNc5JvhfrWqTI2ZS2ftQP(D!A)(w7=7GJqG{t} z4G6w#N&1N?l(-UslG&YfjY@2F@1<-WB+%_{d*(t~1x<+!C&LfT{A)1V=g~@wwfD?n zQ;k^4Tj{RO*zl8sC`q(Wqit^Snp`6ZgjKi$<(b&(o{PANL4J5FkC0!cj}1`ZbflRZ zP3KmiKCBfK@7xw9QE2s09jkEz62Ff23R*$2boLf)$e<+a6_oNeHud#QBpdXtADu}c z2)Jx+SJz|`EnvT?f$t2T9~;OEM9{PUut1)h&Rs_(*1mNbn;an!L9e#e+3)-3nHJb& z>6~s$lu-CwLL0o>;jN0Ma`*Ulhi`GPe+aAO%=|5mI6NRs2X_zZ*t@)Cv`=E+TLAxs zcE<$h35ib)dg{tQed(Fa1ql>vVz_M?n@*=R(VBSf8^!jyKCu4w^^pOUpk__6HDftv>0000< KMNUMnLSTXzsH2Gh literal 0 HcmV?d00001 diff --git a/ansible_collection/Juniper/junos/docs/ansible2rst.py b/ansible_collection/Juniper/junos/docs/ansible2rst.py new file mode 100755 index 00000000..a5809813 --- /dev/null +++ b/ansible_collection/Juniper/junos/docs/ansible2rst.py @@ -0,0 +1,426 @@ +#!/usr/bin/env python +# (c) 2012, Jan-Piet Mens +# +# This file is part of Ansible +# +# Modified to support stand-alone Galaxy documentation +# Copyright (c) 2014, 2017-2018 Juniper Networks Inc. +# 2014, Rick Sherman +# +# 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 . +# + +import os +import re +import sys +import datetime +import cgi +from distutils.version import LooseVersion +from jinja2 import Environment, FileSystemLoader +import yaml +from six import print_ + +from collections import MutableMapping, MutableSet, MutableSequence + +from ansible.module_utils.six import iteritems, string_types +from ansible.parsing.plugin_docs import read_docstring +from ansible.parsing.yaml.loader import AnsibleLoader +from ansible.plugins.loader import fragment_loader +from ansible.module_utils._text import to_bytes + +try: + from html import escape as html_escape +except ImportError: + # Python-3.2 or later + import cgi + + def html_escape(text, quote=True): + return cgi.escape(text, quote) + +from ansible import __version__ as ansible_version + +##################################################################################### +# constants and paths + +# if a module is added in a version of Ansible older than this, don't print the version added information +# in the module documentation because everyone is assumed to be running something newer than this already. +TO_OLD_TO_BE_NOTABLE = 1.3 + +_ITALIC = re.compile(r"I\(([^)]+)\)") +_BOLD = re.compile(r"B\(([^)]+)\)") +_MODULE = re.compile(r"M\(([^)]+)\)") +_URL_W_TEXT = re.compile(r"U\(([^)^|]+)\|([^)]+)\)") +_URL = re.compile(r"U\(([^)^|]+)\)") +_CONST = re.compile(r"C\(([^)]+)\)") +_UNDERSCORE = re.compile(r"_") +DEPRECATED = b" (D)" + +MODULE_NAME_STARTS_WITH = "juniper_junos_" +MODULEDIR = "../library/" +OUTPUTDIR = "./" + +##################################################################################### + +def too_old(added): + if not added: + return False + try: + added_tokens = str(added).split(".") + readded = added_tokens[0] + "." + added_tokens[1] + added_float = float(readded) + except ValueError as e: + warnings.warn("Could not parse %s: %s" % (added, str(e))) + return False + return added_float < TO_OLD_TO_BE_NOTABLE + +##################################################################################### + +def rst_ify(text): + ''' convert symbols like I(this is in italics) to valid restructured text ''' + + try: + t = _ITALIC.sub(r'*' + r"\1" + r"*", text) + t = _BOLD.sub(r'**' + r"\1" + r"**", t) + t = _MODULE.sub(r':ref:`' + r"\1 <\1>" + r"`", t) + t = _URL_W_TEXT.sub(r'`' + r"\1" + r" <" + r"\2" + r">`_", t) + t = _URL.sub(r'`' + r"\1" + r" <" + r"\1" + r">`_", t) + t = _CONST.sub(r'``' + r"\1" + r"``", t) + except Exception as e: + raise AnsibleError("Could not process (%s) : %s" % (str(text), str(e))) + + return t + +##################################################################################### + +def module_to_html(matchobj): + if matchobj.group(1) is not None: + module_name = matchobj.group(1) + module_href = _UNDERSCORE.sub('-', module_name) + return '' + \ + module_name + '' + return '' + +def html_ify(text): + ''' convert symbols like I(this is in italics) to valid HTML ''' + + t = html_escape(text) + t = _ITALIC.sub("" + r"\1" + "", t) + t = _BOLD.sub("" + r"\1" + "", t) + t = _MODULE.sub(module_to_html, t) + t = _URL_W_TEXT.sub("" + r"\1" + "", t) + t = _URL.sub("" + r"\1" + "", t) + t = _CONST.sub("" + r"\1" + "", t) + + return t + + +##################################################################################### + + +def rst_fmt(text, fmt): + ''' helper for Jinja2 to do format strings ''' + + return fmt % (text) + +##################################################################################### + + +def rst_xline(width, char="="): + ''' return a restructured text line of a given length ''' + + return char * width + +##################################################################################### + + +def write_data(text, outputname, module, output_dir=None): + ''' dumps module output to a file or the screen, as requested ''' + + if output_dir is not None: + if not os.path.exists(output_dir): + os.makedirs(output_dir) + fname = os.path.join(output_dir, outputname % (module)) + with open(fname, 'wb') as f: + f.write(to_bytes(text)) + else: + print(text) + +##################################################################################### + + +def jinja2_environment(template_dir, template_type): + + env = Environment(loader=FileSystemLoader(template_dir), + variable_start_string="@{", + variable_end_string="}@", + trim_blocks=True, + ) + env.globals['xline'] = rst_xline + + if template_type == 'rst': + env.filters['convert_symbols_to_format'] = rst_ify + env.filters['html_ify'] = html_ify + env.filters['fmt'] = rst_fmt + env.filters['xline'] = rst_xline + template = env.get_template('rst.j2') + outputname = "%s.rst" + else: + raise Exception("unknown module format type: %s" % template_type) + + return env, template, outputname + +##################################################################################### + +def add_fragments(doc, filename): + + fragments = doc.get('extends_documentation_fragment', []) + + if isinstance(fragments, string_types): + fragments = [fragments] + + # Allow the module to specify a var other than DOCUMENTATION + # to pull the fragment from, using dot notation as a separator + for fragment_slug in fragments: + fragment_slug = fragment_slug.lower() + if '.' in fragment_slug: + fragment_name, fragment_var = fragment_slug.split('.', 1) + fragment_var = fragment_var.upper() + else: + fragment_name, fragment_var = fragment_slug, 'DOCUMENTATION' + + fragment_loader.add_directory('../module_utils/') + fragment_class = fragment_loader.get(fragment_name) + assert fragment_class is not None + + fragment_yaml = getattr(fragment_class, fragment_var, '{}') + fragment = AnsibleLoader(fragment_yaml, file_name=filename).get_single_data() + + if 'notes' in fragment: + notes = fragment.pop('notes') + if notes: + if 'notes' not in doc: + doc['notes'] = [] + doc['notes'].extend(notes) + + if 'options' not in fragment and 'logging_options' not in fragment and 'connection_options' not in fragment: + raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename)) + + for key, value in iteritems(fragment): + if key in doc: + # assumes both structures have same type + if isinstance(doc[key], MutableMapping): + value.update(doc[key]) + elif isinstance(doc[key], MutableSet): + value.add(doc[key]) + elif isinstance(doc[key], MutableSequence): + value = sorted(frozenset(value + doc[key])) + else: + raise Exception("Attempt to extend a documentation fragement (%s) of unknown type: %s" % (fragment_name, filename)) + doc[key] = value + + + +def get_docstring(filename, verbose=False): + """ + DOCUMENTATION can be extended using documentation fragments loaded by the PluginLoader from the module_docs_fragments directory. + """ + + data = read_docstring(filename, verbose=verbose) + + # add fragments to documentation + if data.get('doc', False): + add_fragments(data['doc'], filename) + + return data['doc'], data['plainexamples'], data['returndocs'], data['metadata'] + +def process_module(fname, template, outputname, aliases=None): + + module_name = fname.replace(".py", "") + + print_("Processing module %s" % (MODULEDIR + fname)) + doc, examples, returndocs, metadata = get_docstring(MODULEDIR + fname, + verbose=True) + + # add some defaults for plugins that dont have most of the info + doc['module'] = doc.get('module', module_name) + doc['version_added'] = doc.get('version_added', 'historical') + doc['plugin_type'] = 'module' + + required_fields = ('short_description',) + for field in required_fields: + if field not in doc: + print_("%s: WARNING: MODULE MISSING field '%s'" % (fname, field)) + + not_nullable_fields = ('short_description',) + for field in not_nullable_fields: + if field in doc and doc[field] in (None, ''): + print_("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field])) + + # + # The present template gets everything from doc so we spend most of this + # function moving data into doc for the template to reference + # + + if aliases: + doc['aliases'] = aliases + + # don't show version added information if it's too old to be called out + added = 0 + if doc['version_added'] == 'historical': + del doc['version_added'] + else: + added = doc['version_added'] + + # Strip old version_added for the module + if too_old(added): + del doc['version_added'] + + option_names = [] + if 'options' in doc and doc['options']: + for (k, v) in iteritems(doc['options']): + # Error out if there's no description + if 'description' not in doc['options'][k]: + raise AnsibleError("Missing required description for option %s in %s " % (k, module)) + + # Error out if required isn't a boolean (people have been putting + # information on when something is required in here. Those need + # to go in the description instead). + required_value = doc['options'][k].get('required', False) + if not isinstance(required_value, bool): + raise AnsibleError("Invalid required value '%s' for option '%s' in '%s' (must be truthy)" % ( + required_value, k, module)) + + # Strip old version_added information for options + if 'version_added' in doc['options'][k] and too_old(doc['options'][k]['version_added']): + del doc['options'][k]['version_added'] + + # Make sure description is a list of lines for later formatting + if not isinstance(doc['options'][k]['description'], list): + doc['options'][k]['description'] = [doc['options'][k]['description']] + option_names.append(k) + option_names.sort() + doc['option_keys'] = option_names + + connection_option_names = [] + if 'connection_options' in doc and doc['connection_options']: + for (k, v) in iteritems(doc['connection_options']): + # Error out if there's no description + if 'description' not in doc['connection_options'][k]: + raise AnsibleError("Missing required description for connection_option %s in %s " % (k, module)) + + # Error out if required isn't a boolean (people have been putting + # information on when something is required in here. Those need + # to go in the description instead). + required_value = doc['connection_options'][k].get('required', False) + if not isinstance(required_value, bool): + raise AnsibleError("Invalid required value '%s' for connection_option '%s' in '%s' (must be truthy)" % + (required_value, k, module)) + + # Strip old version_added information for options + if ('version_added' in doc['connection_options'][k] and + too_old(doc['connection_options'][k]['version_added'])): + del doc['connection_options'][k]['version_added'] + + # Make sure description is a list of lines for later formatting + if not isinstance(doc['connection_options'][k]['description'], list): + doc['connection_options'][k]['description'] = [doc['connection_options'][k]['description']] + connection_option_names.append(k) + connection_option_names.sort() + doc['connection_option_keys'] = connection_option_names + + logging_option_names = [] + if 'logging_options' in doc and doc['logging_options']: + for (k, v) in iteritems(doc['logging_options']): + # Error out if there's no description + if 'description' not in doc['logging_options'][k]: + raise AnsibleError("Missing required description for logging_option %s in %s " % (k, module)) + + # Error out if required isn't a boolean (people have been putting + # information on when something is required in here. Those need + # to go in the description instead). + required_value = doc['logging_options'][k].get('required', False) + if not isinstance(required_value, bool): + raise AnsibleError("Invalid required value '%s' for logging_option '%s' in '%s' (must be truthy)" % + (required_value, k, module)) + + # Strip old version_added information for options + if ('version_added' in doc['logging_options'][k] and + too_old(doc['logging_options'][k]['version_added'])): + del doc['logging_options'][k]['version_added'] + + # Make sure description is a list of lines for later formatting + if not isinstance(doc['logging_options'][k]['description'], list): + doc['logging_options'][k]['description'] = [doc['logging_options'][k]['description']] + logging_option_names.append(k) + logging_option_names.sort() + doc['logging_option_keys'] = logging_option_names + + doc['filename'] = fname + doc['docuri'] = doc['module'].replace('_', '-') + doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') + doc['ansible_version'] = ansible_version + doc['plainexamples'] = examples # plain text + doc['metadata'] = metadata + + if returndocs: + try: + doc['returndocs'] = yaml.safe_load(returndocs) + returndocs_keys = list(doc['returndocs'].keys()) + returndocs_keys.sort() + doc['returndocs_keys'] = returndocs_keys + except Exception as e: + print_("%s:%s:yaml error:%s:returndocs=%s" % (fname, module_name, e, returndocs)) + doc['returndocs'] = None + doc['returndocs_keys'] = None + else: + doc['returndocs'] = None + doc['returndocs_keys'] = None + + doc['author'] = doc.get('author', ['UNKNOWN']) + if isinstance(doc['author'], string_types): + doc['author'] = [doc['author']] + + # here is where we build the table of contents... + text = template.render(doc) + write_data(text, outputname, module_name, OUTPUTDIR) + +##################################################################################### + + +def main(): + + env, template, outputname = jinja2_environment('.', 'rst') + module_names = [] + + for module in os.listdir(MODULEDIR): + if module.startswith(MODULE_NAME_STARTS_WITH): + process_module(module, template, outputname) + module_names.append(module.replace(".py", "")) + + index_file_path = os.path.join(OUTPUTDIR, "index.rst") + index_file = open(index_file_path, "w") + index_file.write('Juniper.junos Ansible Modules\n') + index_file.write('=================================================\n') + index_file.write('\n') + index_file.write('Contents:\n') + index_file.write('\n') + index_file.write('.. toctree::\n') + index_file.write(' :maxdepth: 1\n') + index_file.write('\n') + + for module_name in module_names: + index_file.write(' %s\n' % module_name) + +if __name__ == '__main__': + main() diff --git a/ansible_collection/Juniper/junos/docs/conf.py b/ansible_collection/Juniper/junos/docs/conf.py new file mode 100644 index 00000000..3371c88b --- /dev/null +++ b/ansible_collection/Juniper/junos/docs/conf.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- +# +# Junos Ansible Modules documentation build configuration file, created by +# sphinx-quickstart on Fri Jul 11 17:28:14 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import sphinx_bootstrap_theme + + +def setup(app): + app.add_stylesheet("juniper-junos-modules.css") + + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(1, os.path.abspath('..')) + +# Import ansible2rst so that RST files can be generated. +import ansible2rst +# Call ansible2rst.main() to generate RST files. +ansible2rst.main() + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +#templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Junos Ansible Modules' +copyright = u'2014-2017, Juniper Networks, Inc' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +from version import VERSION +# The short X.Y version. +version = VERSION +# The full version, including alpha/beta/rc tags. +release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'bootstrap' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { + 'bootswatch_theme': "spacelab", + 'navbar_sidebarrel': False, + 'navbar_site_name': "Modules", + 'source_link_position': "footer", + 'navbar_links': [ + ("Wiki", "https://techwiki.juniper.net/Automation_Scripting", True), + ("Forum", "http://groups.google.com/group/junos-python-ez", True), + ], + } + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = '_static/juniper.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': [ + 'globaltoc.html', + ] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'JunosAnsibleModulesdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'JunosAnsibleModules.tex', u'Junos Ansible Modules Documentation', + u'Juniper Networks, Inc.', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'junosansiblemodules', u'Junos Ansible Modules Documentation', + [u'Juniper Networks, Inc.'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'JunosAnsibleModules', u'Junos Ansible Modules Documentation', + u'Juniper Networks, Inc.', 'JunosAnsibleModules', 'Ansible Modules for ' + 'Junos', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/ansible_collection/Juniper/junos/docs/docreq.txt b/ansible_collection/Juniper/junos/docs/docreq.txt new file mode 100644 index 00000000..bc96b3b0 --- /dev/null +++ b/ansible_collection/Juniper/junos/docs/docreq.txt @@ -0,0 +1,2 @@ +git+https://github.com/ryan-roemer/sphinx-bootstrap-theme.git#egg=sphinx-bootstrap-theme +ansible diff --git a/ansible_collection/Juniper/junos/docs/rst.j2 b/ansible_collection/Juniper/junos/docs/rst.j2 new file mode 100644 index 00000000..3b7338f0 --- /dev/null +++ b/ansible_collection/Juniper/junos/docs/rst.j2 @@ -0,0 +1,560 @@ +.. _@{ module }@: + +{% set title = module %} +{% set title_len = title|length %} +@{ title }@ +@{ '+' * title_len }@ +{% if short_description %} +@{ short_description|convert_symbols_to_format }@ +{% endif %} + +{% if version_added is defined and version_added != '' -%} +.. versionadded:: @{ version_added | default('') }@ + + +{% endif %} + + +.. contents:: + :local: + :depth: 2 + +{# ------------------------------------------ + # + # Please note: this looks like a core dump + # but it isn't one. + # + --------------------------------------------#} +{% if deprecated is defined -%} + + +DEPRECATED +---------- + +{# use unknown here? skip the fields? #} +:In: version: @{ deprecated['version'] | default('') | string | convert_symbols_to_format }@ +:Why: @{ deprecated['why'] | default('') | convert_symbols_to_format }@ +:Alternative: @{ deprecated['alternative'] | default('')| convert_symbols_to_format }@ + + +{% endif %} + +Synopsis +-------- + +{% if description %} + +{% for desc in description -%} +* @{ desc | convert_symbols_to_format }@ +{% endfor %} + + +{% endif %} +{% if aliases is defined -%} + +Aliases: @{ ','.join(aliases) }@ + + +{% endif %} +{% if requirements %} + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +{% for req in requirements %} +* @{ req | convert_symbols_to_format }@ +{% endfor %} + + + +{% endif %} +{% if options -%} + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + +{% for k in option_keys -%} +{% set v = options[k] -%} +{% if not v['suboptions'] %} + + + + + + +{% if v.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + + + + + + + + + + +{% endif %} + + +{% endfor %} + +
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v.description is string %} +
@{ v.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +{% else %} + +
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +
+ + + + + + + + + + + +{% for k2 in v['suboptions'] %} +{% set v2 = v['suboptions'] [k2] %} + + + + + + +{% if v2.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + +{% endfor %} + +
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v2.description is string %} +
@{ v2.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v2.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v2.aliases %} +
aliases: @{ v2.aliases|join(', ') }@
+{% endif %} +
+ +
+
+ +{% endif %} +{% if connection_options -%} + + +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + +{% for k in connection_option_keys -%} +{% set v = connection_options[k] -%} +{% if not v['suboptions'] %} + + + + + + +{% if v.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + + + + + + + + + + +{% endif %} + + +{% endfor %} + +
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v.description is string %} +
@{ v.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +{% else %} + +
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +
+ + + + + + + + + + + +{% for k2 in v['suboptions'] %} +{% set v2 = v['suboptions'] [k2] %} + + + + + + +{% if v2.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + +{% endfor %} + +
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v2.description is string %} +
@{ v2.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v2.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v2.aliases %} +
aliases: @{ v2.aliases|join(', ') }@
+{% endif %} +
+ +
+
+ +{% endif %} +{% if logging_options -%} + + +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + +{% for k in logging_option_keys -%} +{% set v = logging_options[k] -%} +{% if not v['suboptions'] %} + + + + + + +{% if v.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + + + + + + + + + + +{% endif %} + + +{% endfor %} + +
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v.description is string %} +
@{ v.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +{% else %} + +
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} +{% for desc in v.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% if 'aliases' in v and v.aliases %} +
aliases: @{ v.aliases|join(', ') }@
+{% endif %} +
+ + + + + + + + + + + +{% for k2 in v['suboptions'] %} +{% set v2 = v['suboptions'] [k2] %} + + + + + + +{% if v2.get('type', 'not_bool') == 'bool' %} + +{% else %} + +{% endif %} + + +{% endfor %} + +
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
+{% if v2.description is string %} +
@{ v2.description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in v2.description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +{% if 'aliases' in v and v2.aliases %} +
aliases: @{ v2.aliases|join(', ') }@
+{% endif %} +
+ +
+
+ +{% endif %} +{% if examples or plainexamples -%} +.. _@{ title }@-examples-label: + +Examples +-------- + +:: + +{% for example in examples %} +{% if example['description'] %} +@{ example['description'] }@ +{% endif %} +@{ example['code'] | escape | indent(4, True) }@ +{% endfor %} +{% if plainexamples %} +@{ plainexamples | indent(4, True) }@ +{% endif %} +{% endif %} + + +{% if returndocs -%} + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + +{% for entry in returndocs_keys %} + + + + + + + + +{% if returndocs[entry].type == 'complex' %} + + + + + +{% endif %} +{% endfor %} + +
namedescriptionreturnedtypesample
@{ entry }@ +{% if returndocs[entry].description is string %} +
@{ returndocs[entry].description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in returndocs[entry].description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +
@{ returndocs[entry].returned | html_ify }@@{ returndocs[entry].type | html_ify }@@{ returndocs[entry].sample | replace('\n', '\n ') | html_ify }@
contains: + + + + + + + + + +{% for sub in returndocs[entry].contains %} + + + + + + + + +{% endfor %} + +
namedescriptionreturnedtypesample
@{ sub }@ +{% if returndocs[entry].contains[sub].description is string %} +
@{ returndocs[entry].contains[sub].description | replace('\n', '\n ') | html_ify }@
+{% else %} +{% for desc in returndocs[entry].contains[sub].description %} +
@{ desc | replace('\n', '\n ') | html_ify }@
+{% endfor %} +{% endif %} +
@{ returndocs[entry].contains[sub].returned | html_ify }@@{ returndocs[entry].contains[sub].type | html_ify }@@{ returndocs[entry].contains[sub].sample }@
+
+
+
+{% endif %} + + +{% if notes -%} + + +Notes +----- + +.. note:: +{% for note in notes %} + - @{ note | convert_symbols_to_format }@ +{% endfor %} + + +{% endif %} +{% if author is defined -%} + + +Author +~~~~~~ + +{% for author_name in author %} +* @{ author_name }@ +{% endfor %} + + +{% endif %} +{% if not deprecated %} +{% set support = { 'core': 'The Ansible Core Team', 'network': 'The Ansible Network Team', 'certified': 'an Ansible Partner', 'community': 'The Ansible Community', 'curated': 'A Third Party'} %} +{% set module_states = { 'preview': 'it is not guaranteed to have a backwards compatible interface', 'stableinterface': 'the maintainers for this module guarantee that no backward incompatible interface changes will be made'} %} +{% if metadata %} +{% if metadata.status %} + + +Status +~~~~~~ + +{% for cur_state in metadata.status %} +This module is flagged as **@{cur_state}@** which means that @{module_states[cur_state]}@. +{% endfor %} + + +{% endif %} +{% endif %} +{% endif %} diff --git a/ansible_collection/Juniper/junos/galaxy.yml b/ansible_collection/Juniper/junos/galaxy.yml new file mode 100644 index 00000000..30d1584e --- /dev/null +++ b/ansible_collection/Juniper/junos/galaxy.yml @@ -0,0 +1,57 @@ +### REQUIRED + +# The namespace of the collection. This can be a company/brand/organization or product namespace under which all +# content lives. May only contain alphanumeric characters and underscores. Additionally namespaces cannot start with +# underscores or numbers and cannot contain consecutive underscores +namespace: Juniper + +# The name of the collection. Has the same character restrictions as 'namespace' +name: junos + +# The version of the collection. Must be compatible with semantic versioning +version: 1.0.0 + +# The path to the Markdown (.md) readme file. This path is relative to the root of the collection +readme: README.md + +# A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) +# @nicks:irc/im.site#channel' +authors: +- your name + + +### OPTIONAL but strongly recommended + +# A short summary description of the collection +description: your collection description + +# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only +# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' +license: +- GPL-2.0-or-later + +# The path to the license file for the collection. This path is relative to the root of the collection. This key is +# mutually exclusive with 'license' +license_file: '' + +# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character +# requirements as 'namespace' and 'name' +tags: [] + +# Collections that this collection requires to be installed for it to be usable. The key of the dict is the +# collection label 'namespace.name'. The value is a version range +# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version +# range specifiers can be set and are separated by ',' +dependencies: {} + +# The URL of the originating SCM repository +repository: http://example.com/repository + +# The URL to any online docs +documentation: http://docs.example.com + +# The URL to the homepage of the collection/project +homepage: http://example.com + +# The URL to the collection issue tracker +issues: http://example.com/issue/tracker diff --git a/ansible_collection/Juniper/junos/plugins/README.md b/ansible_collection/Juniper/junos/plugins/README.md new file mode 100644 index 00000000..6541cf7c --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/README.md @@ -0,0 +1,31 @@ +# Collections Plugins Directory + +This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that +is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that +would contain module utils and modules respectively. + +Here is an example directory of the majority of plugins currently supported by Ansible: + +``` +└── plugins + ├── action + ├── become + ├── cache + ├── callback + ├── cliconf + ├── connection + ├── filter + ├── httpapi + ├── inventory + ├── lookup + ├── module_utils + ├── modules + ├── netconf + ├── shell + ├── strategy + ├── terminal + ├── test + └── vars +``` + +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/2.9/plugins/plugins.html). \ No newline at end of file diff --git a/action_plugins/juniper_junos_common_action.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_command.py similarity index 100% rename from action_plugins/juniper_junos_common_action.py rename to ansible_collection/Juniper/junos/plugins/action/juniper_junos_command.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_common_action.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_common_action.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_common_action.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_config.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_config.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_config.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_facts.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_facts.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_facts.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_jsnapy.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_jsnapy.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_jsnapy.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_ping.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_ping.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_ping.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_pmtud.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_pmtud.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_pmtud.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_rpc.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_rpc.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_rpc.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_software.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_software.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_software.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_system.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_system.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_system.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_table.py b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_table.py new file mode 100755 index 00000000..a6b8336f --- /dev/null +++ b/ansible_collection/Juniper/junos/plugins/action/juniper_junos_table.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from ansible.plugins.action.normal import ActionModule as ActionNormal +import os + + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + + +# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py +# Use the custom behavior defined below as our ActionModule. +# The Ansible core engine will call ActionModule.run() +class ActionModule(ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + + All juniper_junos_* modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + # Get the current connection args from either provider or the top-level + if 'provider' in self._task.args: + connection_args = self._task.args['provider'] + else: + connection_args = self._task.args + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + # Copy the new connection arguments back into either top-level or + # the provider dictionary. + if 'provider' in self._task.args: + self._task.args['provider'].update(new_connection_args) + else: + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/callback_plugins/jsnapy.py b/ansible_collection/Juniper/junos/plugins/callback/jsnapy.py similarity index 76% rename from callback_plugins/jsnapy.py rename to ansible_collection/Juniper/junos/plugins/callback/jsnapy.py index 826192c8..66f1dec9 100644 --- a/callback_plugins/jsnapy.py +++ b/ansible_collection/Juniper/junos/plugins/callback/jsnapy.py @@ -86,3 +86,25 @@ def v2_playbook_on_stats(self, stats): str(testlet['xpath']), json.dumps(data)), color=C.COLOR_ERROR) + + elif testlet['count']['pass'] != 0 : + if not has_printed_banner: + self._display.banner("JSNAPy Results for: " + str(host)) + has_printed_banner = True + + for test in testlet['passed']: + + # Check if POST exist in the response + data = '' + if 'post' in test: + data = test['post'] + else: + data = test + + self._display.display( + "Value of '{0}' '{1}' at '{2}' with {3}".format( + str(testlet['node_name']), + str(testlet['testoperation']), + str(testlet['xpath']), + json.dumps(data)), + color=C.COLOR_DEBUG) diff --git a/module_utils/__init__.py b/ansible_collection/Juniper/junos/plugins/module_utils/__init__.py similarity index 100% rename from module_utils/__init__.py rename to ansible_collection/Juniper/junos/plugins/module_utils/__init__.py diff --git a/module_utils/juniper_junos_common.py b/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py similarity index 100% rename from module_utils/juniper_junos_common.py rename to ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py diff --git a/library/juniper_junos_command.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_command.py similarity index 99% rename from library/juniper_junos_command.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_command.py index 9a2b66a6..259ef687 100644 --- a/library/juniper_junos_command.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_command.py @@ -309,8 +309,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/library/juniper_junos_config.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_config.py similarity index 99% rename from library/juniper_junos_config.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_config.py index 10101d42..6cbeba61 100644 --- a/library/juniper_junos_config.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_config.py @@ -740,8 +740,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Choices which are defined in the common module. diff --git a/library/juniper_junos_facts.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_facts.py similarity index 99% rename from library/juniper_junos_facts.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_facts.py index 3c5fc811..75d167fd 100644 --- a/library/juniper_junos_facts.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_facts.py @@ -177,10 +177,9 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common from ansible.module_utils._text import to_bytes - def get_facts_dict(junos_module): """Retreive PyEZ facts and convert to a standard dict w/o custom types. diff --git a/library/juniper_junos_jsnapy.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py similarity index 99% rename from library/juniper_junos_jsnapy.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py index f5669c30..581d454b 100644 --- a/library/juniper_junos_jsnapy.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py @@ -209,8 +209,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] diff --git a/library/juniper_junos_ping.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_ping.py similarity index 99% rename from library/juniper_junos_ping.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_ping.py index ce6c81ec..230031ca 100644 --- a/library/juniper_junos_ping.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_ping.py @@ -386,8 +386,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # The argument spec for the module. diff --git a/library/juniper_junos_pmtud.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_pmtud.py similarity index 99% rename from library/juniper_junos_pmtud.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_pmtud.py index 7947b7bf..c5f12dd8 100644 --- a/library/juniper_junos_pmtud.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_pmtud.py @@ -248,8 +248,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Constants for MTU size diff --git a/library/juniper_junos_rpc.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_rpc.py similarity index 99% rename from library/juniper_junos_rpc.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_rpc.py index 8f848f35..f23e310b 100644 --- a/library/juniper_junos_rpc.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_rpc.py @@ -376,8 +376,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/library/juniper_junos_software.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_software.py similarity index 99% rename from library/juniper_junos_software.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_software.py index ccfa5a72..8fd2bfce 100644 --- a/library/juniper_junos_software.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_software.py @@ -391,8 +391,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def parse_version_from_filename(filename): """Attempts to parse a version string from the filename of a Junos package. diff --git a/library/juniper_junos_srx_cluster.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py similarity index 99% rename from library/juniper_junos_srx_cluster.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py index 4fb7b71a..acfd5a42 100644 --- a/library/juniper_junos_srx_cluster.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py @@ -150,8 +150,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/library/juniper_junos_system.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_system.py similarity index 99% rename from library/juniper_junos_system.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_system.py index b186f037..d9a2ea18 100644 --- a/library/juniper_junos_system.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_system.py @@ -250,8 +250,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/library/juniper_junos_table.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py similarity index 99% rename from library/juniper_junos_table.py rename to ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py index 73916673..0be48943 100644 --- a/library/juniper_junos_table.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py @@ -292,8 +292,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def expand_items(module, data): """Recursively expand any table items diff --git a/ansible_collection/Juniper/junos/tests/.travis.yml b/ansible_collection/Juniper/junos/tests/.travis.yml new file mode 100644 index 00000000..482d79d0 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/.travis.yml @@ -0,0 +1,30 @@ +language: python +python: + - 2.7 + - 3.6 + +sudo: required +dist: trusty + +env: + - ANSIBLE_VERSION=2.4.0.0 + - ANSIBLE_VERSION=2.3.0.0 + +install: +## Create Docker with Ansible modules and all dependancies + - docker build --build-arg ver_ansible=$ANSIBLE_VERSION -t juniper/pyez-ansible:travis . + - docker pull juniper/ravello-ansible:v0.1 +## Install Ansible locally for Ravello and install Roles + - cd tests + +script: +## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses +## Anyone can connect here to see the list of applications running and see the VMs +## https://cloud.ravellosystems.com/#/GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf/apps + - docker run -t -i -v $(pwd):/project -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" juniper/ravello-ansible:v0.1 ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml + - docker run -t -i -v $(pwd):/project -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" juniper/ravello-ansible:v0.1 ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml + +## Execute Tests with Docker + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_pmtud.yaml + - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_jsnapy.yaml diff --git a/ansible_collection/Juniper/junos/tests/ansible.cfg b/ansible_collection/Juniper/junos/tests/ansible.cfg new file mode 100644 index 00000000..4e457e28 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/ansible.cfg @@ -0,0 +1,5 @@ + +[defaults] +hash_behaviour=merge +roles_path = /Users/rahkumar/PycharmProjects +#callback_whitelist = jsnapy \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set b/ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set new file mode 100644 index 00000000..6019602d --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set @@ -0,0 +1,3 @@ +set routing-instances LO10 interface lo0.10 +set routing-instances LO10 instance-type virtual-router +set interfaces lo0 unit 10 family inet diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set b/ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set new file mode 100644 index 00000000..818ff206 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set @@ -0,0 +1,2 @@ +delete routing-instances LO10 +delete interfaces lo0 unit 10 diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml new file mode 100644 index 00000000..3473c27e --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml @@ -0,0 +1,11 @@ +tests_include: + - check_storage + +check_storage: + - command: show system storage + - iterate: + xpath: //system-storage-information/filesystem[normalize-space(mounted-on)='/'] + tests: + - is-lt: used-percent, 95 + info: "File system {{post['mounted-on']}} use less than 95%" + err: "File system {{post['mounted-on']}} use {{post['used-percent']}} %" diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml new file mode 100644 index 00000000..cd27467d --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml @@ -0,0 +1,16 @@ +tests_include: + - test_command_version + +test_command_version: + - command: show interfaces terse lo* + - iterate: + xpath: //logical-interface + id: './name' + tests: + - list-not-more: admin-status + err: "Test Failed!! name list changed, <{{pre['admin-status']}}> with name <{{id_0}}> is not found in post snapshot" + info: "Test successful!! name list is same, with name <{{id_0}}>" + + - list-not-less: admin-status + err: "Test Failed!! name list changed, <{{pre['admin-status']}}> with name <{{id_0}}> was not present in PRE snapshot" + info: "Test successful!! name list is same, with name <{{id_0}}>" diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml new file mode 100644 index 00000000..ed2571c8 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml @@ -0,0 +1,12 @@ +tests_include: + - test_version_check + +test_version_check: + - command: show version + - iterate: + id: host-name + xpath: //software-information + tests: + - exists: //package-information/name + info: "Test Succeeded!! node //package-information/name exists with name <{{pre['//package-information/name']}}> and hostname: <{{id_0}} > " + err: "Test Failed!!! node //package-information/name does not exists in hostname: <{{id_0}}> !! " diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml new file mode 100644 index 00000000..5ecbc4cf --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml @@ -0,0 +1,118 @@ +--- +- name: Test juniper_junos_config module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: +################# + - name: Retrieve the committed configuration + juniper_junos_config: + retrieve: 'committed' + diff: false + check: false + commit: false + register: test1 + ignore_errors: True + tags: [ test1 ] + + - name: Check TEST 1 + assert: + that: + - test1.config + - "'host-name choc-qfx-a;' in test1.config" +################# + - name: Append .foo to the hostname using private config mode. + juniper_junos_config: + config_mode: 'private' + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + register: test2 + ignore_errors: True + tags: [ test2 ] + + - name: Check TEST 2 + assert: + that: + - test2.diff_lines + - "'+ host-name choc-qfx-a.foo;' in test2.diff_lines" +################# + - name: Rollback to the previous config. + juniper_junos_config: + config_mode: 'private' + rollback: "1" + register: test3 + ignore_errors: True + tags: [ test3 ] + + - name: Check TEST 3 + assert: + that: + - test3.diff_lines + - "'- host-name choc-qfx-a.foo;' in test3.diff_lines" +################# + - name: Creates directory + file: + path: out + state: directory + + - name: Configure LLDP + juniper_junos_config: + load: 'merge' + lines: + - "set protocols lldp advertisement-interval 30" + - "set protocols lldp transmit-delay 2" + - "set protocols lldp hold-multiplier 4" + - "set protocols lldp ptopo-configuration-trap-interval 30" + - "set protocols lldp ptopo-configuration-maximum-hold-time 300" + - "set protocols lldp lldp-configuration-notification-interval 30" + - "set protocols lldp interface all disable" + - "set protocols lldp interface ge-1/1/1" + format: 'set' + comment: 'Start LLDP with given options' + dest_dir: './out' + register: test4 + ignore_errors: True + tags: [ test4 ] + + - name: Rollback to the rescue config. + juniper_junos_config: + rollback: 'rescue' + + - name: Check out/choc-qfx-a.diff exists + stat: + path: out/choc-qfx-a.diff + register: stat_result_1 + + - name: Check TEST 4 + assert: + that: + - stat_result_1.stat.exists == True + - test4.diff_lines + - "'+ interface ge-1/1/1;' in test4.diff_lines" + + - name: Clean up TEST 4 + file: + path: out + state: absent +################# + - name: Retrieve [edit system services] of current committed config. + juniper_junos_config: + retrieve: 'committed' + filter: 'system/services' + diff: true + check: false + commit: false + register: test5 + ignore_errors: True + tags: [ test5 ] + + - name: Check TEST 5 + assert: + that: + - test5.failed == False + - "'system {' in test5.config_lines" +################# +#TODO: Add tests for commit check and commit confirmed workflows diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml new file mode 100644 index 00000000..c585b025 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml @@ -0,0 +1,26 @@ +--- +- name: Test juniper_junos_facts module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Gather Facts" + juniper_junos_facts: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + ignore_errors: True + register: test1 + + # - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1.facts.hostname + - test1.facts.serialnumber + - test1.facts.model + - test1.facts.fqdn diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml new file mode 100644 index 00000000..4318c76f --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml @@ -0,0 +1,217 @@ +--- +- name: Test juniper_junos_ping module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: +################################################## +#### TEST 1 ## +################################################## + - name: "TEST 1 - Execute SNAPCHECK with 1 test file / no dir" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_junos_storage.yml + action: snapcheck + register: test1 + ignore_errors: True + tags: [ test1 ] + # - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1|succeeded + - test1.passPercentage == 100 + - test1.total_tests == 1 + tags: [ test1 ] + +################################################## +#### TEST 2 ## +################################################## + - name: "TEST 2 - Execute SNAPCHECK with 2 test file & dir" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: + - test_junos_storage.yml + - test_version.yml + dir: junos_jsnapy + action: snapcheck + register: test2 + ignore_errors: True + tags: [ test2 ] + # - debug: var=test2 + + - name: Check TEST 2 + assert: + that: + - test2|succeeded + - test2.passPercentage == 100 + - test2.total_tests == 2 + tags: [ test2 ] + +################################################## +#### TEST 3 ## +################################################## + - name: "TEST 3 - Wrong test file" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: file_that_doesnt_exist.yml + action: snapcheck + register: test3 + ignore_errors: True + tags: [ test3 ] + # - debug: var=test3 + + - name: Check TEST 3 + assert: + that: + - test3|failed + tags: [ test3 ] + +################################################## +#### TEST 4 ## +################################################## + - name: "TEST 4 - SNAP_PRE" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: snap_pre + register: test4 + ignore_errors: True + tags: [ test4 ] + + # - debug: var=test4 + + - name: Check TEST 4 + assert: + that: + - test4|succeeded + tags: [ test4 ] + +################################################## +#### TEST 5 ## +################################################## + - name: "TEST 5 - SNAP_POST" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: snap_post + register: test5 + ignore_errors: True + tags: [ test5 ] + + # - debug: var=test5 + + - name: Check TEST 5 + assert: + that: + - test5|succeeded + tags: [ test5 ] + +################################################## +#### TEST 6 ## +################################################## + - name: "TEST 6 - CHECK" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: check + register: test6 + ignore_errors: True + tags: [ test6 ] + + - debug: var=test6 + + - name: Check TEST 6 + assert: + that: + - test6|succeeded + - test6.passPercentage == 100 + tags: [ test6 ] + +################################################## +#### TEST 7 ## +################################################## + - name: "PRE-TEST 7 - Add loopback address" + juniper_junos_config: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + load: 'merge' + file: junos_jsnapy/add_loopback.set + register: test7_1 + ignore_errors: True + tags: [ test7 ] + + - name: Wait for loopback to come up + pause: seconds=15 + + - name: "TEST 7 - SNAP_POST with additional loopback" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: snap_post + register: test7_2 + ignore_errors: True + tags: [ test7 ] + # - debug: var=pretest7 + + - name: "TEST 7 - CHECK" + juniper_junos_jsnapy: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + test_files: junos_jsnapy/test_loopback.yml + action: check + register: test7 + ignore_errors: True + tags: [ test7 ] + - debug: var=test7 + + - name: "TEST 7 - Cleanup" + juniper_junos_config: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + file: junos_jsnapy/delete_loopback.set + load: 'merge' + register: test7_3 + ignore_errors: True + tags: [ test7 ] + + - name: Check TEST 7 + assert: + that: + - test7_1|succeeded + - test7_2|succeeded + - test7_3|succeeded + - test7|succeeded + - test7.passPercentage == 50 + - test7.total_tests == 2 + tags: [ test7 ] diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml new file mode 100644 index 00000000..c8fb77c2 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml @@ -0,0 +1,102 @@ +--- +- name: Test juniper_junos_ping module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Ping Google DNS" + juniper_junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + register: test1 + ignore_errors: True +# - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - test1.packet_loss == '0' + +############ + + - name: "TEST 2 - Ping Wrong IP" + juniper_junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.1.1 + register: test2 + ignore_errors: True +# - debug: var=test2 + + - name: Check TEST 2 + assert: + that: + - test2.packet_loss == '100' +################# + + - name: "TEST 3 - Change nbr packets" + juniper_junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + count: 3 + register: test3 + ignore_errors: True +# - debug: var=test3 + + - name: Check TEST 3 + assert: + that: + - test3.packets_sent == '3' + +################# + + - name: "TEST 4 - Ping with DF-bit set" + juniper_junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + count: 3 + do_not_fragment: True + size: 64 + register: test4 + ignore_errors: True +# - debug: var=test4 + + - name: Check TEST 4 + assert: + that: + - test4.packets_received == '3' + +################# + + - name: "TEST 5 - Ping with DF-bit set and size that well exceeds jumbo sizes" + juniper_junos_ping: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + count: 3 + do_not_fragment: True + size: 9999 + register: test5 + ignore_errors: True +# - debug: var=test5 + + - name: Check TEST 5 + assert: + that: + - test5.packets_received == '0' + diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml new file mode 100644 index 00000000..cdf3fb41 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml @@ -0,0 +1,23 @@ +--- +- name: Test juniper_junos_pmtud module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: "TEST 1 - Check path MTU to Google DNS" + juniper_junos_pmtud: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + dest_ip: 8.8.8.8 + register: test1 + ignore_errors: True + - debug: var=test1 + + - name: Check TEST 1 + assert: + that: + - 768 <= test1.inet_mtu <= 1500 diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml new file mode 100644 index 00000000..22a195da --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml @@ -0,0 +1,217 @@ +--- +- name: Test juniper_junos_rpc module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: +################# + - name: "Execute single RPC get-software-information without any kwargs" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + register: test1 + ignore_errors: True + tags: [ test1 ] + + - name: Check TEST 1 + assert: + that: + - test1.msg == "The RPC executed successfully." + tags: [ test1 ] + +################# + - name: "Get Device Configuration with dest" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpc: get-config + dest: get_config.conf + register: test2 + ignore_errors: True + tags: [ test2 ] + + - name: Check that the get_config.conf exists + stat: + path: get_config.conf + register: stat_result + + - name: Check TEST 2 + assert: + that: + - test2.msg == "The \"get-config\" RPC executed successfully." + - stat_result.stat.exists == True + tags: [ test2 ] + + - name: Clean up TEST 2 + file: + path: get_config.conf + state: absent +################# + + - name: "Get Device Configuration in text" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpc: get-interface-information + kwargs: "interface_name=em0" + format: text + register: test3 + ignore_errors: True + tags: [ test3 ] + + - name: Check TEST 3 + assert: + that: + - test3.msg == "The RPC executed successfully." + tags: [ test3 ] + +################# + + - name: "Execute multiple RPCs without any kwargs" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + - "get-interface-information" + dest: get_config.conf + register: test4 + ignore_errors: True + tags: [ test4 ] + + - name: Check TEST 4 + assert: + that: + - test4.results[0].msg == "The RPC executed successfully." + - test4.results[1].msg == "The RPC executed successfully." + tags: [ test4 ] + + - name: Clean up TEST 4 + file: + path: get_config.conf + state: absent + +################# + + - name: "Execute multiple RPCs with multiple kwargs" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + - "get-interface-information" + kwargs: + - {} + - "interface_name=em0" + register: test5 + ignore_errors: True + tags: [ test5 ] + + - name: Check TEST 5 + assert: + that: + - test5.results[0].msg == "The RPC executed successfully." + - test5.results[1].msg == "The RPC executed successfully." + tags: [ test5 ] + +################# + - name: Creates directory + file: + path: out + state: directory + + - name: "Execute multiple RPCs with multiple kwargs and dest-dir" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "get-software-information" + - "get-interface-information" + kwargs: + - {} + - "interface_name=em0" + dest_dir: "out" + register: test6 + ignore_errors: True + tags: [ test6 ] + + - name: Check get-interface-information.xml exists + stat: + path: "out/{{ ansible_ssh_host }}_get-interface-information.xml" + register: stat_result_1 + + - name: Check get-software-information.xml exists + stat: + path: "out/{{ ansible_ssh_host }}_get-software-information.xml" + register: stat_result_2 + + - name: Check TEST 6 + assert: + that: + - test6.results[0].msg == "The RPC executed successfully." + - test6.results[1].msg == "The RPC executed successfully." + - stat_result_1.stat.exists == True + - stat_result_2.stat.exists == True + tags: [ test6 ] + + - name: Clean up TEST 6 + file: + path: out + state: absent + +################# + - name: Get Device Configuration for interface + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpc: get-config + filter_xml: "" + register: test7 + ignore_errors: True + tags: [ test7 ] + + - name: Check TEST 7 + assert: + that: + - test7.msg == "The \"get-config\" RPC executed successfully." + tags: [ test7 ] + +################# + - name: "Execute wrong RPC to generate RPC error" + juniper_junos_rpc: + host: "{{ ansible_ssh_host }}" + port: "{{ ansible_ssh_port }}" + user: "{{ ansible_ssh_user }}" + passwd: "{{ ansible_ssh_pass }}" + rpcs: + - "wrong-rpc" + register: test8 + ignore_errors: True + tags: [ test8 ] + + - name: Check TEST 8 + assert: + that: + - '"Unable to execute the RPC" in test8.msg' + tags: [ test8 ] + +################# diff --git a/ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml b/ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml new file mode 100644 index 00000000..e1d8fadc --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml @@ -0,0 +1,34 @@ +--- +- name: Get FQDN for all VM on Ravello + connection: local + hosts: all + gather_facts: no + roles: + - ravello.lib + + tasks: +############################### +## Get VM ID ### +############################### + - name: Get App ID from Ravello + ravello_get_id: + resource_name: "{{ ravello_ci_app_name }}" + resource_type: applications + token: "{{ ravello_ci_token }}" + register: app + run_once: true + + +############################### +## Delete the application ## +############################### + - name: Stop Application on Ravello + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/stop" + method: POST + status_code: 202 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + run_once: true + changed_when: true diff --git a/ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml b/ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml new file mode 100644 index 00000000..4f789e35 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml @@ -0,0 +1,85 @@ +--- +- name: Create Application on Ravello for CI + connection: local + hosts: all + gather_facts: no + roles: + - ravello.lib + vars: + ravello_deploy_topology_cloud: AMAZON + ravello_deploy_topology_region: Oregon + ravello_deploy_topology_optimization: PERFORMANCE_OPTIMIZED + ravello_deploy_topology_start_all: true + + tasks: + - name: Create Application from Blueprint for CI + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/" + method: POST + status_code: 201 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + body: + name: "{{ ravello_ci_app_name }}" + description: "App created by Travis CI" + baseBlueprintId: "{{ ravello_ci_blueprint }}" + body_format: json + run_once: true + changed_when: true + + - name: Get App ID from Ravello + ravello_get_id: + resource_name: "{{ ravello_ci_app_name }}" + resource_type: applications + token: "{{ ravello_ci_token }}" + register: app + run_once: true + + - debug: var=app + run_once: true + +####################################################### +## Deploy Application(s) ## +####################################################### + - name: Deploy Application On Ravello + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/publish" + method: POST + status_code: 202 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + body: > + { + "preferredCloud": "{{ ravello_deploy_topology_cloud }}", + "preferredRegion": "{{ ravello_deploy_topology_region }}", + "optimizationLevel": "{{ ravello_deploy_topology_optimization }}", + "startAllVms": "{{ ravello_deploy_topology_start_all }}" + } + body_format: json + run_once: true + +######################################## +## Set application Expiration time ## +######################################## + - name: Set Application Expiration time + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/setExpiration" + method: POST + status_code: 200 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + body: '{ "expirationFromNowSeconds": {{ ravello_ci_expiration_time_min * 60 }} }' + body_format: json + run_once: true + + - name: Wait for devices to come up + pause: minutes=5 + + - name: Wait for devices to come up + pause: minutes=5 + + - name: Wait for devices to come up + pause: minutes=2 diff --git a/ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml b/ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml new file mode 100644 index 00000000..6304c2cf --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml @@ -0,0 +1,54 @@ +--- +- name: Get FQDN for all VM on Ravello + connection: local + hosts: junos + gather_facts: no + roles: + - ravello.lib + + tasks: + +############################### +## Get VM ID ### +############################### + - name: Get App ID from Ravello + ravello_get_id: + resource_name: "{{ ravello_ci_app_name }}" + resource_type: applications + token: "{{ ravello_ci_token }}" + register: app + run_once: true + + # - debug: var=app + + - name: Get VM ID from Ravello + ravello_get_id: + application_id: "{{ app.json.id }}" + resource_type: vms + resource_name: "{{ inventory_hostname }}" + token: "{{ ravello_ci_token }}" + failed_if_not_found: true + register: vm + + # - debug: var=vm + + - name: Get VM public FQDN + uri: + url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/vms/{{ vm.json.id }}/fqdn;deployment" + method: GET + status_code: 200 + HEADER_Content-Type: 'application/json' + HEADER_Accept: 'application/json' + HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" + register: ravello_public_ip + + - name: Delete previous file + file: + path: "host_vars/{{ inventory_hostname}}/fqdn.yaml" + state: absent + + - name: Populate ansible_ssh_host Variable based on FQDN + lineinfile: + create: yes + dest: "host_vars/{{ inventory_hostname}}/fqdn.yaml" + line: "ansible_ssh_host: {{ ravello_public_ip.json.value }}" diff --git a/ansible_collection/Juniper/junos/tests/ravello.ini b/ansible_collection/Juniper/junos/tests/ravello.ini new file mode 100644 index 00000000..36fffd6d --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/ravello.ini @@ -0,0 +1,18 @@ +[all:children] +junos + +[junos] +vqfx-01 + +################################### +### Define variables per groups ### +################################### +[all:vars] +ansible_ssh_user=root +ansible_ssh_pass=Juniper +ansible_ssh_port=22 +ravello_ci_app_name="Ansible-junos-stdlib Ansible_{{ lookup('env','ANSIBLE_VERSION') }} Travis_{{ lookup('env','TRAVIS_JOB_ID') }} {{ lookup('env','TRAVIS_COMMIT') }}" +ravello_ci_blueprint="75695295" +ravello_ci_token="GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf" +ravello_ci_expiration_time_min=50 +ansible_python_interpreter=/Users/rahkumar/PycharmProjects/Juniper.junos/venv38/bin/python \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tests/test.yml b/ansible_collection/Juniper/junos/tests/test.yml new file mode 100644 index 00000000..6fa4cd80 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/test.yml @@ -0,0 +1,27 @@ +--- +- name: Test juniper_junos_ping module + hosts: all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + +############# + - name: 'Performa a snap_check of storage' + juniper_junos_jsnapy: + host: "10.221.130.141" + user: "regress" + passwd: "MaRtInI" + action: "snapcheck" + test_files: "test_case.yaml" + register: response + + - name: "Print the response" + debug: + var: response + + - name: Verify all JSNAPy tests passed + assert: + that: + - "response.passPercentage == 100" \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tests/test_case.yaml b/ansible_collection/Juniper/junos/tests/test_case.yaml new file mode 100644 index 00000000..9e713545 --- /dev/null +++ b/ansible_collection/Juniper/junos/tests/test_case.yaml @@ -0,0 +1,20 @@ +tests_include: + - test_interfaces_down + - test_interface_up + +test_interfaces_down: + - command: show interfaces terse + - iterate: + id: ./name + xpath: //physical-interface + tests: + - is-equal: admin-status, down + + +test_interface_up: + - command: show interfaces terse + - iterate: + id: ./name + xpath: //physical-interface + tests: + - is-equal: admin-status, up \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tools/sw_upgrade b/ansible_collection/Juniper/junos/tools/sw_upgrade new file mode 100755 index 00000000..ec5cb8b4 --- /dev/null +++ b/ansible_collection/Juniper/junos/tools/sw_upgrade @@ -0,0 +1,129 @@ +#!/usr/bin/env python2.7 + +import argparse +import os, sys, re +import logging +from getpass import getpass +from jnpr.junos import Device + +JUNOSDIR = '/usr/local/junos' +PACKAGEDIR = JUNOSDIR + '/packages' +LOGDIR = JUNOSDIR + '/log' + + +def die(message, errno=1): + sys.stderr.write("ERROR:{0}\n".format(message)) + sys.exit(errno) + +# ------------------------------------------------------------------------- +# CLI args processing +# ------------------------------------------------------------------------- + + +def cli_args(): + p = argparse.ArgumentParser(add_help=True) + + # ------------------------------------------------------------------------- + # login + # ------------------------------------------------------------------------- + + p.add_argument('hostname', nargs='?', + help='hostname or ipaddr') + + p.add_argument('-u', '--user', default=os.getenv('USER'), + help='login user name, defaults to $USER') + + p.add_argument('-P', '--passwd', default='', + help='login user password, defaults assumes ssh-keys') + + p.add_argument('-k', action='store_true', default=False, + dest='passwd_prompt', + help='prompt for user password') + + # ------------------------------------------------------------------------- + # softawre + # ------------------------------------------------------------------------- + + p.add_argument('-v', '--version', + help="Junos version string for checking device facts") + + p.add_argument('-p', '--package', + help='Junos package file') + + # ------------------------------------------------------------------------- + # modes/flags/etc. + # ------------------------------------------------------------------------- + + p.add_argument('--dry-run', dest='dry_run_mode', action='store_true', + help='Check for need to upgrade, but do not do it') + + args = p.parse_args() + if args.passwd_prompt is True: + args.passwd = getpass() + + if args.version is None: + # extract from package file + m = re.search('-([^\\-]*)-domestic.*', args.package) + args.version = m.group(1) + + if args.version is None: + die("No version-string") + + return args + +# ------------------------------------------------------------------------- +# software upgrade process +# ------------------------------------------------------------------------- + + +def update_my_progress(dev, report): + logging.info(report) + + +def do_sw_upgrade(dev): + from jnpr.junos.utils.sw import SW + sw = SW(dev) + + logfile = LOGDIR + '/' + args.hostname + '.log' + + logging.basicConfig(filename=logfile, level=logging.INFO, + format='%(asctime)s:%(name)s:%(message)s') + logging.getLogger().name = args.hostname + + print "logging to file: {0}".format(logfile) + + logging.info(" Starting the software upgrade process: %s", args.package) + ok = sw.install(args.package, progress=update_my_progress) + if ok is not True: + die("Unable to install software") + logging.info("") + rsp = sw.reboot() + +# ------------------------------------------------------------------------- +# MAIN +# ------------------------------------------------------------------------- + +args = cli_args() +dev = Device(args.hostname, user=args.user, password=args.passwd) +try: + print "{0}@{1} connecting ...".format(args.user, args.hostname) + dev.open() +except: + die("Unable to connect to device: {0}".format(args.hostname)) + +has_ver = dev.facts['version'] +should_ver = args.version +need_upgrade = bool(has_ver != should_ver) +y_n = ('no', 'yes')[need_upgrade].upper() +print "UPGRADE={0}::HAS:{1} == SHOULD:{2}".format(y_n, has_ver, should_ver) + +if args.dry_run_mode is True: + dev.close() + sys.exit(0) + +if need_upgrade is False: + dev.close() + sys.exit(0) + +do_sw_upgrade(dev) +dev.close() From 412a54e96824d86d87c530186c2ff17486e3c022 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 4 Mar 2020 12:41:20 +0530 Subject: [PATCH 295/426] Adding soft-links to ansible-collection folders --- action_plugins | 1 + callback_plugins | 1 + library | 1 + module_utils | 1 + 4 files changed, 4 insertions(+) create mode 120000 action_plugins create mode 120000 callback_plugins create mode 120000 library create mode 120000 module_utils diff --git a/action_plugins b/action_plugins new file mode 120000 index 00000000..3836d008 --- /dev/null +++ b/action_plugins @@ -0,0 +1 @@ +ansible_collection/Juniper/junos/plugins/action \ No newline at end of file diff --git a/callback_plugins b/callback_plugins new file mode 120000 index 00000000..672f918b --- /dev/null +++ b/callback_plugins @@ -0,0 +1 @@ +ansible_collection/Juniper/junos/plugins/callback \ No newline at end of file diff --git a/library b/library new file mode 120000 index 00000000..de06a658 --- /dev/null +++ b/library @@ -0,0 +1 @@ +ansible_collection/Juniper/junos/plugins/modules \ No newline at end of file diff --git a/module_utils b/module_utils new file mode 120000 index 00000000..356a3b32 --- /dev/null +++ b/module_utils @@ -0,0 +1 @@ +ansible_collection/Juniper/junos/plugins/module_utils \ No newline at end of file From 4a6f863215fe751570e594f06297b3ecac84fe1b Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 5 Mar 2020 10:31:47 +0530 Subject: [PATCH 296/426] Updating galaxy.yaml and removing test cases from collection directory. --- ansible_collection/Juniper/junos/galaxy.yml | 23 +- .../Juniper/junos/tests/.travis.yml | 30 --- .../Juniper/junos/tests/ansible.cfg | 5 - .../junos/tests/junos_jsnapy/add_loopback.set | 3 - .../tests/junos_jsnapy/delete_loopback.set | 2 - .../tests/junos_jsnapy/test_junos_storage.yml | 11 - .../tests/junos_jsnapy/test_loopback.yml | 16 -- .../junos/tests/junos_jsnapy/test_version.yml | 12 - .../junos/tests/pb.juniper_junos_config.yml | 118 ---------- .../junos/tests/pb.juniper_junos_facts.yml | 26 --- .../junos/tests/pb.juniper_junos_jsnapy.yml | 217 ------------------ .../junos/tests/pb.juniper_junos_ping.yml | 102 -------- .../junos/tests/pb.juniper_junos_pmtud.yml | 23 -- .../junos/tests/pb.juniper_junos_rpc.yml | 217 ------------------ .../junos/tests/pb.rav.token.app_stop.yml | 34 --- .../tests/pb.rav.token.create-deploy.yml | 85 ------- .../junos/tests/pb.rav.token.fqdn_get.yml | 54 ----- .../Juniper/junos/tests/ravello.ini | 18 -- .../Juniper/junos/tests/test.yml | 27 --- .../Juniper/junos/tests/test_case.yaml | 20 -- .../Juniper/junos/tools/sw_upgrade | 129 ----------- 21 files changed, 10 insertions(+), 1162 deletions(-) delete mode 100644 ansible_collection/Juniper/junos/tests/.travis.yml delete mode 100644 ansible_collection/Juniper/junos/tests/ansible.cfg delete mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set delete mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set delete mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml delete mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml delete mode 100644 ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml delete mode 100644 ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml delete mode 100644 ansible_collection/Juniper/junos/tests/ravello.ini delete mode 100644 ansible_collection/Juniper/junos/tests/test.yml delete mode 100644 ansible_collection/Juniper/junos/tests/test_case.yaml delete mode 100755 ansible_collection/Juniper/junos/tools/sw_upgrade diff --git a/ansible_collection/Juniper/junos/galaxy.yml b/ansible_collection/Juniper/junos/galaxy.yml index 30d1584e..1b9593db 100644 --- a/ansible_collection/Juniper/junos/galaxy.yml +++ b/ansible_collection/Juniper/junos/galaxy.yml @@ -9,7 +9,7 @@ namespace: Juniper name: junos # The version of the collection. Must be compatible with semantic versioning -version: 1.0.0 +version: 0.1.0 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md @@ -17,26 +17,23 @@ readme: README.md # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) # @nicks:irc/im.site#channel' authors: -- your name - - -### OPTIONAL but strongly recommended +- Jeremy # A short summary description of the collection -description: your collection description +description: set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' license: -- GPL-2.0-or-later +- Apache-2.0-or-later # The path to the license file for the collection. This path is relative to the root of the collection. This key is # mutually exclusive with 'license' -license_file: '' +license_file: 'LICENSE' # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' -tags: [] +tags: ['Juniper', 'junos', 'network'] # Collections that this collection requires to be installed for it to be usable. The key of the dict is the # collection label 'namespace.name'. The value is a version range @@ -45,13 +42,13 @@ tags: [] dependencies: {} # The URL of the originating SCM repository -repository: http://example.com/repository +repository: https://github.com/Juniper/ansible-junos-stdlib # The URL to any online docs -documentation: http://docs.example.com +documentation: https://github.com/Juniper/ansible-junos-stdlib # The URL to the homepage of the collection/project -homepage: http://example.com +homepage: https://github.com/Juniper/ansible-junos-stdlib # The URL to the collection issue tracker -issues: http://example.com/issue/tracker +issues: https://github.com/Juniper/ansible-junos-stdlib/issues diff --git a/ansible_collection/Juniper/junos/tests/.travis.yml b/ansible_collection/Juniper/junos/tests/.travis.yml deleted file mode 100644 index 482d79d0..00000000 --- a/ansible_collection/Juniper/junos/tests/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: python -python: - - 2.7 - - 3.6 - -sudo: required -dist: trusty - -env: - - ANSIBLE_VERSION=2.4.0.0 - - ANSIBLE_VERSION=2.3.0.0 - -install: -## Create Docker with Ansible modules and all dependancies - - docker build --build-arg ver_ansible=$ANSIBLE_VERSION -t juniper/pyez-ansible:travis . - - docker pull juniper/ravello-ansible:v0.1 -## Install Ansible locally for Ravello and install Roles - - cd tests - -script: -## Start Virtual topology on Ravello with 2 VQFX and collect IP addresses -## Anyone can connect here to see the list of applications running and see the VMs -## https://cloud.ravellosystems.com/#/GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf/apps - - docker run -t -i -v $(pwd):/project -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" juniper/ravello-ansible:v0.1 ansible-playbook -i ravello.ini pb.rav.token.create-deploy.yaml - - docker run -t -i -v $(pwd):/project -e "ANSIBLE_VERSION=$(echo $ANSIBLE_VERSION)" -e "TRAVIS_JOB_ID=$(echo $TRAVIS_JOB_ID)" -e "TRAVIS_COMMIT=$(echo $TRAVIS_COMMIT)" juniper/ravello-ansible:v0.1 ansible-playbook -i ravello.ini pb.rav.token.fqdn_get.yaml - -## Execute Tests with Docker - - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_ping.yaml - - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_pmtud.yaml - - docker run -t -i -v $(pwd):/project juniper/pyez-ansible:travis ansible-playbook -i ravello.ini pb.junos_jsnapy.yaml diff --git a/ansible_collection/Juniper/junos/tests/ansible.cfg b/ansible_collection/Juniper/junos/tests/ansible.cfg deleted file mode 100644 index 4e457e28..00000000 --- a/ansible_collection/Juniper/junos/tests/ansible.cfg +++ /dev/null @@ -1,5 +0,0 @@ - -[defaults] -hash_behaviour=merge -roles_path = /Users/rahkumar/PycharmProjects -#callback_whitelist = jsnapy \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set b/ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set deleted file mode 100644 index 6019602d..00000000 --- a/ansible_collection/Juniper/junos/tests/junos_jsnapy/add_loopback.set +++ /dev/null @@ -1,3 +0,0 @@ -set routing-instances LO10 interface lo0.10 -set routing-instances LO10 instance-type virtual-router -set interfaces lo0 unit 10 family inet diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set b/ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set deleted file mode 100644 index 818ff206..00000000 --- a/ansible_collection/Juniper/junos/tests/junos_jsnapy/delete_loopback.set +++ /dev/null @@ -1,2 +0,0 @@ -delete routing-instances LO10 -delete interfaces lo0 unit 10 diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml deleted file mode 100644 index 3473c27e..00000000 --- a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_junos_storage.yml +++ /dev/null @@ -1,11 +0,0 @@ -tests_include: - - check_storage - -check_storage: - - command: show system storage - - iterate: - xpath: //system-storage-information/filesystem[normalize-space(mounted-on)='/'] - tests: - - is-lt: used-percent, 95 - info: "File system {{post['mounted-on']}} use less than 95%" - err: "File system {{post['mounted-on']}} use {{post['used-percent']}} %" diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml deleted file mode 100644 index cd27467d..00000000 --- a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_loopback.yml +++ /dev/null @@ -1,16 +0,0 @@ -tests_include: - - test_command_version - -test_command_version: - - command: show interfaces terse lo* - - iterate: - xpath: //logical-interface - id: './name' - tests: - - list-not-more: admin-status - err: "Test Failed!! name list changed, <{{pre['admin-status']}}> with name <{{id_0}}> is not found in post snapshot" - info: "Test successful!! name list is same, with name <{{id_0}}>" - - - list-not-less: admin-status - err: "Test Failed!! name list changed, <{{pre['admin-status']}}> with name <{{id_0}}> was not present in PRE snapshot" - info: "Test successful!! name list is same, with name <{{id_0}}>" diff --git a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml b/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml deleted file mode 100644 index ed2571c8..00000000 --- a/ansible_collection/Juniper/junos/tests/junos_jsnapy/test_version.yml +++ /dev/null @@ -1,12 +0,0 @@ -tests_include: - - test_version_check - -test_version_check: - - command: show version - - iterate: - id: host-name - xpath: //software-information - tests: - - exists: //package-information/name - info: "Test Succeeded!! node //package-information/name exists with name <{{pre['//package-information/name']}}> and hostname: <{{id_0}} > " - err: "Test Failed!!! node //package-information/name does not exists in hostname: <{{id_0}}> !! " diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml deleted file mode 100644 index 5ecbc4cf..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_config.yml +++ /dev/null @@ -1,118 +0,0 @@ ---- -- name: Test juniper_junos_config module - hosts: all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: -################# - - name: Retrieve the committed configuration - juniper_junos_config: - retrieve: 'committed' - diff: false - check: false - commit: false - register: test1 - ignore_errors: True - tags: [ test1 ] - - - name: Check TEST 1 - assert: - that: - - test1.config - - "'host-name choc-qfx-a;' in test1.config" -################# - - name: Append .foo to the hostname using private config mode. - juniper_junos_config: - config_mode: 'private' - load: 'merge' - lines: - - "set system host-name {{ inventory_hostname }}.foo" - register: test2 - ignore_errors: True - tags: [ test2 ] - - - name: Check TEST 2 - assert: - that: - - test2.diff_lines - - "'+ host-name choc-qfx-a.foo;' in test2.diff_lines" -################# - - name: Rollback to the previous config. - juniper_junos_config: - config_mode: 'private' - rollback: "1" - register: test3 - ignore_errors: True - tags: [ test3 ] - - - name: Check TEST 3 - assert: - that: - - test3.diff_lines - - "'- host-name choc-qfx-a.foo;' in test3.diff_lines" -################# - - name: Creates directory - file: - path: out - state: directory - - - name: Configure LLDP - juniper_junos_config: - load: 'merge' - lines: - - "set protocols lldp advertisement-interval 30" - - "set protocols lldp transmit-delay 2" - - "set protocols lldp hold-multiplier 4" - - "set protocols lldp ptopo-configuration-trap-interval 30" - - "set protocols lldp ptopo-configuration-maximum-hold-time 300" - - "set protocols lldp lldp-configuration-notification-interval 30" - - "set protocols lldp interface all disable" - - "set protocols lldp interface ge-1/1/1" - format: 'set' - comment: 'Start LLDP with given options' - dest_dir: './out' - register: test4 - ignore_errors: True - tags: [ test4 ] - - - name: Rollback to the rescue config. - juniper_junos_config: - rollback: 'rescue' - - - name: Check out/choc-qfx-a.diff exists - stat: - path: out/choc-qfx-a.diff - register: stat_result_1 - - - name: Check TEST 4 - assert: - that: - - stat_result_1.stat.exists == True - - test4.diff_lines - - "'+ interface ge-1/1/1;' in test4.diff_lines" - - - name: Clean up TEST 4 - file: - path: out - state: absent -################# - - name: Retrieve [edit system services] of current committed config. - juniper_junos_config: - retrieve: 'committed' - filter: 'system/services' - diff: true - check: false - commit: false - register: test5 - ignore_errors: True - tags: [ test5 ] - - - name: Check TEST 5 - assert: - that: - - test5.failed == False - - "'system {' in test5.config_lines" -################# -#TODO: Add tests for commit check and commit confirmed workflows diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml deleted file mode 100644 index c585b025..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_facts.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -- name: Test juniper_junos_facts module - hosts: all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: - - name: "TEST 1 - Gather Facts" - juniper_junos_facts: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - ignore_errors: True - register: test1 - - # - debug: var=test1 - - - name: Check TEST 1 - assert: - that: - - test1.facts.hostname - - test1.facts.serialnumber - - test1.facts.model - - test1.facts.fqdn diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml deleted file mode 100644 index 4318c76f..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_jsnapy.yml +++ /dev/null @@ -1,217 +0,0 @@ ---- -- name: Test juniper_junos_ping module - hosts: all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: -################################################## -#### TEST 1 ## -################################################## - - name: "TEST 1 - Execute SNAPCHECK with 1 test file / no dir" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: junos_jsnapy/test_junos_storage.yml - action: snapcheck - register: test1 - ignore_errors: True - tags: [ test1 ] - # - debug: var=test1 - - - name: Check TEST 1 - assert: - that: - - test1|succeeded - - test1.passPercentage == 100 - - test1.total_tests == 1 - tags: [ test1 ] - -################################################## -#### TEST 2 ## -################################################## - - name: "TEST 2 - Execute SNAPCHECK with 2 test file & dir" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: - - test_junos_storage.yml - - test_version.yml - dir: junos_jsnapy - action: snapcheck - register: test2 - ignore_errors: True - tags: [ test2 ] - # - debug: var=test2 - - - name: Check TEST 2 - assert: - that: - - test2|succeeded - - test2.passPercentage == 100 - - test2.total_tests == 2 - tags: [ test2 ] - -################################################## -#### TEST 3 ## -################################################## - - name: "TEST 3 - Wrong test file" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: file_that_doesnt_exist.yml - action: snapcheck - register: test3 - ignore_errors: True - tags: [ test3 ] - # - debug: var=test3 - - - name: Check TEST 3 - assert: - that: - - test3|failed - tags: [ test3 ] - -################################################## -#### TEST 4 ## -################################################## - - name: "TEST 4 - SNAP_PRE" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: junos_jsnapy/test_loopback.yml - action: snap_pre - register: test4 - ignore_errors: True - tags: [ test4 ] - - # - debug: var=test4 - - - name: Check TEST 4 - assert: - that: - - test4|succeeded - tags: [ test4 ] - -################################################## -#### TEST 5 ## -################################################## - - name: "TEST 5 - SNAP_POST" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: junos_jsnapy/test_loopback.yml - action: snap_post - register: test5 - ignore_errors: True - tags: [ test5 ] - - # - debug: var=test5 - - - name: Check TEST 5 - assert: - that: - - test5|succeeded - tags: [ test5 ] - -################################################## -#### TEST 6 ## -################################################## - - name: "TEST 6 - CHECK" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: junos_jsnapy/test_loopback.yml - action: check - register: test6 - ignore_errors: True - tags: [ test6 ] - - - debug: var=test6 - - - name: Check TEST 6 - assert: - that: - - test6|succeeded - - test6.passPercentage == 100 - tags: [ test6 ] - -################################################## -#### TEST 7 ## -################################################## - - name: "PRE-TEST 7 - Add loopback address" - juniper_junos_config: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - load: 'merge' - file: junos_jsnapy/add_loopback.set - register: test7_1 - ignore_errors: True - tags: [ test7 ] - - - name: Wait for loopback to come up - pause: seconds=15 - - - name: "TEST 7 - SNAP_POST with additional loopback" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: junos_jsnapy/test_loopback.yml - action: snap_post - register: test7_2 - ignore_errors: True - tags: [ test7 ] - # - debug: var=pretest7 - - - name: "TEST 7 - CHECK" - juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - test_files: junos_jsnapy/test_loopback.yml - action: check - register: test7 - ignore_errors: True - tags: [ test7 ] - - debug: var=test7 - - - name: "TEST 7 - Cleanup" - juniper_junos_config: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - file: junos_jsnapy/delete_loopback.set - load: 'merge' - register: test7_3 - ignore_errors: True - tags: [ test7 ] - - - name: Check TEST 7 - assert: - that: - - test7_1|succeeded - - test7_2|succeeded - - test7_3|succeeded - - test7|succeeded - - test7.passPercentage == 50 - - test7.total_tests == 2 - tags: [ test7 ] diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml deleted file mode 100644 index c8fb77c2..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_ping.yml +++ /dev/null @@ -1,102 +0,0 @@ ---- -- name: Test juniper_junos_ping module - hosts: all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: - - name: "TEST 1 - Ping Google DNS" - juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - dest_ip: 8.8.8.8 - register: test1 - ignore_errors: True -# - debug: var=test1 - - - name: Check TEST 1 - assert: - that: - - test1.packet_loss == '0' - -############ - - - name: "TEST 2 - Ping Wrong IP" - juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - dest_ip: 8.8.1.1 - register: test2 - ignore_errors: True -# - debug: var=test2 - - - name: Check TEST 2 - assert: - that: - - test2.packet_loss == '100' -################# - - - name: "TEST 3 - Change nbr packets" - juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - dest_ip: 8.8.8.8 - count: 3 - register: test3 - ignore_errors: True -# - debug: var=test3 - - - name: Check TEST 3 - assert: - that: - - test3.packets_sent == '3' - -################# - - - name: "TEST 4 - Ping with DF-bit set" - juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - dest_ip: 8.8.8.8 - count: 3 - do_not_fragment: True - size: 64 - register: test4 - ignore_errors: True -# - debug: var=test4 - - - name: Check TEST 4 - assert: - that: - - test4.packets_received == '3' - -################# - - - name: "TEST 5 - Ping with DF-bit set and size that well exceeds jumbo sizes" - juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - dest_ip: 8.8.8.8 - count: 3 - do_not_fragment: True - size: 9999 - register: test5 - ignore_errors: True -# - debug: var=test5 - - - name: Check TEST 5 - assert: - that: - - test5.packets_received == '0' - diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml deleted file mode 100644 index cdf3fb41..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_pmtud.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -- name: Test juniper_junos_pmtud module - hosts: all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: - - name: "TEST 1 - Check path MTU to Google DNS" - juniper_junos_pmtud: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - dest_ip: 8.8.8.8 - register: test1 - ignore_errors: True - - debug: var=test1 - - - name: Check TEST 1 - assert: - that: - - 768 <= test1.inet_mtu <= 1500 diff --git a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml b/ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml deleted file mode 100644 index 22a195da..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.juniper_junos_rpc.yml +++ /dev/null @@ -1,217 +0,0 @@ ---- -- name: Test juniper_junos_rpc module - hosts: all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: -################# - - name: "Execute single RPC get-software-information without any kwargs" - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpcs: - - "get-software-information" - register: test1 - ignore_errors: True - tags: [ test1 ] - - - name: Check TEST 1 - assert: - that: - - test1.msg == "The RPC executed successfully." - tags: [ test1 ] - -################# - - name: "Get Device Configuration with dest" - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpc: get-config - dest: get_config.conf - register: test2 - ignore_errors: True - tags: [ test2 ] - - - name: Check that the get_config.conf exists - stat: - path: get_config.conf - register: stat_result - - - name: Check TEST 2 - assert: - that: - - test2.msg == "The \"get-config\" RPC executed successfully." - - stat_result.stat.exists == True - tags: [ test2 ] - - - name: Clean up TEST 2 - file: - path: get_config.conf - state: absent -################# - - - name: "Get Device Configuration in text" - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpc: get-interface-information - kwargs: "interface_name=em0" - format: text - register: test3 - ignore_errors: True - tags: [ test3 ] - - - name: Check TEST 3 - assert: - that: - - test3.msg == "The RPC executed successfully." - tags: [ test3 ] - -################# - - - name: "Execute multiple RPCs without any kwargs" - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpcs: - - "get-software-information" - - "get-interface-information" - dest: get_config.conf - register: test4 - ignore_errors: True - tags: [ test4 ] - - - name: Check TEST 4 - assert: - that: - - test4.results[0].msg == "The RPC executed successfully." - - test4.results[1].msg == "The RPC executed successfully." - tags: [ test4 ] - - - name: Clean up TEST 4 - file: - path: get_config.conf - state: absent - -################# - - - name: "Execute multiple RPCs with multiple kwargs" - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpcs: - - "get-software-information" - - "get-interface-information" - kwargs: - - {} - - "interface_name=em0" - register: test5 - ignore_errors: True - tags: [ test5 ] - - - name: Check TEST 5 - assert: - that: - - test5.results[0].msg == "The RPC executed successfully." - - test5.results[1].msg == "The RPC executed successfully." - tags: [ test5 ] - -################# - - name: Creates directory - file: - path: out - state: directory - - - name: "Execute multiple RPCs with multiple kwargs and dest-dir" - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpcs: - - "get-software-information" - - "get-interface-information" - kwargs: - - {} - - "interface_name=em0" - dest_dir: "out" - register: test6 - ignore_errors: True - tags: [ test6 ] - - - name: Check get-interface-information.xml exists - stat: - path: "out/{{ ansible_ssh_host }}_get-interface-information.xml" - register: stat_result_1 - - - name: Check get-software-information.xml exists - stat: - path: "out/{{ ansible_ssh_host }}_get-software-information.xml" - register: stat_result_2 - - - name: Check TEST 6 - assert: - that: - - test6.results[0].msg == "The RPC executed successfully." - - test6.results[1].msg == "The RPC executed successfully." - - stat_result_1.stat.exists == True - - stat_result_2.stat.exists == True - tags: [ test6 ] - - - name: Clean up TEST 6 - file: - path: out - state: absent - -################# - - name: Get Device Configuration for interface - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpc: get-config - filter_xml: "" - register: test7 - ignore_errors: True - tags: [ test7 ] - - - name: Check TEST 7 - assert: - that: - - test7.msg == "The \"get-config\" RPC executed successfully." - tags: [ test7 ] - -################# - - name: "Execute wrong RPC to generate RPC error" - juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" - rpcs: - - "wrong-rpc" - register: test8 - ignore_errors: True - tags: [ test8 ] - - - name: Check TEST 8 - assert: - that: - - '"Unable to execute the RPC" in test8.msg' - tags: [ test8 ] - -################# diff --git a/ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml b/ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml deleted file mode 100644 index e1d8fadc..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.rav.token.app_stop.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -- name: Get FQDN for all VM on Ravello - connection: local - hosts: all - gather_facts: no - roles: - - ravello.lib - - tasks: -############################### -## Get VM ID ### -############################### - - name: Get App ID from Ravello - ravello_get_id: - resource_name: "{{ ravello_ci_app_name }}" - resource_type: applications - token: "{{ ravello_ci_token }}" - register: app - run_once: true - - -############################### -## Delete the application ## -############################### - - name: Stop Application on Ravello - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/stop" - method: POST - status_code: 202 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - run_once: true - changed_when: true diff --git a/ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml b/ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml deleted file mode 100644 index 4f789e35..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.rav.token.create-deploy.yml +++ /dev/null @@ -1,85 +0,0 @@ ---- -- name: Create Application on Ravello for CI - connection: local - hosts: all - gather_facts: no - roles: - - ravello.lib - vars: - ravello_deploy_topology_cloud: AMAZON - ravello_deploy_topology_region: Oregon - ravello_deploy_topology_optimization: PERFORMANCE_OPTIMIZED - ravello_deploy_topology_start_all: true - - tasks: - - name: Create Application from Blueprint for CI - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/" - method: POST - status_code: 201 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - body: - name: "{{ ravello_ci_app_name }}" - description: "App created by Travis CI" - baseBlueprintId: "{{ ravello_ci_blueprint }}" - body_format: json - run_once: true - changed_when: true - - - name: Get App ID from Ravello - ravello_get_id: - resource_name: "{{ ravello_ci_app_name }}" - resource_type: applications - token: "{{ ravello_ci_token }}" - register: app - run_once: true - - - debug: var=app - run_once: true - -####################################################### -## Deploy Application(s) ## -####################################################### - - name: Deploy Application On Ravello - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/publish" - method: POST - status_code: 202 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - body: > - { - "preferredCloud": "{{ ravello_deploy_topology_cloud }}", - "preferredRegion": "{{ ravello_deploy_topology_region }}", - "optimizationLevel": "{{ ravello_deploy_topology_optimization }}", - "startAllVms": "{{ ravello_deploy_topology_start_all }}" - } - body_format: json - run_once: true - -######################################## -## Set application Expiration time ## -######################################## - - name: Set Application Expiration time - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/setExpiration" - method: POST - status_code: 200 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - body: '{ "expirationFromNowSeconds": {{ ravello_ci_expiration_time_min * 60 }} }' - body_format: json - run_once: true - - - name: Wait for devices to come up - pause: minutes=5 - - - name: Wait for devices to come up - pause: minutes=5 - - - name: Wait for devices to come up - pause: minutes=2 diff --git a/ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml b/ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml deleted file mode 100644 index 6304c2cf..00000000 --- a/ansible_collection/Juniper/junos/tests/pb.rav.token.fqdn_get.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -- name: Get FQDN for all VM on Ravello - connection: local - hosts: junos - gather_facts: no - roles: - - ravello.lib - - tasks: - -############################### -## Get VM ID ### -############################### - - name: Get App ID from Ravello - ravello_get_id: - resource_name: "{{ ravello_ci_app_name }}" - resource_type: applications - token: "{{ ravello_ci_token }}" - register: app - run_once: true - - # - debug: var=app - - - name: Get VM ID from Ravello - ravello_get_id: - application_id: "{{ app.json.id }}" - resource_type: vms - resource_name: "{{ inventory_hostname }}" - token: "{{ ravello_ci_token }}" - failed_if_not_found: true - register: vm - - # - debug: var=vm - - - name: Get VM public FQDN - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/vms/{{ vm.json.id }}/fqdn;deployment" - method: GET - status_code: 200 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - register: ravello_public_ip - - - name: Delete previous file - file: - path: "host_vars/{{ inventory_hostname}}/fqdn.yaml" - state: absent - - - name: Populate ansible_ssh_host Variable based on FQDN - lineinfile: - create: yes - dest: "host_vars/{{ inventory_hostname}}/fqdn.yaml" - line: "ansible_ssh_host: {{ ravello_public_ip.json.value }}" diff --git a/ansible_collection/Juniper/junos/tests/ravello.ini b/ansible_collection/Juniper/junos/tests/ravello.ini deleted file mode 100644 index 36fffd6d..00000000 --- a/ansible_collection/Juniper/junos/tests/ravello.ini +++ /dev/null @@ -1,18 +0,0 @@ -[all:children] -junos - -[junos] -vqfx-01 - -################################### -### Define variables per groups ### -################################### -[all:vars] -ansible_ssh_user=root -ansible_ssh_pass=Juniper -ansible_ssh_port=22 -ravello_ci_app_name="Ansible-junos-stdlib Ansible_{{ lookup('env','ANSIBLE_VERSION') }} Travis_{{ lookup('env','TRAVIS_JOB_ID') }} {{ lookup('env','TRAVIS_COMMIT') }}" -ravello_ci_blueprint="75695295" -ravello_ci_token="GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf" -ravello_ci_expiration_time_min=50 -ansible_python_interpreter=/Users/rahkumar/PycharmProjects/Juniper.junos/venv38/bin/python \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tests/test.yml b/ansible_collection/Juniper/junos/tests/test.yml deleted file mode 100644 index 6fa4cd80..00000000 --- a/ansible_collection/Juniper/junos/tests/test.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -- name: Test juniper_junos_ping module - hosts: all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: - -############# - - name: 'Performa a snap_check of storage' - juniper_junos_jsnapy: - host: "10.221.130.141" - user: "regress" - passwd: "MaRtInI" - action: "snapcheck" - test_files: "test_case.yaml" - register: response - - - name: "Print the response" - debug: - var: response - - - name: Verify all JSNAPy tests passed - assert: - that: - - "response.passPercentage == 100" \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tests/test_case.yaml b/ansible_collection/Juniper/junos/tests/test_case.yaml deleted file mode 100644 index 9e713545..00000000 --- a/ansible_collection/Juniper/junos/tests/test_case.yaml +++ /dev/null @@ -1,20 +0,0 @@ -tests_include: - - test_interfaces_down - - test_interface_up - -test_interfaces_down: - - command: show interfaces terse - - iterate: - id: ./name - xpath: //physical-interface - tests: - - is-equal: admin-status, down - - -test_interface_up: - - command: show interfaces terse - - iterate: - id: ./name - xpath: //physical-interface - tests: - - is-equal: admin-status, up \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/tools/sw_upgrade b/ansible_collection/Juniper/junos/tools/sw_upgrade deleted file mode 100755 index ec5cb8b4..00000000 --- a/ansible_collection/Juniper/junos/tools/sw_upgrade +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python2.7 - -import argparse -import os, sys, re -import logging -from getpass import getpass -from jnpr.junos import Device - -JUNOSDIR = '/usr/local/junos' -PACKAGEDIR = JUNOSDIR + '/packages' -LOGDIR = JUNOSDIR + '/log' - - -def die(message, errno=1): - sys.stderr.write("ERROR:{0}\n".format(message)) - sys.exit(errno) - -# ------------------------------------------------------------------------- -# CLI args processing -# ------------------------------------------------------------------------- - - -def cli_args(): - p = argparse.ArgumentParser(add_help=True) - - # ------------------------------------------------------------------------- - # login - # ------------------------------------------------------------------------- - - p.add_argument('hostname', nargs='?', - help='hostname or ipaddr') - - p.add_argument('-u', '--user', default=os.getenv('USER'), - help='login user name, defaults to $USER') - - p.add_argument('-P', '--passwd', default='', - help='login user password, defaults assumes ssh-keys') - - p.add_argument('-k', action='store_true', default=False, - dest='passwd_prompt', - help='prompt for user password') - - # ------------------------------------------------------------------------- - # softawre - # ------------------------------------------------------------------------- - - p.add_argument('-v', '--version', - help="Junos version string for checking device facts") - - p.add_argument('-p', '--package', - help='Junos package file') - - # ------------------------------------------------------------------------- - # modes/flags/etc. - # ------------------------------------------------------------------------- - - p.add_argument('--dry-run', dest='dry_run_mode', action='store_true', - help='Check for need to upgrade, but do not do it') - - args = p.parse_args() - if args.passwd_prompt is True: - args.passwd = getpass() - - if args.version is None: - # extract from package file - m = re.search('-([^\\-]*)-domestic.*', args.package) - args.version = m.group(1) - - if args.version is None: - die("No version-string") - - return args - -# ------------------------------------------------------------------------- -# software upgrade process -# ------------------------------------------------------------------------- - - -def update_my_progress(dev, report): - logging.info(report) - - -def do_sw_upgrade(dev): - from jnpr.junos.utils.sw import SW - sw = SW(dev) - - logfile = LOGDIR + '/' + args.hostname + '.log' - - logging.basicConfig(filename=logfile, level=logging.INFO, - format='%(asctime)s:%(name)s:%(message)s') - logging.getLogger().name = args.hostname - - print "logging to file: {0}".format(logfile) - - logging.info(" Starting the software upgrade process: %s", args.package) - ok = sw.install(args.package, progress=update_my_progress) - if ok is not True: - die("Unable to install software") - logging.info("") - rsp = sw.reboot() - -# ------------------------------------------------------------------------- -# MAIN -# ------------------------------------------------------------------------- - -args = cli_args() -dev = Device(args.hostname, user=args.user, password=args.passwd) -try: - print "{0}@{1} connecting ...".format(args.user, args.hostname) - dev.open() -except: - die("Unable to connect to device: {0}".format(args.hostname)) - -has_ver = dev.facts['version'] -should_ver = args.version -need_upgrade = bool(has_ver != should_ver) -y_n = ('no', 'yes')[need_upgrade].upper() -print "UPGRADE={0}::HAS:{1} == SHOULD:{2}".format(y_n, has_ver, should_ver) - -if args.dry_run_mode is True: - dev.close() - sys.exit(0) - -if need_upgrade is False: - dev.close() - sys.exit(0) - -do_sw_upgrade(dev) -dev.close() From c2da2b3da0f078e639e8d59ba348eceb3b974c92 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 5 Mar 2020 10:36:06 +0530 Subject: [PATCH 297/426] Merging changes for resolving conflict --- .../Juniper/junos/plugins/modules/juniper_junos_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py index 0be48943..ded0bcca 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py +++ b/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py @@ -328,7 +328,7 @@ def juniper_items_to_list_of_dicts(module, data): # table_fields - element 1 is also a list of tuples temp = {} for key, value in table_fields: - if (value and isinstance(value, module.pyez_factory_table.Table)): + if isinstance(value, module.pyez_factory_table.Table): value = juniper_items_to_list_of_dicts(module, value) temp[key] = value resources.append(temp) From ef81cd98d2c660d5faea7e9ffac4b9387fc0d802 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 5 Mar 2020 14:21:12 +0530 Subject: [PATCH 298/426] Modifying galaxy.yml and updating docs folder location --- COPYRIGHT | 2 +- ansible_collection/Juniper/junos/galaxy.yml | 6 +- docs | 1 + docs/Makefile | 234 -------- docs/_static/juniper-junos-modules.css | 7 - docs/_static/juniper.png | Bin 5044 -> 0 bytes docs/ansible2rst.py | 426 --------------- docs/conf.py | 285 ---------- docs/docreq.txt | 2 - docs/rst.j2 | 560 -------------------- 10 files changed, 5 insertions(+), 1518 deletions(-) create mode 120000 docs delete mode 100644 docs/Makefile delete mode 100644 docs/_static/juniper-junos-modules.css delete mode 100755 docs/_static/juniper.png delete mode 100755 docs/ansible2rst.py delete mode 100644 docs/conf.py delete mode 100644 docs/docreq.txt delete mode 100644 docs/rst.j2 diff --git a/COPYRIGHT b/COPYRIGHT index 63dd8797..5274b3e4 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,5 +1,5 @@ - Copyright (c) 1999-2018, Juniper Networks Inc. + Copyright (c) 1999-2020, Juniper Networks Inc. 2014, Jeremy Schulman All rights reserved. diff --git a/ansible_collection/Juniper/junos/galaxy.yml b/ansible_collection/Juniper/junos/galaxy.yml index 1b9593db..264702e4 100644 --- a/ansible_collection/Juniper/junos/galaxy.yml +++ b/ansible_collection/Juniper/junos/galaxy.yml @@ -17,7 +17,7 @@ readme: README.md # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) # @nicks:irc/im.site#channel' authors: -- Jeremy +- Juniper Networks # A short summary description of the collection description: set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. @@ -25,7 +25,7 @@ description: set of Ansible modules that perform specific operational and config # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' license: -- Apache-2.0-or-later +- Apache-2.0 # The path to the license file for the collection. This path is relative to the root of the collection. This key is # mutually exclusive with 'license' @@ -45,7 +45,7 @@ dependencies: {} repository: https://github.com/Juniper/ansible-junos-stdlib # The URL to any online docs -documentation: https://github.com/Juniper/ansible-junos-stdlib +documentation: https://junos-ansible-modules.readthedocs.io # The URL to the homepage of the collection/project homepage: https://github.com/Juniper/ansible-junos-stdlib diff --git a/docs b/docs new file mode 120000 index 00000000..0a437da1 --- /dev/null +++ b/docs @@ -0,0 +1 @@ +ansible_collection/Juniper/junos/docs \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 7b3aac77..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,234 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -LOGFILE = sphinx.log -FULL_TRACEBACKS = -T -CPUS ?= 4 -VERBOSITY ?= -v -FORCE_REBUILD = -a -E -CONFIG_DIR = -c . -NITPICK ?= -n -SPHINXOPTS = -j $(CPUS) -w $(LOGFILE) $(FULL_TRACEBACKS) $(FORCE_REBUILD) $(NITPICK) $(VERBOSITY) $(CONFIG_DIR) -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build -RSTDIR = . -MODULES_PATH = ../library -EXCLUDE_PATHS = ../library/_junos* -DOC_PROJECTS = "Ansible API" - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - rm -rf $(RSTDIR)/*.rst - rm -rf $(LOGFILE) - -.PHONY: apidoc -apidoc: - sphinx-apidoc --module-first --doc-project $(DOC_PROJECT) --force --maxdepth 7 -o $(RSTDIR) $(MODULES_PATH) $(EXCLUDE_PATHS) - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(RSTDIR) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: docs -docs: clean apidoc html - -.PHONY: webdocs -webdocs: clean apidoc html - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Ansible.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Ansible.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Ansible" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Ansible" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/_static/juniper-junos-modules.css b/docs/_static/juniper-junos-modules.css deleted file mode 100644 index 2723c307..00000000 --- a/docs/_static/juniper-junos-modules.css +++ /dev/null @@ -1,7 +0,0 @@ -td, th { - padding: 20px; -} -code { - color: #3a87ad; - background-color: #d9edf7; -} \ No newline at end of file diff --git a/docs/_static/juniper.png b/docs/_static/juniper.png deleted file mode 100755 index e3db7d8f91ca51d3e9c63fba27801d08875cb28f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5044 zcmV;l6HDxgP)100004XF*Lt006O$ zeEU(80000WV@Og>004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ0#b(FyTAa_ zdy`&8VVD_UC<6{NG_fI~0ue<-nj%P0#DLLIBvwSR5EN9f2P6n6F&ITuEN@2Ei>|D^ z_ww@lRz|vC zuzLs)$;-`!o*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT!&C1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2h zoGcOF60t^#FqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTX za!E_i;d2ub1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqK zG_|(0G&D0Z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoDiKdLpOAxi2$L0#SX*@cY z_n(^h55xYX#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^b zXThc7C4-yrInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0fwx1%qj zZ=)yBuQ3=54Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK% z>{;v(b^`kbN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<) z0>40zCTJ7v2qAyk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01) zS~6}jY?%U?gEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j z*2tcg9i<^OEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYWVlfKTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu761 zjmyXF)a;mc^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQq zHZJR2&bcD49Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^ zTY0bZ?)4%01p8F`JoeS|<@=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK z8LKk71XR(_RKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS z<&CX#T35dwS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@ zqL5!WvekBL-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW z%ue3U;av{94wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J#oSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%o zZ=0JGnu?n~9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8N zo_-(u{qS+0<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-U zsyQuty7Ua;Ou?B?XLHZaol8GAb3Wnxcu!2v{R_`T4=x`(GvqLI{-*2AOSimk zUAw*F_TX^n@STz9kDQ z$NC=!KfXWC8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgU zAAWQEt$#LRcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6 z?<+s(e(3(_^YOu_)K8!O1p}D#{JO;G(*OVf32;bRa{vGr5dZ)e5dq33^FIIp02Xvb zSaefwW^{L9a%BK;VQFr3E^cLXAT%y8JLvUk000QXNklRjx*t{$T#NAgMw;47sXR}~fVfAw~IAluM z%oGK@o;X{nE19YYmJoBO9XfuHsRC$#wvfBDUoMuAUm@6iNWhoHJnZ&jTz7EO5}?iB zDN$V280dGXg7LkJ?$Tq+w-ag)xId$>KY{%h57_G%au}+uNB}-bwaqdiu#3Ze~?I(#Emk+KP`i!Ay7Uk z;FmEqD6s5j>RE9Vke zm=61YqI^!1jA@PG#vO>!7N{;1nyP*INzKokA%U6ak3p!VOlgdSKm_<$&id$?MN8++7GnW_$>Es&+uPuTyk1ft(7+Lx5SVoGDA z1PX;hCnBi_(e^J)K_Imo{e8sMVF7Jl$Jh?wRPVFn0u`Yefw12+vH!#ShE)40$q?@S z;TBqnXx|Sv`muRjpdt)cAo^XyZD@~}%QX2u0FEA+|4?m#n{k1H1i~!-)CyZaELZ5a z723_m*g9dqSH~yRR(7EQZvQ~v)nZ(rA{<@~6_+nC#*RoNGKpPiAgOq&le@V3KVeh_ zg7bC6AvsquRS}8=TFcbY6o_VjSJ{G?9HkO=SS`el(9^kT39t&^pxYe1++?b1jP44; z?Be9y$W%ojL3KN!9$@Ng`;ZU>^yTS3!{NZU@x#IB3Z&E>{y>A@D3=2UwN$r5d#1*e zK#r74bf1`@OjV6hUP0nA-?@5$sfqymZQ57SH;%C+5Z(IVXFuZDGn{vEK^QTC5bT}g zh<+C^WjKyA8A1OId_NL&1Ji>7erCh|-zc9UxhF6^rUW8j_1sJS!sqYnC)=|N$K zYzO0d*0$|1Q-cXq@e5-O1ly()D9me^(!gfw>JE@}BCu z0KW5BC=hab9c>$q z5RFU?4FsDZM`pi`d!#JL7KGPlfh2H+|!Erb9|H)rNeP8ha^B8La5h8{$3Bufs zzUX6|A0PEAq9?u()0r9^^f(FUAEUDajkUFb z{{x^^gWp3pAyMlw=wD(Wui^M6I>#?(`5W+?Q5Khbbfy11VDr6=-cB=B5$M|twR6Je zwM=Q)!u&CfhpQSCG{z9;>WG2#r$hj{Ozn>?xeDEEgB85*j~!ieO5= z!Kjn+qJbaw%Ap1wtko(@^*nRq_&;F+?H4p7>ZP29+fIyT0f~c|u#aXr+kNd3PxcmO zWF+y@q~SfxAp4(C@e@kenzw-t3Qjkwr)OdQ99Q^ z=PhlX8B2Tfqv>p`^#w zL$Yqt1>&@%ydUWEWcEcmx~bek5(n4x*V3EUK7B?s>3tYUB=qYZH|0H?fZ#ZKuvyxY z^e&5~vUj1L;OpJsZv_9NXv%xYP4&)e>G0;Fel3ZL{yNc5JvhfrWqTI2ZS2ftQP(D!A)(w7=7GJqG{t} z4G6w#N&1N?l(-UslG&YfjY@2F@1<-WB+%_{d*(t~1x<+!C&LfT{A)1V=g~@wwfD?n zQ;k^4Tj{RO*zl8sC`q(Wqit^Snp`6ZgjKi$<(b&(o{PANL4J5FkC0!cj}1`ZbflRZ zP3KmiKCBfK@7xw9QE2s09jkEz62Ff23R*$2boLf)$e<+a6_oNeHud#QBpdXtADu}c z2)Jx+SJz|`EnvT?f$t2T9~;OEM9{PUut1)h&Rs_(*1mNbn;an!L9e#e+3)-3nHJb& z>6~s$lu-CwLL0o>;jN0Ma`*Ulhi`GPe+aAO%=|5mI6NRs2X_zZ*t@)Cv`=E+TLAxs zcE<$h35ib)dg{tQed(Fa1ql>vVz_M?n@*=R(VBSf8^!jyKCu4w^^pOUpk__6HDftv>0000< KMNUMnLSTXzsH2Gh diff --git a/docs/ansible2rst.py b/docs/ansible2rst.py deleted file mode 100755 index a5809813..00000000 --- a/docs/ansible2rst.py +++ /dev/null @@ -1,426 +0,0 @@ -#!/usr/bin/env python -# (c) 2012, Jan-Piet Mens -# -# This file is part of Ansible -# -# Modified to support stand-alone Galaxy documentation -# Copyright (c) 2014, 2017-2018 Juniper Networks Inc. -# 2014, Rick Sherman -# -# 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 . -# - -import os -import re -import sys -import datetime -import cgi -from distutils.version import LooseVersion -from jinja2 import Environment, FileSystemLoader -import yaml -from six import print_ - -from collections import MutableMapping, MutableSet, MutableSequence - -from ansible.module_utils.six import iteritems, string_types -from ansible.parsing.plugin_docs import read_docstring -from ansible.parsing.yaml.loader import AnsibleLoader -from ansible.plugins.loader import fragment_loader -from ansible.module_utils._text import to_bytes - -try: - from html import escape as html_escape -except ImportError: - # Python-3.2 or later - import cgi - - def html_escape(text, quote=True): - return cgi.escape(text, quote) - -from ansible import __version__ as ansible_version - -##################################################################################### -# constants and paths - -# if a module is added in a version of Ansible older than this, don't print the version added information -# in the module documentation because everyone is assumed to be running something newer than this already. -TO_OLD_TO_BE_NOTABLE = 1.3 - -_ITALIC = re.compile(r"I\(([^)]+)\)") -_BOLD = re.compile(r"B\(([^)]+)\)") -_MODULE = re.compile(r"M\(([^)]+)\)") -_URL_W_TEXT = re.compile(r"U\(([^)^|]+)\|([^)]+)\)") -_URL = re.compile(r"U\(([^)^|]+)\)") -_CONST = re.compile(r"C\(([^)]+)\)") -_UNDERSCORE = re.compile(r"_") -DEPRECATED = b" (D)" - -MODULE_NAME_STARTS_WITH = "juniper_junos_" -MODULEDIR = "../library/" -OUTPUTDIR = "./" - -##################################################################################### - -def too_old(added): - if not added: - return False - try: - added_tokens = str(added).split(".") - readded = added_tokens[0] + "." + added_tokens[1] - added_float = float(readded) - except ValueError as e: - warnings.warn("Could not parse %s: %s" % (added, str(e))) - return False - return added_float < TO_OLD_TO_BE_NOTABLE - -##################################################################################### - -def rst_ify(text): - ''' convert symbols like I(this is in italics) to valid restructured text ''' - - try: - t = _ITALIC.sub(r'*' + r"\1" + r"*", text) - t = _BOLD.sub(r'**' + r"\1" + r"**", t) - t = _MODULE.sub(r':ref:`' + r"\1 <\1>" + r"`", t) - t = _URL_W_TEXT.sub(r'`' + r"\1" + r" <" + r"\2" + r">`_", t) - t = _URL.sub(r'`' + r"\1" + r" <" + r"\1" + r">`_", t) - t = _CONST.sub(r'``' + r"\1" + r"``", t) - except Exception as e: - raise AnsibleError("Could not process (%s) : %s" % (str(text), str(e))) - - return t - -##################################################################################### - -def module_to_html(matchobj): - if matchobj.group(1) is not None: - module_name = matchobj.group(1) - module_href = _UNDERSCORE.sub('-', module_name) - return '' + \ - module_name + '' - return '' - -def html_ify(text): - ''' convert symbols like I(this is in italics) to valid HTML ''' - - t = html_escape(text) - t = _ITALIC.sub("" + r"\1" + "", t) - t = _BOLD.sub("" + r"\1" + "", t) - t = _MODULE.sub(module_to_html, t) - t = _URL_W_TEXT.sub("" + r"\1" + "", t) - t = _URL.sub("" + r"\1" + "", t) - t = _CONST.sub("" + r"\1" + "", t) - - return t - - -##################################################################################### - - -def rst_fmt(text, fmt): - ''' helper for Jinja2 to do format strings ''' - - return fmt % (text) - -##################################################################################### - - -def rst_xline(width, char="="): - ''' return a restructured text line of a given length ''' - - return char * width - -##################################################################################### - - -def write_data(text, outputname, module, output_dir=None): - ''' dumps module output to a file or the screen, as requested ''' - - if output_dir is not None: - if not os.path.exists(output_dir): - os.makedirs(output_dir) - fname = os.path.join(output_dir, outputname % (module)) - with open(fname, 'wb') as f: - f.write(to_bytes(text)) - else: - print(text) - -##################################################################################### - - -def jinja2_environment(template_dir, template_type): - - env = Environment(loader=FileSystemLoader(template_dir), - variable_start_string="@{", - variable_end_string="}@", - trim_blocks=True, - ) - env.globals['xline'] = rst_xline - - if template_type == 'rst': - env.filters['convert_symbols_to_format'] = rst_ify - env.filters['html_ify'] = html_ify - env.filters['fmt'] = rst_fmt - env.filters['xline'] = rst_xline - template = env.get_template('rst.j2') - outputname = "%s.rst" - else: - raise Exception("unknown module format type: %s" % template_type) - - return env, template, outputname - -##################################################################################### - -def add_fragments(doc, filename): - - fragments = doc.get('extends_documentation_fragment', []) - - if isinstance(fragments, string_types): - fragments = [fragments] - - # Allow the module to specify a var other than DOCUMENTATION - # to pull the fragment from, using dot notation as a separator - for fragment_slug in fragments: - fragment_slug = fragment_slug.lower() - if '.' in fragment_slug: - fragment_name, fragment_var = fragment_slug.split('.', 1) - fragment_var = fragment_var.upper() - else: - fragment_name, fragment_var = fragment_slug, 'DOCUMENTATION' - - fragment_loader.add_directory('../module_utils/') - fragment_class = fragment_loader.get(fragment_name) - assert fragment_class is not None - - fragment_yaml = getattr(fragment_class, fragment_var, '{}') - fragment = AnsibleLoader(fragment_yaml, file_name=filename).get_single_data() - - if 'notes' in fragment: - notes = fragment.pop('notes') - if notes: - if 'notes' not in doc: - doc['notes'] = [] - doc['notes'].extend(notes) - - if 'options' not in fragment and 'logging_options' not in fragment and 'connection_options' not in fragment: - raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename)) - - for key, value in iteritems(fragment): - if key in doc: - # assumes both structures have same type - if isinstance(doc[key], MutableMapping): - value.update(doc[key]) - elif isinstance(doc[key], MutableSet): - value.add(doc[key]) - elif isinstance(doc[key], MutableSequence): - value = sorted(frozenset(value + doc[key])) - else: - raise Exception("Attempt to extend a documentation fragement (%s) of unknown type: %s" % (fragment_name, filename)) - doc[key] = value - - - -def get_docstring(filename, verbose=False): - """ - DOCUMENTATION can be extended using documentation fragments loaded by the PluginLoader from the module_docs_fragments directory. - """ - - data = read_docstring(filename, verbose=verbose) - - # add fragments to documentation - if data.get('doc', False): - add_fragments(data['doc'], filename) - - return data['doc'], data['plainexamples'], data['returndocs'], data['metadata'] - -def process_module(fname, template, outputname, aliases=None): - - module_name = fname.replace(".py", "") - - print_("Processing module %s" % (MODULEDIR + fname)) - doc, examples, returndocs, metadata = get_docstring(MODULEDIR + fname, - verbose=True) - - # add some defaults for plugins that dont have most of the info - doc['module'] = doc.get('module', module_name) - doc['version_added'] = doc.get('version_added', 'historical') - doc['plugin_type'] = 'module' - - required_fields = ('short_description',) - for field in required_fields: - if field not in doc: - print_("%s: WARNING: MODULE MISSING field '%s'" % (fname, field)) - - not_nullable_fields = ('short_description',) - for field in not_nullable_fields: - if field in doc and doc[field] in (None, ''): - print_("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field])) - - # - # The present template gets everything from doc so we spend most of this - # function moving data into doc for the template to reference - # - - if aliases: - doc['aliases'] = aliases - - # don't show version added information if it's too old to be called out - added = 0 - if doc['version_added'] == 'historical': - del doc['version_added'] - else: - added = doc['version_added'] - - # Strip old version_added for the module - if too_old(added): - del doc['version_added'] - - option_names = [] - if 'options' in doc and doc['options']: - for (k, v) in iteritems(doc['options']): - # Error out if there's no description - if 'description' not in doc['options'][k]: - raise AnsibleError("Missing required description for option %s in %s " % (k, module)) - - # Error out if required isn't a boolean (people have been putting - # information on when something is required in here. Those need - # to go in the description instead). - required_value = doc['options'][k].get('required', False) - if not isinstance(required_value, bool): - raise AnsibleError("Invalid required value '%s' for option '%s' in '%s' (must be truthy)" % ( - required_value, k, module)) - - # Strip old version_added information for options - if 'version_added' in doc['options'][k] and too_old(doc['options'][k]['version_added']): - del doc['options'][k]['version_added'] - - # Make sure description is a list of lines for later formatting - if not isinstance(doc['options'][k]['description'], list): - doc['options'][k]['description'] = [doc['options'][k]['description']] - option_names.append(k) - option_names.sort() - doc['option_keys'] = option_names - - connection_option_names = [] - if 'connection_options' in doc and doc['connection_options']: - for (k, v) in iteritems(doc['connection_options']): - # Error out if there's no description - if 'description' not in doc['connection_options'][k]: - raise AnsibleError("Missing required description for connection_option %s in %s " % (k, module)) - - # Error out if required isn't a boolean (people have been putting - # information on when something is required in here. Those need - # to go in the description instead). - required_value = doc['connection_options'][k].get('required', False) - if not isinstance(required_value, bool): - raise AnsibleError("Invalid required value '%s' for connection_option '%s' in '%s' (must be truthy)" % - (required_value, k, module)) - - # Strip old version_added information for options - if ('version_added' in doc['connection_options'][k] and - too_old(doc['connection_options'][k]['version_added'])): - del doc['connection_options'][k]['version_added'] - - # Make sure description is a list of lines for later formatting - if not isinstance(doc['connection_options'][k]['description'], list): - doc['connection_options'][k]['description'] = [doc['connection_options'][k]['description']] - connection_option_names.append(k) - connection_option_names.sort() - doc['connection_option_keys'] = connection_option_names - - logging_option_names = [] - if 'logging_options' in doc and doc['logging_options']: - for (k, v) in iteritems(doc['logging_options']): - # Error out if there's no description - if 'description' not in doc['logging_options'][k]: - raise AnsibleError("Missing required description for logging_option %s in %s " % (k, module)) - - # Error out if required isn't a boolean (people have been putting - # information on when something is required in here. Those need - # to go in the description instead). - required_value = doc['logging_options'][k].get('required', False) - if not isinstance(required_value, bool): - raise AnsibleError("Invalid required value '%s' for logging_option '%s' in '%s' (must be truthy)" % - (required_value, k, module)) - - # Strip old version_added information for options - if ('version_added' in doc['logging_options'][k] and - too_old(doc['logging_options'][k]['version_added'])): - del doc['logging_options'][k]['version_added'] - - # Make sure description is a list of lines for later formatting - if not isinstance(doc['logging_options'][k]['description'], list): - doc['logging_options'][k]['description'] = [doc['logging_options'][k]['description']] - logging_option_names.append(k) - logging_option_names.sort() - doc['logging_option_keys'] = logging_option_names - - doc['filename'] = fname - doc['docuri'] = doc['module'].replace('_', '-') - doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') - doc['ansible_version'] = ansible_version - doc['plainexamples'] = examples # plain text - doc['metadata'] = metadata - - if returndocs: - try: - doc['returndocs'] = yaml.safe_load(returndocs) - returndocs_keys = list(doc['returndocs'].keys()) - returndocs_keys.sort() - doc['returndocs_keys'] = returndocs_keys - except Exception as e: - print_("%s:%s:yaml error:%s:returndocs=%s" % (fname, module_name, e, returndocs)) - doc['returndocs'] = None - doc['returndocs_keys'] = None - else: - doc['returndocs'] = None - doc['returndocs_keys'] = None - - doc['author'] = doc.get('author', ['UNKNOWN']) - if isinstance(doc['author'], string_types): - doc['author'] = [doc['author']] - - # here is where we build the table of contents... - text = template.render(doc) - write_data(text, outputname, module_name, OUTPUTDIR) - -##################################################################################### - - -def main(): - - env, template, outputname = jinja2_environment('.', 'rst') - module_names = [] - - for module in os.listdir(MODULEDIR): - if module.startswith(MODULE_NAME_STARTS_WITH): - process_module(module, template, outputname) - module_names.append(module.replace(".py", "")) - - index_file_path = os.path.join(OUTPUTDIR, "index.rst") - index_file = open(index_file_path, "w") - index_file.write('Juniper.junos Ansible Modules\n') - index_file.write('=================================================\n') - index_file.write('\n') - index_file.write('Contents:\n') - index_file.write('\n') - index_file.write('.. toctree::\n') - index_file.write(' :maxdepth: 1\n') - index_file.write('\n') - - for module_name in module_names: - index_file.write(' %s\n' % module_name) - -if __name__ == '__main__': - main() diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 3371c88b..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,285 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Junos Ansible Modules documentation build configuration file, created by -# sphinx-quickstart on Fri Jul 11 17:28:14 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os -import sphinx_bootstrap_theme - - -def setup(app): - app.add_stylesheet("juniper-junos-modules.css") - - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(1, os.path.abspath('..')) - -# Import ansible2rst so that RST files can be generated. -import ansible2rst -# Call ansible2rst.main() to generate RST files. -ansible2rst.main() - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -#templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Junos Ansible Modules' -copyright = u'2014-2017, Juniper Networks, Inc' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -from version import VERSION -# The short X.Y version. -version = VERSION -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'bootstrap' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = { - 'bootswatch_theme': "spacelab", - 'navbar_sidebarrel': False, - 'navbar_site_name': "Modules", - 'source_link_position': "footer", - 'navbar_links': [ - ("Wiki", "https://techwiki.juniper.net/Automation_Scripting", True), - ("Forum", "http://groups.google.com/group/junos-python-ez", True), - ], - } - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -html_logo = '_static/juniper.png' - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -html_sidebars = { - '**': [ - 'globaltoc.html', - ] -} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'JunosAnsibleModulesdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'JunosAnsibleModules.tex', u'Junos Ansible Modules Documentation', - u'Juniper Networks, Inc.', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'junosansiblemodules', u'Junos Ansible Modules Documentation', - [u'Juniper Networks, Inc.'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'JunosAnsibleModules', u'Junos Ansible Modules Documentation', - u'Juniper Networks, Inc.', 'JunosAnsibleModules', 'Ansible Modules for ' - 'Junos', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/docreq.txt b/docs/docreq.txt deleted file mode 100644 index bc96b3b0..00000000 --- a/docs/docreq.txt +++ /dev/null @@ -1,2 +0,0 @@ -git+https://github.com/ryan-roemer/sphinx-bootstrap-theme.git#egg=sphinx-bootstrap-theme -ansible diff --git a/docs/rst.j2 b/docs/rst.j2 deleted file mode 100644 index 3b7338f0..00000000 --- a/docs/rst.j2 +++ /dev/null @@ -1,560 +0,0 @@ -.. _@{ module }@: - -{% set title = module %} -{% set title_len = title|length %} -@{ title }@ -@{ '+' * title_len }@ -{% if short_description %} -@{ short_description|convert_symbols_to_format }@ -{% endif %} - -{% if version_added is defined and version_added != '' -%} -.. versionadded:: @{ version_added | default('') }@ - - -{% endif %} - - -.. contents:: - :local: - :depth: 2 - -{# ------------------------------------------ - # - # Please note: this looks like a core dump - # but it isn't one. - # - --------------------------------------------#} -{% if deprecated is defined -%} - - -DEPRECATED ----------- - -{# use unknown here? skip the fields? #} -:In: version: @{ deprecated['version'] | default('') | string | convert_symbols_to_format }@ -:Why: @{ deprecated['why'] | default('') | convert_symbols_to_format }@ -:Alternative: @{ deprecated['alternative'] | default('')| convert_symbols_to_format }@ - - -{% endif %} - -Synopsis --------- - -{% if description %} - -{% for desc in description -%} -* @{ desc | convert_symbols_to_format }@ -{% endfor %} - - -{% endif %} -{% if aliases is defined -%} - -Aliases: @{ ','.join(aliases) }@ - - -{% endif %} -{% if requirements %} - -Requirements ------------- -The following software packages must be installed on hosts that execute this module: - -{% for req in requirements %} -* @{ req | convert_symbols_to_format }@ -{% endfor %} - - - -{% endif %} -{% if options -%} - -.. _module-specific-options-label: - -Module-specific Options ------------------------ -The following options may be specified for this module: - -.. raw:: html - - - - - - - - - - - -{% for k in option_keys -%} -{% set v = options[k] -%} -{% if not v['suboptions'] %} - - - - - - -{% if v.get('type', 'not_bool') == 'bool' %} - -{% else %} - -{% endif %} - - - - - - - - - - - -{% endif %} - - -{% endfor %} - -
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
-{% if v.description is string %} -
@{ v.description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in v.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -{% if 'aliases' in v and v.aliases %} -
aliases: @{ v.aliases|join(', ') }@
-{% endif %} -{% else %} - -
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} -{% for desc in v.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% if 'aliases' in v and v.aliases %} -
aliases: @{ v.aliases|join(', ') }@
-{% endif %} -
- - - - - - - - - - - -{% for k2 in v['suboptions'] %} -{% set v2 = v['suboptions'] [k2] %} - - - - - - -{% if v2.get('type', 'not_bool') == 'bool' %} - -{% else %} - -{% endif %} - - -{% endfor %} - -
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
-{% if v2.description is string %} -
@{ v2.description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in v2.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -{% if 'aliases' in v and v2.aliases %} -
aliases: @{ v2.aliases|join(', ') }@
-{% endif %} -
- -
-
- -{% endif %} -{% if connection_options -%} - - -Common Connection-related Options ---------------------------------- -In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: - -.. raw:: html - - - - - - - - - - - -{% for k in connection_option_keys -%} -{% set v = connection_options[k] -%} -{% if not v['suboptions'] %} - - - - - - -{% if v.get('type', 'not_bool') == 'bool' %} - -{% else %} - -{% endif %} - - - - - - - - - - - -{% endif %} - - -{% endfor %} - -
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
-{% if v.description is string %} -
@{ v.description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in v.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -{% if 'aliases' in v and v.aliases %} -
aliases: @{ v.aliases|join(', ') }@
-{% endif %} -{% else %} - -
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} -{% for desc in v.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% if 'aliases' in v and v.aliases %} -
aliases: @{ v.aliases|join(', ') }@
-{% endif %} -
- - - - - - - - - - - -{% for k2 in v['suboptions'] %} -{% set v2 = v['suboptions'] [k2] %} - - - - - - -{% if v2.get('type', 'not_bool') == 'bool' %} - -{% else %} - -{% endif %} - - -{% endfor %} - -
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
-{% if v2.description is string %} -
@{ v2.description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in v2.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -{% if 'aliases' in v and v2.aliases %} -
aliases: @{ v2.aliases|join(', ') }@
-{% endif %} -
- -
-
- -{% endif %} -{% if logging_options -%} - - -Common Logging-related Options ------------------------------- -In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: - -.. raw:: html - - - - - - - - - - - -{% for k in logging_option_keys -%} -{% set v = logging_options[k] -%} -{% if not v['suboptions'] %} - - - - - - -{% if v.get('type', 'not_bool') == 'bool' %} - -{% else %} - -{% endif %} - - - - - - - - - - - -{% endif %} - - -{% endfor %} - -
parametertyperequireddefaultchoicescomments
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else %}no{% endif -%}{% if v.get('default', None) is not none -%}@{ v['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v['choices'] -%}
    {% for choice in v.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
-{% if v.description is string %} -
@{ v.description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in v.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -{% if 'aliases' in v and v.aliases %} -
aliases: @{ v.aliases|join(', ') }@
-{% endif %} -{% else %} - -
@{ k }@
{% if v['version_added'] -%} (added in @{v['version_added']}@){% endif -%}
{% if v['type'] -%}@{ v['type'] }@{% endif -%}{% if v.get('required', False) -%}yes{% else -%}no{% endif -%} -{% for desc in v.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% if 'aliases' in v and v.aliases %} -
aliases: @{ v.aliases|join(', ') }@
-{% endif %} -
- - - - - - - - - - - -{% for k2 in v['suboptions'] %} -{% set v2 = v['suboptions'] [k2] %} - - - - - - -{% if v2.get('type', 'not_bool') == 'bool' %} - -{% else %} - -{% endif %} - - -{% endfor %} - -
Dictionary object @{ k }@
parametertyperequireddefaultchoicescomments
@{ k2 }@
{% if v2['version_added'] -%} (added in @{v2['version_added']}@){% endif -%}
{% if v2['type'] -%}@{ v2['type'] }@{% endif -%}{% if v2.get('required', False) -%}yes{% else -%}no{% endif -%}{% if v2.get('default', None) is not none -%}@{ v2['default'] | string | html_ify }@{% endif -%}
  • yes
  • no
{% if v2['choices'] -%}
    {% for choice in v2.get('choices',[]) -%}
  • @{ choice }@
  • {% endfor -%}
{% endif -%}
-{% if v2.description is string %} -
@{ v2.description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in v2.description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -{% if 'aliases' in v and v2.aliases %} -
aliases: @{ v2.aliases|join(', ') }@
-{% endif %} -
- -
-
- -{% endif %} -{% if examples or plainexamples -%} -.. _@{ title }@-examples-label: - -Examples --------- - -:: - -{% for example in examples %} -{% if example['description'] %} -@{ example['description'] }@ -{% endif %} -@{ example['code'] | escape | indent(4, True) }@ -{% endfor %} -{% if plainexamples %} -@{ plainexamples | indent(4, True) }@ -{% endif %} -{% endif %} - - -{% if returndocs -%} - - -Return Values -------------- - -.. raw:: html - - - - - - - - - - - -{% for entry in returndocs_keys %} - - - - - - - - -{% if returndocs[entry].type == 'complex' %} - - - - - -{% endif %} -{% endfor %} - -
namedescriptionreturnedtypesample
@{ entry }@ -{% if returndocs[entry].description is string %} -
@{ returndocs[entry].description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in returndocs[entry].description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -
@{ returndocs[entry].returned | html_ify }@@{ returndocs[entry].type | html_ify }@@{ returndocs[entry].sample | replace('\n', '\n ') | html_ify }@
contains: - - - - - - - - - -{% for sub in returndocs[entry].contains %} - - - - - - - - -{% endfor %} - -
namedescriptionreturnedtypesample
@{ sub }@ -{% if returndocs[entry].contains[sub].description is string %} -
@{ returndocs[entry].contains[sub].description | replace('\n', '\n ') | html_ify }@
-{% else %} -{% for desc in returndocs[entry].contains[sub].description %} -
@{ desc | replace('\n', '\n ') | html_ify }@
-{% endfor %} -{% endif %} -
@{ returndocs[entry].contains[sub].returned | html_ify }@@{ returndocs[entry].contains[sub].type | html_ify }@@{ returndocs[entry].contains[sub].sample }@
-
-
-
-{% endif %} - - -{% if notes -%} - - -Notes ------ - -.. note:: -{% for note in notes %} - - @{ note | convert_symbols_to_format }@ -{% endfor %} - - -{% endif %} -{% if author is defined -%} - - -Author -~~~~~~ - -{% for author_name in author %} -* @{ author_name }@ -{% endfor %} - - -{% endif %} -{% if not deprecated %} -{% set support = { 'core': 'The Ansible Core Team', 'network': 'The Ansible Network Team', 'certified': 'an Ansible Partner', 'community': 'The Ansible Community', 'curated': 'A Third Party'} %} -{% set module_states = { 'preview': 'it is not guaranteed to have a backwards compatible interface', 'stableinterface': 'the maintainers for this module guarantee that no backward incompatible interface changes will be made'} %} -{% if metadata %} -{% if metadata.status %} - - -Status -~~~~~~ - -{% for cur_state in metadata.status %} -This module is flagged as **@{cur_state}@** which means that @{module_states[cur_state]}@. -{% endfor %} - - -{% endif %} -{% endif %} -{% endif %} From d43b88f087220947eb8dd5487c5fc7fab03e9c28 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 5 Mar 2020 14:33:15 +0530 Subject: [PATCH 299/426] reverted library folder --- library | 1 - library/juniper_junos_command.py | 502 +++++++++++ library/juniper_junos_config.py | 1143 ++++++++++++++++++++++++++ library/juniper_junos_facts.py | 344 ++++++++ library/juniper_junos_jsnapy.py | 358 ++++++++ library/juniper_junos_ping.py | 504 ++++++++++++ library/juniper_junos_pmtud.py | 410 +++++++++ library/juniper_junos_rpc.py | 624 ++++++++++++++ library/juniper_junos_software.py | 806 ++++++++++++++++++ library/juniper_junos_srx_cluster.py | 295 +++++++ library/juniper_junos_system.py | 476 +++++++++++ library/juniper_junos_table.py | 477 +++++++++++ 12 files changed, 5939 insertions(+), 1 deletion(-) delete mode 120000 library create mode 100644 library/juniper_junos_command.py create mode 100644 library/juniper_junos_config.py create mode 100644 library/juniper_junos_facts.py create mode 100644 library/juniper_junos_jsnapy.py create mode 100644 library/juniper_junos_ping.py create mode 100644 library/juniper_junos_pmtud.py create mode 100644 library/juniper_junos_rpc.py create mode 100644 library/juniper_junos_software.py create mode 100644 library/juniper_junos_srx_cluster.py create mode 100644 library/juniper_junos_system.py create mode 100644 library/juniper_junos_table.py diff --git a/library b/library deleted file mode 120000 index de06a658..00000000 --- a/library +++ /dev/null @@ -1 +0,0 @@ -ansible_collection/Juniper/junos/plugins/modules \ No newline at end of file diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py new file mode 100644 index 00000000..9a2b66a6 --- /dev/null +++ b/library/juniper_junos_command.py @@ -0,0 +1,502 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_command +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Execute one or more CLI commands on a Junos device +description: + - Execute one or more CLI commands on a Junos device. + - This module does NOT use the Junos CLI to execute the CLI command. + Instead, it uses the C() RPC over a NETCONF channel. The + C() RPC takes a CLI command as it's input and is very similar to + executing the command on the CLI, but you can NOT include any pipe modifies + (i.e. C(| match), C(| count), etc.) with the CLI commands executed by this + module. +options: + commands: + description: + - A list of one or more CLI commands to execute on the Junos device. + required: true + default: none + type: list + aliases: + - cli + - command + - cmd + - cmds + dest: + description: + - The path to a file, on the Ansible control machine, where the output of + the cli command will be saved. + - The file must be writeable. If the file already exists, it is + overwritten. + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the value of the I(dest) option. It is + the user's responsibility to ensure this value is unique per target + host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine, where + the output of the cli command will be saved. The output will be logged + to a file named C({{ inventory_hostname }}_)I(command)C(.)I(format) + in the directory specified by the value of the I(dest_dir) option. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: None + type: path + aliases: + - destination_dir + - destdir + formats: + description: + - The format of the reply for the CLI command(s) specified by the + I(commands) option. The specified format(s) must be supported by the + target Junos device. The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all command(s) specified by the I(commands) option. If a + list of formats are specified, there must be one value in the list for + each command specified by the I(commands) option. Specifying the value + C(xml) for the I(formats) option is similar to appending + C(| display xml) to a CLI command, and specifying the value C(json) + for the I(formats) option is similar to appending C(| display json) to + a CLI command. + required: false + default: text + type: str or list of str + choices: + - text + - xml + - json + aliases: + - format + - display + - output + return_output: + description: + - Indicates if the output of the command should be returned in the + module's response. You might want to set this option to C(false), + and set the I(dest_dir) option, if the command output is very large + and you only need to save the output rather than using it's content in + subsequent tasks/plays of your playbook. + required: false + default: true + type: bool +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_command + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Execute single "show version" command. + juniper_junos_command: + commands: "show version" + register: response + + - name: Print the command output + debug: + var: response.stdout + + - name: Execute three commands. + juniper_junos_command: + commands: + - "show version" + - "show system uptime" + - "show interface terse" + register: response + + - name: Print the command output of each. + debug: + var: item.stdout + with_items: "{{ response.results }}" + + - name: Two commands with XML output. + juniper_junos_command: + commands: + - "show route" + - "show lldp neighbors" + format: xml + + - name: show route with XML output - show version with JSON output + juniper_junos_command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + + - name: save outputs in dest_dir + juniper_junos_command: + commands: + - "show route" + - "show version" + dest_dir: "./output" + + - name: save output to dest + juniper_junos_command: + command: "show system uptime" + dest: "/tmp/{{ inventory_hostname }}.uptime.output" + + - name: save output to dest + juniper_junos_command: + command: + - "show route" + - "show lldp neighbors" + dest: "/tmp/{{ inventory_hostname }}.commands.output" + + - name: Multiple commands, save outputs, but don't return them + juniper_junos_command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + dest_dir: "/tmp/outputs/" + return_output: false +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's state has changed. Since this module does not + change the operational or configuration state of the device, the value + is always set to false. + - You could use this module to execute a command which + changes the operational state of the the device. For example, + C(clear ospf neighbors). Beware, this module is unable to detect + this situation, and will still return the value C(false) for I(changed) + in this case. + returned: success + type: bool + sample: false +command: + description: + - The CLI command which was executed. + returned: always + type: str +failed: + description: + - Indicates if the task failed. See the I(results) key for additional + details. + returned: always + type: bool +format: + description: + - The format of the command response. + returned: always + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +parsed_output: + description: + - The command reply from the Junos device parsed into a JSON data structure. + For XML replies, the response is parsed into JSON using the + U(jxmlease|https://github.com/Juniper/jxmlease) + library. For JSON the response is parsed using the Python + U(json|https://docs.python.org/2/library/json.html) library. + - When Ansible converts the jxmlease or native Python data structure + into JSON, it does not guarantee that the order of dictionary/object keys + are maintained. + returned: when command executed successfully, I(return_output) is true, + and the value of the I(formats) option is C(xml) or C(json). + type: dict +results: + description: + - The other keys are returned when a single command is specified for the + I(commands) option. When the value of the I(commands) option is a list + of commands, this key is returned instead. The value of this key is a + list of dictionaries. Each element in the list corresponds to the + commands in the I(commands) option. The keys for each element in the list + include all of the other keys listed. The I(failed) key indicates if the + individual command failed. In this case, there is also a top-level + I(failed) key. The top-level I(failed) key will have a value of C(false) + if ANY of the commands ran successfully. In this case, check the value + of the I(failed) key for each element in the I(results) list for the + results of individual commands. + returned: when the I(commands) option is a list value. + type: list of dict +stdout: + description: + - The command reply from the Junos device as a single multi-line string. + returned: when command executed successfully and I(return_output) is C(true). + type: str +stdout_lines: + description: + - The command reply from the Junos device as a list of single-line strings. + returned: when command executed successfully and I(return_output) is C(true). + type: list of str +''' + +import sys + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + commands=dict(required=True, + type='list', + aliases=['cli', 'command', 'cmd', 'cmds'], + default=None), + formats=dict(required=False, + type='list', + aliases=['format', 'display', 'output'], + default=None), + dest=dict(required=False, + type='path', + aliases=['destination'], + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir'], + default=None), + return_output=dict(required=False, + type='bool', + default=True) + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. Well, that's not completely true. It does depend on the + # command executed. See the I(changed) key in the RETURN documentation + # for more details. + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, + ) + + # Check over commands + commands = junos_module.params.get('commands') + # Ansible allows users to specify a commands argument with no value. + if commands is None: + junos_module.fail_json(msg="The commands option must have a value.") + # Make sure the commands don't include any pipe modifiers. + for command in commands: + pipe_index = command.find('|') + if (pipe_index != -1 and + command[pipe_index:].strip() != 'display xml rpc'): + # Allow "show configuration | display set" + if ('show configuration' in command and + 'display set' in command[pipe_index:] and + '|' not in command[pipe_index+1:]): + continue + # Any other "| display " should use the format option instead. + for valid_format in juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES: + if 'display ' + valid_format in command[pipe_index:]: + junos_module.fail_json( + msg='The pipe modifier (%s) in the command ' + '(%s) is not supported. Use format: "%s" ' + 'instead.' % + (command[pipe_index:], command, valid_format)) + # Any other "| " is going to produce an error anyway, so fail + # with a meaningful message. + junos_module.fail_json(msg='The pipe modifier (%s) in the command ' + '(%s) is not supported.' % + (command[pipe_index:], command)) + + # Check over formats + formats = junos_module.params.get('formats') + if formats is None: + # Default to text format + formats = ['text'] + valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES + # Check format values + for format in formats: + # Is it a valid format? + if format not in valid_formats: + junos_module.fail_json(msg="The value %s in formats is invalid. " + "Must be one of: %s" % + (format, ', '.join(map(str, + valid_formats)))) + # Correct number of format values? + if len(formats) != 1 and len(formats) != len(commands): + junos_module.fail_json(msg="The formats option must have a single " + "value, or one value per command. There " + "are %d commands and %d formats." % + (len(commands), len(formats))) + # Same format for all commands + elif len(formats) == 1 and len(commands) > 1: + formats = formats * len(commands) + + results = list() + for (command, format) in zip(commands, formats): + # Set initial result values. Assume failure until we know it's success. + result = {'msg': '', + 'command': command, + 'format': format, + 'changed': False, + 'failed': True} + + # Execute the CLI command + try: + junos_module.logger.debug('Executing command "%s".', + command) + rpc = junos_module.etree.Element('command', format=format) + rpc.text = command + resp = junos_module.dev.rpc(rpc, normalize=bool(format == 'xml')) + result['msg'] = 'The command executed successfully.' + junos_module.logger.debug('Command "%s" executed successfully.', + command) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Unable to execute "%s". Error: %s', + command, str(ex)) + result['msg'] = 'Unable to execute the command: %s. Error: %s' % \ + (command, str(ex)) + results.append(result) + continue + + text_output = None + parsed_output = None + if resp is True: + text_output = '' + elif (resp, junos_module.etree._Element): + # Handle the output based on format + if format == 'text': + if resp.tag in ['output', 'rpc-reply']: + text_output = resp.text + junos_module.logger.debug('Text output set.') + elif resp.tag == 'configuration-information': + text_output = resp.findtext('configuration-output') + junos_module.logger.debug('Text configuration output set.') + else: + result['msg'] = 'Unexpected text response tag: %s.' % ( + (resp.tag)) + results.append(result) + junos_module.logger.debug('Unexpected text response tag ' + '%s.', resp.tag) + continue + elif format == 'xml': + encode = None if sys.version < '3' else 'unicode' + text_output = junos_module.etree.tostring(resp, + pretty_print=True, + encoding=encode) + parsed_output = junos_module.jxmlease.parse_etree(resp) + junos_module.logger.debug('XML output set.') + elif format == 'json': + text_output = str(resp) + parsed_output = resp + junos_module.logger.debug('JSON output set.') + else: + result['msg'] = 'Unexpected format %s.' % (format) + results.append(result) + junos_module.logger.debug('Unexpected format %s.', format) + continue + else: + result['msg'] = 'Unexpected response type %s.' % (type(resp)) + results.append(result) + junos_module.logger.debug('Unexpected response type %s.', + type(resp)) + continue + + # Set the output keys + if junos_module.params['return_output'] is True: + if text_output is not None: + result['stdout'] = text_output + result['stdout_lines'] = text_output.splitlines() + if parsed_output is not None: + result['parsed_output'] = parsed_output + # Save the output + junos_module.save_text_output(command, format, text_output) + # This command succeeded. + result['failed'] = False + # Append to the list of results + results.append(result) + + # Return response. + if len(results) == 1: + junos_module.exit_json(**results[0]) + else: + # Calculate the overall failed. Only failed if all commands failed. + failed = True + for result in results: + if result.get('failed') is False: + failed = False + break + junos_module.exit_json(results=results, + changed=False, + failed=failed) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py new file mode 100644 index 00000000..10101d42 --- /dev/null +++ b/library/juniper_junos_config.py @@ -0,0 +1,1143 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# 2015, Rick Sherman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_config +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Manipulate the configuration of a Junos device +description: + - > + Manipulate the configuration of a Junos device. This module allows a + combination of loading or rolling back, checking, diffing, retrieving, and + committing the configuration of a Junos device. It performs the following + steps in order: + + + #. Open a candidate configuration database. + + * If the I(config_mode) option has a value of C(exclusive), the default, + take a lock on the candidate configuration database. If the lock fails + the module fails and reports an error. + * If the I(config_mode) option has a value of C(private), open a private + candidate configuration database. If opening the private configuration + database fails the module fails and reports an error. + #. Load configuration data into the candidate configuration database. + + * Configuration data may be loaded using the I(load) or I(rollback) + options. If either of these options are specified, new configuration + data is loaded. If neither option is specified, this step is skipped. + * If the I(rollback) option is specified, replace the candidate + configuration with the previous configuration specified by the value + of the I(rollback) option. + * If the I(load) option is specified, load new configuration data. + * The value of the I(load) option defines the type of load which is + performed. + * The source of the new configuration data is one of the following: + + * I(src) - A file path on the local Ansible control machine. + * I(lines) - A list of strings containing the configuration data. + * I(template) - A file path to a Jinja2 template on the local + Ansible control machine. This template is rendered with the variables + specified by the I(vars) option. If the I(template) option is + specified, the I(vars) option must also be specified. + * I(url) - A URL reachable from the target Junos device. + * If the I(format) option is specified, the configuration file being + loaded is in the specified format, rather than the format determined + from the file name. + #. Check the validity of the candidate configuration database. + + * If the I(check) option is C(true), the default, check the validity + of the configuration by performing a "commit check" operation. + * This option may be specified with I(diff) C(false) and I(commit) + C(false) to confirm a previous "commit confirmed " operation + without actually performing an additional commit. + * If the configuration check fails, further processing stops, the module + fails, and an error is reported. + #. Determine differences between the candidate and committed configuration + databases. + + * If step 2 was not skipped, and the I(diff) option is C(true), + the default, perform a diff between the candidate and committed + configuration databases. + * If the I(diffs_file) or I(dest_dir) option is specified, save the + generated configuration differences. + * If the I(return_output) option is C(true), the default, include the + generated configuration difference in the I(diff) and I(diff_lines) + keys of the module's response. + #. Retrieve the configuration database from the Junos device. + + * If the I(retrieve) option is specified, retrieve the configuration + database specified by the I(retrieve) value from the target Junos + device to the local Ansible control machine. + * The format in which the configuration is retrieved is specified by the + value of the I(format) option. + * The optional I(filter) controls which portions of the configuration + are retrieved. + * If I(options) are specified, they control the content of the + configuration retrieved. + * If the I(dest) or I(dest_dir) option is specified, save the + retrieved configuration to a file on the local Ansible control + machine. + * If the I(return_output) option is C(true), the default, include the + retrieved configuration in the I(config), I(config_lines), and + I(config_parsed) keys of the module's response. + #. Commit the configuration changes. + + * If the I(commit) option is C(true), the default, commit the + configuration changes. + * This option may be specified with I(diff) C(false) and I(check) + C(false) to confirm a previous "commit confirmed " operation. + * If the I(comment) option is specified, add the comment to the commit. + * If the I(confirmed) option is specified, perform a + C(commit confirmed) I(min) operation where I(min) is the value of the + I(confirmed) option. + * If the I(check) option is C(true) and the I(check_commit_wait) + option is specified, wait I(check_commit_wait) seconds before + performing the commit. + #. Close the candidate configuration database. + + * Close and discard the candidate configuration database. + * If the I(config_mode) option has a value of C(exclusive), the default, + unlock the candidate configuration database. +options: + check: + description: + - Perform a commit check operation. + required: false + default: true (false if retrieve is set and load and rollback are not set) + type: bool + aliases: + - check_commit + - commit_check + check_commit_wait: + description: + - The number of seconds to wait between check and commit operations. + - This option is only valid if I(check) is C(true) and I(commit) is + C(true). + - This option should not normally be needed. It works around an issue in + some versions of Junos. + required: false + default: none + type: int + comment: + description: + - Provide a comment to be used with the commit operation. + - This option is only valid if the I(commit) option is true. + required: false + default: none + type: str + commit: + description: + - Perform a commit operation. + required: false + default: true (false if retrieve is set and load and rollback are not set) + type: bool + commit_empty_changes: + description: + - Perform a commit operation, even if there are no changes between the + candidate configuration and the committed configuration. + required: false + default: false + type: bool + config_mode: + description: + - The mode used to access the candidate configuration database. + required: false + default: exclusive + type: str + choices: + - exclusive + - private + aliases: + - config_access + - edit_mode + - edit_access + confirmed: + description: + - Provide a confirmed timeout, in minutes, to be used with the commit + operation. + - This option is only valid if the I(commit) option is C(true). + - The value of this option is the number of minutes to wait for another + commit operation before automatically rolling back the configuration + change performed by this task. In other words, this option causes the + module to perform a C(commit confirmed )I(min) where I(min) is the + value of the I(confirmed) option. This option DOES NOT confirm a + previous C(commit confirmed )I(min) operation. To confirm a previous + commit operation, invoke this module with the I(check) or I(commit) + option set to C(true). + required: false + default: none + type: int + aliases: + - confirm + dest: + description: + - The path to a file, on the local Ansible control machine, where the + configuration will be saved if the I(retrieve) option is specified. + - The file must be writeable. If the file already exists, it is + overwritten. + - This option is only valid if the I(retrieve) option is not C(none). + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(dest) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: none + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine. This is the + directory where the configuration will be saved if the I(retrieve) + option is specified. It is also the directory where the configuration + diff will be specified if the I(diff) option is C(true). + - This option is only valid if the I(retrieve) option is not C(none) or + the I(diff) option is C(true). + - The retrieved configuration will be saved to a file named + C({{ inventory_hostname }}.)I(format_extension) in the I(dest_dir) + directory. Where I(format_extension) is C(conf) for text format, C(xml) + for XML format, C(json) for JSON format, and C(set) for set format. + - If the I(diff) option is C(true), the configuration diff will be saved + to a file named C({{ inventory_hostname }}.diff) in the I(dest_dir) + directory. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + - The I(dest_dir) and I(diff_file) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: none + type: path + aliases: + - destination_dir + - destdir + - savedir + - save_dir + diff: + description: + - Perform a configuration compare (aka diff) operation. + required: false + default: true (false if retrieve is set and load and rollback are not set) + type: bool + aliases: + - compare + - diffs + diffs_file: + description: + - The path to a file, on the Ansible control machine, where the + configuration differences will be saved if the I(diff) option is + specified. + - The file must be writeable. If the file already exists, it is + overwritten. + - This option is only valid if the I(diff) option is C(true). + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(diffs_file) value. It is the + user's responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + - The I(diffs_file) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + format: + description: + - Specifies the format of the configuration retrieved, if I(retrieve) + is not C(none). + - Specifies the format of the configuration to be loaded, if I(load) is + not C(none). + - The specified format must be supported by the target Junos device. + required: false + default: none (auto-detect on load, text on retrieve) + type: str + choices: + - xml + - set + - text + - json + filter: + description: + - A string of XML, or '/'-separated configuration hierarchies, + which specifies a filter used to restrict the portions of the + configuration which are retrieved. See + U(PyEZ's get_config method documentation|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + for details on the value of this option. + required: false + default: none + type: 'str' + aliases: + - filter_xml + ignore_warning: + description: + - A boolean, string or list of strings. If the value is C(true), + ignore all warnings regardless of the warning message. If the value + is a string, it will ignore warning(s) if the message of each warning + matches the string. If the value is a list of strings, ignore + warning(s) if the message of each warning matches at least one of the + strings in the list. The value of the I(ignore_warning) option is + applied to the load and commit operations performed by this module. + required: false + default: none + type: bool, str, or list of str + lines: + description: + - Used with the I(load) option. Specifies a list of list of + configuration strings containing the configuration to be loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is auto-dectected by + the content of the first line in the I(lines) list. + - If the I(format) option is specified, the I(format) value overrides the + format auto-detection. + required: false + default: none + type: list + load: + description: + - Specifies the type of load operation to be performed. + - The I(load) and I(rollback) options are mutually exclusive. + - > + The choices have the following meanings: + - B(none) - Do not perform a load operation. + - B(merge) - Combine the new configuration with the existing + configuration. If statements in the new configuration conflict with + statements in the existing configuration, the statements in + the new configuration replace those in the existing + configuration. + - B(replace) - This option is a superset of the B(merge) option. It + combines the new configuration with the existing configuration. If the + new configuration is in text format and a hierarchy level in the new + configuartion is prefixed with the string C(replace:), then the + hierarchy level in the new configuration replaces the entire + corresponding hierarchy level in the existing configuration, regardles + of the existence or content of that hierarchy level in the existing + configuration. If the configuration is in XML format, the XML attribute + C(replace = "replace") is equivalent to the text format's C(replace:) + prefix. If a configuration hierarchy in the new configuration is not + prefixed with C(replace:), then the B(merge) behavior is used. + Specifically, for any statements in the new configuration which + conflict with statements in the existing configuration, the statements + in the new configuration replace those in the existing configuration. + - B(override) - Discard the entire existing configuration and replace it + with the new configuration. When the configuration is later committed, + all system processes are notified and the entire new configuration is + marked as 'changed' even if some statements previously existed in the + configuration. The value B(overwrite) is a synonym for B(override). + - B(update) - This option is similar to the B(override) option. The new + configuration completely replaces the existing configuration. The + difference comes when the configuration is later committed. This option + performs a 'diff' between the new candidate configuration and the + existing committed configuration. It then only notifies system + processes repsonsible for the changed portions of the configuration, + and only marks the actual configuration changes as 'changed'. + - B(set) - This option is used when the new configuration data is in set + format (a series of configuration mode commands). The new configuration + data is loaded line by line and may contain any configuration mode + commands, such as set, delete, edit, or deactivate. This value must be + specified if the new configuration is in set format. + required: false + default: none + choices: + - none + - set + - merge + - update + - replace + - override + - overwrite + type: str + options: + description: + - Additional options, specified as a dictionary of key/value pairs, used + when retrieving the configuration. See the + U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) + for information on available options. + required: false + default: None + type: dict + retrieve: + description: + - The configuration database to be retrieved. + required: false + default: none + choices: + - none + - candidate + - committed + type: str + return_output: + description: + - Indicates if the output of the I(diff) and I(retreive) options should + be returned in the module's response. You might want to set this option + to C(false), and set the I(dest_dir) option, if the configuration or + diff output is very large and you only need to save the output rather + than using it's content in subsequent tasks/plays of your playbook. + required: false + default: true + type: bool + rollback: + description: + - Populate the candidate configuration from a previously committed + configuration. This value can be a configuration number between 0 and + 49, or the keyword C(rescue) to load the previously saved rescue + configuration. + - By default, some Junos platforms store fewer than 50 previous + configurations. Specifying a value greater than the number + of previous configurations available, or specifying C(rescue) when no + rescue configuration has been saved, will result in an error when the + module attempts to perform the rollback. + - The I(rollback) and I(load) options are mutually exclusive. + required: false + default: none + choices: + - 0-49 + - rescue + type: int or str + src: + description: + - Used with the I(load) option. Specifies the path to a file, on the + local Ansible control machine, containing the configuration to be + loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is determined by the + file extension of this path name. If the file has a C(.conf) + extension, the content is treated as text format. If the file has a + C(.xml) extension, the content is treated as XML format. If the file + has a C(.set) extension, the content is treated as Junos B(set) + commands. + - If the I(format) option is specified, the I(format) value overrides the + file-extension based format detection. + required: false + default: none + type: 'path' + aliases: + - source + - file + template: + description: + - The path to a Jinja2 template file, on the local Ansible control + machine. This template file, along with the I(vars) option, is used to + generate the configuration to be loaded on the target Junos device. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. + required: false + default: none + type: path + aliases: + - template_path + url: + description: + - A URL which specifies the configuration data to load on the target + Junos device. + - The Junos device uses this URL to load the configuration, therefore + this URL must be reachable by the target Junos device. + - The possible formats of this value are documented in the 'url' section + of the + U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + required: false + default: none + type: str + vars: + description: + - A dictionary of keys and values used to render the Jinja2 template + specified by the I(template) option. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. + required: false + default: none + type: dict + aliases: + - template_vars +''' + +EXAMPLES = ''' +--- +- name: Manipulate the configuration of Junos devices + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: Retrieve the committed configuration + juniper_junos_config: + retrieve: 'committed' + diff: false + check: false + commit: false + register: response + - name: Print the lines in the config. + debug: + var: response.config_lines + + - name: Append .foo to the hostname using private config mode. + juniper_junos_config: + config_mode: 'private' + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + register: response + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the previous config. + juniper_junos_config: + config_mode: 'private' + rollback: 1 + register: response + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the rescue config. + juniper_junos_config: + rollback: 'rescue' + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load override from a file. + juniper_junos_config: + load: 'override' + src: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a Jinja2 template. + juniper_junos_config: + load: 'merge' + format: 'xml' + template: "{{ inventory_hostname }}.j2" + vars: + host: "{{ inventory_hostname }}" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device. + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device, skip the commit check + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + check: false + register: response + - name: Print the msg. + debug: + var: response.msg + + - name: Print diff between current and rollback 10. No check. No commit. + juniper_junos_config: + rollback: 11 + diff: true + check: false + commit: false + register: response + - name: Print the msg. + debug: + var: response + + - name: Retrieve [edit system services] of current committed config. + juniper_junos_config: + retrieve: 'committed' + filter: 'system/services' + diff: true + check: false + commit: false + register: response + - name: Print the resulting config lines. + debug: + var: response.config_lines + + - name: Enable NETCONF SSH and traceoptions, save config, and diffs. + juniper_junos_config: + load: 'merge' + lines: + - 'set system services netconf ssh' + - 'set system services netconf traceoptions flag all' + - 'set system services netconf traceoptions file netconf.log' + format: 'set' + retrieve: 'candidate' + filter: 'system/services' + comment: 'Enable NETCONF with traceoptions' + dest_dir: './output' + register: response + - name: Print the complete response + debug: + var: response + + - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + confirm: 5 + check_commit_wait: 3 + register: response + - name: Print the complete response + debug: + var: response + - name: Confirm the previous commit with a commit check (but no commit) + juniper_junos_config: + check: true + diff: false + commit: false + register: response + - name: Print the complete response + debug: + var: response +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed, or would have + changed when in check mode. + returned: success + type: bool +config: + description: + - The retrieved configuration. The value is a single multi-line + string in the format specified by the I(format) option. + returned: when I(retrieved) is not C(none) and I(return_output) is C(true). + type: str +config_lines: + description: + - The retrieved configuration. The value is a list of single-line + strings in the format specified by the I(format) option. + returned: when I(retrieved) is not C(none) and I(return_output) is C(true). + type: list +config_parsed: + description: + - The retrieved configuration parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the + jxmlease library. For JSON the response is parsed using the + Python json library. + - When Ansible converts the jxmlease or native Python data + structure into JSON, it does not guarantee that the order of + dictionary/object keys are maintained. + returned: when I(retrieved) is not C(none), the I(format) option is C(xml) or + C(json) and I(return_output) is C(true). + type: dict +diff: + description: + - The configuration differences between the previous and new + configurations. The value is a dict that contains a single key named + "prepared". Value associated with that key is a single multi-line string + in "diff" format. + returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and + I(return_output) is C(true). + type: dict +diff_lines: + description: + - The configuration differences between the previous and new + configurations. The value is a list of single-line strings in "diff" + format. + returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and + I(return_output) is C(true). + type: list +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +file: + description: + - The value of the I(src) option. + returned: when I(load) is not C(none) and I(src) is not C(none) + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +''' + + +# Standard library imports +import time + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + # Choices which are defined in the common module. + config_format_choices = juniper_junos_common.CONFIG_FORMAT_CHOICES + config_database_choices = [None] + \ + juniper_junos_common.CONFIG_DATABASE_CHOICES + config_action_choices = [None] + juniper_junos_common.CONFIG_ACTION_CHOICES + config_mode_choices = juniper_junos_common.CONFIG_MODE_CHOICES + config_model_choices = juniper_junos_common.CONFIG_MODEL_CHOICES + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + ignore_warning=dict(required=False, + type='list', + default=None), + config_mode=dict(choices=config_mode_choices, + type='str', + required=False, + aliases=['config_access', 'edit_mode', + 'edit_access'], + default='exclusive'), + rollback=dict(type='str', + required=False, + default=None), + load=dict(choices=config_action_choices, + type='str', + required=False, + default=None), + src=dict(type='path', + required=False, + aliases=['source', 'file'], + default=None), + lines=dict(type='list', + required=False, + default=None), + template=dict(type='path', + required=False, + aliases=['template_path'], + default=None), + vars=dict(type='dict', + required=False, + aliases=['template_vars'], + default=None), + url=dict(type='str', + required=False, + default=None), + format=dict(choices=config_format_choices, + type='str', + required=False, + default=None), + model=dict(required=False, + choices=config_model_choices, + type='str', + default=None), + remove_ns=dict(required=False, + type='bool', + default=None), + namespace=dict(required=False, + type='str', + default=None), + check=dict(required=False, + type='bool', + aliases=['check_commit', 'commit_check'], + default=None), + diff=dict(required=False, + type='bool', + aliases=['compare', 'diffs'], + default=None), + diffs_file=dict(type='path', + required=False, + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir', 'savedir', + 'save_dir'], + default=None), + return_output=dict(required=False, + type='bool', + default=True), + retrieve=dict(choices=config_database_choices, + type='str', + required=False, + default=None), + options=dict(type='dict', + required=False, + default={}), + filter=dict(required=False, + type='str', + aliases=['filter_xml'], + default=None), + dest=dict(type='path', + required=False, + aliases=['destination'], + default=None), + commit=dict(required=False, + type='bool', + default=None), + commit_empty_changes=dict(required=False, + type='bool', + default=False), + confirmed=dict(required=False, + type='int', + aliases=['confirm'], + default=None), + comment=dict(required=False, + type='str', + default=None), + check_commit_wait=dict(required=False, + type='int', + default=None) + ), + # Mutually exclusive options. + mutually_exclusive=[['load', 'rollback'], + ['src', 'lines', 'template', 'url'], + ['diffs_file', 'dest_dir'], + ['dest', 'dest_dir']], + # Required together options. + required_together=[['template', 'vars']], + # Check mode is implemented. + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, + ) + # Do additional argument verification. + + # Parse ignore_warning value + ignore_warning = junos_module.parse_ignore_warning_option() + + # Straight from params + config_mode = junos_module.params.get('config_mode') + + # Parse rollback value + rollback = junos_module.parse_rollback_option() + + # Straight from params + load = junos_module.params.get('load') + src = junos_module.params.get('src') + lines = junos_module.params.get('lines') + template = junos_module.params.get('template') + vars = junos_module.params.get('vars') + url = junos_module.params.get('url') + format = junos_module.params.get('format') + check = junos_module.params.get('check') + diff = junos_module.params.get('diff') + diffs_file = junos_module.params.get('diffs_file') + dest_dir = junos_module.params.get('dest_dir') + return_output = junos_module.params.get('return_output') + retrieve = junos_module.params.get('retrieve') + options = junos_module.params.get('options') + filter = junos_module.params.get('filter') + dest = junos_module.params.get('dest') + commit = junos_module.params.get('commit') + commit_empty_changes = junos_module.params.get('commit_empty_changes') + confirmed = junos_module.params.get('confirmed') + comment = junos_module.params.get('comment') + check_commit_wait = junos_module.params.get('check_commit_wait') + model = junos_module.params.get('model') + remove_ns = junos_module.params.get('remove_ns') + namespace = junos_module.params.get('namespace') + + + # If retrieve is set and load and rollback are not set, then + # check, diff, and commit default to False. + if retrieve is not None and load is None and rollback is None: + if diff is None: + diff = False + if check is None: + check = False + if commit is None: + commit = False + # Otherwise, diff, check, and commit default to True. + else: + if diff is None: + diff = True + if check is None: + check = True + if commit is None: + commit = True + + # If load is not None, must have one of src, template, url, lines + if load is not None: + for option in ['src', 'lines', 'template', 'url']: + if junos_module.params.get(option) is not None: + break + # for/else only executed if we didn't break out of the loop. + else: + junos_module.fail_json(msg="The load option (%s) is specified, " + "but none of 'src', 'lines', " + "'template', or 'url' are specified. " + "Must specify one of the 'src', " + "'lines', 'template', or 'url' options." + % (load)) + + # format is valid if retrieve is not None or load is not None. + if format is not None: + if load is None and retrieve is None: + junos_module.fail_json(msg="The format option (%s) is specified, " + "but neither 'load' or 'retrieve' are " + "specified. Must specify one of " + "'load' or 'retrieve' options." + % (format)) + + # dest_dir is valid if retrieve is not None or diff is True. + if dest_dir is not None: + if retrieve is None and diff is False: + junos_module.fail_json(msg="The dest_dir option (%s) is specified," + " but neither 'retrieve' or 'diff' " + "are specified. Must specify one of " + "'retrieve' or 'diff' options." + % (dest_dir)) + + # dest is valid if retrieve is not None + if dest is not None: + if retrieve is None: + junos_module.fail_json(msg="The dest option (%s) is specified," + " but 'retrieve' is not specified. " + "Must specify the 'retrieve' option." + % (dest)) + + # diffs_file is valid if diff is True + if diffs_file is not None: + if diff is False: + junos_module.fail_json(msg="The diffs_file option (%s) is " + "specified, but 'diff' is false." + % (diffs_file)) + + # commit_empty_changes is valid if commit is True + if commit_empty_changes is True: + if commit is False: + junos_module.fail_json(msg="The commit_empty_changes option " + "is true, but 'commit' is false. " + "The commit_empty_changes option " + "may only be specified when " + "'commit' is true.") + + # comment is valid if commit is True + if comment is not None: + if commit is False: + junos_module.fail_json(msg="The comment option (%s) is " + "specified, but 'commit' is false." + % (comment)) + + # confirmed is valid if commit is True + if confirmed is not None: + if commit is False: + junos_module.fail_json(msg="The confirmed option (%s) is " + "specified, but 'commit' is false." + % (confirmed)) + # Must be greater >= 1. + if confirmed < 1: + junos_module.fail_json(msg="The confirmed option (%s) must have a " + "positive integer value." % (confirmed)) + + # check_commit_wait is valid if check is True and commit is True + if check_commit_wait is not None: + if commit is False: + junos_module.fail_json(msg="The check_commit_wait option (%s) is " + "specified, but 'commit' is false." + % (check_commit_wait)) + if check is False: + junos_module.fail_json(msg="The check_commit_wait option (%s) is " + "specified, but 'check' is false." + % (check_commit_wait)) + # Must be greater >= 1. + if check_commit_wait < 1: + junos_module.fail_json(msg="The check_commit_wait option (%s) " + "must have a positive integer value." % + (check_commit_wait)) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': 'Configuration has been: ', + 'changed': False, + 'failed': True} + + junos_module.logger.debug("Step 1 - Open a candidate configuration " + "database.") + junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning) + results['msg'] += 'opened' + + junos_module.logger.debug("Step 2 - Load configuration data into the " + "candidate configuration database.") + if rollback is not None: + junos_module.rollback_configuration(id=rollback) + # Assume configuration changed in case we don't perform a diff later. + # If diff is set, we'll check for actual differences later. + results['changed'] = True + results['msg'] += ', rolled back' + elif load is not None: + if src is not None: + junos_module.load_configuration(action=load, + src=src, + ignore_warning=ignore_warning, + format=format) + results['file'] = src + elif lines is not None: + junos_module.load_configuration(action=load, + lines=lines, + ignore_warning=ignore_warning, + format=format) + elif template is not None: + junos_module.load_configuration(action=load, + template=template, + vars=vars, + ignore_warning=ignore_warning, + format=format) + elif url is not None: + junos_module.load_configuration(action=load, + url=url, + ignore_warning=ignore_warning, + format=format) + else: + junos_module.fail_json(msg="The load option was set to: %s, but " + "no 'src', 'lines', 'template', or " + "'url' option was set." % + (load)) + # Assume configuration changed in case we don't perform a diff later. + # If diff is set, we'll check for actual differences later. + results['changed'] = True + results['msg'] += ', loaded' + + junos_module.logger.debug("Step 3 - Check the validity of the candidate " + "configuration database.") + if check is True: + junos_module.check_configuration() + results['msg'] += ', checked' + + junos_module.logger.debug("Step 4 - Determine differences between the " + "candidate and committed configuration " + "databases.") + if diff is True or junos_module._diff: + diff = junos_module.diff_configuration() + if diff is not None: + results['changed'] = True + if return_output is True or junos_module._diff: + results['diff'] = {'prepared': diff} + results['diff_lines'] = diff.splitlines() + # Save the diff output + junos_module.save_text_output('diff', 'diff', diff) + else: + results['changed'] = False + results['msg'] += ', diffed' + + junos_module.logger.debug("Step 5 - Retrieve the configuration database " + "from the Junos device.") + if retrieve is not None: + if format is None: + format = 'text' + (config, config_parsed) = junos_module.get_configuration( + database=retrieve, + format=format, + options=options, + filter=filter, + model=model, + namespace=namespace, + remove_ns=remove_ns) + if return_output is True: + if config is not None: + results['config'] = config + results['config_lines'] = config.splitlines() + if config_parsed is not None: + results['config_parsed'] = config_parsed + # Save the output + format_extension = 'config' if format == 'text' else format + junos_module.save_text_output('config', format_extension, config) + results['msg'] += ', retrieved' + + junos_module.logger.debug("Step 6 - Commit the configuration changes.") + if commit is True and not junos_module.check_mode: + # Perform the commit if: + # 1) commit_empty_changes is True + # 2) Neither rollback or load is set. i.e. confirming a previous commit + # 3) rollback or load is set, and there were actual changes. + if (commit_empty_changes is True or + (rollback is None and load is None) or + ((rollback is not None or load is not None) and + results['changed'] is True)): + if check_commit_wait is not None: + time.sleep(check_commit_wait) + junos_module.commit_configuration(ignore_warning=ignore_warning, + comment=comment, + confirmed=confirmed) + results['msg'] += ', committed' + else: + junos_module.logger.debug("Skipping commit. Nothing changed.") + + junos_module.logger.debug("Step 7 - Close the candidate configuration " + "database.") + junos_module.close_configuration() + results['msg'] += ', closed.' + + # If we made it this far, everything was successful. + results['failed'] = False + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py new file mode 100644 index 00000000..3c5fc811 --- /dev/null +++ b/library/juniper_junos_facts.py @@ -0,0 +1,344 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_facts +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Retrieve facts from a Junos device +description: + - Retrieve facts from a Junos device using the + U(PyEZ fact gathering system|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html). + - Also returns the committed configuration of the Junos device if the + I(config_format) option has a value other than C(none). +options: + config_format: + description: + - The format of the configuration returned. The specified format must be + supported by the target Junos device. + required: false + default: none + choices: + - none + - xml + - set + - text + - json + savedir: + description: + - A path to a directory, on the Ansible control machine, where facts + will be stored in a JSON file. + - The resulting JSON file is saved in + I(savedir)C(/)I(hostname)C(-facts.json). + - The I(savedir) directory is the value of the I(savedir) option. + - The I(hostname)C(-facts.json) filename begins with the value of the + C(hostname) fact returned from the Junos device, which might be + different than the value of the I(host) option passed to the module. + - If the value of the I(savedir) option is C(none), the default, then + facts are NOT saved to a file. + required: false + default: none + type: path +''' + +EXAMPLES = ''' +--- +- name: Gather facts from Junos devices + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: Gather Junos facts with no configuration + juniper_junos_facts: + +# Print a fact + +# Using config_format option + +# Print the config + +# Using savedir option + +# Print the saved JSON file +''' + +RETURN = ''' +ansible_facts.junos: + description: + - Facts collected from the Junos device. This dictionary contains the + keys listed in the I(contains) section of this documentation PLUS all + of the keys returned from PyEZ's fact gathering system. See + U(PyEZ facts|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) + for a complete list of these keys and their meaning. + returned: success + type: complex + contains: + config: + description: + - The device's committed configuration, in the format specified by + I(config_format), as a single multi-line string. + returned: when I(config_format) is not C(none). + type: str + has_2RE: + description: + - Indicates if the device has more than one Routing Engine installed. + Because Ansible does not allow keys to begin with a number, this fact + is returned in place of PyEZ's C(2RE) fact. + returned: success + type: bool + re_name: + description: + - The name of the current Routing Engine to which Ansible is connected. + returned: success + type: str + master_state: + description: + - The mastership state of the Routing Engine to which Ansible is + connected. C(true) if the RE is the master Routing Engine. C(false) + if the RE is not the master Routing Engine. + returned: success + type: bool +changed: + description: + - Indicates if the device's state has changed. Since this module does not + change the operational or configuration state of the device, the value is + always set to C(false). + returned: success + type: bool + sample: false +facts: + description: + - Returned for backwards compatibility. Returns the same keys and values + which are returned under I(ansible_facts.junos). + returned: success + type: dict +failed: + description: + - Indicates if the task failed. + returned: always + type: bool + sample: false +''' + +# Standard library imports +import json +import os.path + + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common +from ansible.module_utils._text import to_bytes + + +def get_facts_dict(junos_module): + """Retreive PyEZ facts and convert to a standard dict w/o custom types. + + Ansible >= 2.0 doesn't like custom objects in a modules return value. + Because PyEZ facts are a custom object rather than a true dict they must be + converted to a standard dict. Since facts are read-only, we must begin by + copying facts into a dict. Since PyEZ facts are "on-demand", the + junos_module.dev instance must be an open PyEZ Device instance ojbect + before this function is called. + + Args: + junos_module: An instance of a JuniperJunosModule. + + Returns: + A dict containing the device facts. + """ + # Retrieve all PyEZ-supported facts and copy to a standard dict. + facts = dict(junos_module.dev.facts) + # Add two useful facts that are implement as PyEZ Device attributes. + facts['re_name'] = junos_module.dev.re_name + facts['master_state'] = junos_module.dev.master + # Ansible doesn't allow keys starting with numbers. + # Replace the '2RE' key with the 'has_2RE' key. + if '2RE' in facts: + facts['has_2RE'] = facts['2RE'] + del facts['2RE'] + # The value of the 'version_info' key is a custom junos.version_info + # object. Convert this value to a dict. + if 'version_info' in facts and facts['version_info'] is not None: + facts['version_info'] = dict(facts['version_info']) + # The values of the ['junos_info'][re_name]['object'] keys are + # custom junos.version_info objects. Convert all of these to dicts. + if 'junos_info' in facts and facts['junos_info'] is not None: + for key in facts['junos_info']: + facts['junos_info'][key]['object'] = dict( + facts['junos_info'][key]['object']) + return facts + + +def save_facts(junos_module, facts): + """If the savedir option was specified, save the facts into a JSON file. + + If the savedir option was specified, save the facts into a JSON file named + savedir/hostname-facts.json. The filename begins with the value of the + hostname fact returned from the Junos device, which might be different than + the value of the host option passed to the module. + + Args: + junos_module: An instance of a JuniperJunosModule. + facts: The facts dict returned by get_facts_dict(). + + Raises: + IOError: Calls junos_module.fail_json if unable to open the facts + file for writing. + """ + if junos_module.params.get('savedir') is not None: + save_dir = junos_module.params.get('savedir') + file_name = '%s-facts.json' % (facts['hostname']) + file_path = os.path.normpath(os.path.join(save_dir, file_name)) + junos_module.logger.debug("Saving facts to: %s.", file_path) + try: + # TODO: Verify does thsi work with Python3 + with open(file_path, 'w') as fact_file: + json.dump(facts, fact_file) + junos_module.logger.debug("Facts saved to: %s.", file_path) + except IOError: + junos_module.fail_json(msg="Unable to save facts. Failed to open " + "the %s file." % (file_path)) + + +def save_inventory(junos_module, inventory): + """If the savedir option was specified, save the XML inventory. + + If the savedir option was specified, save the inventory XML output into + an XML file named savedir/hostname-inventory.xml. The filename begins with + the value of the hostname fact returned from the Junos device, which might + be different than the value of the host option passed to the module. + + Args: + junos_module: An instance of a JuniperJunosModule. + inventory: The XML string of inventory to save. + + Raises: + IOError: Calls junos_module.fail_json if unable to open the inventory + file for writing. + """ + if junos_module.params.get('savedir') is not None: + save_dir = junos_module.params.get('savedir') + file_name = '%s-inventory.xml' % (junos_module.dev.facts['hostname']) + file_path = os.path.normpath(os.path.join(save_dir, file_name)) + junos_module.logger.debug("Saving inventory to: %s.", file_path) + try: + with open(file_path, 'wb') as fact_file: + fact_file.write(to_bytes(inventory, encoding='utf-8')) + junos_module.logger.debug("Inventory saved to: %s.", file_path) + except IOError: + junos_module.fail_json(msg="Unable to save inventory. Failed to " + "open the %s file." % (file_path)) + + +def main(): + config_format_choices = [None] + config_format_choices += juniper_junos_common.CONFIG_FORMAT_CHOICES + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + config_format=dict(choices=config_format_choices, + required=False, + default=None), + savedir=dict(type='path', required=False, default=None), + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, + ) + + junos_module.logger.debug("Gathering facts.") + # Get the facts dictionary from the device. + facts = get_facts_dict(junos_module) + junos_module.logger.debug("Facts gathered.") + + if junos_module.params.get('savedir') is not None: + # Save the facts. + save_facts(junos_module, facts) + + # Get and save the inventory + try: + junos_module.logger.debug("Gathering inventory.") + inventory = junos_module.dev.rpc.get_chassis_inventory() + junos_module.logger.debug("Inventory gathered.") + save_inventory(junos_module, + junos_module.etree.tostring(inventory, + pretty_print=True)) + except junos_module.pyez_exception.RpcError as ex: + junos_module.fail_json(msg='Unable to retrieve hardware ' + 'inventory: %s' % (str(ex))) + + config_format = junos_module.params.get('config_format') + if config_format is not None: + (config, config_parsed) = junos_module.get_configuration( + format=config_format) + if config is not None: + facts.update({'config': config}) + # Need to wait until the ordering issues are figured out before + # using config_parsed. + # if config_parsed is not None: + # facts.update({'config_parsed': config_parsed}) + + # Return response. + junos_module.exit_json( + changed=False, + failed=False, + ansible_facts={'junos': facts}, + facts=facts) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py new file mode 100644 index 00000000..f5669c30 --- /dev/null +++ b/library/juniper_junos_jsnapy.py @@ -0,0 +1,358 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2016, Roslan Zaki +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_jsnapy +version_added: "2.0.0" # of Juniper.junos role +author: + - Juniper Networks + - Roslan Zaki + - Damien Garros + - Stacy Smith (@stacywsmith)" +short_description: Execute JSNAPy tests on a Junos device +description: + - Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. + JSNAPy is documented on U(Github|https://github.com/Juniper/jsnapy) and + this + U(Day One Book|https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) + - This module only reports C(failed) if the module encounters an error and + fails to execute the JSNAPy tests. If does NOT report C(failed) if one or + more of the JSNAPy tests fail. To check the test results, register the + module's response and use the assert module to verify the expected result + in the response. (See :ref:`juniper_junos_jsnapy-examples-label`.) + - A callback plugin which formats and prints JSNAPy test results for human + consumption is also available. This callback plugin is enabled by adding + C(callback_whitelist = jsnapy) to the Ansible configuration file. +options: + action: + description: + - The JSNAPy action to perform. + required: true + default: none + type: str + choices: + - check + - snapcheck + - snap_pre + - snap_post + config_file: + description: + - The filename of a JSNAPy configuration file (in YAML format). The + I(test_files) option and the I(config_file) option are mutually + exclusive. Either the I(test_files) option or the I(config_file) + option is required. + required: false + type: path + default: none + dir: + description: + - The path to the directory containing the JSNAPy test file(s) specified + by the I(test_files) option or the JSNAPy configuration file specified + by the I(config_file) option. + required: false + type: path + default: /etc/jsnapy/testfiles + aliases: + - directory + test_files: + description: + - The filename of file(s) in the I(dir) directory. Each file contains + JSNAPy test case definitions. The I(test_files) option and the + I(config_file) option are mutually exclusive. Either the I(test_files) + option or the I(config_file) option is required. + required: false + type: list of path + default: none +''' + + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_jsnapy + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: JUNOS Post Checklist + juniper_junos_jsnapy: + action: "snap_post" + config_file: "first_test.yml" + logfile: "migration_post.log" + register: test1 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test1.passPercentage == 100" + - name: Print the full test response + debug: + var: test1 + + - name: Test based on a test_file directly + juniper_junos_jsnapy: + action: "snapcheck" + test_files: "tests/test_junos_interface.yaml" + register: test2 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test2.passPercentage == 100" + - name: Print the full test response + debug: + var: test2 + + - name: "Collect Pre Snapshot" + juniper_junos_jsnapy: + action: "snap_pre" + test_files: "tests/test_loopback.yml" + + - name: "Collect Post Snapshot" + juniper_junos_jsnapy: + action: "snap_post" + test_files: "tests/test_loopback.yml" + + - name: "Check after Pre and Post Snapshots" + juniper_junos_jsnapy: + action: "check" + test_files: "tests/test_loopback.yml" + register: test3 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test3.|succeeded" + - "test3.passPercentage == 100" + - name: Print the full test response + debug: + var: test3 +''' + +RETURN = ''' +action: + description: + - The JSNAPy action performed as specified by the I(action) option. + returned: success + type: str +changed: + description: + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to C(false). + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +# final_result: +msg: + description: + - A human-readable message indicating the result of the JSNAPy tests. + returned: always + type: str +# total_passed: +# total_failed: +''' + +# Standard Library imports +import os.path + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + action=dict(required=True, + choices=JSNAPY_ACTION_CHOICES, + type='str', + default=None), + test_files=dict(required=False, + type='list', + default=None), + config_file=dict(required=False, + type='path', + default=None), + dir=dict(required=False, + type='path', + aliases=['directory'], + default='/etc/jsnapy/testfiles')), + # Mutually exclusive options. + mutually_exclusive=[['test_files', 'config_file']], + # One of test_files or config_file is required. + required_one_of=[['test_files', 'config_file']], + supports_check_mode=True, + min_jsnapy_version=juniper_junos_common.MIN_JSNAPY_VERSION, + ) + + # Straight from params + action = junos_module.params.get('action') + test_files = junos_module.params.get('test_files') + config_file = junos_module.params.get('config_file') + dir = junos_module.params.get('dir') + + # Initialize the results. Assume failure until we know otherwise. + results = {'msg': '', + 'action': action, + 'changed': False, + 'failed': True} + + if config_file is not None: + junos_module.logger.debug('Checking config file: %s.', config_file) + config_file_path = os.path.abspath(config_file) + config_dir_file_path = os.path.abspath(os.path.join(dir, config_file)) + if os.path.isfile(config_file_path): + data = config_file_path + elif os.path.isfile(config_dir_file_path): + data = config_dir_file_path + else: + junos_module.fail_json(msg="Unable to locate the %s config file " + "at %s or %s." % (config_file, + config_file_path, + config_dir_file_path)) + elif test_files is not None and len(test_files) > 0: + data = {'tests': []} + for test_file in test_files: + junos_module.logger.debug('Checking test file: %s.', test_file) + test_file_path = os.path.abspath(test_file) + test_dir_file_path = os.path.abspath(os.path.join(dir, test_file)) + if os.path.isfile(test_file_path): + data['tests'].append(test_file_path) + elif os.path.isfile(test_dir_file_path): + data['tests'].append(test_dir_file_path) + else: + junos_module.fail_json(msg="Unable to locate the %s test file " + "at %s or %s." % + (test_file, + test_file_path, + test_dir_file_path)) + else: + junos_module.fail_json(msg="No config_file or test_files specified.") + + try: + junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') + jsa = junos_module.jsnapy.SnapAdmin() + junos_module.logger.debug('Executing %s action.', action) + if action == 'check': + responses = jsa.check(data=data, + dev=junos_module.dev, + pre_file='PRE', + post_file='POST') + elif action == 'snapcheck': + responses = jsa.snapcheck(data=data, + dev=junos_module.dev) + elif action == 'snap_pre': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='PRE') + elif action == 'snap_post': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='POST') + else: + junos_module.fail_json(msg="Unexpected action: %s." % (action)) + junos_module.logger.debug('The %s action executed successfully.', + action) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + junos_module.fail_json(msg="Error communicating with the device: %s" % + (str(ex))) + except Exception as ex: + junos_module.fail_json(msg="Uncaught exception - please report: %s" % + (str(ex))) + + if isinstance(responses, list) and len(responses) == 1: + if action in ('snapcheck', 'check'): + for response in responses: + results['device'] = response.device + results['router'] = response.device + results['final_result'] = response.result + results['total_passed'] = response.no_passed + results['total_failed'] = response.no_failed + results['test_results'] = response.test_results + total_tests = int(response.no_passed) + int(response.no_failed) + results['total_tests'] = total_tests + pass_percentage = 0 + if total_tests > 0: + pass_percentage = ((int(response.no_passed) * 100) // + total_tests) + results['passPercentage'] = pass_percentage + results['pass_percentage'] = pass_percentage + if results['final_result'] == 'Failed': + results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + else: + results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + elif action in ('snap_pre', 'snap_post'): + results['msg'] = "The %s action successfully executed." % (action) + else: + junos_module.fail_json(msg="Unexpected JSNAPy responses. Type: %s." + "Responses: %s" % + (type(responses), str(responses))) + + # If we made it this far, it's success. + results['failed'] = False + + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py new file mode 100644 index 00000000..ce6c81ec --- /dev/null +++ b/library/juniper_junos_ping.py @@ -0,0 +1,504 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2016, Damien Garros +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_ping +version_added: "2.0.0" # of Juniper.junos role +author: Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Execute ping from a Junos device +description: + - Execute the ping command from a Junos device to a specified destination in + order to test network reachability from the Junos device . +options: + acceptable_percent_loss: + description: + - Maximum percentage of packets that may be lost and still consider the + task not to have failed. + required: false + default: 0 + type: int + aliases: + - acceptable_packet_loss + count: + description: + - Number of packets to send. + required: false + default: 5 + type: int + dest: + description: + - The IP address, or hostname if DNS is configured on the Junos device, + used as the destination of the ping. + required: true + default: none + type: str + aliases: + - dest_ip + - dest_host + - destination + - destination_ip + - destination_host + do_not_fragment: + description: + - Set Do Not Fragment bit on ping packets. + required: false + default: false + type: bool + interface: + description: + - The source interface from which the the ping is sent. If not + specified, the default Junos algorithm for determining the source + interface is used. + required: false + default: none + type: str + rapid: + description: + - Send ping requests rapidly + required: false + default: true + type: bool + routing_instance: + description: + - Name of the source routing instance from which the ping is + originated. If not specified, the default routing instance is used. + required: false + default: none + type: str + size: + description: + - The size of the ICMP payload of the ping. + - Total size of the IP packet is I(size) + the 20 byte IP header + + the 8 byte ICMP header. Therefore, I(size) of C(1472) generates an IP + packet of size 1500. + required: false + default: none (default size for device) + type: int + source: + description: + - The IP address, or hostname if DNS is configured on the Junos device, + used as the source address of the ping. If not specified, the Junos + default algorithm for determining the source address is used. + required: false + default: none + type: str + aliases: + - source_ip + - source_host + - src + - src_ip + - src_host + ttl: + description: + - Maximum number of IP routers (hops) allowed between source and + destination. + required: false + default: none (default ttl for device) + type: int +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_ping + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. + juniper_junos_ping: + dest: "192.68.1.1" + + - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + acceptable_percent_loss: 50 + register: response + - name: Print all keys in the response. + debug: + var: response + + - name: Ping 192.68.1.1. Send 20 packets. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + count: 20 + register: response + - name: Print packet sent from the response. + debug: + var: response.packets_sent + + - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + count: 10 + rapid: false + register: response + - name: Print the average round-trip-time from the response. + debug: + var: response.rtt_average + + - name: Ping www.juniper.net with ttl 15. Register response. + juniper_junos_ping: + dest: "www.juniper.net" + ttl: 15 + register: response + - name: Print the packet_loss percentage from the response. + debug: + var: response.packet_loss + + - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + size: 1472 + register: response + - name: Print the packets_received from the response. + debug: + var: response.packets_received + + - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + do_not_fragment: true + register: response + - name: Print the maximum round-trip-time from the response. + debug: + var: response.rtt_maximum + + - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + source: "192.68.1.2" + register: response + - name: Print the source from the response. + debug: + var: response.source + + - name: Ping 192.168.1.1 from the red routing-instance. + juniper_junos_ping: + dest: "192.168.1.1" + routing_instance: "red" + + - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface + juniper_junos_ping: + dest: "224.0.0.1" + interface: "ge-0/0/0.0" +''' + +RETURN = ''' +acceptable_percent_loss: + description: + - The acceptable packet loss (as a percentage) for this task as specified + by the I(acceptable_percent_loss) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +changed: + description: + - Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to C(false). + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +count: + description: + - The number of pings sent, as specified by the I(count) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +do_not_fragment: + description: + - Whether or not the do not fragment bit was set on the pings sent, as + specified by the I(do_not_fragment) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +host: + description: + - The destination IP/host of the pings sent as specified by the I(dest) + option. + - Keys I(dest) and I(dest_ip) are also returned for backwards + compatibility. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +interface: + description: + - The source interface of the pings sent as specified by the + I(interface) option. + returned: when ping successfully executed and the I(interface) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +packet_loss: + description: + - The percentage of packets lost. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +packets_sent: + description: + - The number of packets sent. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +packets_received: + description: + - The number of packets received. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +rapid: + description: + - Whether or not the pings were sent rapidly, as specified by the + I(rapid) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +routing_instance: + description: + - The routing-instance from which the pings were sent as specified by + the I(routing_instance) option. + returned: when ping successfully executed and the I(routing_instance) + option was specified, even if the I(acceptable_percent_loss) was + exceeded. + type: str +rtt_average: + description: + - The average round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_maximum: + description: + - The maximum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_minimum: + description: + - The minimum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_stddev: + description: + - The standard deviation of round-trip-time, in microseconds, of all ping + responses received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +size: + description: + - The size in bytes of the ICMP payload on the pings sent as specified + by the I(size) option. + - Total size of the IP packet is I(size) + the 20 byte IP header + the 8 + byte ICMP header. Therefore, I(size) of 1472 generates an IP packet of + size 1500. + returned: when ping successfully executed and the I(size) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +source: + description: + - The source IP/host of the pings sent as specified by the I(source) + option. + - Key I(source_ip) is also returned for backwards compatibility. + returned: when ping successfully executed and the I(source) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +timeout: + description: + - The number of seconds to wait for a response from the ping RPC. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +ttl: + description: + - The time-to-live set on the pings sent as specified by the + I(ttl) option. + returned: when ping successfully executed and the I(ttl) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list +''' + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + # The argument spec for the module. + argument_spec = dict( + dest=dict(type='str', + required=True, + aliases=['dest_ip', 'dest_host', 'destination', + 'destination_ip', 'destination_host'], + default=None), + acceptable_percent_loss=dict(type='int', + required=False, + aliases=['acceptable_packet_loss'], + default=0), + ) + + # The portion of the argument spec that's specifically a parameter + # to the ping RPC. + ping_argument_spec = dict( + count=dict(type='int', + required=False, + default=5), + rapid=dict(type='bool', + required=False, + default=True), + ttl=dict(type='int', + required=False, + default=None), + size=dict(type='int', + required=False, + default=None), + do_not_fragment=dict(type='bool', + required=False, + default=False), + source=dict(type='str', + required=False, + aliases=['source_ip', 'source_host', 'src', + 'src_ip', 'src_host'], + default=None), + interface=dict(type='str', + required=False, + default=None), + routing_instance=dict(type='str', + required=False, + default=None), + ) + + # Add the ping RPC parameter argument spec fo the full argument_spec. + argument_spec.update(ping_argument_spec) + + argument_spec_keys = list(argument_spec.keys()) + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=argument_spec, + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + # acceptable packet loss is a percentage. Check to make sure it's between + # 0 and 100 inclusive + if (params['acceptable_percent_loss'] > 100 or + params['acceptable_percent_loss'] < 0): + junos_module.fail_json(msg='The value of the acceptable_percent_loss' + 'option (%d) is a percentage and must have ' + 'a value between 0 and 100.' % + (params['acceptable_percent_loss'])) + + # All of the params keys which are also keys in ping_argument_spec are the + # ping_params. Omit None and False values because they don't need to be + # passed to the RPC. + ping_params = {'host': params.get('dest')} + for key in ping_argument_spec: + value = params.get(key) + # Convert int (but not bool) to str + if not isinstance(value, bool) and isinstance(value, int): + params[key] = str(params[key]) + value = params.get(key) + # None and False values are the default for the RPC and shouldn't be + # passed to the device. + if value is not None and value is not False: + ping_params.update({key: value}) + + # Set initial results values. Assume failure until we know it's success. + results = {'msg': '', 'changed': False, 'failed': True} + # Results should include all the ping params in argument_spec_keys. + for key in argument_spec_keys: + results[key] = params.get(key) + # Overwrite to be a string in the results + results['acceptable_percent_loss'] = str( + params.get('acceptable_percent_loss')) + # Add timeout to the response even though it's a connect parameter. + results['timeout'] = str(params.get('timeout')) + # Add aliases for backwards compatibility + results.update({'host': params.get('dest'), + 'dest_ip': params.get('dest'), + 'source_ip': params.get('source')}) + + # Execute the ping. + results = junos_module.ping( + ping_params, + acceptable_percent_loss=params['acceptable_percent_loss'], + results=results) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py new file mode 100644 index 00000000..7947b7bf --- /dev/null +++ b/library/juniper_junos_pmtud.py @@ -0,0 +1,410 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2017, Martin Komon +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_pmtud +version_added: "2.0.0" # of Juniper.junos role +author: + - Martin Komon (@mkomon) + - Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Perform path MTU discovery from a Junos device to a + destination +description: + - Determine the maximum IP MTU supported along a path from a Junos device to + a user-specified destination by performing path MTU discovery (PMTUD) using + the ping command. The reported MTU will be between min_test_size and + I(max_size) where I(min_test_size) = (I(max_size) - I(max_range) + 1). + If the actual path MTU is greater than I(max_size), then I(max_size) will + be reported. If the actual path MTU is less than I(min_test_size), then a + failure will be reported. +options: + dest: + description: + - The IPv4 address, or hostname if DNS is configured on the Junos device, + used as the destination of the PMTUD. + required: true + default: none + type: str + aliases: + - dest_ip + - dest_host + - destination + - destination_ip + - destination_host + interface: + description: + - The source interface from which the the PMTUD is performed. If not + specified, the default Junos algorithm for determining the source + interface is used. + required: false + default: none + type: str + max_range: + description: + - The maximum range of MTU values, in bytes, which will be searched + when performing path MTU discovery. This value must be C(0) or + a power of 2 (2^n) between C(2) and C(65536). The minimum IPv4 MTU + value attempted when performing path MTU discovery is + I(min_test_size) = (I(max_size) - I(max_range) + 1) + required: false + default: 512 + type: int + max_size: + description: + - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU + discovery. + - The value returned for I(inet_mtu) will be no more + than this value even if the path actually supports a higher MTU. + - This value must be between 68 and 65496. + required: false + default: 1500 + type: int + routing_instance: + description: + - Name of the source routing instance from which the ping is + originated. + - If not specified, the default routing instance is used. + required: false + default: none + type: str + source: + description: + - The IPv4 address, or hostname if DNS is configured on the Junos device, + used as the source address of the PMTUD. If not specified, the Junos + default algorithm for determining the source address is used. + required: false + default: none + type: str + aliases: + - source_ip + - source_host + - src + - src_ip + - src_host +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_mtud + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Perform PMTUD to 192.68.1.1 with default parameters. + juniper_junos_pmtud: + dest: "192.68.1.1" + + - name: Perform PMTUD to 192.68.1.1. Register response. + juniper_junos_pmtud: + dest: "192.68.1.1" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. + juniper_junos_pmtud: + dest: "192.68.1.1" + max_size: 65496 + max_range: 65536 + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. + juniper_junos_pmtud: + dest: "192.68.1.1" + interface: "ge-0/0/0.0" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. + juniper_junos_pmtud: + dest: "192.68.1.1" + source: "192.168.1.2" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. + juniper_junos_pmtud: + dest: "192.68.1.1" + routing_instance: "red" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to C(false). + returned: when PMTUD successfully executed. + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +host: + description: + - The destination IP/host of the PMTUD as specified by the I(dest) + option. + - Keys I(dest) and I(dest_ip) are also returned for backwards + compatibility. + returned: when PMTUD successfully executed. + type: str +inet_mtu: + description: + - The IPv4 path MTU size in bytes to the I(dest). This is the lesser of + I(max_size) and the actual path MTU to I(dest). If the actual path + MTU is less than I(min_test_size), then a failure is reported. Where + I(min_test_size) = (I(max_size) - I(max_range) + 1) + returned: when PMTUD successfully executed. + type: str +interface: + description: + - The source interface of the PMTUD as specified by the I(interface) + option. + returned: when the I(interface) option was specified. + type: str +routing_instance: + description: + - The routing-instance from which the PMTUD was performed as specified by + the I(routing_instance) option. + returned: when the I(routing_instance) option was specified. + type: str +source: + description: + - The source IP/host of the PMTUD as specified by the I(source) + option. + - Key I(source_ip) is also returned for backwards compatibility. + returned: when the I(source) option was specified. + type: str +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list +''' + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + # Constants for MTU size + INET_MIN_MTU_SIZE = 68 # As prescribed by RFC 791, Section 3.2 - + # Fragmentation and Reassembly. + INET_MAX_MTU_SIZE = 65496 # Size of inet header's total length field is + # 16 bits. Therefore max inet packet size is 2^16 + # or 65536, but Junos only supports max IP size + # of 65496 for the ping command in order to + # accomodate a (potentially) maximum sized IP + # header. + + # Constants for the size of headers + INET_HEADER_SIZE = 20 + ICMP_HEADER_SIZE = 8 + INET_AND_ICMP_HEADER_SIZE = INET_HEADER_SIZE + ICMP_HEADER_SIZE + + # Choices for max_size + MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2 ** x, range(1, 17))) + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + dest=dict(type='str', + required=True, + aliases=['dest_ip', 'dest_host', 'destination', + 'destination_ip', 'destination_host'], + default=None), + max_size=dict(type='int', + required=False, + default=1500), + max_range=dict(type='int', + required=False, + choices=MAX_SIZE_CHOICES, + default=512), + source=dict(type='str', + required=False, + aliases=['source_ip', 'source_host', 'src', + 'src_ip', 'src_host'], + default=None), + interface=dict(type='str', + required=False, + default=None), + routing_instance=dict(type='str', + required=False, + default=None), + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + # max_size must be between INET_MIN_MTU_SIZE and INET_MAX_MTU_SIZE + if (params['max_size'] < INET_MIN_MTU_SIZE or + params['max_size'] > INET_MAX_MTU_SIZE): + junos_module.fail_json(msg='The value of the max_size option(%d) ' + 'must be between %d and %d.' % + (params['max_size'], INET_MIN_MTU_SIZE, + INET_MAX_MTU_SIZE)) + + # Initialize ping parameters. + ping_params = {'host': params.get('dest'), + 'count': '3', + 'rapid': True, + 'inet': True, + 'do_not_fragment': True} + + # Add optional ping parameters + o_ping_params = {} + if params['source'] is not None: + o_ping_params['source'] = params['source'] + if params['interface'] is not None: + o_ping_params['interface'] = params['interface'] + if params['routing_instance'] is not None: + o_ping_params['routing_instance'] = params['routing_instance'] + ping_params.update(o_ping_params) + + # Set initial results values. Assume failure until we know it's success. + results = {'changed': False, + 'failed': True, + 'inet_mtu': 0, + 'host': params.get('dest')} + # Results should include all the o_ping_params. + for key in o_ping_params: + results[key] = ping_params.get(key) + # Add aliases for backwards compatibility + results.update({'dest': ping_params.get('host'), + 'dest_ip': ping_params.get('host'), + 'source_ip': ping_params.get('source')}) + + # Execute a minimally-sized ping just to verify basic connectivity. + junos_module.logger.debug("Verifying basic connectivity.") + ping_params['size'] = str(INET_MIN_MTU_SIZE - + INET_AND_ICMP_HEADER_SIZE) + results_for_minimal = dict(results) + results_for_minimal = junos_module.ping(ping_params, + acceptable_percent_loss=100, + results=results_for_minimal) + if int(results_for_minimal.get('packet_loss', 100)) == 100: + results['msg'] = "Basic connectivity to %s failed." % (results['host']) + junos_module.exit_json(**results) + + # Initialize test_size and step + test_size = params['max_size'] + step = params['max_range'] + min_test_size = test_size - (params['max_range'] - 1) + if min_test_size < INET_MIN_MTU_SIZE: + min_test_size = INET_MIN_MTU_SIZE + + while True: + if test_size < INET_MIN_MTU_SIZE: + test_size = INET_MIN_MTU_SIZE + if test_size > params['max_size']: + test_size = params['max_size'] + junos_module.logger.debug("Probing with size: %d", test_size) + step = step // 2 if step >= 2 else 0 + ping_params['size'] = str(test_size - INET_AND_ICMP_HEADER_SIZE) + current_results = dict(results) + current_results = junos_module.ping(ping_params, + acceptable_percent_loss=100, + results=current_results) + loss = int(current_results.get('packet_loss', 100)) + if loss < 100 and test_size == params['max_size']: + # ping success with max test_size, save and break + results['failed'] = False + results['inet_mtu'] = test_size + break + elif loss < 100: + # ping success, increase test_size + results['failed'] = False + results['inet_mtu'] = test_size + test_size += step + else: + # ping fail, lower size + test_size -= step + if step < 1: + break + + if results.get('inet_mtu', 0) == 0: + junos_module.fail_json(msg='The MTU of the path to %s is less than ' + 'the minimum tested size(%d). Try ' + 'decreasing max_size(%d) or increasing ' + 'max_range(%d).' % (results['host'], + min_test_size, + params['max_size'], + params['max_range']), + **results) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py new file mode 100644 index 00000000..8f848f35 --- /dev/null +++ b/library/juniper_junos_rpc.py @@ -0,0 +1,624 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2016, Nitin Kumar +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from six import iteritems + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_rpc +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Execute one or more NETCONF RPCs on a Junos device +description: + - Execute one or more NETCONF RPCs on a Junos device. + - Use the C(| display xml rpc) modifier to determine the equivalent RPC + name for a Junos CLI command. For example, + C(show version | display xml rpc) reveals the equivalent RPC name is + C(get-software-information). +options: + attrs: + description: + - The attributes and values to the RPCs specified by the + I(rpcs) option. The value of this option can either be a single + dictionary of keywords and values, or a list of dictionaries + containing keywords and values. + - There is a one-to-one correspondence between the elements in the + I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the + two lists must always contain the same number of elements. + required: false + default: none + type: dict or list of dict + aliases: + - attr + dest: + description: + - The path to a file, on the Ansible control machine, where the output of + the RPC will be saved. + - The file must be writeable. If the file already exists, it is + overwritten. + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(dest) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine, where + the output of the RPC will be saved. The output will be logged + to a file named C({{ inventory_hostname }}_)I(rpc).I(format) + in the I(dest_dir) directory. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: None + type: path + aliases: + - destination_dir + - destdir + filter: + description: + - This argument only applies if the I(rpcs) option contains a single + RPC with the value C(get-config). When used, this value specifies an + XML filter used to restrict the portions of the configuration which are + retrieved. See the PyEZ + U(get_config method|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + for details on the value of this option. + required: false + default: none + type: str + aliases: + - filter_xml + formats: + description: + - The format of the reply for the RPCs specified by the + I(rpcs) option. + - The specified format(s) must be supported by the + target Junos device. + - The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all RPCs specified by the I(rpcs) option. If a + list of formats are specified, there must be one value in the list for + each RPC specified by the I(rpcs) option. + required: false + default: xml + type: str or list of str + choices: + - text + - xml + - json + aliases: + - format + - display + - output + kwargs: + description: + - The keyword arguments and values to the RPCs specified by the + I(rpcs) option. The value of this option can either be a single + dictionary of keywords and values, or a list of dictionaries + containing keywords and values. + - There must be a one-to-one correspondence between the elements in the + I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the + two lists must always contain the same number of elements. For RPC + arguments which do not require a value, specify the value of True as + shown in the :ref:`juniper_junos_rpc-examples-label`. + required: false + default: none + type: dict or list of dict + aliases: + - kwarg + - args + - arg + return_output: + description: + - Indicates if the output of the RPC should be returned in the + module's response. You might want to set this option to C(false), + and set the I(dest_dir) option, if the RPC output is very large + and you only need to save the output rather than using it's content in + subsequent tasks/plays of your playbook. + required: false + default: true + type: bool + rpcs: + description: + - A list of one or more NETCONF RPCs to execute on the Junos device. + required: true + default: none + type: list + aliases: + - rpc +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_rpc + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Execute single get-software-information RPC. + juniper_junos_rpc: + rpcs: "get-software-information" + register: response + - name: Print the RPC's output as a single multi-line string. + debug: + var: response.stdout + +###### OLD EXAMPLES ########## +- junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + dest=get_interface_information.conf + register=junos + +- junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + kwargs="interface_name=em0" + format=xml/text/json + dest=get_interface_information.conf + register=junos + +# Example to fetch device configuration +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + dest=get_config.conf + +# Fetch configuration over console server connection using PyEZ >= 2.0 +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + port=7005 + mode='telnet' + rpc=get-config + dest=get_config.conf + +# Example to fetch device configuration +- name: Get Device Configuration for interface + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + filter_xml="" + dest=get_config.conf + register: junos + +# Example to fetch configuration in json for >=14.2 +# and use it with rpc_reply +- name: Get Device Configuration + hosts: all + roles: + - Juniper.junos + connection: local + gather_facts: no + tasks: + - name: Get interface information + junos_rpc: + host: "{{ inventory_hostname }}" + rpc: get-interface-information + kwargs: + interface_name: em0 + media: True + format: json + dest: get_interface_information.conf + register: junos + + - name: Print configuration + debug: msg="{{ junos.rpc_reply }}" +###### OLD EXAMPLES ########## +''' + +RETURN = ''' +attrs: + description: + - The RPC attributes and values from the list of dictionaries in the + I(attrs) option. This will be none if no attributes are applied to the + RPC. + returned: always + type: dict +changed: + description: + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to C(false). + - You could use this module to execute an RPC which + changes the operational state of the the device. For example, + C(clear-ospf-neighbor-information). Beware, this module is unable to + detect this situation, and will still return a I(changed) value of + C(false) in this case. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. See the I(results) key for additional + details. + returned: always + type: bool +format: + description: + - The format of the RPC response from the list of formats in the I(formats) + option. + returned: always + type: str + choices: + - text + - xml + - json +kwargs: + description: + - The keyword arguments from the list of dictionaries in the I(kwargs) + option. This will be C(none) if no kwargs are applied to the RPC. + returned: always + type: dict +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +parsed_output: + description: + - The RPC reply from the Junos device parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the + U(jxmlease|https://github.com/Juniper/jxmlease) + library. For JSON the response is parsed using the Python + U(json|https://docs.python.org/2/library/json.html) library. + - When Ansible converts the jxmlease or native Python data structure + into JSON, it does not guarantee that the order of dictionary/object keys + are maintained. + returned: when RPC executed successfully, I(return_output) is C(true), + and the RPC format is C(xml) or C(json). + type: dict +results: + description: + - The other keys are returned when a single RPC is specified for the + I(rpcs) option. When the value of the I(rpcs) option is a list + of RPCs, this key is returned instead. The value of this key is a + list of dictionaries. Each element in the list corresponds to the + RPCs in the I(rpcs) option. The keys for each element in the list + include all of the other keys listed. The I(failed) key indicates if the + individual RPC failed. In this case, there is also a top-level + I(failed) key. The top-level I(failed) key will have a value of C(false) + if ANY of the RPCs ran successfully. In this case, check the value + of the I(failed) key for each element in the I(results) list for the + results of individual RPCs. + returned: when the I(rpcs) option is a list value. + type: list of dict +rpc: + description: + - The RPC which was executed from the list of RPCs in the I(rpcs) option. + returned: always + type: str +stdout: + description: + - The RPC reply from the Junos device as a single multi-line string. + returned: when RPC executed successfully and I(return_output) is C(true). + type: str +stdout_lines: + description: + - The RPC reply from the Junos device as a list of single-line strings. + returned: when RPC executed successfully and I(return_output) is C(true). + type: list of str +''' + +import os.path + + +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + rpcs=dict(required=True, + type='list', + aliases=['rpc'], + default=None), + formats=dict(required=False, + type='list', + aliases=['format', 'display', 'output'], + default=None), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='str', + default=None), + attrs=dict(required=False, + type='str', + aliases=['attr'], + default=None), + filter=dict(required=False, + type='str', + aliases=['filter_xml'], + default=None), + dest=dict(required=False, + type='path', + aliases=['destination'], + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir'], + default=None), + return_output=dict(required=False, + type='bool', + default=True) + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. Well, that's not completely true. It does depend on the + # RPC executed. See the I(changed) key in the RETURN documentation + # for more details. + supports_check_mode=True, + min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, + ) + + # Check over rpcs + rpcs = junos_module.params.get('rpcs') + # Ansible allows users to specify a rpcs argument with no value. + if rpcs is None: + junos_module.fail_json(msg="The rpcs option must have a value.") + + # Check over formats + formats = junos_module.params.get('formats') + if formats is None: + # Default to xml format + formats = ['xml'] + valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES + # Check format values + for format in formats: + # Is it a valid format? + if format not in valid_formats: + junos_module.fail_json(msg="The value %s in formats is invalid. " + "Must be one of: %s" % + (format, ', '.join(map(str, + valid_formats)))) + # Correct number of format values? + if len(formats) != 1 and len(formats) != len(rpcs): + junos_module.fail_json(msg="The formats option must have a single " + "value, or one value per rpc. There " + "are %d rpcs and %d formats." % + (len(rpcs), len(formats))) + # Same format for all rpcs + elif len(formats) == 1 and len(rpcs) > 1: + formats = formats * len(rpcs) + + # Check over kwargs + kwstring = junos_module.params.get('kwargs') + kwargs = junos_module.parse_arg_to_list_of_dicts('kwargs', + kwstring, + allow_bool_values=True) + if kwargs is not None: + if len(kwargs) != len(rpcs): + junos_module.fail_json(msg="The kwargs option must have one value " + "per rpc. There are %d rpcs and %d " + "kwargs." % + (len(rpcs), len(kwargs))) + else: + kwargs = [None] * len(rpcs) + + # Check over attrs + attrstring = junos_module.params.get('attrs') + attrs = junos_module.parse_arg_to_list_of_dicts('attrs', + attrstring) + if attrs is not None: + if len(attrs) != len(rpcs): + junos_module.fail_json(msg="The attrs option must have one value" + "per rpc. There are %d rpcs and %d " + "attrs." % + (len(rpcs), len(attrs))) + else: + attrs = [None] * len(rpcs) + + # Check filter + if junos_module.params.get('filter') is not None: + if (len(rpcs) != 1 or (rpcs[0] != 'get-config' and + rpcs[0] != 'get_config')): + junos_module.fail_json(msg="The filter option is only valid " + "when the rpcs option value is a " + "single 'get-config' RPC.") + + results = list() + for (rpc_string, format, kwarg, attr) in zip(rpcs, formats, kwargs, attrs): + # Replace underscores with dashes in RPC name. + rpc_string = rpc_string.replace('_', '-') + # Set initial result values. Assume failure until we know it's success. + result = {'msg': '', + 'rpc': rpc_string, + 'format': format, + 'kwargs': kwarg, + 'attrs': attr, + 'changed': False, + 'failed': True} + + # Execute the RPC + try: + if rpc_string == 'get-config': + filter = junos_module.params.get('filter') + if attr is None: + attr = {} + if kwarg is None: + kwarg = {} + if format is not None: + attr['format'] = format + junos_module.logger.debug('Executing "get-config" RPC. ' + 'filter_xml=%s, options=%s, ' + 'kwargs=%s', + filter, str(attr), str(kwarg)) + resp = junos_module.dev.rpc.get_config(filter_xml=filter, + options=attr, **kwarg) + result['msg'] = 'The "get-config" RPC executed successfully.' + junos_module.logger.debug('The "get-config" RPC executed ' + 'successfully.') + else: + rpc = junos_module.etree.Element(rpc_string, format=format) + if kwarg is not None: + # Add kwarg + for (key, value) in iteritems(kwarg): + # Replace underscores with dashes in key name. + key = key.replace('_', '-') + sub_element = junos_module.etree.SubElement(rpc, key) + if not isinstance(value, bool): + sub_element.text = value + if attr is not None: + # Add attr + for (key, value) in iteritems(attr): + # Replace underscores with dashes in key name. + key = key.replace('_', '-') + rpc.set(key, value) + junos_module.logger.debug('Executing RPC "%s".', + junos_module.etree.tostring( + rpc, + pretty_print=True)) + resp = junos_module.dev.rpc(rpc, + normalize=bool(format == 'xml')) + result['msg'] = 'The RPC executed successfully.' + junos_module.logger.debug('RPC "%s" executed successfully.', + junos_module.etree.tostring( + rpc, + pretty_print=True)) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Unable to execute RPC "%s". Error: %s', + junos_module.etree.tostring( + rpc, + pretty_print=True), str(ex)) + result['msg'] = 'Unable to execute the RPC: %s. Error: %s' % \ + (junos_module.etree.tostring(rpc, + pretty_print=True), + str(ex)) + results.append(result) + continue + + text_output = None + parsed_output = None + if resp is True: + text_output = '' + elif (resp, junos_module.etree._Element): + # Handle the output based on format + if format == 'text': + text_output = resp.text + junos_module.logger.debug('Text output set.') + elif format == 'xml': + text_output = junos_module.etree.tostring(resp, + pretty_print=True) + parsed_output = junos_module.jxmlease.parse_etree(resp) + junos_module.logger.debug('XML output set.') + elif format == 'json': + text_output = str(resp) + parsed_output = resp + junos_module.logger.debug('JSON output set.') + else: + result['msg'] = 'Unexpected format %s.' % (format) + results.append(result) + junos_module.logger.debug('Unexpected format %s.', format) + continue + else: + result['msg'] = 'Unexpected response type %s.' % (type(resp)) + results.append(result) + junos_module.logger.debug('Unexpected response type %s.', + type(resp)) + continue + + # Set the output keys + if junos_module.params['return_output'] is True: + if text_output is not None: + result['stdout'] = text_output + result['stdout_lines'] = text_output.splitlines() + if parsed_output is not None: + result['parsed_output'] = parsed_output + # Save the output + junos_module.save_text_output(rpc_string, format, text_output) + # This command succeeded. + result['failed'] = False + # Append to the list of results + results.append(result) + + # Return response. + if len(results) == 1: + junos_module.exit_json(**results[0]) + else: + # Calculate the overall failed. Only failed if all commands failed. + failed = True + for result in results: + if result.get('failed') is False: + failed = False + break + junos_module.exit_json(results=results, + changed=False, + failed=failed) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py new file mode 100644 index 00000000..ccfa5a72 --- /dev/null +++ b/library/juniper_junos_software.py @@ -0,0 +1,806 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_software +version_added: "2.0.0" # of Juniper.junos role +author: + - Jeremy Schulman + - "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Install software on a Junos device +description: + - > + Install a Junos OS image, or other software package, on a Junos device. + This action is generally equivalent to the C(request system software add) + operational-mode CLI command. It performs the following + steps in order: + + + #. Compare the currently installed Junos version to the desired version + specified by the I(version) option. + + * If the current and desired versions are the same, stop and return + I(changed) with a value of C(false). + * If running in check mode, and the current and desired versions differ, + stop and return I(changed) with a value of C(true). + * Otherwise, proceed. + #. If the I(local_package) option is specified, compute the MD5 checksum + of the I(local_package) file on the local Ansible control machine. + #. Check if the file exists at the I(remote_package) location on the target + Junos device. If so, compute the MD5 checksum of the file on the target + Junos device. + #. If the I(cleanfs) option is C(true), the default, then perform the + equivalent of the C(request system storage cleanup) CLI command. + #. If the checksums computed in steps 2 and 3 differ, or if the + I(remote_package) file does not exist on the target Junos device, then + copy the package from I(local_package) on the local Ansible control + machine to I(remote_package) on the target Junos device. + #. Install the software pacakge from the I(remote_package) location on the + target Junos device using the options specified. + #. If the I(reboot) option is C(true), the default, initiate a reboot of + the target Junos device. +options: + all_re: + description: + - Whether or not to install the software on all Routing Engines of the + target Junos device. If C(true), and the device has multiple Routing + Engines, the software is installed on all Routing Engines. If C(false), + the software is only installed on the current Routing Engine. + required: false + default: true + type: bool + checksum: + description: + - The pre-calculated checksum, using the I(checksum_algorithm) of the + file specified by the I(local_package) option. Specifying this option + is simply an optimization to avoid repeatedly computing the checksum of + the I(local_package) file once for each target Junos host. + required: false + default: none + type: str + checksum_algorithm: + description: + - The algorithm to use when calculating the checksum of the local and + remote software packages. + required: false + default: md5 + type: str + checksum_timeout: + description: + - The number of seconds to wait for the calculation of the checksum to + complete on the target Junos device. + required: false + default: 300 (5 minutes) + type: int + cleanfs: + description: + - Whether or not to perform a C(request system storage cleanup) prior to + copying or installing the software. + required: false + default: true (unless I(no_copy) is C(true), then C(false)) + type: bool + cleanfs_timeout: + description: + - The number of seconds to wait for the + C(request system storage cleanup) to complete on the target Junos + device. + required: false + default: 300 (5 minutes) + type: int + force_host: + description: + - Forces the upgrade of the Host Software package on QFX-series devices. + required: false + default: false + type: bool + install_timeout: + description: + - The number of seconds to wait for the software installation to + complete on the target Junos device. + required: false + default: 1800 (30 minutes) + type: int + issu: + description: + - Indicates if a unified in-service software upgrade (ISSU) should be + attempted. ISSU enables the upgrade between two different + Junos OS releases with no control plane disruption and minimal data + plane traffic disruption. + - In order for an ISSU to succeed, ISSU must be supported. This includes + support for the current to desired Junos versions, the hardware + of the target Junos device, and the current software configuration of + the target Junos device. + - The I(issu) and I(nssu) options are mutually exclusive. + required: false + default: false + type: bool + kwargs: + description: + - Additional keyword arguments and values which are passed to the + C() RPC used to install the software package. The + value of this option is a dictionary of keywords and values. + required: false + default: none + type: dict + aliases: + - kwarg + - args + - arg + local_package: + description: + - The path, on the local Ansible control machine, of a Junos software + package. This Junos software package will be installed on the target + Junos device. + - If this option is specified, and a file with the same MD5 checksum + doesn't already exist at the I(remote_package) location on the target + Junos device, then the file is copied from the local Ansible control + machine to the target Junos device. + - If this option is not specified, it is assumed that the + software package already exists on the target Junos device. In this + case, the I(remote_package) option must be specified. + required: false + default: none + type: path + aliases: + - package + no_copy: + description: + - Indicates if the file containing the software package should be copied + from the I(local_package) location on the local Ansible control + machine to the I(remote_package) location on the target Junos device. + - If the value is C(true), or if the I(local_package) option is not + specified, then the copy is skipped and the file must already exist + at the I(remote_package) location on the target Junos device. + required: false + default: false + type: bool + nssu: + description: + - Indicates if a non-stop software upgrade (NSSU) should be + attempted. NSSU enables the upgrade between two different + Junos OS releases with minimal data plane traffic disruption. + - NSSU is specific to EX-series Virtual Chassis systems or EX-series + stand-alone systems with redundant Routing Engines. + - In order for an NSSU to succeed, NSSU must be supported. This includes + support for the current to desired Junos versions, the hardware + of the target Junos device, and the current software configuration of + the target Junos device. + - The I(nssu) and I(issu) options are mutually exclusive. + required: false + default: false + type: bool + reboot: + description: + - Indicates if the target Junos device should be rebooted after + performing the software install. + required: false + default: true + type: bool + reboot_pause: + description: + - The amount of time, in seconds, to wait after the reboot is issued + before the module returns. This gives time for the reboot to begin. The + default value of 10 seconds is designed to ensure the device is no + longer reachable (because the reboot has begun) when the next task + begins. The value must be an integer greater than or equal to 0. + required: false + default: 10 + type: int + remote_package: + description: + - This option may take one of two formats. + - The first format is a URL, from the perspective of the target Junos + device, from which the device retrieves the software package to be + installed. The acceptable formats for the URL value may be found + U(here|https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-software-formats-filenames-urls.html). + - When using the URL format, the I(local_package) and I(no_copy) options + must not be specified. + - The second format is a file path, on the taget Junos device, to the + software package. + - If the I(local_package) option is also specified, and the + I(no_copy) option is C(false), the software package will be copied + from I(local_package) to I(remote_package), if necessary. + - If the I(no_copy) option is C(true) or the I(local_package) option + is not specified, then the file specified by this option must already + exist on the target Junos device. + - If this option is not specified, it is assumed that the software + package will be copied into the C(/var/tmp) directory on the target + Junos device using the filename portion of the I(local_package) option. + In this case, the I(local_package) option must be specified. + - Specifying the I(remote_package) option and not specifying the + I(local_package) option is equivalent to specifying the + I(local_package) option and the I(no_copy) option. In this case, + you no longer have to explicitly specify the I(no_copy) option. + - If the I(remote_package) value is a directory (ends with /), then + the filename portion of I(local_package) will be appended to the + I(remote_package) value. + - If the I(remote_package) value is a file (does not end with /), + then the filename portion of I(remote_package) must be the same as + the filename portion of I(local_package). + required: false + default: C(/var/tmp/) + filename portion of I(local_package) + type: path + pkg_set: + description: + - install software on the members in a mixed Virtual Chassis. Currently + we are not doing target package check this option is provided. + required: false + default: false + type: list + validate: + description: + - Whether or not to have the target Junos device should validate the + current configuration against the new software package. + required: false + default: false + type: bool + version: + description: + - The version of software contained in the file specified by the + I(local_package) and/or I(remote_package) options. This value should + match the Junos version which will be reported by the device once the + new software is installed. If the device is already running a version + of software which matches the I(version) option value, the software + install is not necessary. In this case the module returns a I(changed) + value of C(false) and an I(failed) value of C(false) and does not + attempt to perform the software install. + required: false + default: Attempt to extract the version from the file name specified by + the I(local_package) or I(remote_package) option values IF the + package appears to be a Junos software package. Otherwise, C(none). + type: str + aliases: + - target_version + - new_version + - desired_version + vmhost: + description: + - Whether or not this is a vmhost software installation. + required: false + default: false + type: bool +notes: + - This module does support connecting to the console of a Junos device, but + does not support copying the software package from the local Ansible + control machine to the target Junos device while connected via the console. + In this situation, the I(remote_package) option must be specified, and the + specified software package must already exist on the target Junos device. + - This module returns after installing the software and, optionally, + initiating a reboot of the target Junos device. It does not wait for + the reboot to complete, and it does not verify that the desired version of + software specified by the I(version) option is actually activated on the + target Junos device. It is the user's responsibility to confirm the + software installation using additional follow on tasks in their playbook. +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_software + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Execute a basic Junos software upgrade. + juniper_junos_software: + local_package: "./images/" + register: response + - name: Print the complete response. + debug: + var: response + +###### OLD EXAMPLES ########## + - junos_install_os: + host={{ inventory_hostname }} + version=12.1X46-D10.2 + package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz + logfile=/usr/local/junos/log/software.log +###### OLD EXAMPLES ########## +''' + + +RETURN = ''' +changed: + description: + - Indicates if the device's state has changed, or if the state would have + changed when executing in check mode. This value is set to C(true) when + the version of software currently running on the target Junos device does + not match the desired version of software specified by the I(version) + option. If the current and desired software versions match, the value + of this key is set to C(false). + returned: success + type: bool +check_mode: + description: + - Indicates whether or not the module ran in check mode. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +msg: + description: + - A human-readable message indicating the result of the software + installation. + returned: always + type: str +''' + +# Standard Library imports +import os.path +import re +import time +try: + # Python 3.x + from urllib.parse import urlparse +except ImportError: + # Python 2.x + from urlparse import urlparse + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def parse_version_from_filename(filename): + """Attempts to parse a version string from the filename of a Junos package. + + There is wide variety in the naming schemes used by Junos software + packages. This function attempts to parse the version string from the + filename, but may not be able to accurately do so. It's also not + guaranteed that the filename of a package accurately reflects the version + of software in the file. (A user may have renamed it.) + If the filename does not appear to be a Junos package (maybe some other + type of package which can be installed on Junos devices), then return None. + + Args: + filename - The filename from which to parse the version string. + + Returns: + The version string, or None if unable to parse. + """ + # Known prefixes for filenames which contain Junos software packages. + JUNOS_PACKAGE_PREFIXES = ['jbundle', 'jinstall', 'junos-install', + 'junos-srx', 'junos-vmhost-install', 'junos-vrr', + 'vmx-bundle'] + for prefix in JUNOS_PACKAGE_PREFIXES: + if filename.startswith(prefix): + # Assumes the version string will be prefixed by -. + # Assume major version will begin with two digits followed by dot. + # Assume the version string ends with the last digit in filename. + match = re.search('-(\d{2}\..*\d).*', filename) + if match is not None: + return match.group(1) + # Not a known Junos package name. + else: + return None + + +def define_progress_callback(junos_module): + """Create callback which can be passed to SW.install(progress=progress) + """ + def myprogress(_, report): + """A progress function which logs report at level INFO. + + Args: + _: The PyEZ device object. Unused because the logger already knows. + report: The string to be logged. + """ + junos_module.logger.info(report) + return myprogress + + +def main(): + CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] + + #Define the argument spec. + software_argument_spec=dict( + local_package=dict(required=False, + aliases=['package'], + type='path', + default=None), + remote_package=dict(required=False, + type='path', + # Default is '/var/tmp/' + filename from the + # local_package option, if set. + default=None), + pkg_set=dict(required=False, + type='list', + default=None), + version=dict(required=False, + aliases=['target_version', 'new_version', + 'desired_version'], + type='str', + # Default is determined from filename portion of + # remote_package option. + default=None), + no_copy=dict(required=False, + type='bool', + default=False), + reboot=dict(required=False, + type='bool', + default=True), + reboot_pause=dict(required=False, + type='int', + default=10), + issu=dict(required=False, + type='bool', + default=False), + nssu=dict(required=False, + type='bool', + default=False), + force_host=dict(required=False, + type='bool', + default=False), + validate=dict(required=False, + type='bool', + default=False), + cleanfs=dict(required=False, + type='bool', + default=True), + all_re=dict(required=False, + type='bool', + default=True), + vmhost=dict(required=False, + type='bool', + default=False), + checksum=dict(required=False, + type='str', + default=None), + checksum_algorithm=dict(required=False, + choices=CHECKSUM_ALGORITHM_CHOICES, + type='str', + default='md5'), + checksum_timeout=dict(required=False, + type='int', + default=300), + cleanfs_timeout=dict(required=False, + type='int', + default=300), + install_timeout=dict(required=False, + type='int', + default=1800), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='dict', + default=None), + ) + # Save keys for later. Must do because software_argument_spec gets + # modified. + option_keys = list(software_argument_spec.keys()) + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec = software_argument_spec, + # Mutually exclusive options. + mutually_exclusive=[['issu', 'nssu']], + # One of local_package and remote_package is required. + required_one_of=[['local_package', 'remote_package', 'pkg_set']], + supports_check_mode=True + ) + + # Straight from params + local_package = junos_module.params.pop('local_package') + remote_package = junos_module.params.pop('remote_package') + pkg_set = junos_module.params.pop('pkg_set') + target_version = junos_module.params.pop('version') + no_copy = junos_module.params.pop('no_copy') + reboot = junos_module.params.pop('reboot') + reboot_pause = junos_module.params.pop('reboot_pause') + install_timeout = junos_module.params.pop('install_timeout') + cleanfs = junos_module.params.pop('cleanfs') + all_re = junos_module.params.pop('all_re') + kwargs = junos_module.params.pop('kwargs') + + url = None + remote_dir = None + if remote_package is not None: + # Is the remote package a URL? + parsed_url = urlparse(remote_package) + if parsed_url.scheme == '': + # A file on the remote host. + (remote_dir, remote_filename) = os.path.split(remote_package) + else: + url = remote_package + (_, remote_filename) = os.path.split(parsed_url.path) + else: + # Default remote_dir value + remote_dir = '/var/tmp' + remote_filename = '' + + if url is not None and local_package is not None: + junos_module.fail_json(msg='There remote_package (%s) is a URL. ' + 'The local_package option is not allowed.' % + remote_package) + + if url is not None and no_copy is True: + junos_module.fail_json(msg='There remote_package (%s) is a URL. ' + 'The no_copy option is not allowed.' % + remote_package) + + if url is None: + local_filename = None + if local_package is not None: + # Expand out the path. + local_package = os.path.abspath(local_package) + (local_dir, local_filename) = os.path.split(local_package) + if local_filename == '': + junos_module.fail_json(msg='There is no filename component to ' + 'the local_package (%s).' % + local_package) + elif remote_package is not None: + # remote package was, so we must assume no_copy. + no_copy = True + + if no_copy is False: + if local_package is not None and not os.path.isfile(local_package): + junos_module.fail_json(msg='The local_package (%s) is not a ' + 'valid file on the local Ansible ' + 'control machine.' % local_package) + elif pkg_set is not None: + pkg_set = [os.path.abspath(item) for item in pkg_set] + for pkg_set_item in pkg_set: + if not os.path.isfile(pkg_set_item): + junos_module.fail_json( + msg='The pkg (%s) is not a valid file on the local' + ' Ansible control machine.' % pkg_set_item) + + if remote_filename == '': + # Use the same name as local_filename + remote_filename = local_filename + + if local_filename is not None and remote_filename != local_filename: + junos_module.fail_json(msg='The filename of the remote_package ' + '(%s) must be the same as the filename ' + 'of the local_package (%s).' % + (remote_filename, local_filename)) + + # If no_copy is True, then we need to turn off cleanfs to keep from + # deleting the software package which is already present on the device. + if no_copy is True: + cleanfs = False + + if target_version is None and pkg_set is None: + target_version = parse_version_from_filename(remote_filename) + junos_module.logger.debug("New target version is: %s.", target_version) + + # Initialize the results. Assume not changed and failure until we know. + results = {'msg': '', + 'changed': False, + 'check_mode': junos_module.check_mode, + 'failed': True} + + # Check version info to see if we need to do the install. + if target_version is not None: + if all_re is True: + junos_info = junos_module.dev.facts['junos_info'] + for current_re in junos_info: + current_version = junos_info[current_re]['text'] + if target_version != current_version: + junos_module.logger.debug("Current version on %s: %s. " + "Target version: %s.", + current_version, current_re, + target_version) + results['changed'] = True + else: + results['msg'] += "Current version on %s: %s same as Targeted " \ + "version: %s.\n" % (current_version, current_re, + target_version) + else: + current_version = junos_module.dev.facts['version'] + re_name = junos_module.dev.re_name + if target_version != current_version: + junos_module.logger.debug("Current version on %s: %s. " + "Target version: %s.", + current_version, re_name, + target_version) + results['changed'] = True + else: + results['msg'] += "Current version on %s: %s same as Targeted " \ + "version: %s.\n" % (current_version, re_name, + target_version) + else: + # A non-Junos install. Always attempt to install. + results['changed'] = True + + # Do the install if necessary + if results['changed'] is True and not junos_module.check_mode: + junos_module.logger.debug("Beginning installation of %s.", + remote_filename) + # Calculate the install parameters + install_params = {} + if url is not None: + install_params['package'] = url + elif local_package is not None: + install_params['package'] = local_package + elif pkg_set is not None: + install_params['pkg_set'] = pkg_set + else: + install_params['package'] = remote_filename + if remote_dir is not None: + install_params['remote_path'] = remote_dir + install_params['progress'] = define_progress_callback(junos_module) + install_params['cleanfs'] = cleanfs + install_params['no_copy'] = no_copy + install_params['timeout'] = install_timeout + install_params['all_re'] = all_re + for key in option_keys: + value = junos_module.params.get(key) + if value is not None: + install_params[key] = value + if kwargs is not None: + install_params.update(kwargs) + try: + junos_module.logger.debug("Install parameters are: %s", + str(install_params)) + junos_module.add_sw() + ok = junos_module.sw.install(**install_params) + if ok is not True: + results['msg'] = 'Unable to install the software' + junos_module.fail_json(**results) + msg = 'Package %s successfully installed.' % ( + install_params.get('package') or + install_params.get('pkg_set')) + results['msg'] = msg + junos_module.logger.debug(msg) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + results['msg'] = 'Installation failed. Error: %s' % str(ex) + junos_module.fail_json(**results) + if reboot is True: + try: + # Try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this + # happens relatively quickly. + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + junos_module.logger.debug('Initiating reboot.') + xpath_list = ['.//request-reboot-status'] + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-reboot') + elif install_params.get('vmhost'): + rpc = junos_module.etree.Element('request-vmhost-reboot') + # RPC reply can contain multiple output tags + xpath_list.append('output') + else: + rpc = junos_module.etree.Element('request-reboot') + + if all_re is True: + if (junos_module.sw._multi_RE is True and + junos_module.sw._multi_VC is False): + junos_module.etree.SubElement(rpc, + 'both-routing-engines') + # At least on some platforms stopping/rebooting both + # REs produces messages and + # messages. + xpath_list.append('output') + elif junos_module.sw._mixed_VC is True: + junos_module.etree.SubElement(rpc, 'all-members') + resp = junos_module.dev.rpc(rpc, + ignore_warning=True, + normalize=True) + junos_module.logger.debug("Reboot RPC executed cleanly.") + if len(xpath_list) > 0: + obj = resp.getparent() + for xpath in xpath_list: + if junos_module.dev.facts['_is_linux']: + got = resp.text + else: + # there are cases where rpc-reply will have multiple + # child element, hence lets work on parent. + # for ex: + # Rebooting fpc1 + # + # + # Shutdown at Mon Jun 24 10:16:35 2019. + # [pid 1949] + # + # + if xpath == 'output': + got = '\n'.join([i.text for i in obj.findall('output') + if i.text is not None]) + else: + got = obj.findtext(xpath) + if got is not None: + results['msg'] += ' Reboot successfully initiated. ' \ + 'Reboot message: %s' % got + break + else: + # This is the else clause of the for loop. + # It only gets executed if the loop finished without + # hitting the break. + results['msg'] += ' Did not find expected response ' \ + 'from reboot RPC. RPC response is ' \ + '%s' % \ + junos_module.etree.tostring(resp) + junos_module.fail_json(**results) + else: + results['msg'] += ' Reboot successfully initiated.' + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['msg'] += ' Reboot failed. It may not have been ' \ + 'initiated.' + junos_module.fail_json(**results) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] += ' Reboot succeeded.' + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) + junos_module.fail_json(**results) + junos_module.logger.debug("Reboot RPC successfully initiated.") + if reboot_pause > 0: + junos_module.logger.debug("Sleeping for %d seconds", + reboot_pause) + time.sleep(reboot_pause) + + # If we made it this far, it's success. + results['failed'] = False + + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py new file mode 100644 index 00000000..4fb7b71a --- /dev/null +++ b/library/juniper_junos_srx_cluster.py @@ -0,0 +1,295 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Patrik Bok +# 2015, Rick Sherman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_srx_cluster +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Add or remove SRX chassis cluster configuration +description: + - Add an SRX chassis cluster configuration and reboot the device. Assuming + the device is capable of forming an SRX cluster and has the correct + cables connected, this will form an SRX cluster. + - If an SRX chassis cluster is already present, setting I(cluster_enable) to + C(false) will remove the SRX chassis cluster configuration and reboot + the device causing the SRX cluster to be broken and the device to return + to stand-alone mode. +options: + enable: + description: + - Enable or disable cluster mode. When C(true) cluster mode is enabled + and I(cluster_id) and I(node_id) must also be specified. When C(false) + cluster mode is disabled and the device returns to stand-alone mode. + required: true + default: none + type: bool + aliases: + - cluster_enable + cluster_id: + description: + - The cluster ID to configure. + - Required when I(enable) is C(true). + required: false + default: none + type: int + aliases: + - cluster + node_id: + description: + - The node ID to configure. (C(0) or C(1)) + - Required when I(enable) is C(true). + required: false + default: none + type: int + aliases: + - node +''' + +EXAMPLES = ''' +--- +- name: Manipulate the SRX cluster configuration of Junos SRX devices + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + tasks: + - name: Enable an SRX cluster + juniper_junos_srx_cluster: + enable: true + cluster_id: 4 + node_id: 0 + register: response + - name: Print the response. + debug: + var: response.config_lines + + - name: Disable an SRX cluster + juniper_junos_srx_cluster: + enable: false + register: response + - name: Print the response. + debug: + var: response.config_lines +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed, or would have + changed when in check mode. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +reboot: + description: + - Indicates if a reboot of the device has been initiated. + returned: success + type: bool +''' + +# Standard library imports + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + enable=dict(type='bool', + required=True, + aliases=['cluster_enable'], + default=None), + cluster_id=dict(type='int', + required=False, + aliases=['cluster'], + default=None), + node_id=dict(type='int', + required=False, + aliases=['node'], + default=None) + ), + # Required if options + # If enable is True, then cluster_id and node_id must be set. + required_if=[['enable', True, ['cluster_id', 'node_id']]], + # Check mode is implemented. + supports_check_mode=True + ) + # Do additional argument verification. + + # Straight from params + enable = junos_module.params.get('enable') + cluster_id = junos_module.params.get('cluster_id') + node_id = junos_module.params.get('node_id') + + # cluster_id must be between 0 and 255 + if cluster_id is not None: + if cluster_id < 0 or cluster_id > 255: + junos_module.fail_json(msg="The cluster_id option (%s) must have " + "an integer value between 0 and 255." % + (cluster_id)) + + # node_id must be between 0 and 1 + if node_id is not None: + if node_id < 0 or node_id > 1: + junos_module.fail_json(msg="The node_id option (%s) must have a " + "value of 0 or 1." % (node_id)) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': '', + 'changed': False, + 'reboot': False, + 'failed': True} + + junos_module.logger.debug("Check current SRX cluster operational state.") + current_cluster_state = junos_module.dev.facts['srx_cluster'] + current_cluster_id = junos_module.dev.facts['srx_cluster_id'] + if current_cluster_id is not None: + current_cluster_id = int(current_cluster_id) + current_node_name = junos_module.dev.re_name + current_node_id = None + if current_node_name is not None: + (_, _, current_node_id) = current_node_name.partition('node') + if current_node_id: + current_node_id = int(current_node_id) + junos_module.logger.debug( + "Current SRX cluster operational state: %s, cluster_id: %s, " + "node_id: %s", + 'enabled' if current_cluster_state else 'disabled', + str(current_cluster_id), + str(current_node_id)) + + # Is a state change needed? + if current_cluster_state != enable: + junos_module.logger.debug( + "SRX cluster configuration change needed. Current state: %s. " + "Desired state: %s", + 'enabled' if current_cluster_state else 'disabled', + 'enabled' if enable else 'disabled') + results['changed'] = True + + # Is a cluster ID change needed? + if (enable is True and current_cluster_id is not None and + current_cluster_id != cluster_id): + junos_module.logger.debug( + "SRX cluster ID change needed. Current cluster ID: %d. " + "Desired cluster ID: %d", + current_cluster_id, cluster_id) + results['changed'] = True + + # Is a node ID change needed? + if (enable is True and current_node_id is not None and + current_node_id != node_id): + junos_module.logger.debug( + "SRX node ID change needed. Current node ID: %d. " + "Desired cluster ID: %d", + current_node_id, node_id) + results['changed'] = True + + results['msg'] = 'Current state: %s, cluster_id: %s, node_id: %s' % \ + ('enabled' if current_cluster_state else 'disabled', + str(current_cluster_id), + str(current_node_id)) + + if results['changed'] is True: + results['msg'] += ' Desired state: %s, cluster_id: %s, ' \ + 'node_id: %s' % \ + ('enabled' if enable else 'disabled', + str(cluster_id), + str(node_id)) + + if not junos_module.check_mode: + results['msg'] += ' Initiating change.' + try: + output = None + if enable is True: + resp = junos_module.dev.rpc.set_chassis_cluster_enable( + cluster_id=str(cluster_id), node=str(node_id), + reboot=True, normalize=True + ) + else: + resp = junos_module.dev.rpc.set_chassis_cluster_disable( + reboot=True, normalize=True + ) + if resp is not None: + output = resp.getparent().findtext('.//output') + if output is None: + output = resp.getparent().findtext('.//message') + results['msg'] += ' Reboot initiated. Response: %s' % (output) + results['reboot'] = True + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Error: %s', str(ex)) + results['msg'] += ' Error: %s' % (str(ex)) + junos_module.fail_json(**results) + + # If we made it this far, everything was successful. + results['failed'] = False + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py new file mode 100644 index 00000000..b186f037 --- /dev/null +++ b/library/juniper_junos_system.py @@ -0,0 +1,476 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_system +version_added: "2.0.0" # of Juniper.junos role +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Initiate operational actions on the Junos system +description: + - Initiate an operational action (shutdown, reboot, halt or zeroize) on a + Junos system. The particular action to execute is defined by the mandatory + I(action) option. +options: + action: + description: + - The action performed by the module. + - > + The following actions are supported: + - B(shutdown) - Power off the Junos devices. The values C(off), + C(power-off), and C(power_off) are aliases for this value. + This is the equivalent of the C(request system power-off) CLI + command. + - B(halt) - Stop the Junos OS running on the RE, but do not power off + the system. Once the system is halted, it will reboot if a + keystroke is entered on the console. This is the equivalent + of the C(request system halt) CLI command. + - B(reboot) - Reboot the system. This is the equivalent of the + C(request system reboot) CLI command. + - B(zeroize) - Restore the system (configuration, log files, etc.) to a + factory default state. This is the equivalent of the + C(request system zeroize) CLI command. + required: true + default: none + type: str + choices: + - shutdown + - halt + - reboot + - zeroize + - 'off' + - power-off + - power_off + at: + description: + - The time at which to shutdown, halt, or reboot the system. + - > + The value may be specified in one of the following ways: + - B(now) - The action takes effect immediately. + - B(+minutes) — The action takes effect in C(minutes) minutes from now. + - B(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute + time, specified as year, month, day, hour, and minute. + - B(hh:mm) — The action takes effect at C(hh:mm) absolute time on the + current day, specified in 24-hour time. + - The I(at) option can not be used when the I(action) option has a + value of C(zeroize). The I(at) option is mutually exclusive with the + I(in_min) option. + required: false + default: none + type: str + in_min: + description: + - Specify a delay, in minutes, before the shutdown, halt, or reboot. + - The I(in_min) option can not be used when the I(action) option has a + value of C(zeroize). The I(in_min) option is mutually exclusive with + the I(at) option. + required: false + default: none + type: int + all_re: + description: + - If the system has multiple Routing Engines and this option is C(true), + then the action is performed on all REs in the system. If the system + does not have multiple Routing Engines, then this option has no effect. + - This option applies to all I(action) values. + - The I(all_re) option is mutually exclusive with the I(other_re) option. + required: false + default: true + type: bool + other_re: + description: + - If the system has dual Routing Engines and this option is C(true), + then the action is performed on the other REs in the system. If the + system does not have dual Routing Engines, then this option has no + effect. + - The I(other_re) option can not be used when the I(action) option has a + value of C(zeroize). + - The I(other_re) option is mutually exclusive with the I(all_re) option. + required: false + default: false + type: bool + media: + description: + - Overwrite media when performing the zeroize operation. This option is + only valid when the I(action) option has a value of C(zeroize). + required: false + default: false + type: bool +notes: + - This module only B(INITIATES) the action. It does B(NOT) wait for the + action to complete. + - Some Junos devices are effected by a Junos defect which causes this Ansible + module to hang indefinitely when connected to the Junos device via + the console. This problem is not seen when connecting to the Junos device + using the normal NETCONF over SSH transport connection. Therefore, it is + recommended to use this module only with a NETCONF over SSH transport + connection. However, this module does still permit connecting to Junos + devices via the console port and this functionality may still be used for + Junos devices running Junos versions less than 15.1. +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_system + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Reboot all REs of the device + juniper_junos_system: + action: "reboot" + + - name: Power off the other RE of the device. + juniper_junos_system: + action: "shutdown" + othe_re: True + + - name: Reboot this RE at 8pm today. + juniper_junos_system: + action: "reboot" + all_re: False + at: "20:00" + + - name: Halt the system on 25 January 2018 at 4pm. + juniper_junos_system: + action: "halt" + at: "1801251600" + + - name: Reboot the system in 30 minutes. + juniper_junos_system: + action: "reboot" + in_min: 30 + + - name: Reboot the system in 30 minutes. + juniper_junos_system: + action: "reboot" + at: "+30m" + + - name: Zeroize the local RE only. + juniper_junos_system: + action: "zeroize" + all_re: False + + - name: Zeroize all REs and overwrite medea. + juniper_junos_system: + action: "zeroize" + media: True +''' + +RETURN = ''' +action: + description: + - The value of the I(action) option. + returned: always + type: str +all_re: + description: + - The value of the I(all_re) option. + returned: always + type: str +changed: + description: + - Indicates if the device's state has changed. If the action is performed + (or if it would have been performed when in check mode) then the value + will be C(true). If there was an error before the action, then the value + will be C(false). + returned: always + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +media: + description: + - The value of the I(media) option. + returned: always + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +other_re: + description: + - The value of the I(other_re) option. + returned: always + type: str +''' + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + action=dict(type='str', + required=True, + choices=['shutdown', 'off', 'power-off', 'power_off', + 'halt', 'reboot', 'zeroize'], + default=None), + at=dict(type='str', + required=False, + default=None), + in_min=dict(type='int', + required=False, + aliases=['in'], + default=None), + all_re=dict(type='bool', + required=False, + default=True), + other_re=dict(type='bool', + required=False, + default=False), + media=dict(type='bool', + required=False, + default=False), + ), + mutually_exclusive=[['at', 'in_min'], ['all_re', 'other_re']], + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + action = params['action'] + # Synonymns for shutdown + if action == 'off' or action == 'power_off' or action == 'power-off': + action = 'shutdown' + + # at option only applies to reboot, shutdown, or halt actions. + if (params.get('at') is not None and + action != 'reboot' and + action != 'shutdown' and + action != 'halt'): + junos_module.fail_json(msg='The at option can only be used when ' + 'the action option has the value "reboot", ' + '"shutdown", or "halt".') + + # in_min option only applies to reboot, shutdown, or halt actions. + if (params.get('in_min') is not None and + action != 'reboot' and + action != 'shutdown' and + action != 'halt'): + junos_module.fail_json(msg='The in_min option can only be used when ' + 'the action option has the value "reboot", ' + '"shutdown", or "halt".') + + # other_re option only applies to reboot, shutdown, or halt actions. + if (params.get('other_re') is True and + action != 'reboot' and + action != 'shutdown' and + action != 'halt'): + junos_module.fail_json(msg='The other_re option can only be used when ' + 'the action option has the value "reboot", ' + '"shutdown", or "halt".') + + # media option only applies to zeroize action. + if params['media'] is True and action != 'zeroize': + junos_module.fail_json(msg='The media option can only be used when ' + 'the action option has the value ' + '"zeroize".') + + # If other_re, then we should turn off all_re + if params['other_re'] is True: + params['all_re'] = False + + # Set initial results values. Assume failure until we know it's success. + # Assume we haven't changed the state until we do. + results = {'changed': False, + 'msg': '', + 'reboot': bool(action == 'reboot'), + 'action': action, + 'all_re': params.get('all_re'), + 'other_re': params.get('other_re'), + 'media': params.get('media'), + 'failed': True} + + # Map the action to an RPC. + rpc = None + xpath_list = [] + if action == 'reboot': + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-reboot') + else: + rpc = junos_module.etree.Element('request-reboot') + xpath_list.append('.//request-reboot-status') + elif action == 'shutdown': + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-power-off') + else: + rpc = junos_module.etree.Element('request-power-off') + xpath_list.append('.//request-reboot-status') + elif action == 'halt': + if junos_module.dev.facts['_is_linux']: + rpc = junos_module.etree.Element('request-shutdown-halt') + else: + rpc = junos_module.etree.Element('request-halt') + xpath_list.append('.//request-reboot-status') + elif action == 'zeroize': + rpc = junos_module.etree.Element('request-system-zeroize') + else: + results['msg'] = 'No RPC found for the %s action.' % (action) + junos_module.fail_json(**results) + + # Add the arguments + if action == 'zeroize': + if params['all_re'] is False: + if junos_module.dev.facts['2RE']: + junos_module.etree.SubElement(rpc, 'local') + if params['media'] is True: + junos_module.etree.SubElement(rpc, 'media') + else: + if params['in_min'] is not None: + junos_module.etree.SubElement(rpc, + 'in').text = str(params['in_min']) + elif params['at'] is not None: + junos_module.etree.SubElement(rpc, + 'at').text = params['at'] + if params['other_re'] is True: + if junos_module.dev.facts['2RE']: + junos_module.etree.SubElement(rpc, 'other-routing-engine') + # At least on some platforms stopping/rebooting the other RE + # just produces messages. + xpath_list.append('..//output') + elif params['all_re'] is True: + junos_module.add_sw() + if (junos_module.sw._multi_RE is True and + junos_module.sw._multi_VC is False): + junos_module.etree.SubElement(rpc, 'both-routing-engines') + # At least on some platforms stopping/rebooting both REs + # produces messages and + # messages. + xpath_list.append('..//output') + elif junos_module.sw._mixed_VC is True: + junos_module.etree.SubElement(rpc, 'all-members') + + # OK, we're ready to do something. Set changed and log the RPC. + results['changed'] = True + junos_module.logger.debug("Ready to execute RPC: %s", + junos_module.etree.tostring(rpc, + pretty_print=True)) + + if not junos_module.check_mode: + if action != 'zeroize': + # If we're going to do a shutdown, reboot, or halt right away then + # try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this happens + # relatively quickly. + if (params['at'] == 'now' or params['in_min'] == 0 or + (params['at'] is None and params['in_min'] is None)): + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + + # Execute the RPC. + try: + junos_module.logger.debug( + "Executing RPC: %s", + junos_module.etree.tostring(rpc, pretty_print=True)) + resp = junos_module.dev.rpc(rpc, + ignore_warning=True, + normalize=True) + junos_module.logger.debug("RPC executed cleanly.") + if len(xpath_list) > 0: + for xpath in xpath_list: + if junos_module.dev.facts['_is_linux']: + got = resp.text + else: + got = resp.findtext(xpath) + if got is not None: + results['msg'] = '%s successfully initiated.' % \ + (action) + results['failed'] = False + break + else: + # This is the else clause of the for loop. + # It only gets executed if the loop finished without + # hitting the break. + results['msg'] = 'Did not find expected RPC response.' + results['changed'] = False + else: + results['msg'] = '%s successfully initiated.' % (action) + results['failed'] = False + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates there was a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['changed'] = False + results['msg'] = '%s failed. %s may not have been ' \ + 'initiated.' % (action, action) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] = '%s succeeded.' % (action) + results['failed'] = False + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + results['changed'] = False + results['msg'] = '%s failed. Error: %s' % (action, str(ex)) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py new file mode 100644 index 00000000..73916673 --- /dev/null +++ b/library/juniper_junos_table.py @@ -0,0 +1,477 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright 2016 Jason Edelman +# Network to Code, LLC +# +# Copyright (c) 2017-2018, Juniper Networks Inc. +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_table +version_added: "2.0.0" # of Juniper.junos role +author: + - Jason Edelman (@jedelman8) + - Updated by Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Retrieve data from a Junos device using a PyEZ table/view +description: + - Retrieve data from a Junos device using PyEZ's operational table/views. + This module may be used with the tables/views which are included in the + PyEZ distribution or it may be used with user-defined tables/views. +options: + file: + description: + - Name of the YAML file, relative to the I(path) option, that contains + the table/view definition. The file name must end with the C(.yml) or + C(.yaml) extension. + required: true + default: none + type: path + kwargs: + description: + - Optional keyword arguments and values to the table's get() method. The + value of this option is a dictionary of keywords and values which are + used to refine the data return from performing a get() on the table. + The exact keywords and values which are supported are specific to the + table's definition and the underlying RPC which the table invokes. + required: false + default: none + type: dict + aliases: + - kwarg + - args + - arg + path: + description: + - The directory containing the YAML table/view definition file as + specified by the I(file) option. The default value is the C(op) + directory in C(jnpr.junos.op). This is the directory containing the + table/view definitions which are included in the PyEZ distribution. + required: false + default: C(op) directory in C(jnpr.junos.op) + type: path + aliases: + - directory + - dir + response_type: + description: + - Defines the format of data returned by the module. See RETURN. + The value of the I(resource) key in the module's response is either + a list of dictionaries C(list_of_dicts) or PyEZ's native return + format C(juniper_items). Because Ansible module's may only return JSON + data, PyEZ's native return format C(juniper_items) is translated into + a list of lists. + required: false + default: list_of_dicts + choices: + - list_of_dicts + - juniper_items + type: str + table: + description: + - Name of the PyEZ table used to retrieve data. If not specified, + defaults to the name of the table defined in the I(file) option. Any + table names in I(file) which begin with C(_) are ignored. If more than + one table is defined in I(file), the module fails with an error + message. In this case, you must manually specify the name of the table + by setting this option. + required: false + default: The name of the table defined in the I(file) option. + type: str +notes: + - This module only works with operational tables/views; it does not work with + configuration tables/views. +''' + +EXAMPLES = ''' +--- +- name: Retrieve data from a Junos device using a PyEZ table/view. + hosts: junos-all + connection: local + gather_facts: no + roles: + - Juniper.junos + + tasks: + - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table + juniper_junos_table: + file: "lldp.yml" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve routes within 192.68.1/8 + juniper_junos_table: + file: "routes.yml" + table: "RouteTable" + kwargs: + destination: "192.68.1.0/8" + response_type: "juniper_items" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve from custom table in playbook directory + juniper_junos_table: + file: "fpc.yaml" + path: "." + register: response + - name: Print response + debug: + var: response +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed. Since this + module does not change the operational or configuration state of the + device, the value is always set to C(false). + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +msg: + description: + - A human-readable message indicating a summary of the result. + returned: always + type: str +resource: + description: + - The items retrieved by the table/view. + returned: success + type: list of dicts if I(response_type) is C(list_of_dicts) or list of + lists if I(respsonse_type) is C(juniper_items). + sample: | + # when response_type == 'list_of_dicts' + [ + { + "local_int": "ge-0/0/3", + "local_parent": "-", + "remote_chassis_id": "00:05:86:08:d4:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/0", + "remote_sysname": "r5", + "remote_type": "Mac address" + }, + { + "local_int": "ge-0/0/0", + "local_parent": "-", + "remote_chassis_id": "00:05:86:18:f3:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/2", + "remote_sysname": "r4", + "remote_type": "Mac address" + } + ] + # when response_type == 'juniper_items' + [ + [ + "ge-0/0/3", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/0" + ], + [ + "remote_chassis_id", + "00:05:86:08:d4:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/3" + ], + [ + "remote_sysname", + "r5" + ] + ] + ], + [ + "ge-0/0/0", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/2" + ], + [ + "remote_chassis_id", + "00:05:86:18:f3:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/0" + ], + [ + "remote_sysname", + "r4" + ] + ] + ] + ] +''' + +# Standard library imports +import os.path + +# Constants +RESPONSE_CHOICES = ['list_of_dicts', 'juniper_items'] + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils import juniper_junos_common + + +def expand_items(module, data): + """Recursively expand any table items + """ + resources = [] + # data.items() is a list of tuples + for table_key, table_fields in data.items(): + # sample: + # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), + # ('neighbor', 'vmx2')] + # table_key - element 0 is the key from the Table - not using at all + # table_fields - element 1 is also a list of tuples + temp = [] + for key, value in table_fields: + # calling it normalized value because YOU/WE created the keys + if value and isinstance(value, module.pyez_factory_table.Table): + value = expand_items(module, value) + temp.append((key, value)) + resources.append((table_key, temp)) + return resources + + +def juniper_items_to_list_of_dicts(module, data): + """Recursively convert Juniper PyEZ Table/View items to list of dicts. + """ + resources = [] + # data.items() is a list of tuples + for table_key, table_fields in data.items(): + # sample: + # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), + # ('neighbor', 'vmx2')] + # table_key - element 0 is the key from the Table - not using at all + # table_fields - element 1 is also a list of tuples + temp = {} + for key, value in table_fields: + if (value and isinstance(value, module.pyez_factory_table.Table)): + value = juniper_items_to_list_of_dicts(module, value) + temp[key] = value + resources.append(temp) + return resources + + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + file=dict(type='path', + required=True, + default=None), + table=dict(type='str', + required=False, + default=None), + path=dict(type='path', + required=False, + aliases=['directory', 'dir'], + default=None), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='dict', + default=None), + response_type=dict(choices=RESPONSE_CHOICES, + type='str', + required=False, + default='list_of_dicts'), + ), + # Check mode is implemented. + supports_check_mode=True, + min_yaml_version=juniper_junos_common.MIN_YAML_VERSION, + ) + + # Straight from params + file = junos_module.params.get('file') + table = junos_module.params.get('table') + path = junos_module.params.get('path') + kwargs = junos_module.params.get('kwargs') + response_type = junos_module.params.get('response_type') + + if not file.endswith('.yml') and not file.endswith('.yaml'): + junos_module.fail_json(msg='The value of the file option must end ' + 'with the .yml or .yaml extension') + + # If needed, get the default path + if path is None: + path = os.path.dirname( + os.path.abspath(junos_module.pyez_op_table.__file__)) + + # file_name is path + file + file_name = os.path.join(path, file) + + junos_module.logger.debug("Attempting to open: %s.", file_name) + try: + with open(file_name, 'r') as fp: + try: + junos_module.logger.debug("Attempting to parse YAML from : " + "%s.", file_name) + table_view = junos_module.yaml.load(fp) + junos_module.logger.debug("YAML from %s successfully parsed.", + file_name) + except junos_module.yaml.YAMLError as ex: + junos_module.fail_json(msg='Failed parsing YAML file %s. ' + 'Error: %s' % (file_name, str(ex))) + except IOError: + junos_module.fail_json(msg='The file name %s could not be opened for' + 'reading.' % (file_name)) + junos_module.logger.debug("%s successfully read.", file_name) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': '', + 'changed': False, + 'failed': True} + + # Default to the table defined in file_name. + # Ignore table names which begin with an underscore. + if table is None: + for key in table_view: + if not key.startswith('_') and 'Table' in key: + if table is not None: + junos_module.fail_json( + msg='The file name %s contains multiple table ' + 'definitions. Specify the desired table with the ' + 'table option.' % (file_name)) + table = key + + if table is None: + junos_module.fail_json( + msg='No table definition was found in the %s file. Specify a ' + 'value for the file option which contains a valid table/view ' + 'definition.' % (file_name)) + junos_module.logger.debug("Table: %s", table) + + try: + loader = \ + junos_module.pyez_factory_loader.FactoryLoader().load(table_view) + junos_module.logger.debug("Loader created successfully.") + except Exception as ex: + junos_module.fail_json(msg='Unable to create a table loader from the ' + '%s file. Error: %s' % (file_name, str(ex))) + try: + data = loader[table](junos_module.dev) + junos_module.logger.debug("Table %s created successfully.", table) + if kwargs is None: + data.get() + else: + data.get(**kwargs) + junos_module.logger.debug("Data retrieved from %s successfully.", + table) + except KeyError: + junos_module.fail_json(msg='Unable to find table %s in the ' + '%s file.' % (table, file_name)) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.fail_json(msg='Unable to retrieve data from table %s. ' + 'Error: %s' % (table, str(ex))) + + if data is not None: + try: + len_data = len(data) + except Exception as ex: + junos_module.fail_json(msg='Unable to parse table %s data into ' + 'items. Error: %s' % (table, str(ex))) + junos_module.logger.debug('Successfully retrieved %d items from %s.', + len_data, table) + results['msg'] = 'Successfully retrieved %d items from %s.' % \ + (len_data, table) + + if response_type == 'list_of_dicts': + junos_module.logger.debug('Converting data to list of dicts.') + resource = juniper_items_to_list_of_dicts(junos_module, data) + else: + resource = expand_items(junos_module, data) + + # If we made it this far, everything was successful. + results['failed'] = False + results['resource'] = resource + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() From 60da33410b3a07c0140a08731f0d1aaa1a9ac8d6 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 5 Mar 2020 14:34:56 +0530 Subject: [PATCH 300/426] single sourced code --- library | 1 + library/juniper_junos_command.py | 502 ----------- library/juniper_junos_config.py | 1143 -------------------------- library/juniper_junos_facts.py | 344 -------- library/juniper_junos_jsnapy.py | 358 -------- library/juniper_junos_ping.py | 504 ------------ library/juniper_junos_pmtud.py | 410 --------- library/juniper_junos_rpc.py | 624 -------------- library/juniper_junos_software.py | 806 ------------------ library/juniper_junos_srx_cluster.py | 295 ------- library/juniper_junos_system.py | 476 ----------- library/juniper_junos_table.py | 477 ----------- 12 files changed, 1 insertion(+), 5939 deletions(-) create mode 120000 library delete mode 100644 library/juniper_junos_command.py delete mode 100644 library/juniper_junos_config.py delete mode 100644 library/juniper_junos_facts.py delete mode 100644 library/juniper_junos_jsnapy.py delete mode 100644 library/juniper_junos_ping.py delete mode 100644 library/juniper_junos_pmtud.py delete mode 100644 library/juniper_junos_rpc.py delete mode 100644 library/juniper_junos_software.py delete mode 100644 library/juniper_junos_srx_cluster.py delete mode 100644 library/juniper_junos_system.py delete mode 100644 library/juniper_junos_table.py diff --git a/library b/library new file mode 120000 index 00000000..de06a658 --- /dev/null +++ b/library @@ -0,0 +1 @@ +ansible_collection/Juniper/junos/plugins/modules \ No newline at end of file diff --git a/library/juniper_junos_command.py b/library/juniper_junos_command.py deleted file mode 100644 index 9a2b66a6..00000000 --- a/library/juniper_junos_command.py +++ /dev/null @@ -1,502 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_command -version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Execute one or more CLI commands on a Junos device -description: - - Execute one or more CLI commands on a Junos device. - - This module does NOT use the Junos CLI to execute the CLI command. - Instead, it uses the C() RPC over a NETCONF channel. The - C() RPC takes a CLI command as it's input and is very similar to - executing the command on the CLI, but you can NOT include any pipe modifies - (i.e. C(| match), C(| count), etc.) with the CLI commands executed by this - module. -options: - commands: - description: - - A list of one or more CLI commands to execute on the Junos device. - required: true - default: none - type: list - aliases: - - cli - - command - - cmd - - cmds - dest: - description: - - The path to a file, on the Ansible control machine, where the output of - the cli command will be saved. - - The file must be writeable. If the file already exists, it is - overwritten. - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the value of the I(dest) option. It is - the user's responsibility to ensure this value is unique per target - host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(dest) and I(dest_dir) options are mutually exclusive. - required: false - default: None - type: path - aliases: - - destination - dest_dir: - description: - - The path to a directory, on the Ansible control machine, where - the output of the cli command will be saved. The output will be logged - to a file named C({{ inventory_hostname }}_)I(command)C(.)I(format) - in the directory specified by the value of the I(dest_dir) option. - - The destination file must be writeable. If the file already exists, - it is overwritten. It is the users responsibility to ensure a unique - I(dest_dir) value is provided for each execution of this module - within a playbook. - - The I(dest_dir) and I(dest) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - required: false - default: None - type: path - aliases: - - destination_dir - - destdir - formats: - description: - - The format of the reply for the CLI command(s) specified by the - I(commands) option. The specified format(s) must be supported by the - target Junos device. The value of this option can either be a single - format, or a list of formats. If a single format is specified, it - applies to all command(s) specified by the I(commands) option. If a - list of formats are specified, there must be one value in the list for - each command specified by the I(commands) option. Specifying the value - C(xml) for the I(formats) option is similar to appending - C(| display xml) to a CLI command, and specifying the value C(json) - for the I(formats) option is similar to appending C(| display json) to - a CLI command. - required: false - default: text - type: str or list of str - choices: - - text - - xml - - json - aliases: - - format - - display - - output - return_output: - description: - - Indicates if the output of the command should be returned in the - module's response. You might want to set this option to C(false), - and set the I(dest_dir) option, if the command output is very large - and you only need to save the output rather than using it's content in - subsequent tasks/plays of your playbook. - required: false - default: true - type: bool -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_command - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: Execute single "show version" command. - juniper_junos_command: - commands: "show version" - register: response - - - name: Print the command output - debug: - var: response.stdout - - - name: Execute three commands. - juniper_junos_command: - commands: - - "show version" - - "show system uptime" - - "show interface terse" - register: response - - - name: Print the command output of each. - debug: - var: item.stdout - with_items: "{{ response.results }}" - - - name: Two commands with XML output. - juniper_junos_command: - commands: - - "show route" - - "show lldp neighbors" - format: xml - - - name: show route with XML output - show version with JSON output - juniper_junos_command: - commands: - - "show route" - - "show version" - formats: - - "xml" - - "json" - - - name: save outputs in dest_dir - juniper_junos_command: - commands: - - "show route" - - "show version" - dest_dir: "./output" - - - name: save output to dest - juniper_junos_command: - command: "show system uptime" - dest: "/tmp/{{ inventory_hostname }}.uptime.output" - - - name: save output to dest - juniper_junos_command: - command: - - "show route" - - "show lldp neighbors" - dest: "/tmp/{{ inventory_hostname }}.commands.output" - - - name: Multiple commands, save outputs, but don't return them - juniper_junos_command: - commands: - - "show route" - - "show version" - formats: - - "xml" - - "json" - dest_dir: "/tmp/outputs/" - return_output: false -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's state has changed. Since this module does not - change the operational or configuration state of the device, the value - is always set to false. - - You could use this module to execute a command which - changes the operational state of the the device. For example, - C(clear ospf neighbors). Beware, this module is unable to detect - this situation, and will still return the value C(false) for I(changed) - in this case. - returned: success - type: bool - sample: false -command: - description: - - The CLI command which was executed. - returned: always - type: str -failed: - description: - - Indicates if the task failed. See the I(results) key for additional - details. - returned: always - type: bool -format: - description: - - The format of the command response. - returned: always - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -parsed_output: - description: - - The command reply from the Junos device parsed into a JSON data structure. - For XML replies, the response is parsed into JSON using the - U(jxmlease|https://github.com/Juniper/jxmlease) - library. For JSON the response is parsed using the Python - U(json|https://docs.python.org/2/library/json.html) library. - - When Ansible converts the jxmlease or native Python data structure - into JSON, it does not guarantee that the order of dictionary/object keys - are maintained. - returned: when command executed successfully, I(return_output) is true, - and the value of the I(formats) option is C(xml) or C(json). - type: dict -results: - description: - - The other keys are returned when a single command is specified for the - I(commands) option. When the value of the I(commands) option is a list - of commands, this key is returned instead. The value of this key is a - list of dictionaries. Each element in the list corresponds to the - commands in the I(commands) option. The keys for each element in the list - include all of the other keys listed. The I(failed) key indicates if the - individual command failed. In this case, there is also a top-level - I(failed) key. The top-level I(failed) key will have a value of C(false) - if ANY of the commands ran successfully. In this case, check the value - of the I(failed) key for each element in the I(results) list for the - results of individual commands. - returned: when the I(commands) option is a list value. - type: list of dict -stdout: - description: - - The command reply from the Junos device as a single multi-line string. - returned: when command executed successfully and I(return_output) is C(true). - type: str -stdout_lines: - description: - - The command reply from the Junos device as a list of single-line strings. - returned: when command executed successfully and I(return_output) is C(true). - type: list of str -''' - -import sys - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - commands=dict(required=True, - type='list', - aliases=['cli', 'command', 'cmd', 'cmds'], - default=None), - formats=dict(required=False, - type='list', - aliases=['format', 'display', 'output'], - default=None), - dest=dict(required=False, - type='path', - aliases=['destination'], - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir'], - default=None), - return_output=dict(required=False, - type='bool', - default=True) - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. Well, that's not completely true. It does depend on the - # command executed. See the I(changed) key in the RETURN documentation - # for more details. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - - # Check over commands - commands = junos_module.params.get('commands') - # Ansible allows users to specify a commands argument with no value. - if commands is None: - junos_module.fail_json(msg="The commands option must have a value.") - # Make sure the commands don't include any pipe modifiers. - for command in commands: - pipe_index = command.find('|') - if (pipe_index != -1 and - command[pipe_index:].strip() != 'display xml rpc'): - # Allow "show configuration | display set" - if ('show configuration' in command and - 'display set' in command[pipe_index:] and - '|' not in command[pipe_index+1:]): - continue - # Any other "| display " should use the format option instead. - for valid_format in juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES: - if 'display ' + valid_format in command[pipe_index:]: - junos_module.fail_json( - msg='The pipe modifier (%s) in the command ' - '(%s) is not supported. Use format: "%s" ' - 'instead.' % - (command[pipe_index:], command, valid_format)) - # Any other "| " is going to produce an error anyway, so fail - # with a meaningful message. - junos_module.fail_json(msg='The pipe modifier (%s) in the command ' - '(%s) is not supported.' % - (command[pipe_index:], command)) - - # Check over formats - formats = junos_module.params.get('formats') - if formats is None: - # Default to text format - formats = ['text'] - valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES - # Check format values - for format in formats: - # Is it a valid format? - if format not in valid_formats: - junos_module.fail_json(msg="The value %s in formats is invalid. " - "Must be one of: %s" % - (format, ', '.join(map(str, - valid_formats)))) - # Correct number of format values? - if len(formats) != 1 and len(formats) != len(commands): - junos_module.fail_json(msg="The formats option must have a single " - "value, or one value per command. There " - "are %d commands and %d formats." % - (len(commands), len(formats))) - # Same format for all commands - elif len(formats) == 1 and len(commands) > 1: - formats = formats * len(commands) - - results = list() - for (command, format) in zip(commands, formats): - # Set initial result values. Assume failure until we know it's success. - result = {'msg': '', - 'command': command, - 'format': format, - 'changed': False, - 'failed': True} - - # Execute the CLI command - try: - junos_module.logger.debug('Executing command "%s".', - command) - rpc = junos_module.etree.Element('command', format=format) - rpc.text = command - resp = junos_module.dev.rpc(rpc, normalize=bool(format == 'xml')) - result['msg'] = 'The command executed successfully.' - junos_module.logger.debug('Command "%s" executed successfully.', - command) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Unable to execute "%s". Error: %s', - command, str(ex)) - result['msg'] = 'Unable to execute the command: %s. Error: %s' % \ - (command, str(ex)) - results.append(result) - continue - - text_output = None - parsed_output = None - if resp is True: - text_output = '' - elif (resp, junos_module.etree._Element): - # Handle the output based on format - if format == 'text': - if resp.tag in ['output', 'rpc-reply']: - text_output = resp.text - junos_module.logger.debug('Text output set.') - elif resp.tag == 'configuration-information': - text_output = resp.findtext('configuration-output') - junos_module.logger.debug('Text configuration output set.') - else: - result['msg'] = 'Unexpected text response tag: %s.' % ( - (resp.tag)) - results.append(result) - junos_module.logger.debug('Unexpected text response tag ' - '%s.', resp.tag) - continue - elif format == 'xml': - encode = None if sys.version < '3' else 'unicode' - text_output = junos_module.etree.tostring(resp, - pretty_print=True, - encoding=encode) - parsed_output = junos_module.jxmlease.parse_etree(resp) - junos_module.logger.debug('XML output set.') - elif format == 'json': - text_output = str(resp) - parsed_output = resp - junos_module.logger.debug('JSON output set.') - else: - result['msg'] = 'Unexpected format %s.' % (format) - results.append(result) - junos_module.logger.debug('Unexpected format %s.', format) - continue - else: - result['msg'] = 'Unexpected response type %s.' % (type(resp)) - results.append(result) - junos_module.logger.debug('Unexpected response type %s.', - type(resp)) - continue - - # Set the output keys - if junos_module.params['return_output'] is True: - if text_output is not None: - result['stdout'] = text_output - result['stdout_lines'] = text_output.splitlines() - if parsed_output is not None: - result['parsed_output'] = parsed_output - # Save the output - junos_module.save_text_output(command, format, text_output) - # This command succeeded. - result['failed'] = False - # Append to the list of results - results.append(result) - - # Return response. - if len(results) == 1: - junos_module.exit_json(**results[0]) - else: - # Calculate the overall failed. Only failed if all commands failed. - failed = True - for result in results: - if result.get('failed') is False: - failed = False - break - junos_module.exit_json(results=results, - changed=False, - failed=failed) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_config.py b/library/juniper_junos_config.py deleted file mode 100644 index 10101d42..00000000 --- a/library/juniper_junos_config.py +++ /dev/null @@ -1,1143 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_config -version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Manipulate the configuration of a Junos device -description: - - > - Manipulate the configuration of a Junos device. This module allows a - combination of loading or rolling back, checking, diffing, retrieving, and - committing the configuration of a Junos device. It performs the following - steps in order: - - - #. Open a candidate configuration database. - - * If the I(config_mode) option has a value of C(exclusive), the default, - take a lock on the candidate configuration database. If the lock fails - the module fails and reports an error. - * If the I(config_mode) option has a value of C(private), open a private - candidate configuration database. If opening the private configuration - database fails the module fails and reports an error. - #. Load configuration data into the candidate configuration database. - - * Configuration data may be loaded using the I(load) or I(rollback) - options. If either of these options are specified, new configuration - data is loaded. If neither option is specified, this step is skipped. - * If the I(rollback) option is specified, replace the candidate - configuration with the previous configuration specified by the value - of the I(rollback) option. - * If the I(load) option is specified, load new configuration data. - * The value of the I(load) option defines the type of load which is - performed. - * The source of the new configuration data is one of the following: - - * I(src) - A file path on the local Ansible control machine. - * I(lines) - A list of strings containing the configuration data. - * I(template) - A file path to a Jinja2 template on the local - Ansible control machine. This template is rendered with the variables - specified by the I(vars) option. If the I(template) option is - specified, the I(vars) option must also be specified. - * I(url) - A URL reachable from the target Junos device. - * If the I(format) option is specified, the configuration file being - loaded is in the specified format, rather than the format determined - from the file name. - #. Check the validity of the candidate configuration database. - - * If the I(check) option is C(true), the default, check the validity - of the configuration by performing a "commit check" operation. - * This option may be specified with I(diff) C(false) and I(commit) - C(false) to confirm a previous "commit confirmed " operation - without actually performing an additional commit. - * If the configuration check fails, further processing stops, the module - fails, and an error is reported. - #. Determine differences between the candidate and committed configuration - databases. - - * If step 2 was not skipped, and the I(diff) option is C(true), - the default, perform a diff between the candidate and committed - configuration databases. - * If the I(diffs_file) or I(dest_dir) option is specified, save the - generated configuration differences. - * If the I(return_output) option is C(true), the default, include the - generated configuration difference in the I(diff) and I(diff_lines) - keys of the module's response. - #. Retrieve the configuration database from the Junos device. - - * If the I(retrieve) option is specified, retrieve the configuration - database specified by the I(retrieve) value from the target Junos - device to the local Ansible control machine. - * The format in which the configuration is retrieved is specified by the - value of the I(format) option. - * The optional I(filter) controls which portions of the configuration - are retrieved. - * If I(options) are specified, they control the content of the - configuration retrieved. - * If the I(dest) or I(dest_dir) option is specified, save the - retrieved configuration to a file on the local Ansible control - machine. - * If the I(return_output) option is C(true), the default, include the - retrieved configuration in the I(config), I(config_lines), and - I(config_parsed) keys of the module's response. - #. Commit the configuration changes. - - * If the I(commit) option is C(true), the default, commit the - configuration changes. - * This option may be specified with I(diff) C(false) and I(check) - C(false) to confirm a previous "commit confirmed " operation. - * If the I(comment) option is specified, add the comment to the commit. - * If the I(confirmed) option is specified, perform a - C(commit confirmed) I(min) operation where I(min) is the value of the - I(confirmed) option. - * If the I(check) option is C(true) and the I(check_commit_wait) - option is specified, wait I(check_commit_wait) seconds before - performing the commit. - #. Close the candidate configuration database. - - * Close and discard the candidate configuration database. - * If the I(config_mode) option has a value of C(exclusive), the default, - unlock the candidate configuration database. -options: - check: - description: - - Perform a commit check operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - aliases: - - check_commit - - commit_check - check_commit_wait: - description: - - The number of seconds to wait between check and commit operations. - - This option is only valid if I(check) is C(true) and I(commit) is - C(true). - - This option should not normally be needed. It works around an issue in - some versions of Junos. - required: false - default: none - type: int - comment: - description: - - Provide a comment to be used with the commit operation. - - This option is only valid if the I(commit) option is true. - required: false - default: none - type: str - commit: - description: - - Perform a commit operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - commit_empty_changes: - description: - - Perform a commit operation, even if there are no changes between the - candidate configuration and the committed configuration. - required: false - default: false - type: bool - config_mode: - description: - - The mode used to access the candidate configuration database. - required: false - default: exclusive - type: str - choices: - - exclusive - - private - aliases: - - config_access - - edit_mode - - edit_access - confirmed: - description: - - Provide a confirmed timeout, in minutes, to be used with the commit - operation. - - This option is only valid if the I(commit) option is C(true). - - The value of this option is the number of minutes to wait for another - commit operation before automatically rolling back the configuration - change performed by this task. In other words, this option causes the - module to perform a C(commit confirmed )I(min) where I(min) is the - value of the I(confirmed) option. This option DOES NOT confirm a - previous C(commit confirmed )I(min) operation. To confirm a previous - commit operation, invoke this module with the I(check) or I(commit) - option set to C(true). - required: false - default: none - type: int - aliases: - - confirm - dest: - description: - - The path to a file, on the local Ansible control machine, where the - configuration will be saved if the I(retrieve) option is specified. - - The file must be writeable. If the file already exists, it is - overwritten. - - This option is only valid if the I(retrieve) option is not C(none). - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the I(dest) value. It is the user's - responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(dest) and I(dest_dir) options are mutually exclusive. - required: false - default: none - type: path - aliases: - - destination - dest_dir: - description: - - The path to a directory, on the Ansible control machine. This is the - directory where the configuration will be saved if the I(retrieve) - option is specified. It is also the directory where the configuration - diff will be specified if the I(diff) option is C(true). - - This option is only valid if the I(retrieve) option is not C(none) or - the I(diff) option is C(true). - - The retrieved configuration will be saved to a file named - C({{ inventory_hostname }}.)I(format_extension) in the I(dest_dir) - directory. Where I(format_extension) is C(conf) for text format, C(xml) - for XML format, C(json) for JSON format, and C(set) for set format. - - If the I(diff) option is C(true), the configuration diff will be saved - to a file named C({{ inventory_hostname }}.diff) in the I(dest_dir) - directory. - - The destination file must be writeable. If the file already exists, - it is overwritten. It is the users responsibility to ensure a unique - I(dest_dir) value is provided for each execution of this module - within a playbook. - - The I(dest_dir) and I(dest) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - - The I(dest_dir) and I(diff_file) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - required: false - default: none - type: path - aliases: - - destination_dir - - destdir - - savedir - - save_dir - diff: - description: - - Perform a configuration compare (aka diff) operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - aliases: - - compare - - diffs - diffs_file: - description: - - The path to a file, on the Ansible control machine, where the - configuration differences will be saved if the I(diff) option is - specified. - - The file must be writeable. If the file already exists, it is - overwritten. - - This option is only valid if the I(diff) option is C(true). - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the I(diffs_file) value. It is the - user's responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - - The I(diffs_file) and I(dest_dir) options are mutually exclusive. - required: false - default: None - type: path - format: - description: - - Specifies the format of the configuration retrieved, if I(retrieve) - is not C(none). - - Specifies the format of the configuration to be loaded, if I(load) is - not C(none). - - The specified format must be supported by the target Junos device. - required: false - default: none (auto-detect on load, text on retrieve) - type: str - choices: - - xml - - set - - text - - json - filter: - description: - - A string of XML, or '/'-separated configuration hierarchies, - which specifies a filter used to restrict the portions of the - configuration which are retrieved. See - U(PyEZ's get_config method documentation|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) - for details on the value of this option. - required: false - default: none - type: 'str' - aliases: - - filter_xml - ignore_warning: - description: - - A boolean, string or list of strings. If the value is C(true), - ignore all warnings regardless of the warning message. If the value - is a string, it will ignore warning(s) if the message of each warning - matches the string. If the value is a list of strings, ignore - warning(s) if the message of each warning matches at least one of the - strings in the list. The value of the I(ignore_warning) option is - applied to the load and commit operations performed by this module. - required: false - default: none - type: bool, str, or list of str - lines: - description: - - Used with the I(load) option. Specifies a list of list of - configuration strings containing the configuration to be loaded. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - By default, the format of the configuration data is auto-dectected by - the content of the first line in the I(lines) list. - - If the I(format) option is specified, the I(format) value overrides the - format auto-detection. - required: false - default: none - type: list - load: - description: - - Specifies the type of load operation to be performed. - - The I(load) and I(rollback) options are mutually exclusive. - - > - The choices have the following meanings: - - B(none) - Do not perform a load operation. - - B(merge) - Combine the new configuration with the existing - configuration. If statements in the new configuration conflict with - statements in the existing configuration, the statements in - the new configuration replace those in the existing - configuration. - - B(replace) - This option is a superset of the B(merge) option. It - combines the new configuration with the existing configuration. If the - new configuration is in text format and a hierarchy level in the new - configuartion is prefixed with the string C(replace:), then the - hierarchy level in the new configuration replaces the entire - corresponding hierarchy level in the existing configuration, regardles - of the existence or content of that hierarchy level in the existing - configuration. If the configuration is in XML format, the XML attribute - C(replace = "replace") is equivalent to the text format's C(replace:) - prefix. If a configuration hierarchy in the new configuration is not - prefixed with C(replace:), then the B(merge) behavior is used. - Specifically, for any statements in the new configuration which - conflict with statements in the existing configuration, the statements - in the new configuration replace those in the existing configuration. - - B(override) - Discard the entire existing configuration and replace it - with the new configuration. When the configuration is later committed, - all system processes are notified and the entire new configuration is - marked as 'changed' even if some statements previously existed in the - configuration. The value B(overwrite) is a synonym for B(override). - - B(update) - This option is similar to the B(override) option. The new - configuration completely replaces the existing configuration. The - difference comes when the configuration is later committed. This option - performs a 'diff' between the new candidate configuration and the - existing committed configuration. It then only notifies system - processes repsonsible for the changed portions of the configuration, - and only marks the actual configuration changes as 'changed'. - - B(set) - This option is used when the new configuration data is in set - format (a series of configuration mode commands). The new configuration - data is loaded line by line and may contain any configuration mode - commands, such as set, delete, edit, or deactivate. This value must be - specified if the new configuration is in set format. - required: false - default: none - choices: - - none - - set - - merge - - update - - replace - - override - - overwrite - type: str - options: - description: - - Additional options, specified as a dictionary of key/value pairs, used - when retrieving the configuration. See the - U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) - for information on available options. - required: false - default: None - type: dict - retrieve: - description: - - The configuration database to be retrieved. - required: false - default: none - choices: - - none - - candidate - - committed - type: str - return_output: - description: - - Indicates if the output of the I(diff) and I(retreive) options should - be returned in the module's response. You might want to set this option - to C(false), and set the I(dest_dir) option, if the configuration or - diff output is very large and you only need to save the output rather - than using it's content in subsequent tasks/plays of your playbook. - required: false - default: true - type: bool - rollback: - description: - - Populate the candidate configuration from a previously committed - configuration. This value can be a configuration number between 0 and - 49, or the keyword C(rescue) to load the previously saved rescue - configuration. - - By default, some Junos platforms store fewer than 50 previous - configurations. Specifying a value greater than the number - of previous configurations available, or specifying C(rescue) when no - rescue configuration has been saved, will result in an error when the - module attempts to perform the rollback. - - The I(rollback) and I(load) options are mutually exclusive. - required: false - default: none - choices: - - 0-49 - - rescue - type: int or str - src: - description: - - Used with the I(load) option. Specifies the path to a file, on the - local Ansible control machine, containing the configuration to be - loaded. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - By default, the format of the configuration data is determined by the - file extension of this path name. If the file has a C(.conf) - extension, the content is treated as text format. If the file has a - C(.xml) extension, the content is treated as XML format. If the file - has a C(.set) extension, the content is treated as Junos B(set) - commands. - - If the I(format) option is specified, the I(format) value overrides the - file-extension based format detection. - required: false - default: none - type: 'path' - aliases: - - source - - file - template: - description: - - The path to a Jinja2 template file, on the local Ansible control - machine. This template file, along with the I(vars) option, is used to - generate the configuration to be loaded on the target Junos device. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - The I(template) and I(vars) options are required together. If one is - specified, the other must be specified. - required: false - default: none - type: path - aliases: - - template_path - url: - description: - - A URL which specifies the configuration data to load on the target - Junos device. - - The Junos device uses this URL to load the configuration, therefore - this URL must be reachable by the target Junos device. - - The possible formats of this value are documented in the 'url' section - of the - U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - required: false - default: none - type: str - vars: - description: - - A dictionary of keys and values used to render the Jinja2 template - specified by the I(template) option. - - The I(template) and I(vars) options are required together. If one is - specified, the other must be specified. - required: false - default: none - type: dict - aliases: - - template_vars -''' - -EXAMPLES = ''' ---- -- name: Manipulate the configuration of Junos devices - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: - - name: Retrieve the committed configuration - juniper_junos_config: - retrieve: 'committed' - diff: false - check: false - commit: false - register: response - - name: Print the lines in the config. - debug: - var: response.config_lines - - - name: Append .foo to the hostname using private config mode. - juniper_junos_config: - config_mode: 'private' - load: 'merge' - lines: - - "set system host-name {{ inventory_hostname }}.foo" - register: response - - name: Print the config changes. - debug: - var: response.diff_lines - - - name: Rollback to the previous config. - juniper_junos_config: - config_mode: 'private' - rollback: 1 - register: response - - name: Print the config changes. - debug: - var: response.diff_lines - - - name: Rollback to the rescue config. - juniper_junos_config: - rollback: 'rescue' - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load override from a file. - juniper_junos_config: - load: 'override' - src: "{{ inventory_hostname }}.conf" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a Jinja2 template. - juniper_junos_config: - load: 'merge' - format: 'xml' - template: "{{ inventory_hostname }}.j2" - vars: - host: "{{ inventory_hostname }}" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a file on the Junos device. - juniper_junos_config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a file on the Junos device, skip the commit check - juniper_junos_config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - check: false - register: response - - name: Print the msg. - debug: - var: response.msg - - - name: Print diff between current and rollback 10. No check. No commit. - juniper_junos_config: - rollback: 11 - diff: true - check: false - commit: false - register: response - - name: Print the msg. - debug: - var: response - - - name: Retrieve [edit system services] of current committed config. - juniper_junos_config: - retrieve: 'committed' - filter: 'system/services' - diff: true - check: false - commit: false - register: response - - name: Print the resulting config lines. - debug: - var: response.config_lines - - - name: Enable NETCONF SSH and traceoptions, save config, and diffs. - juniper_junos_config: - load: 'merge' - lines: - - 'set system services netconf ssh' - - 'set system services netconf traceoptions flag all' - - 'set system services netconf traceoptions file netconf.log' - format: 'set' - retrieve: 'candidate' - filter: 'system/services' - comment: 'Enable NETCONF with traceoptions' - dest_dir: './output' - register: response - - name: Print the complete response - debug: - var: response - - - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit - juniper_junos_config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - confirm: 5 - check_commit_wait: 3 - register: response - - name: Print the complete response - debug: - var: response - - name: Confirm the previous commit with a commit check (but no commit) - juniper_junos_config: - check: true - diff: false - commit: false - register: response - - name: Print the complete response - debug: - var: response -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's configuration has changed, or would have - changed when in check mode. - returned: success - type: bool -config: - description: - - The retrieved configuration. The value is a single multi-line - string in the format specified by the I(format) option. - returned: when I(retrieved) is not C(none) and I(return_output) is C(true). - type: str -config_lines: - description: - - The retrieved configuration. The value is a list of single-line - strings in the format specified by the I(format) option. - returned: when I(retrieved) is not C(none) and I(return_output) is C(true). - type: list -config_parsed: - description: - - The retrieved configuration parsed into a JSON datastructure. - For XML replies, the response is parsed into JSON using the - jxmlease library. For JSON the response is parsed using the - Python json library. - - When Ansible converts the jxmlease or native Python data - structure into JSON, it does not guarantee that the order of - dictionary/object keys are maintained. - returned: when I(retrieved) is not C(none), the I(format) option is C(xml) or - C(json) and I(return_output) is C(true). - type: dict -diff: - description: - - The configuration differences between the previous and new - configurations. The value is a dict that contains a single key named - "prepared". Value associated with that key is a single multi-line string - in "diff" format. - returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and - I(return_output) is C(true). - type: dict -diff_lines: - description: - - The configuration differences between the previous and new - configurations. The value is a list of single-line strings in "diff" - format. - returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and - I(return_output) is C(true). - type: list -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -file: - description: - - The value of the I(src) option. - returned: when I(load) is not C(none) and I(src) is not C(none) - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -''' - - -# Standard library imports -import time - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - # Choices which are defined in the common module. - config_format_choices = juniper_junos_common.CONFIG_FORMAT_CHOICES - config_database_choices = [None] + \ - juniper_junos_common.CONFIG_DATABASE_CHOICES - config_action_choices = [None] + juniper_junos_common.CONFIG_ACTION_CHOICES - config_mode_choices = juniper_junos_common.CONFIG_MODE_CHOICES - config_model_choices = juniper_junos_common.CONFIG_MODEL_CHOICES - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - ignore_warning=dict(required=False, - type='list', - default=None), - config_mode=dict(choices=config_mode_choices, - type='str', - required=False, - aliases=['config_access', 'edit_mode', - 'edit_access'], - default='exclusive'), - rollback=dict(type='str', - required=False, - default=None), - load=dict(choices=config_action_choices, - type='str', - required=False, - default=None), - src=dict(type='path', - required=False, - aliases=['source', 'file'], - default=None), - lines=dict(type='list', - required=False, - default=None), - template=dict(type='path', - required=False, - aliases=['template_path'], - default=None), - vars=dict(type='dict', - required=False, - aliases=['template_vars'], - default=None), - url=dict(type='str', - required=False, - default=None), - format=dict(choices=config_format_choices, - type='str', - required=False, - default=None), - model=dict(required=False, - choices=config_model_choices, - type='str', - default=None), - remove_ns=dict(required=False, - type='bool', - default=None), - namespace=dict(required=False, - type='str', - default=None), - check=dict(required=False, - type='bool', - aliases=['check_commit', 'commit_check'], - default=None), - diff=dict(required=False, - type='bool', - aliases=['compare', 'diffs'], - default=None), - diffs_file=dict(type='path', - required=False, - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir', 'savedir', - 'save_dir'], - default=None), - return_output=dict(required=False, - type='bool', - default=True), - retrieve=dict(choices=config_database_choices, - type='str', - required=False, - default=None), - options=dict(type='dict', - required=False, - default={}), - filter=dict(required=False, - type='str', - aliases=['filter_xml'], - default=None), - dest=dict(type='path', - required=False, - aliases=['destination'], - default=None), - commit=dict(required=False, - type='bool', - default=None), - commit_empty_changes=dict(required=False, - type='bool', - default=False), - confirmed=dict(required=False, - type='int', - aliases=['confirm'], - default=None), - comment=dict(required=False, - type='str', - default=None), - check_commit_wait=dict(required=False, - type='int', - default=None) - ), - # Mutually exclusive options. - mutually_exclusive=[['load', 'rollback'], - ['src', 'lines', 'template', 'url'], - ['diffs_file', 'dest_dir'], - ['dest', 'dest_dir']], - # Required together options. - required_together=[['template', 'vars']], - # Check mode is implemented. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - # Do additional argument verification. - - # Parse ignore_warning value - ignore_warning = junos_module.parse_ignore_warning_option() - - # Straight from params - config_mode = junos_module.params.get('config_mode') - - # Parse rollback value - rollback = junos_module.parse_rollback_option() - - # Straight from params - load = junos_module.params.get('load') - src = junos_module.params.get('src') - lines = junos_module.params.get('lines') - template = junos_module.params.get('template') - vars = junos_module.params.get('vars') - url = junos_module.params.get('url') - format = junos_module.params.get('format') - check = junos_module.params.get('check') - diff = junos_module.params.get('diff') - diffs_file = junos_module.params.get('diffs_file') - dest_dir = junos_module.params.get('dest_dir') - return_output = junos_module.params.get('return_output') - retrieve = junos_module.params.get('retrieve') - options = junos_module.params.get('options') - filter = junos_module.params.get('filter') - dest = junos_module.params.get('dest') - commit = junos_module.params.get('commit') - commit_empty_changes = junos_module.params.get('commit_empty_changes') - confirmed = junos_module.params.get('confirmed') - comment = junos_module.params.get('comment') - check_commit_wait = junos_module.params.get('check_commit_wait') - model = junos_module.params.get('model') - remove_ns = junos_module.params.get('remove_ns') - namespace = junos_module.params.get('namespace') - - - # If retrieve is set and load and rollback are not set, then - # check, diff, and commit default to False. - if retrieve is not None and load is None and rollback is None: - if diff is None: - diff = False - if check is None: - check = False - if commit is None: - commit = False - # Otherwise, diff, check, and commit default to True. - else: - if diff is None: - diff = True - if check is None: - check = True - if commit is None: - commit = True - - # If load is not None, must have one of src, template, url, lines - if load is not None: - for option in ['src', 'lines', 'template', 'url']: - if junos_module.params.get(option) is not None: - break - # for/else only executed if we didn't break out of the loop. - else: - junos_module.fail_json(msg="The load option (%s) is specified, " - "but none of 'src', 'lines', " - "'template', or 'url' are specified. " - "Must specify one of the 'src', " - "'lines', 'template', or 'url' options." - % (load)) - - # format is valid if retrieve is not None or load is not None. - if format is not None: - if load is None and retrieve is None: - junos_module.fail_json(msg="The format option (%s) is specified, " - "but neither 'load' or 'retrieve' are " - "specified. Must specify one of " - "'load' or 'retrieve' options." - % (format)) - - # dest_dir is valid if retrieve is not None or diff is True. - if dest_dir is not None: - if retrieve is None and diff is False: - junos_module.fail_json(msg="The dest_dir option (%s) is specified," - " but neither 'retrieve' or 'diff' " - "are specified. Must specify one of " - "'retrieve' or 'diff' options." - % (dest_dir)) - - # dest is valid if retrieve is not None - if dest is not None: - if retrieve is None: - junos_module.fail_json(msg="The dest option (%s) is specified," - " but 'retrieve' is not specified. " - "Must specify the 'retrieve' option." - % (dest)) - - # diffs_file is valid if diff is True - if diffs_file is not None: - if diff is False: - junos_module.fail_json(msg="The diffs_file option (%s) is " - "specified, but 'diff' is false." - % (diffs_file)) - - # commit_empty_changes is valid if commit is True - if commit_empty_changes is True: - if commit is False: - junos_module.fail_json(msg="The commit_empty_changes option " - "is true, but 'commit' is false. " - "The commit_empty_changes option " - "may only be specified when " - "'commit' is true.") - - # comment is valid if commit is True - if comment is not None: - if commit is False: - junos_module.fail_json(msg="The comment option (%s) is " - "specified, but 'commit' is false." - % (comment)) - - # confirmed is valid if commit is True - if confirmed is not None: - if commit is False: - junos_module.fail_json(msg="The confirmed option (%s) is " - "specified, but 'commit' is false." - % (confirmed)) - # Must be greater >= 1. - if confirmed < 1: - junos_module.fail_json(msg="The confirmed option (%s) must have a " - "positive integer value." % (confirmed)) - - # check_commit_wait is valid if check is True and commit is True - if check_commit_wait is not None: - if commit is False: - junos_module.fail_json(msg="The check_commit_wait option (%s) is " - "specified, but 'commit' is false." - % (check_commit_wait)) - if check is False: - junos_module.fail_json(msg="The check_commit_wait option (%s) is " - "specified, but 'check' is false." - % (check_commit_wait)) - # Must be greater >= 1. - if check_commit_wait < 1: - junos_module.fail_json(msg="The check_commit_wait option (%s) " - "must have a positive integer value." % - (check_commit_wait)) - - # Initialize the results. Assume failure until we know it's success. - results = {'msg': 'Configuration has been: ', - 'changed': False, - 'failed': True} - - junos_module.logger.debug("Step 1 - Open a candidate configuration " - "database.") - junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning) - results['msg'] += 'opened' - - junos_module.logger.debug("Step 2 - Load configuration data into the " - "candidate configuration database.") - if rollback is not None: - junos_module.rollback_configuration(id=rollback) - # Assume configuration changed in case we don't perform a diff later. - # If diff is set, we'll check for actual differences later. - results['changed'] = True - results['msg'] += ', rolled back' - elif load is not None: - if src is not None: - junos_module.load_configuration(action=load, - src=src, - ignore_warning=ignore_warning, - format=format) - results['file'] = src - elif lines is not None: - junos_module.load_configuration(action=load, - lines=lines, - ignore_warning=ignore_warning, - format=format) - elif template is not None: - junos_module.load_configuration(action=load, - template=template, - vars=vars, - ignore_warning=ignore_warning, - format=format) - elif url is not None: - junos_module.load_configuration(action=load, - url=url, - ignore_warning=ignore_warning, - format=format) - else: - junos_module.fail_json(msg="The load option was set to: %s, but " - "no 'src', 'lines', 'template', or " - "'url' option was set." % - (load)) - # Assume configuration changed in case we don't perform a diff later. - # If diff is set, we'll check for actual differences later. - results['changed'] = True - results['msg'] += ', loaded' - - junos_module.logger.debug("Step 3 - Check the validity of the candidate " - "configuration database.") - if check is True: - junos_module.check_configuration() - results['msg'] += ', checked' - - junos_module.logger.debug("Step 4 - Determine differences between the " - "candidate and committed configuration " - "databases.") - if diff is True or junos_module._diff: - diff = junos_module.diff_configuration() - if diff is not None: - results['changed'] = True - if return_output is True or junos_module._diff: - results['diff'] = {'prepared': diff} - results['diff_lines'] = diff.splitlines() - # Save the diff output - junos_module.save_text_output('diff', 'diff', diff) - else: - results['changed'] = False - results['msg'] += ', diffed' - - junos_module.logger.debug("Step 5 - Retrieve the configuration database " - "from the Junos device.") - if retrieve is not None: - if format is None: - format = 'text' - (config, config_parsed) = junos_module.get_configuration( - database=retrieve, - format=format, - options=options, - filter=filter, - model=model, - namespace=namespace, - remove_ns=remove_ns) - if return_output is True: - if config is not None: - results['config'] = config - results['config_lines'] = config.splitlines() - if config_parsed is not None: - results['config_parsed'] = config_parsed - # Save the output - format_extension = 'config' if format == 'text' else format - junos_module.save_text_output('config', format_extension, config) - results['msg'] += ', retrieved' - - junos_module.logger.debug("Step 6 - Commit the configuration changes.") - if commit is True and not junos_module.check_mode: - # Perform the commit if: - # 1) commit_empty_changes is True - # 2) Neither rollback or load is set. i.e. confirming a previous commit - # 3) rollback or load is set, and there were actual changes. - if (commit_empty_changes is True or - (rollback is None and load is None) or - ((rollback is not None or load is not None) and - results['changed'] is True)): - if check_commit_wait is not None: - time.sleep(check_commit_wait) - junos_module.commit_configuration(ignore_warning=ignore_warning, - comment=comment, - confirmed=confirmed) - results['msg'] += ', committed' - else: - junos_module.logger.debug("Skipping commit. Nothing changed.") - - junos_module.logger.debug("Step 7 - Close the candidate configuration " - "database.") - junos_module.close_configuration() - results['msg'] += ', closed.' - - # If we made it this far, everything was successful. - results['failed'] = False - - # Return response. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_facts.py b/library/juniper_junos_facts.py deleted file mode 100644 index 3c5fc811..00000000 --- a/library/juniper_junos_facts.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_facts -version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Retrieve facts from a Junos device -description: - - Retrieve facts from a Junos device using the - U(PyEZ fact gathering system|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html). - - Also returns the committed configuration of the Junos device if the - I(config_format) option has a value other than C(none). -options: - config_format: - description: - - The format of the configuration returned. The specified format must be - supported by the target Junos device. - required: false - default: none - choices: - - none - - xml - - set - - text - - json - savedir: - description: - - A path to a directory, on the Ansible control machine, where facts - will be stored in a JSON file. - - The resulting JSON file is saved in - I(savedir)C(/)I(hostname)C(-facts.json). - - The I(savedir) directory is the value of the I(savedir) option. - - The I(hostname)C(-facts.json) filename begins with the value of the - C(hostname) fact returned from the Junos device, which might be - different than the value of the I(host) option passed to the module. - - If the value of the I(savedir) option is C(none), the default, then - facts are NOT saved to a file. - required: false - default: none - type: path -''' - -EXAMPLES = ''' ---- -- name: Gather facts from Junos devices - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: - - name: Gather Junos facts with no configuration - juniper_junos_facts: - -# Print a fact - -# Using config_format option - -# Print the config - -# Using savedir option - -# Print the saved JSON file -''' - -RETURN = ''' -ansible_facts.junos: - description: - - Facts collected from the Junos device. This dictionary contains the - keys listed in the I(contains) section of this documentation PLUS all - of the keys returned from PyEZ's fact gathering system. See - U(PyEZ facts|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) - for a complete list of these keys and their meaning. - returned: success - type: complex - contains: - config: - description: - - The device's committed configuration, in the format specified by - I(config_format), as a single multi-line string. - returned: when I(config_format) is not C(none). - type: str - has_2RE: - description: - - Indicates if the device has more than one Routing Engine installed. - Because Ansible does not allow keys to begin with a number, this fact - is returned in place of PyEZ's C(2RE) fact. - returned: success - type: bool - re_name: - description: - - The name of the current Routing Engine to which Ansible is connected. - returned: success - type: str - master_state: - description: - - The mastership state of the Routing Engine to which Ansible is - connected. C(true) if the RE is the master Routing Engine. C(false) - if the RE is not the master Routing Engine. - returned: success - type: bool -changed: - description: - - Indicates if the device's state has changed. Since this module does not - change the operational or configuration state of the device, the value is - always set to C(false). - returned: success - type: bool - sample: false -facts: - description: - - Returned for backwards compatibility. Returns the same keys and values - which are returned under I(ansible_facts.junos). - returned: success - type: dict -failed: - description: - - Indicates if the task failed. - returned: always - type: bool - sample: false -''' - -# Standard library imports -import json -import os.path - - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common -from ansible.module_utils._text import to_bytes - - -def get_facts_dict(junos_module): - """Retreive PyEZ facts and convert to a standard dict w/o custom types. - - Ansible >= 2.0 doesn't like custom objects in a modules return value. - Because PyEZ facts are a custom object rather than a true dict they must be - converted to a standard dict. Since facts are read-only, we must begin by - copying facts into a dict. Since PyEZ facts are "on-demand", the - junos_module.dev instance must be an open PyEZ Device instance ojbect - before this function is called. - - Args: - junos_module: An instance of a JuniperJunosModule. - - Returns: - A dict containing the device facts. - """ - # Retrieve all PyEZ-supported facts and copy to a standard dict. - facts = dict(junos_module.dev.facts) - # Add two useful facts that are implement as PyEZ Device attributes. - facts['re_name'] = junos_module.dev.re_name - facts['master_state'] = junos_module.dev.master - # Ansible doesn't allow keys starting with numbers. - # Replace the '2RE' key with the 'has_2RE' key. - if '2RE' in facts: - facts['has_2RE'] = facts['2RE'] - del facts['2RE'] - # The value of the 'version_info' key is a custom junos.version_info - # object. Convert this value to a dict. - if 'version_info' in facts and facts['version_info'] is not None: - facts['version_info'] = dict(facts['version_info']) - # The values of the ['junos_info'][re_name]['object'] keys are - # custom junos.version_info objects. Convert all of these to dicts. - if 'junos_info' in facts and facts['junos_info'] is not None: - for key in facts['junos_info']: - facts['junos_info'][key]['object'] = dict( - facts['junos_info'][key]['object']) - return facts - - -def save_facts(junos_module, facts): - """If the savedir option was specified, save the facts into a JSON file. - - If the savedir option was specified, save the facts into a JSON file named - savedir/hostname-facts.json. The filename begins with the value of the - hostname fact returned from the Junos device, which might be different than - the value of the host option passed to the module. - - Args: - junos_module: An instance of a JuniperJunosModule. - facts: The facts dict returned by get_facts_dict(). - - Raises: - IOError: Calls junos_module.fail_json if unable to open the facts - file for writing. - """ - if junos_module.params.get('savedir') is not None: - save_dir = junos_module.params.get('savedir') - file_name = '%s-facts.json' % (facts['hostname']) - file_path = os.path.normpath(os.path.join(save_dir, file_name)) - junos_module.logger.debug("Saving facts to: %s.", file_path) - try: - # TODO: Verify does thsi work with Python3 - with open(file_path, 'w') as fact_file: - json.dump(facts, fact_file) - junos_module.logger.debug("Facts saved to: %s.", file_path) - except IOError: - junos_module.fail_json(msg="Unable to save facts. Failed to open " - "the %s file." % (file_path)) - - -def save_inventory(junos_module, inventory): - """If the savedir option was specified, save the XML inventory. - - If the savedir option was specified, save the inventory XML output into - an XML file named savedir/hostname-inventory.xml. The filename begins with - the value of the hostname fact returned from the Junos device, which might - be different than the value of the host option passed to the module. - - Args: - junos_module: An instance of a JuniperJunosModule. - inventory: The XML string of inventory to save. - - Raises: - IOError: Calls junos_module.fail_json if unable to open the inventory - file for writing. - """ - if junos_module.params.get('savedir') is not None: - save_dir = junos_module.params.get('savedir') - file_name = '%s-inventory.xml' % (junos_module.dev.facts['hostname']) - file_path = os.path.normpath(os.path.join(save_dir, file_name)) - junos_module.logger.debug("Saving inventory to: %s.", file_path) - try: - with open(file_path, 'wb') as fact_file: - fact_file.write(to_bytes(inventory, encoding='utf-8')) - junos_module.logger.debug("Inventory saved to: %s.", file_path) - except IOError: - junos_module.fail_json(msg="Unable to save inventory. Failed to " - "open the %s file." % (file_path)) - - -def main(): - config_format_choices = [None] - config_format_choices += juniper_junos_common.CONFIG_FORMAT_CHOICES - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - config_format=dict(choices=config_format_choices, - required=False, - default=None), - savedir=dict(type='path', required=False, default=None), - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - - junos_module.logger.debug("Gathering facts.") - # Get the facts dictionary from the device. - facts = get_facts_dict(junos_module) - junos_module.logger.debug("Facts gathered.") - - if junos_module.params.get('savedir') is not None: - # Save the facts. - save_facts(junos_module, facts) - - # Get and save the inventory - try: - junos_module.logger.debug("Gathering inventory.") - inventory = junos_module.dev.rpc.get_chassis_inventory() - junos_module.logger.debug("Inventory gathered.") - save_inventory(junos_module, - junos_module.etree.tostring(inventory, - pretty_print=True)) - except junos_module.pyez_exception.RpcError as ex: - junos_module.fail_json(msg='Unable to retrieve hardware ' - 'inventory: %s' % (str(ex))) - - config_format = junos_module.params.get('config_format') - if config_format is not None: - (config, config_parsed) = junos_module.get_configuration( - format=config_format) - if config is not None: - facts.update({'config': config}) - # Need to wait until the ordering issues are figured out before - # using config_parsed. - # if config_parsed is not None: - # facts.update({'config_parsed': config_parsed}) - - # Return response. - junos_module.exit_json( - changed=False, - failed=False, - ansible_facts={'junos': facts}, - facts=facts) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_jsnapy.py b/library/juniper_junos_jsnapy.py deleted file mode 100644 index f5669c30..00000000 --- a/library/juniper_junos_jsnapy.py +++ /dev/null @@ -1,358 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Roslan Zaki -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_jsnapy -version_added: "2.0.0" # of Juniper.junos role -author: - - Juniper Networks - - Roslan Zaki - - Damien Garros - - Stacy Smith (@stacywsmith)" -short_description: Execute JSNAPy tests on a Junos device -description: - - Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. - JSNAPy is documented on U(Github|https://github.com/Juniper/jsnapy) and - this - U(Day One Book|https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) - - This module only reports C(failed) if the module encounters an error and - fails to execute the JSNAPy tests. If does NOT report C(failed) if one or - more of the JSNAPy tests fail. To check the test results, register the - module's response and use the assert module to verify the expected result - in the response. (See :ref:`juniper_junos_jsnapy-examples-label`.) - - A callback plugin which formats and prints JSNAPy test results for human - consumption is also available. This callback plugin is enabled by adding - C(callback_whitelist = jsnapy) to the Ansible configuration file. -options: - action: - description: - - The JSNAPy action to perform. - required: true - default: none - type: str - choices: - - check - - snapcheck - - snap_pre - - snap_post - config_file: - description: - - The filename of a JSNAPy configuration file (in YAML format). The - I(test_files) option and the I(config_file) option are mutually - exclusive. Either the I(test_files) option or the I(config_file) - option is required. - required: false - type: path - default: none - dir: - description: - - The path to the directory containing the JSNAPy test file(s) specified - by the I(test_files) option or the JSNAPy configuration file specified - by the I(config_file) option. - required: false - type: path - default: /etc/jsnapy/testfiles - aliases: - - directory - test_files: - description: - - The filename of file(s) in the I(dir) directory. Each file contains - JSNAPy test case definitions. The I(test_files) option and the - I(config_file) option are mutually exclusive. Either the I(test_files) - option or the I(config_file) option is required. - required: false - type: list of path - default: none -''' - - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_jsnapy - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: JUNOS Post Checklist - juniper_junos_jsnapy: - action: "snap_post" - config_file: "first_test.yml" - logfile: "migration_post.log" - register: test1 - - name: Verify all JSNAPy tests passed - assert: - that: - - "test1.passPercentage == 100" - - name: Print the full test response - debug: - var: test1 - - - name: Test based on a test_file directly - juniper_junos_jsnapy: - action: "snapcheck" - test_files: "tests/test_junos_interface.yaml" - register: test2 - - name: Verify all JSNAPy tests passed - assert: - that: - - "test2.passPercentage == 100" - - name: Print the full test response - debug: - var: test2 - - - name: "Collect Pre Snapshot" - juniper_junos_jsnapy: - action: "snap_pre" - test_files: "tests/test_loopback.yml" - - - name: "Collect Post Snapshot" - juniper_junos_jsnapy: - action: "snap_post" - test_files: "tests/test_loopback.yml" - - - name: "Check after Pre and Post Snapshots" - juniper_junos_jsnapy: - action: "check" - test_files: "tests/test_loopback.yml" - register: test3 - - name: Verify all JSNAPy tests passed - assert: - that: - - "test3.|succeeded" - - "test3.passPercentage == 100" - - name: Print the full test response - debug: - var: test3 -''' - -RETURN = ''' -action: - description: - - The JSNAPy action performed as specified by the I(action) option. - returned: success - type: str -changed: - description: - - Indicates if the device's state has changed. Since this module doesn't - change the operational or configuration state of the device, the value - is always set to C(false). - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -# final_result: -msg: - description: - - A human-readable message indicating the result of the JSNAPy tests. - returned: always - type: str -# total_passed: -# total_failed: -''' - -# Standard Library imports -import os.path - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - action=dict(required=True, - choices=JSNAPY_ACTION_CHOICES, - type='str', - default=None), - test_files=dict(required=False, - type='list', - default=None), - config_file=dict(required=False, - type='path', - default=None), - dir=dict(required=False, - type='path', - aliases=['directory'], - default='/etc/jsnapy/testfiles')), - # Mutually exclusive options. - mutually_exclusive=[['test_files', 'config_file']], - # One of test_files or config_file is required. - required_one_of=[['test_files', 'config_file']], - supports_check_mode=True, - min_jsnapy_version=juniper_junos_common.MIN_JSNAPY_VERSION, - ) - - # Straight from params - action = junos_module.params.get('action') - test_files = junos_module.params.get('test_files') - config_file = junos_module.params.get('config_file') - dir = junos_module.params.get('dir') - - # Initialize the results. Assume failure until we know otherwise. - results = {'msg': '', - 'action': action, - 'changed': False, - 'failed': True} - - if config_file is not None: - junos_module.logger.debug('Checking config file: %s.', config_file) - config_file_path = os.path.abspath(config_file) - config_dir_file_path = os.path.abspath(os.path.join(dir, config_file)) - if os.path.isfile(config_file_path): - data = config_file_path - elif os.path.isfile(config_dir_file_path): - data = config_dir_file_path - else: - junos_module.fail_json(msg="Unable to locate the %s config file " - "at %s or %s." % (config_file, - config_file_path, - config_dir_file_path)) - elif test_files is not None and len(test_files) > 0: - data = {'tests': []} - for test_file in test_files: - junos_module.logger.debug('Checking test file: %s.', test_file) - test_file_path = os.path.abspath(test_file) - test_dir_file_path = os.path.abspath(os.path.join(dir, test_file)) - if os.path.isfile(test_file_path): - data['tests'].append(test_file_path) - elif os.path.isfile(test_dir_file_path): - data['tests'].append(test_dir_file_path) - else: - junos_module.fail_json(msg="Unable to locate the %s test file " - "at %s or %s." % - (test_file, - test_file_path, - test_dir_file_path)) - else: - junos_module.fail_json(msg="No config_file or test_files specified.") - - try: - junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') - jsa = junos_module.jsnapy.SnapAdmin() - junos_module.logger.debug('Executing %s action.', action) - if action == 'check': - responses = jsa.check(data=data, - dev=junos_module.dev, - pre_file='PRE', - post_file='POST') - elif action == 'snapcheck': - responses = jsa.snapcheck(data=data, - dev=junos_module.dev) - elif action == 'snap_pre': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='PRE') - elif action == 'snap_post': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='POST') - else: - junos_module.fail_json(msg="Unexpected action: %s." % (action)) - junos_module.logger.debug('The %s action executed successfully.', - action) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - junos_module.fail_json(msg="Error communicating with the device: %s" % - (str(ex))) - except Exception as ex: - junos_module.fail_json(msg="Uncaught exception - please report: %s" % - (str(ex))) - - if isinstance(responses, list) and len(responses) == 1: - if action in ('snapcheck', 'check'): - for response in responses: - results['device'] = response.device - results['router'] = response.device - results['final_result'] = response.result - results['total_passed'] = response.no_passed - results['total_failed'] = response.no_failed - results['test_results'] = response.test_results - total_tests = int(response.no_passed) + int(response.no_failed) - results['total_tests'] = total_tests - pass_percentage = 0 - if total_tests > 0: - pass_percentage = ((int(response.no_passed) * 100) // - total_tests) - results['passPercentage'] = pass_percentage - results['pass_percentage'] = pass_percentage - if results['final_result'] == 'Failed': - results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) - else: - results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) - elif action in ('snap_pre', 'snap_post'): - results['msg'] = "The %s action successfully executed." % (action) - else: - junos_module.fail_json(msg="Unexpected JSNAPy responses. Type: %s." - "Responses: %s" % - (type(responses), str(responses))) - - # If we made it this far, it's success. - results['failed'] = False - - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_ping.py b/library/juniper_junos_ping.py deleted file mode 100644 index ce6c81ec..00000000 --- a/library/juniper_junos_ping.py +++ /dev/null @@ -1,504 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Damien Garros -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_ping -version_added: "2.0.0" # of Juniper.junos role -author: Juniper Networks - Stacy Smith (@stacywsmith) -short_description: Execute ping from a Junos device -description: - - Execute the ping command from a Junos device to a specified destination in - order to test network reachability from the Junos device . -options: - acceptable_percent_loss: - description: - - Maximum percentage of packets that may be lost and still consider the - task not to have failed. - required: false - default: 0 - type: int - aliases: - - acceptable_packet_loss - count: - description: - - Number of packets to send. - required: false - default: 5 - type: int - dest: - description: - - The IP address, or hostname if DNS is configured on the Junos device, - used as the destination of the ping. - required: true - default: none - type: str - aliases: - - dest_ip - - dest_host - - destination - - destination_ip - - destination_host - do_not_fragment: - description: - - Set Do Not Fragment bit on ping packets. - required: false - default: false - type: bool - interface: - description: - - The source interface from which the the ping is sent. If not - specified, the default Junos algorithm for determining the source - interface is used. - required: false - default: none - type: str - rapid: - description: - - Send ping requests rapidly - required: false - default: true - type: bool - routing_instance: - description: - - Name of the source routing instance from which the ping is - originated. If not specified, the default routing instance is used. - required: false - default: none - type: str - size: - description: - - The size of the ICMP payload of the ping. - - Total size of the IP packet is I(size) + the 20 byte IP header + - the 8 byte ICMP header. Therefore, I(size) of C(1472) generates an IP - packet of size 1500. - required: false - default: none (default size for device) - type: int - source: - description: - - The IP address, or hostname if DNS is configured on the Junos device, - used as the source address of the ping. If not specified, the Junos - default algorithm for determining the source address is used. - required: false - default: none - type: str - aliases: - - source_ip - - source_host - - src - - src_ip - - src_host - ttl: - description: - - Maximum number of IP routers (hops) allowed between source and - destination. - required: false - default: none (default ttl for device) - type: int -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_ping - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. - juniper_junos_ping: - dest: "192.68.1.1" - - - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - acceptable_percent_loss: 50 - register: response - - name: Print all keys in the response. - debug: - var: response - - - name: Ping 192.68.1.1. Send 20 packets. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - count: 20 - register: response - - name: Print packet sent from the response. - debug: - var: response.packets_sent - - - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - count: 10 - rapid: false - register: response - - name: Print the average round-trip-time from the response. - debug: - var: response.rtt_average - - - name: Ping www.juniper.net with ttl 15. Register response. - juniper_junos_ping: - dest: "www.juniper.net" - ttl: 15 - register: response - - name: Print the packet_loss percentage from the response. - debug: - var: response.packet_loss - - - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - size: 1472 - register: response - - name: Print the packets_received from the response. - debug: - var: response.packets_received - - - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - do_not_fragment: true - register: response - - name: Print the maximum round-trip-time from the response. - debug: - var: response.rtt_maximum - - - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - source: "192.68.1.2" - register: response - - name: Print the source from the response. - debug: - var: response.source - - - name: Ping 192.168.1.1 from the red routing-instance. - juniper_junos_ping: - dest: "192.168.1.1" - routing_instance: "red" - - - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface - juniper_junos_ping: - dest: "224.0.0.1" - interface: "ge-0/0/0.0" -''' - -RETURN = ''' -acceptable_percent_loss: - description: - - The acceptable packet loss (as a percentage) for this task as specified - by the I(acceptable_percent_loss) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -changed: - description: - - Indicates if the device's state has changed. Since this module - doesn't change the operational or configuration state of the - device, the value is always set to C(false). - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: bool -count: - description: - - The number of pings sent, as specified by the I(count) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -do_not_fragment: - description: - - Whether or not the do not fragment bit was set on the pings sent, as - specified by the I(do_not_fragment) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -host: - description: - - The destination IP/host of the pings sent as specified by the I(dest) - option. - - Keys I(dest) and I(dest_ip) are also returned for backwards - compatibility. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -interface: - description: - - The source interface of the pings sent as specified by the - I(interface) option. - returned: when ping successfully executed and the I(interface) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -packet_loss: - description: - - The percentage of packets lost. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -packets_sent: - description: - - The number of packets sent. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -packets_received: - description: - - The number of packets received. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -rapid: - description: - - Whether or not the pings were sent rapidly, as specified by the - I(rapid) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: bool -routing_instance: - description: - - The routing-instance from which the pings were sent as specified by - the I(routing_instance) option. - returned: when ping successfully executed and the I(routing_instance) - option was specified, even if the I(acceptable_percent_loss) was - exceeded. - type: str -rtt_average: - description: - - The average round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -rtt_maximum: - description: - - The maximum round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -rtt_minimum: - description: - - The minimum round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -rtt_stddev: - description: - - The standard deviation of round-trip-time, in microseconds, of all ping - responses received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -size: - description: - - The size in bytes of the ICMP payload on the pings sent as specified - by the I(size) option. - - Total size of the IP packet is I(size) + the 20 byte IP header + the 8 - byte ICMP header. Therefore, I(size) of 1472 generates an IP packet of - size 1500. - returned: when ping successfully executed and the I(size) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -source: - description: - - The source IP/host of the pings sent as specified by the I(source) - option. - - Key I(source_ip) is also returned for backwards compatibility. - returned: when ping successfully executed and the I(source) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -timeout: - description: - - The number of seconds to wait for a response from the ping RPC. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -ttl: - description: - - The time-to-live set on the pings sent as specified by the - I(ttl) option. - returned: when ping successfully executed and the I(ttl) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -warnings: - description: - - A list of warning strings, if any, produced from the ping. - returned: when warnings are present - type: list -''' - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - # The argument spec for the module. - argument_spec = dict( - dest=dict(type='str', - required=True, - aliases=['dest_ip', 'dest_host', 'destination', - 'destination_ip', 'destination_host'], - default=None), - acceptable_percent_loss=dict(type='int', - required=False, - aliases=['acceptable_packet_loss'], - default=0), - ) - - # The portion of the argument spec that's specifically a parameter - # to the ping RPC. - ping_argument_spec = dict( - count=dict(type='int', - required=False, - default=5), - rapid=dict(type='bool', - required=False, - default=True), - ttl=dict(type='int', - required=False, - default=None), - size=dict(type='int', - required=False, - default=None), - do_not_fragment=dict(type='bool', - required=False, - default=False), - source=dict(type='str', - required=False, - aliases=['source_ip', 'source_host', 'src', - 'src_ip', 'src_host'], - default=None), - interface=dict(type='str', - required=False, - default=None), - routing_instance=dict(type='str', - required=False, - default=None), - ) - - # Add the ping RPC parameter argument spec fo the full argument_spec. - argument_spec.update(ping_argument_spec) - - argument_spec_keys = list(argument_spec.keys()) - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=argument_spec, - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. - supports_check_mode=True - ) - - # We're going to be using params a lot - params = junos_module.params - - # acceptable packet loss is a percentage. Check to make sure it's between - # 0 and 100 inclusive - if (params['acceptable_percent_loss'] > 100 or - params['acceptable_percent_loss'] < 0): - junos_module.fail_json(msg='The value of the acceptable_percent_loss' - 'option (%d) is a percentage and must have ' - 'a value between 0 and 100.' % - (params['acceptable_percent_loss'])) - - # All of the params keys which are also keys in ping_argument_spec are the - # ping_params. Omit None and False values because they don't need to be - # passed to the RPC. - ping_params = {'host': params.get('dest')} - for key in ping_argument_spec: - value = params.get(key) - # Convert int (but not bool) to str - if not isinstance(value, bool) and isinstance(value, int): - params[key] = str(params[key]) - value = params.get(key) - # None and False values are the default for the RPC and shouldn't be - # passed to the device. - if value is not None and value is not False: - ping_params.update({key: value}) - - # Set initial results values. Assume failure until we know it's success. - results = {'msg': '', 'changed': False, 'failed': True} - # Results should include all the ping params in argument_spec_keys. - for key in argument_spec_keys: - results[key] = params.get(key) - # Overwrite to be a string in the results - results['acceptable_percent_loss'] = str( - params.get('acceptable_percent_loss')) - # Add timeout to the response even though it's a connect parameter. - results['timeout'] = str(params.get('timeout')) - # Add aliases for backwards compatibility - results.update({'host': params.get('dest'), - 'dest_ip': params.get('dest'), - 'source_ip': params.get('source')}) - - # Execute the ping. - results = junos_module.ping( - ping_params, - acceptable_percent_loss=params['acceptable_percent_loss'], - results=results) - - # Return results. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_pmtud.py b/library/juniper_junos_pmtud.py deleted file mode 100644 index 7947b7bf..00000000 --- a/library/juniper_junos_pmtud.py +++ /dev/null @@ -1,410 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2017, Martin Komon -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_pmtud -version_added: "2.0.0" # of Juniper.junos role -author: - - Martin Komon (@mkomon) - - Juniper Networks - Stacy Smith (@stacywsmith) -short_description: Perform path MTU discovery from a Junos device to a - destination -description: - - Determine the maximum IP MTU supported along a path from a Junos device to - a user-specified destination by performing path MTU discovery (PMTUD) using - the ping command. The reported MTU will be between min_test_size and - I(max_size) where I(min_test_size) = (I(max_size) - I(max_range) + 1). - If the actual path MTU is greater than I(max_size), then I(max_size) will - be reported. If the actual path MTU is less than I(min_test_size), then a - failure will be reported. -options: - dest: - description: - - The IPv4 address, or hostname if DNS is configured on the Junos device, - used as the destination of the PMTUD. - required: true - default: none - type: str - aliases: - - dest_ip - - dest_host - - destination - - destination_ip - - destination_host - interface: - description: - - The source interface from which the the PMTUD is performed. If not - specified, the default Junos algorithm for determining the source - interface is used. - required: false - default: none - type: str - max_range: - description: - - The maximum range of MTU values, in bytes, which will be searched - when performing path MTU discovery. This value must be C(0) or - a power of 2 (2^n) between C(2) and C(65536). The minimum IPv4 MTU - value attempted when performing path MTU discovery is - I(min_test_size) = (I(max_size) - I(max_range) + 1) - required: false - default: 512 - type: int - max_size: - description: - - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU - discovery. - - The value returned for I(inet_mtu) will be no more - than this value even if the path actually supports a higher MTU. - - This value must be between 68 and 65496. - required: false - default: 1500 - type: int - routing_instance: - description: - - Name of the source routing instance from which the ping is - originated. - - If not specified, the default routing instance is used. - required: false - default: none - type: str - source: - description: - - The IPv4 address, or hostname if DNS is configured on the Junos device, - used as the source address of the PMTUD. If not specified, the Junos - default algorithm for determining the source address is used. - required: false - default: none - type: str - aliases: - - source_ip - - source_host - - src - - src_ip - - src_host -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_mtud - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: Perform PMTUD to 192.68.1.1 with default parameters. - juniper_junos_pmtud: - dest: "192.68.1.1" - - - name: Perform PMTUD to 192.68.1.1. Register response. - juniper_junos_pmtud: - dest: "192.68.1.1" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. - juniper_junos_pmtud: - dest: "192.68.1.1" - max_size: 65496 - max_range: 65536 - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. - juniper_junos_pmtud: - dest: "192.68.1.1" - interface: "ge-0/0/0.0" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. - juniper_junos_pmtud: - dest: "192.68.1.1" - source: "192.168.1.2" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. - juniper_junos_pmtud: - dest: "192.68.1.1" - routing_instance: "red" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's state has changed. Since this module - doesn't change the operational or configuration state of the - device, the value is always set to C(false). - returned: when PMTUD successfully executed. - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -host: - description: - - The destination IP/host of the PMTUD as specified by the I(dest) - option. - - Keys I(dest) and I(dest_ip) are also returned for backwards - compatibility. - returned: when PMTUD successfully executed. - type: str -inet_mtu: - description: - - The IPv4 path MTU size in bytes to the I(dest). This is the lesser of - I(max_size) and the actual path MTU to I(dest). If the actual path - MTU is less than I(min_test_size), then a failure is reported. Where - I(min_test_size) = (I(max_size) - I(max_range) + 1) - returned: when PMTUD successfully executed. - type: str -interface: - description: - - The source interface of the PMTUD as specified by the I(interface) - option. - returned: when the I(interface) option was specified. - type: str -routing_instance: - description: - - The routing-instance from which the PMTUD was performed as specified by - the I(routing_instance) option. - returned: when the I(routing_instance) option was specified. - type: str -source: - description: - - The source IP/host of the PMTUD as specified by the I(source) - option. - - Key I(source_ip) is also returned for backwards compatibility. - returned: when the I(source) option was specified. - type: str -warnings: - description: - - A list of warning strings, if any, produced from the ping. - returned: when warnings are present - type: list -''' - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - # Constants for MTU size - INET_MIN_MTU_SIZE = 68 # As prescribed by RFC 791, Section 3.2 - - # Fragmentation and Reassembly. - INET_MAX_MTU_SIZE = 65496 # Size of inet header's total length field is - # 16 bits. Therefore max inet packet size is 2^16 - # or 65536, but Junos only supports max IP size - # of 65496 for the ping command in order to - # accomodate a (potentially) maximum sized IP - # header. - - # Constants for the size of headers - INET_HEADER_SIZE = 20 - ICMP_HEADER_SIZE = 8 - INET_AND_ICMP_HEADER_SIZE = INET_HEADER_SIZE + ICMP_HEADER_SIZE - - # Choices for max_size - MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2 ** x, range(1, 17))) - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - dest=dict(type='str', - required=True, - aliases=['dest_ip', 'dest_host', 'destination', - 'destination_ip', 'destination_host'], - default=None), - max_size=dict(type='int', - required=False, - default=1500), - max_range=dict(type='int', - required=False, - choices=MAX_SIZE_CHOICES, - default=512), - source=dict(type='str', - required=False, - aliases=['source_ip', 'source_host', 'src', - 'src_ip', 'src_host'], - default=None), - interface=dict(type='str', - required=False, - default=None), - routing_instance=dict(type='str', - required=False, - default=None), - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. - supports_check_mode=True - ) - - # We're going to be using params a lot - params = junos_module.params - - # max_size must be between INET_MIN_MTU_SIZE and INET_MAX_MTU_SIZE - if (params['max_size'] < INET_MIN_MTU_SIZE or - params['max_size'] > INET_MAX_MTU_SIZE): - junos_module.fail_json(msg='The value of the max_size option(%d) ' - 'must be between %d and %d.' % - (params['max_size'], INET_MIN_MTU_SIZE, - INET_MAX_MTU_SIZE)) - - # Initialize ping parameters. - ping_params = {'host': params.get('dest'), - 'count': '3', - 'rapid': True, - 'inet': True, - 'do_not_fragment': True} - - # Add optional ping parameters - o_ping_params = {} - if params['source'] is not None: - o_ping_params['source'] = params['source'] - if params['interface'] is not None: - o_ping_params['interface'] = params['interface'] - if params['routing_instance'] is not None: - o_ping_params['routing_instance'] = params['routing_instance'] - ping_params.update(o_ping_params) - - # Set initial results values. Assume failure until we know it's success. - results = {'changed': False, - 'failed': True, - 'inet_mtu': 0, - 'host': params.get('dest')} - # Results should include all the o_ping_params. - for key in o_ping_params: - results[key] = ping_params.get(key) - # Add aliases for backwards compatibility - results.update({'dest': ping_params.get('host'), - 'dest_ip': ping_params.get('host'), - 'source_ip': ping_params.get('source')}) - - # Execute a minimally-sized ping just to verify basic connectivity. - junos_module.logger.debug("Verifying basic connectivity.") - ping_params['size'] = str(INET_MIN_MTU_SIZE - - INET_AND_ICMP_HEADER_SIZE) - results_for_minimal = dict(results) - results_for_minimal = junos_module.ping(ping_params, - acceptable_percent_loss=100, - results=results_for_minimal) - if int(results_for_minimal.get('packet_loss', 100)) == 100: - results['msg'] = "Basic connectivity to %s failed." % (results['host']) - junos_module.exit_json(**results) - - # Initialize test_size and step - test_size = params['max_size'] - step = params['max_range'] - min_test_size = test_size - (params['max_range'] - 1) - if min_test_size < INET_MIN_MTU_SIZE: - min_test_size = INET_MIN_MTU_SIZE - - while True: - if test_size < INET_MIN_MTU_SIZE: - test_size = INET_MIN_MTU_SIZE - if test_size > params['max_size']: - test_size = params['max_size'] - junos_module.logger.debug("Probing with size: %d", test_size) - step = step // 2 if step >= 2 else 0 - ping_params['size'] = str(test_size - INET_AND_ICMP_HEADER_SIZE) - current_results = dict(results) - current_results = junos_module.ping(ping_params, - acceptable_percent_loss=100, - results=current_results) - loss = int(current_results.get('packet_loss', 100)) - if loss < 100 and test_size == params['max_size']: - # ping success with max test_size, save and break - results['failed'] = False - results['inet_mtu'] = test_size - break - elif loss < 100: - # ping success, increase test_size - results['failed'] = False - results['inet_mtu'] = test_size - test_size += step - else: - # ping fail, lower size - test_size -= step - if step < 1: - break - - if results.get('inet_mtu', 0) == 0: - junos_module.fail_json(msg='The MTU of the path to %s is less than ' - 'the minimum tested size(%d). Try ' - 'decreasing max_size(%d) or increasing ' - 'max_range(%d).' % (results['host'], - min_test_size, - params['max_size'], - params['max_range']), - **results) - - # Return results. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_rpc.py b/library/juniper_junos_rpc.py deleted file mode 100644 index 8f848f35..00000000 --- a/library/juniper_junos_rpc.py +++ /dev/null @@ -1,624 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Nitin Kumar -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from six import iteritems - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_rpc -version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Execute one or more NETCONF RPCs on a Junos device -description: - - Execute one or more NETCONF RPCs on a Junos device. - - Use the C(| display xml rpc) modifier to determine the equivalent RPC - name for a Junos CLI command. For example, - C(show version | display xml rpc) reveals the equivalent RPC name is - C(get-software-information). -options: - attrs: - description: - - The attributes and values to the RPCs specified by the - I(rpcs) option. The value of this option can either be a single - dictionary of keywords and values, or a list of dictionaries - containing keywords and values. - - There is a one-to-one correspondence between the elements in the - I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the - two lists must always contain the same number of elements. - required: false - default: none - type: dict or list of dict - aliases: - - attr - dest: - description: - - The path to a file, on the Ansible control machine, where the output of - the RPC will be saved. - - The file must be writeable. If the file already exists, it is - overwritten. - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the I(dest) value. It is the user's - responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(dest) and I(dest_dir) options are mutually exclusive. - required: false - default: None - type: path - aliases: - - destination - dest_dir: - description: - - The path to a directory, on the Ansible control machine, where - the output of the RPC will be saved. The output will be logged - to a file named C({{ inventory_hostname }}_)I(rpc).I(format) - in the I(dest_dir) directory. - - The destination file must be writeable. If the file already exists, - it is overwritten. It is the users responsibility to ensure a unique - I(dest_dir) value is provided for each execution of this module - within a playbook. - - The I(dest_dir) and I(dest) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - required: false - default: None - type: path - aliases: - - destination_dir - - destdir - filter: - description: - - This argument only applies if the I(rpcs) option contains a single - RPC with the value C(get-config). When used, this value specifies an - XML filter used to restrict the portions of the configuration which are - retrieved. See the PyEZ - U(get_config method|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) - for details on the value of this option. - required: false - default: none - type: str - aliases: - - filter_xml - formats: - description: - - The format of the reply for the RPCs specified by the - I(rpcs) option. - - The specified format(s) must be supported by the - target Junos device. - - The value of this option can either be a single - format, or a list of formats. If a single format is specified, it - applies to all RPCs specified by the I(rpcs) option. If a - list of formats are specified, there must be one value in the list for - each RPC specified by the I(rpcs) option. - required: false - default: xml - type: str or list of str - choices: - - text - - xml - - json - aliases: - - format - - display - - output - kwargs: - description: - - The keyword arguments and values to the RPCs specified by the - I(rpcs) option. The value of this option can either be a single - dictionary of keywords and values, or a list of dictionaries - containing keywords and values. - - There must be a one-to-one correspondence between the elements in the - I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the - two lists must always contain the same number of elements. For RPC - arguments which do not require a value, specify the value of True as - shown in the :ref:`juniper_junos_rpc-examples-label`. - required: false - default: none - type: dict or list of dict - aliases: - - kwarg - - args - - arg - return_output: - description: - - Indicates if the output of the RPC should be returned in the - module's response. You might want to set this option to C(false), - and set the I(dest_dir) option, if the RPC output is very large - and you only need to save the output rather than using it's content in - subsequent tasks/plays of your playbook. - required: false - default: true - type: bool - rpcs: - description: - - A list of one or more NETCONF RPCs to execute on the Junos device. - required: true - default: none - type: list - aliases: - - rpc -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_rpc - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: Execute single get-software-information RPC. - juniper_junos_rpc: - rpcs: "get-software-information" - register: response - - name: Print the RPC's output as a single multi-line string. - debug: - var: response.stdout - -###### OLD EXAMPLES ########## -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - dest=get_interface_information.conf - register=junos - -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - kwargs="interface_name=em0" - format=xml/text/json - dest=get_interface_information.conf - register=junos - -# Example to fetch device configuration -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - dest=get_config.conf - -# Fetch configuration over console server connection using PyEZ >= 2.0 -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - port=7005 - mode='telnet' - rpc=get-config - dest=get_config.conf - -# Example to fetch device configuration -- name: Get Device Configuration for interface - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - filter_xml="" - dest=get_config.conf - register: junos - -# Example to fetch configuration in json for >=14.2 -# and use it with rpc_reply -- name: Get Device Configuration - hosts: all - roles: - - Juniper.junos - connection: local - gather_facts: no - tasks: - - name: Get interface information - junos_rpc: - host: "{{ inventory_hostname }}" - rpc: get-interface-information - kwargs: - interface_name: em0 - media: True - format: json - dest: get_interface_information.conf - register: junos - - - name: Print configuration - debug: msg="{{ junos.rpc_reply }}" -###### OLD EXAMPLES ########## -''' - -RETURN = ''' -attrs: - description: - - The RPC attributes and values from the list of dictionaries in the - I(attrs) option. This will be none if no attributes are applied to the - RPC. - returned: always - type: dict -changed: - description: - - Indicates if the device's state has changed. Since this module doesn't - change the operational or configuration state of the device, the value - is always set to C(false). - - You could use this module to execute an RPC which - changes the operational state of the the device. For example, - C(clear-ospf-neighbor-information). Beware, this module is unable to - detect this situation, and will still return a I(changed) value of - C(false) in this case. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. See the I(results) key for additional - details. - returned: always - type: bool -format: - description: - - The format of the RPC response from the list of formats in the I(formats) - option. - returned: always - type: str - choices: - - text - - xml - - json -kwargs: - description: - - The keyword arguments from the list of dictionaries in the I(kwargs) - option. This will be C(none) if no kwargs are applied to the RPC. - returned: always - type: dict -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -parsed_output: - description: - - The RPC reply from the Junos device parsed into a JSON datastructure. - For XML replies, the response is parsed into JSON using the - U(jxmlease|https://github.com/Juniper/jxmlease) - library. For JSON the response is parsed using the Python - U(json|https://docs.python.org/2/library/json.html) library. - - When Ansible converts the jxmlease or native Python data structure - into JSON, it does not guarantee that the order of dictionary/object keys - are maintained. - returned: when RPC executed successfully, I(return_output) is C(true), - and the RPC format is C(xml) or C(json). - type: dict -results: - description: - - The other keys are returned when a single RPC is specified for the - I(rpcs) option. When the value of the I(rpcs) option is a list - of RPCs, this key is returned instead. The value of this key is a - list of dictionaries. Each element in the list corresponds to the - RPCs in the I(rpcs) option. The keys for each element in the list - include all of the other keys listed. The I(failed) key indicates if the - individual RPC failed. In this case, there is also a top-level - I(failed) key. The top-level I(failed) key will have a value of C(false) - if ANY of the RPCs ran successfully. In this case, check the value - of the I(failed) key for each element in the I(results) list for the - results of individual RPCs. - returned: when the I(rpcs) option is a list value. - type: list of dict -rpc: - description: - - The RPC which was executed from the list of RPCs in the I(rpcs) option. - returned: always - type: str -stdout: - description: - - The RPC reply from the Junos device as a single multi-line string. - returned: when RPC executed successfully and I(return_output) is C(true). - type: str -stdout_lines: - description: - - The RPC reply from the Junos device as a list of single-line strings. - returned: when RPC executed successfully and I(return_output) is C(true). - type: list of str -''' - -import os.path - - -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - rpcs=dict(required=True, - type='list', - aliases=['rpc'], - default=None), - formats=dict(required=False, - type='list', - aliases=['format', 'display', 'output'], - default=None), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='str', - default=None), - attrs=dict(required=False, - type='str', - aliases=['attr'], - default=None), - filter=dict(required=False, - type='str', - aliases=['filter_xml'], - default=None), - dest=dict(required=False, - type='path', - aliases=['destination'], - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir'], - default=None), - return_output=dict(required=False, - type='bool', - default=True) - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. Well, that's not completely true. It does depend on the - # RPC executed. See the I(changed) key in the RETURN documentation - # for more details. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - - # Check over rpcs - rpcs = junos_module.params.get('rpcs') - # Ansible allows users to specify a rpcs argument with no value. - if rpcs is None: - junos_module.fail_json(msg="The rpcs option must have a value.") - - # Check over formats - formats = junos_module.params.get('formats') - if formats is None: - # Default to xml format - formats = ['xml'] - valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES - # Check format values - for format in formats: - # Is it a valid format? - if format not in valid_formats: - junos_module.fail_json(msg="The value %s in formats is invalid. " - "Must be one of: %s" % - (format, ', '.join(map(str, - valid_formats)))) - # Correct number of format values? - if len(formats) != 1 and len(formats) != len(rpcs): - junos_module.fail_json(msg="The formats option must have a single " - "value, or one value per rpc. There " - "are %d rpcs and %d formats." % - (len(rpcs), len(formats))) - # Same format for all rpcs - elif len(formats) == 1 and len(rpcs) > 1: - formats = formats * len(rpcs) - - # Check over kwargs - kwstring = junos_module.params.get('kwargs') - kwargs = junos_module.parse_arg_to_list_of_dicts('kwargs', - kwstring, - allow_bool_values=True) - if kwargs is not None: - if len(kwargs) != len(rpcs): - junos_module.fail_json(msg="The kwargs option must have one value " - "per rpc. There are %d rpcs and %d " - "kwargs." % - (len(rpcs), len(kwargs))) - else: - kwargs = [None] * len(rpcs) - - # Check over attrs - attrstring = junos_module.params.get('attrs') - attrs = junos_module.parse_arg_to_list_of_dicts('attrs', - attrstring) - if attrs is not None: - if len(attrs) != len(rpcs): - junos_module.fail_json(msg="The attrs option must have one value" - "per rpc. There are %d rpcs and %d " - "attrs." % - (len(rpcs), len(attrs))) - else: - attrs = [None] * len(rpcs) - - # Check filter - if junos_module.params.get('filter') is not None: - if (len(rpcs) != 1 or (rpcs[0] != 'get-config' and - rpcs[0] != 'get_config')): - junos_module.fail_json(msg="The filter option is only valid " - "when the rpcs option value is a " - "single 'get-config' RPC.") - - results = list() - for (rpc_string, format, kwarg, attr) in zip(rpcs, formats, kwargs, attrs): - # Replace underscores with dashes in RPC name. - rpc_string = rpc_string.replace('_', '-') - # Set initial result values. Assume failure until we know it's success. - result = {'msg': '', - 'rpc': rpc_string, - 'format': format, - 'kwargs': kwarg, - 'attrs': attr, - 'changed': False, - 'failed': True} - - # Execute the RPC - try: - if rpc_string == 'get-config': - filter = junos_module.params.get('filter') - if attr is None: - attr = {} - if kwarg is None: - kwarg = {} - if format is not None: - attr['format'] = format - junos_module.logger.debug('Executing "get-config" RPC. ' - 'filter_xml=%s, options=%s, ' - 'kwargs=%s', - filter, str(attr), str(kwarg)) - resp = junos_module.dev.rpc.get_config(filter_xml=filter, - options=attr, **kwarg) - result['msg'] = 'The "get-config" RPC executed successfully.' - junos_module.logger.debug('The "get-config" RPC executed ' - 'successfully.') - else: - rpc = junos_module.etree.Element(rpc_string, format=format) - if kwarg is not None: - # Add kwarg - for (key, value) in iteritems(kwarg): - # Replace underscores with dashes in key name. - key = key.replace('_', '-') - sub_element = junos_module.etree.SubElement(rpc, key) - if not isinstance(value, bool): - sub_element.text = value - if attr is not None: - # Add attr - for (key, value) in iteritems(attr): - # Replace underscores with dashes in key name. - key = key.replace('_', '-') - rpc.set(key, value) - junos_module.logger.debug('Executing RPC "%s".', - junos_module.etree.tostring( - rpc, - pretty_print=True)) - resp = junos_module.dev.rpc(rpc, - normalize=bool(format == 'xml')) - result['msg'] = 'The RPC executed successfully.' - junos_module.logger.debug('RPC "%s" executed successfully.', - junos_module.etree.tostring( - rpc, - pretty_print=True)) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Unable to execute RPC "%s". Error: %s', - junos_module.etree.tostring( - rpc, - pretty_print=True), str(ex)) - result['msg'] = 'Unable to execute the RPC: %s. Error: %s' % \ - (junos_module.etree.tostring(rpc, - pretty_print=True), - str(ex)) - results.append(result) - continue - - text_output = None - parsed_output = None - if resp is True: - text_output = '' - elif (resp, junos_module.etree._Element): - # Handle the output based on format - if format == 'text': - text_output = resp.text - junos_module.logger.debug('Text output set.') - elif format == 'xml': - text_output = junos_module.etree.tostring(resp, - pretty_print=True) - parsed_output = junos_module.jxmlease.parse_etree(resp) - junos_module.logger.debug('XML output set.') - elif format == 'json': - text_output = str(resp) - parsed_output = resp - junos_module.logger.debug('JSON output set.') - else: - result['msg'] = 'Unexpected format %s.' % (format) - results.append(result) - junos_module.logger.debug('Unexpected format %s.', format) - continue - else: - result['msg'] = 'Unexpected response type %s.' % (type(resp)) - results.append(result) - junos_module.logger.debug('Unexpected response type %s.', - type(resp)) - continue - - # Set the output keys - if junos_module.params['return_output'] is True: - if text_output is not None: - result['stdout'] = text_output - result['stdout_lines'] = text_output.splitlines() - if parsed_output is not None: - result['parsed_output'] = parsed_output - # Save the output - junos_module.save_text_output(rpc_string, format, text_output) - # This command succeeded. - result['failed'] = False - # Append to the list of results - results.append(result) - - # Return response. - if len(results) == 1: - junos_module.exit_json(**results[0]) - else: - # Calculate the overall failed. Only failed if all commands failed. - failed = True - for result in results: - if result.get('failed') is False: - failed = False - break - junos_module.exit_json(results=results, - changed=False, - failed=failed) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_software.py b/library/juniper_junos_software.py deleted file mode 100644 index ccfa5a72..00000000 --- a/library/juniper_junos_software.py +++ /dev/null @@ -1,806 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_software -version_added: "2.0.0" # of Juniper.junos role -author: - - Jeremy Schulman - - "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Install software on a Junos device -description: - - > - Install a Junos OS image, or other software package, on a Junos device. - This action is generally equivalent to the C(request system software add) - operational-mode CLI command. It performs the following - steps in order: - - - #. Compare the currently installed Junos version to the desired version - specified by the I(version) option. - - * If the current and desired versions are the same, stop and return - I(changed) with a value of C(false). - * If running in check mode, and the current and desired versions differ, - stop and return I(changed) with a value of C(true). - * Otherwise, proceed. - #. If the I(local_package) option is specified, compute the MD5 checksum - of the I(local_package) file on the local Ansible control machine. - #. Check if the file exists at the I(remote_package) location on the target - Junos device. If so, compute the MD5 checksum of the file on the target - Junos device. - #. If the I(cleanfs) option is C(true), the default, then perform the - equivalent of the C(request system storage cleanup) CLI command. - #. If the checksums computed in steps 2 and 3 differ, or if the - I(remote_package) file does not exist on the target Junos device, then - copy the package from I(local_package) on the local Ansible control - machine to I(remote_package) on the target Junos device. - #. Install the software pacakge from the I(remote_package) location on the - target Junos device using the options specified. - #. If the I(reboot) option is C(true), the default, initiate a reboot of - the target Junos device. -options: - all_re: - description: - - Whether or not to install the software on all Routing Engines of the - target Junos device. If C(true), and the device has multiple Routing - Engines, the software is installed on all Routing Engines. If C(false), - the software is only installed on the current Routing Engine. - required: false - default: true - type: bool - checksum: - description: - - The pre-calculated checksum, using the I(checksum_algorithm) of the - file specified by the I(local_package) option. Specifying this option - is simply an optimization to avoid repeatedly computing the checksum of - the I(local_package) file once for each target Junos host. - required: false - default: none - type: str - checksum_algorithm: - description: - - The algorithm to use when calculating the checksum of the local and - remote software packages. - required: false - default: md5 - type: str - checksum_timeout: - description: - - The number of seconds to wait for the calculation of the checksum to - complete on the target Junos device. - required: false - default: 300 (5 minutes) - type: int - cleanfs: - description: - - Whether or not to perform a C(request system storage cleanup) prior to - copying or installing the software. - required: false - default: true (unless I(no_copy) is C(true), then C(false)) - type: bool - cleanfs_timeout: - description: - - The number of seconds to wait for the - C(request system storage cleanup) to complete on the target Junos - device. - required: false - default: 300 (5 minutes) - type: int - force_host: - description: - - Forces the upgrade of the Host Software package on QFX-series devices. - required: false - default: false - type: bool - install_timeout: - description: - - The number of seconds to wait for the software installation to - complete on the target Junos device. - required: false - default: 1800 (30 minutes) - type: int - issu: - description: - - Indicates if a unified in-service software upgrade (ISSU) should be - attempted. ISSU enables the upgrade between two different - Junos OS releases with no control plane disruption and minimal data - plane traffic disruption. - - In order for an ISSU to succeed, ISSU must be supported. This includes - support for the current to desired Junos versions, the hardware - of the target Junos device, and the current software configuration of - the target Junos device. - - The I(issu) and I(nssu) options are mutually exclusive. - required: false - default: false - type: bool - kwargs: - description: - - Additional keyword arguments and values which are passed to the - C() RPC used to install the software package. The - value of this option is a dictionary of keywords and values. - required: false - default: none - type: dict - aliases: - - kwarg - - args - - arg - local_package: - description: - - The path, on the local Ansible control machine, of a Junos software - package. This Junos software package will be installed on the target - Junos device. - - If this option is specified, and a file with the same MD5 checksum - doesn't already exist at the I(remote_package) location on the target - Junos device, then the file is copied from the local Ansible control - machine to the target Junos device. - - If this option is not specified, it is assumed that the - software package already exists on the target Junos device. In this - case, the I(remote_package) option must be specified. - required: false - default: none - type: path - aliases: - - package - no_copy: - description: - - Indicates if the file containing the software package should be copied - from the I(local_package) location on the local Ansible control - machine to the I(remote_package) location on the target Junos device. - - If the value is C(true), or if the I(local_package) option is not - specified, then the copy is skipped and the file must already exist - at the I(remote_package) location on the target Junos device. - required: false - default: false - type: bool - nssu: - description: - - Indicates if a non-stop software upgrade (NSSU) should be - attempted. NSSU enables the upgrade between two different - Junos OS releases with minimal data plane traffic disruption. - - NSSU is specific to EX-series Virtual Chassis systems or EX-series - stand-alone systems with redundant Routing Engines. - - In order for an NSSU to succeed, NSSU must be supported. This includes - support for the current to desired Junos versions, the hardware - of the target Junos device, and the current software configuration of - the target Junos device. - - The I(nssu) and I(issu) options are mutually exclusive. - required: false - default: false - type: bool - reboot: - description: - - Indicates if the target Junos device should be rebooted after - performing the software install. - required: false - default: true - type: bool - reboot_pause: - description: - - The amount of time, in seconds, to wait after the reboot is issued - before the module returns. This gives time for the reboot to begin. The - default value of 10 seconds is designed to ensure the device is no - longer reachable (because the reboot has begun) when the next task - begins. The value must be an integer greater than or equal to 0. - required: false - default: 10 - type: int - remote_package: - description: - - This option may take one of two formats. - - The first format is a URL, from the perspective of the target Junos - device, from which the device retrieves the software package to be - installed. The acceptable formats for the URL value may be found - U(here|https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-software-formats-filenames-urls.html). - - When using the URL format, the I(local_package) and I(no_copy) options - must not be specified. - - The second format is a file path, on the taget Junos device, to the - software package. - - If the I(local_package) option is also specified, and the - I(no_copy) option is C(false), the software package will be copied - from I(local_package) to I(remote_package), if necessary. - - If the I(no_copy) option is C(true) or the I(local_package) option - is not specified, then the file specified by this option must already - exist on the target Junos device. - - If this option is not specified, it is assumed that the software - package will be copied into the C(/var/tmp) directory on the target - Junos device using the filename portion of the I(local_package) option. - In this case, the I(local_package) option must be specified. - - Specifying the I(remote_package) option and not specifying the - I(local_package) option is equivalent to specifying the - I(local_package) option and the I(no_copy) option. In this case, - you no longer have to explicitly specify the I(no_copy) option. - - If the I(remote_package) value is a directory (ends with /), then - the filename portion of I(local_package) will be appended to the - I(remote_package) value. - - If the I(remote_package) value is a file (does not end with /), - then the filename portion of I(remote_package) must be the same as - the filename portion of I(local_package). - required: false - default: C(/var/tmp/) + filename portion of I(local_package) - type: path - pkg_set: - description: - - install software on the members in a mixed Virtual Chassis. Currently - we are not doing target package check this option is provided. - required: false - default: false - type: list - validate: - description: - - Whether or not to have the target Junos device should validate the - current configuration against the new software package. - required: false - default: false - type: bool - version: - description: - - The version of software contained in the file specified by the - I(local_package) and/or I(remote_package) options. This value should - match the Junos version which will be reported by the device once the - new software is installed. If the device is already running a version - of software which matches the I(version) option value, the software - install is not necessary. In this case the module returns a I(changed) - value of C(false) and an I(failed) value of C(false) and does not - attempt to perform the software install. - required: false - default: Attempt to extract the version from the file name specified by - the I(local_package) or I(remote_package) option values IF the - package appears to be a Junos software package. Otherwise, C(none). - type: str - aliases: - - target_version - - new_version - - desired_version - vmhost: - description: - - Whether or not this is a vmhost software installation. - required: false - default: false - type: bool -notes: - - This module does support connecting to the console of a Junos device, but - does not support copying the software package from the local Ansible - control machine to the target Junos device while connected via the console. - In this situation, the I(remote_package) option must be specified, and the - specified software package must already exist on the target Junos device. - - This module returns after installing the software and, optionally, - initiating a reboot of the target Junos device. It does not wait for - the reboot to complete, and it does not verify that the desired version of - software specified by the I(version) option is actually activated on the - target Junos device. It is the user's responsibility to confirm the - software installation using additional follow on tasks in their playbook. -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_software - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: Execute a basic Junos software upgrade. - juniper_junos_software: - local_package: "./images/" - register: response - - name: Print the complete response. - debug: - var: response - -###### OLD EXAMPLES ########## - - junos_install_os: - host={{ inventory_hostname }} - version=12.1X46-D10.2 - package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz - logfile=/usr/local/junos/log/software.log -###### OLD EXAMPLES ########## -''' - - -RETURN = ''' -changed: - description: - - Indicates if the device's state has changed, or if the state would have - changed when executing in check mode. This value is set to C(true) when - the version of software currently running on the target Junos device does - not match the desired version of software specified by the I(version) - option. If the current and desired software versions match, the value - of this key is set to C(false). - returned: success - type: bool -check_mode: - description: - - Indicates whether or not the module ran in check mode. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -msg: - description: - - A human-readable message indicating the result of the software - installation. - returned: always - type: str -''' - -# Standard Library imports -import os.path -import re -import time -try: - # Python 3.x - from urllib.parse import urlparse -except ImportError: - # Python 2.x - from urlparse import urlparse - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def parse_version_from_filename(filename): - """Attempts to parse a version string from the filename of a Junos package. - - There is wide variety in the naming schemes used by Junos software - packages. This function attempts to parse the version string from the - filename, but may not be able to accurately do so. It's also not - guaranteed that the filename of a package accurately reflects the version - of software in the file. (A user may have renamed it.) - If the filename does not appear to be a Junos package (maybe some other - type of package which can be installed on Junos devices), then return None. - - Args: - filename - The filename from which to parse the version string. - - Returns: - The version string, or None if unable to parse. - """ - # Known prefixes for filenames which contain Junos software packages. - JUNOS_PACKAGE_PREFIXES = ['jbundle', 'jinstall', 'junos-install', - 'junos-srx', 'junos-vmhost-install', 'junos-vrr', - 'vmx-bundle'] - for prefix in JUNOS_PACKAGE_PREFIXES: - if filename.startswith(prefix): - # Assumes the version string will be prefixed by -. - # Assume major version will begin with two digits followed by dot. - # Assume the version string ends with the last digit in filename. - match = re.search('-(\d{2}\..*\d).*', filename) - if match is not None: - return match.group(1) - # Not a known Junos package name. - else: - return None - - -def define_progress_callback(junos_module): - """Create callback which can be passed to SW.install(progress=progress) - """ - def myprogress(_, report): - """A progress function which logs report at level INFO. - - Args: - _: The PyEZ device object. Unused because the logger already knows. - report: The string to be logged. - """ - junos_module.logger.info(report) - return myprogress - - -def main(): - CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] - - #Define the argument spec. - software_argument_spec=dict( - local_package=dict(required=False, - aliases=['package'], - type='path', - default=None), - remote_package=dict(required=False, - type='path', - # Default is '/var/tmp/' + filename from the - # local_package option, if set. - default=None), - pkg_set=dict(required=False, - type='list', - default=None), - version=dict(required=False, - aliases=['target_version', 'new_version', - 'desired_version'], - type='str', - # Default is determined from filename portion of - # remote_package option. - default=None), - no_copy=dict(required=False, - type='bool', - default=False), - reboot=dict(required=False, - type='bool', - default=True), - reboot_pause=dict(required=False, - type='int', - default=10), - issu=dict(required=False, - type='bool', - default=False), - nssu=dict(required=False, - type='bool', - default=False), - force_host=dict(required=False, - type='bool', - default=False), - validate=dict(required=False, - type='bool', - default=False), - cleanfs=dict(required=False, - type='bool', - default=True), - all_re=dict(required=False, - type='bool', - default=True), - vmhost=dict(required=False, - type='bool', - default=False), - checksum=dict(required=False, - type='str', - default=None), - checksum_algorithm=dict(required=False, - choices=CHECKSUM_ALGORITHM_CHOICES, - type='str', - default='md5'), - checksum_timeout=dict(required=False, - type='int', - default=300), - cleanfs_timeout=dict(required=False, - type='int', - default=300), - install_timeout=dict(required=False, - type='int', - default=1800), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='dict', - default=None), - ) - # Save keys for later. Must do because software_argument_spec gets - # modified. - option_keys = list(software_argument_spec.keys()) - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec = software_argument_spec, - # Mutually exclusive options. - mutually_exclusive=[['issu', 'nssu']], - # One of local_package and remote_package is required. - required_one_of=[['local_package', 'remote_package', 'pkg_set']], - supports_check_mode=True - ) - - # Straight from params - local_package = junos_module.params.pop('local_package') - remote_package = junos_module.params.pop('remote_package') - pkg_set = junos_module.params.pop('pkg_set') - target_version = junos_module.params.pop('version') - no_copy = junos_module.params.pop('no_copy') - reboot = junos_module.params.pop('reboot') - reboot_pause = junos_module.params.pop('reboot_pause') - install_timeout = junos_module.params.pop('install_timeout') - cleanfs = junos_module.params.pop('cleanfs') - all_re = junos_module.params.pop('all_re') - kwargs = junos_module.params.pop('kwargs') - - url = None - remote_dir = None - if remote_package is not None: - # Is the remote package a URL? - parsed_url = urlparse(remote_package) - if parsed_url.scheme == '': - # A file on the remote host. - (remote_dir, remote_filename) = os.path.split(remote_package) - else: - url = remote_package - (_, remote_filename) = os.path.split(parsed_url.path) - else: - # Default remote_dir value - remote_dir = '/var/tmp' - remote_filename = '' - - if url is not None and local_package is not None: - junos_module.fail_json(msg='There remote_package (%s) is a URL. ' - 'The local_package option is not allowed.' % - remote_package) - - if url is not None and no_copy is True: - junos_module.fail_json(msg='There remote_package (%s) is a URL. ' - 'The no_copy option is not allowed.' % - remote_package) - - if url is None: - local_filename = None - if local_package is not None: - # Expand out the path. - local_package = os.path.abspath(local_package) - (local_dir, local_filename) = os.path.split(local_package) - if local_filename == '': - junos_module.fail_json(msg='There is no filename component to ' - 'the local_package (%s).' % - local_package) - elif remote_package is not None: - # remote package was, so we must assume no_copy. - no_copy = True - - if no_copy is False: - if local_package is not None and not os.path.isfile(local_package): - junos_module.fail_json(msg='The local_package (%s) is not a ' - 'valid file on the local Ansible ' - 'control machine.' % local_package) - elif pkg_set is not None: - pkg_set = [os.path.abspath(item) for item in pkg_set] - for pkg_set_item in pkg_set: - if not os.path.isfile(pkg_set_item): - junos_module.fail_json( - msg='The pkg (%s) is not a valid file on the local' - ' Ansible control machine.' % pkg_set_item) - - if remote_filename == '': - # Use the same name as local_filename - remote_filename = local_filename - - if local_filename is not None and remote_filename != local_filename: - junos_module.fail_json(msg='The filename of the remote_package ' - '(%s) must be the same as the filename ' - 'of the local_package (%s).' % - (remote_filename, local_filename)) - - # If no_copy is True, then we need to turn off cleanfs to keep from - # deleting the software package which is already present on the device. - if no_copy is True: - cleanfs = False - - if target_version is None and pkg_set is None: - target_version = parse_version_from_filename(remote_filename) - junos_module.logger.debug("New target version is: %s.", target_version) - - # Initialize the results. Assume not changed and failure until we know. - results = {'msg': '', - 'changed': False, - 'check_mode': junos_module.check_mode, - 'failed': True} - - # Check version info to see if we need to do the install. - if target_version is not None: - if all_re is True: - junos_info = junos_module.dev.facts['junos_info'] - for current_re in junos_info: - current_version = junos_info[current_re]['text'] - if target_version != current_version: - junos_module.logger.debug("Current version on %s: %s. " - "Target version: %s.", - current_version, current_re, - target_version) - results['changed'] = True - else: - results['msg'] += "Current version on %s: %s same as Targeted " \ - "version: %s.\n" % (current_version, current_re, - target_version) - else: - current_version = junos_module.dev.facts['version'] - re_name = junos_module.dev.re_name - if target_version != current_version: - junos_module.logger.debug("Current version on %s: %s. " - "Target version: %s.", - current_version, re_name, - target_version) - results['changed'] = True - else: - results['msg'] += "Current version on %s: %s same as Targeted " \ - "version: %s.\n" % (current_version, re_name, - target_version) - else: - # A non-Junos install. Always attempt to install. - results['changed'] = True - - # Do the install if necessary - if results['changed'] is True and not junos_module.check_mode: - junos_module.logger.debug("Beginning installation of %s.", - remote_filename) - # Calculate the install parameters - install_params = {} - if url is not None: - install_params['package'] = url - elif local_package is not None: - install_params['package'] = local_package - elif pkg_set is not None: - install_params['pkg_set'] = pkg_set - else: - install_params['package'] = remote_filename - if remote_dir is not None: - install_params['remote_path'] = remote_dir - install_params['progress'] = define_progress_callback(junos_module) - install_params['cleanfs'] = cleanfs - install_params['no_copy'] = no_copy - install_params['timeout'] = install_timeout - install_params['all_re'] = all_re - for key in option_keys: - value = junos_module.params.get(key) - if value is not None: - install_params[key] = value - if kwargs is not None: - install_params.update(kwargs) - try: - junos_module.logger.debug("Install parameters are: %s", - str(install_params)) - junos_module.add_sw() - ok = junos_module.sw.install(**install_params) - if ok is not True: - results['msg'] = 'Unable to install the software' - junos_module.fail_json(**results) - msg = 'Package %s successfully installed.' % ( - install_params.get('package') or - install_params.get('pkg_set')) - results['msg'] = msg - junos_module.logger.debug(msg) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - results['msg'] = 'Installation failed. Error: %s' % str(ex) - junos_module.fail_json(**results) - if reboot is True: - try: - # Try to deal with the fact that we might not get the closing - # and therefore might get an RpcTimeout. - # (This is a known Junos bug.) Set the timeout low so this - # happens relatively quickly. - if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") - junos_module.dev.timeout = 5 - junos_module.logger.debug('Initiating reboot.') - xpath_list = ['.//request-reboot-status'] - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-reboot') - elif install_params.get('vmhost'): - rpc = junos_module.etree.Element('request-vmhost-reboot') - # RPC reply can contain multiple output tags - xpath_list.append('output') - else: - rpc = junos_module.etree.Element('request-reboot') - - if all_re is True: - if (junos_module.sw._multi_RE is True and - junos_module.sw._multi_VC is False): - junos_module.etree.SubElement(rpc, - 'both-routing-engines') - # At least on some platforms stopping/rebooting both - # REs produces messages and - # messages. - xpath_list.append('output') - elif junos_module.sw._mixed_VC is True: - junos_module.etree.SubElement(rpc, 'all-members') - resp = junos_module.dev.rpc(rpc, - ignore_warning=True, - normalize=True) - junos_module.logger.debug("Reboot RPC executed cleanly.") - if len(xpath_list) > 0: - obj = resp.getparent() - for xpath in xpath_list: - if junos_module.dev.facts['_is_linux']: - got = resp.text - else: - # there are cases where rpc-reply will have multiple - # child element, hence lets work on parent. - # for ex: - # Rebooting fpc1 - # - # - # Shutdown at Mon Jun 24 10:16:35 2019. - # [pid 1949] - # - # - if xpath == 'output': - got = '\n'.join([i.text for i in obj.findall('output') - if i.text is not None]) - else: - got = obj.findtext(xpath) - if got is not None: - results['msg'] += ' Reboot successfully initiated. ' \ - 'Reboot message: %s' % got - break - else: - # This is the else clause of the for loop. - # It only gets executed if the loop finished without - # hitting the break. - results['msg'] += ' Did not find expected response ' \ - 'from reboot RPC. RPC response is ' \ - '%s' % \ - junos_module.etree.tostring(resp) - junos_module.fail_json(**results) - else: - results['msg'] += ' Reboot successfully initiated.' - except (junos_module.pyez_exception.RpcTimeoutError) as ex: - # This might be OK. It might just indicate the device didn't - # send the closing (known Junos bug). - # Try to close the device. If it closes cleanly, then it was - # still reachable, which probably indicates a problem. - try: - junos_module.close(raise_exceptions=True) - # This means the device wasn't already disconnected. - results['msg'] += ' Reboot failed. It may not have been ' \ - 'initiated.' - junos_module.fail_json(**results) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError): - # This is expected. The device has already disconnected. - results['msg'] += ' Reboot succeeded.' - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) - junos_module.fail_json(**results) - junos_module.logger.debug("Reboot RPC successfully initiated.") - if reboot_pause > 0: - junos_module.logger.debug("Sleeping for %d seconds", - reboot_pause) - time.sleep(reboot_pause) - - # If we made it this far, it's success. - results['failed'] = False - - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_srx_cluster.py b/library/juniper_junos_srx_cluster.py deleted file mode 100644 index 4fb7b71a..00000000 --- a/library/juniper_junos_srx_cluster.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Patrik Bok -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_srx_cluster -version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Add or remove SRX chassis cluster configuration -description: - - Add an SRX chassis cluster configuration and reboot the device. Assuming - the device is capable of forming an SRX cluster and has the correct - cables connected, this will form an SRX cluster. - - If an SRX chassis cluster is already present, setting I(cluster_enable) to - C(false) will remove the SRX chassis cluster configuration and reboot - the device causing the SRX cluster to be broken and the device to return - to stand-alone mode. -options: - enable: - description: - - Enable or disable cluster mode. When C(true) cluster mode is enabled - and I(cluster_id) and I(node_id) must also be specified. When C(false) - cluster mode is disabled and the device returns to stand-alone mode. - required: true - default: none - type: bool - aliases: - - cluster_enable - cluster_id: - description: - - The cluster ID to configure. - - Required when I(enable) is C(true). - required: false - default: none - type: int - aliases: - - cluster - node_id: - description: - - The node ID to configure. (C(0) or C(1)) - - Required when I(enable) is C(true). - required: false - default: none - type: int - aliases: - - node -''' - -EXAMPLES = ''' ---- -- name: Manipulate the SRX cluster configuration of Junos SRX devices - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - tasks: - - name: Enable an SRX cluster - juniper_junos_srx_cluster: - enable: true - cluster_id: 4 - node_id: 0 - register: response - - name: Print the response. - debug: - var: response.config_lines - - - name: Disable an SRX cluster - juniper_junos_srx_cluster: - enable: false - register: response - - name: Print the response. - debug: - var: response.config_lines -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's configuration has changed, or would have - changed when in check mode. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -reboot: - description: - - Indicates if a reboot of the device has been initiated. - returned: success - type: bool -''' - -# Standard library imports - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - enable=dict(type='bool', - required=True, - aliases=['cluster_enable'], - default=None), - cluster_id=dict(type='int', - required=False, - aliases=['cluster'], - default=None), - node_id=dict(type='int', - required=False, - aliases=['node'], - default=None) - ), - # Required if options - # If enable is True, then cluster_id and node_id must be set. - required_if=[['enable', True, ['cluster_id', 'node_id']]], - # Check mode is implemented. - supports_check_mode=True - ) - # Do additional argument verification. - - # Straight from params - enable = junos_module.params.get('enable') - cluster_id = junos_module.params.get('cluster_id') - node_id = junos_module.params.get('node_id') - - # cluster_id must be between 0 and 255 - if cluster_id is not None: - if cluster_id < 0 or cluster_id > 255: - junos_module.fail_json(msg="The cluster_id option (%s) must have " - "an integer value between 0 and 255." % - (cluster_id)) - - # node_id must be between 0 and 1 - if node_id is not None: - if node_id < 0 or node_id > 1: - junos_module.fail_json(msg="The node_id option (%s) must have a " - "value of 0 or 1." % (node_id)) - - # Initialize the results. Assume failure until we know it's success. - results = {'msg': '', - 'changed': False, - 'reboot': False, - 'failed': True} - - junos_module.logger.debug("Check current SRX cluster operational state.") - current_cluster_state = junos_module.dev.facts['srx_cluster'] - current_cluster_id = junos_module.dev.facts['srx_cluster_id'] - if current_cluster_id is not None: - current_cluster_id = int(current_cluster_id) - current_node_name = junos_module.dev.re_name - current_node_id = None - if current_node_name is not None: - (_, _, current_node_id) = current_node_name.partition('node') - if current_node_id: - current_node_id = int(current_node_id) - junos_module.logger.debug( - "Current SRX cluster operational state: %s, cluster_id: %s, " - "node_id: %s", - 'enabled' if current_cluster_state else 'disabled', - str(current_cluster_id), - str(current_node_id)) - - # Is a state change needed? - if current_cluster_state != enable: - junos_module.logger.debug( - "SRX cluster configuration change needed. Current state: %s. " - "Desired state: %s", - 'enabled' if current_cluster_state else 'disabled', - 'enabled' if enable else 'disabled') - results['changed'] = True - - # Is a cluster ID change needed? - if (enable is True and current_cluster_id is not None and - current_cluster_id != cluster_id): - junos_module.logger.debug( - "SRX cluster ID change needed. Current cluster ID: %d. " - "Desired cluster ID: %d", - current_cluster_id, cluster_id) - results['changed'] = True - - # Is a node ID change needed? - if (enable is True and current_node_id is not None and - current_node_id != node_id): - junos_module.logger.debug( - "SRX node ID change needed. Current node ID: %d. " - "Desired cluster ID: %d", - current_node_id, node_id) - results['changed'] = True - - results['msg'] = 'Current state: %s, cluster_id: %s, node_id: %s' % \ - ('enabled' if current_cluster_state else 'disabled', - str(current_cluster_id), - str(current_node_id)) - - if results['changed'] is True: - results['msg'] += ' Desired state: %s, cluster_id: %s, ' \ - 'node_id: %s' % \ - ('enabled' if enable else 'disabled', - str(cluster_id), - str(node_id)) - - if not junos_module.check_mode: - results['msg'] += ' Initiating change.' - try: - output = None - if enable is True: - resp = junos_module.dev.rpc.set_chassis_cluster_enable( - cluster_id=str(cluster_id), node=str(node_id), - reboot=True, normalize=True - ) - else: - resp = junos_module.dev.rpc.set_chassis_cluster_disable( - reboot=True, normalize=True - ) - if resp is not None: - output = resp.getparent().findtext('.//output') - if output is None: - output = resp.getparent().findtext('.//message') - results['msg'] += ' Reboot initiated. Response: %s' % (output) - results['reboot'] = True - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Error: %s', str(ex)) - results['msg'] += ' Error: %s' % (str(ex)) - junos_module.fail_json(**results) - - # If we made it this far, everything was successful. - results['failed'] = False - - # Return response. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_system.py b/library/juniper_junos_system.py deleted file mode 100644 index b186f037..00000000 --- a/library/juniper_junos_system.py +++ /dev/null @@ -1,476 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_system -version_added: "2.0.0" # of Juniper.junos role -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Initiate operational actions on the Junos system -description: - - Initiate an operational action (shutdown, reboot, halt or zeroize) on a - Junos system. The particular action to execute is defined by the mandatory - I(action) option. -options: - action: - description: - - The action performed by the module. - - > - The following actions are supported: - - B(shutdown) - Power off the Junos devices. The values C(off), - C(power-off), and C(power_off) are aliases for this value. - This is the equivalent of the C(request system power-off) CLI - command. - - B(halt) - Stop the Junos OS running on the RE, but do not power off - the system. Once the system is halted, it will reboot if a - keystroke is entered on the console. This is the equivalent - of the C(request system halt) CLI command. - - B(reboot) - Reboot the system. This is the equivalent of the - C(request system reboot) CLI command. - - B(zeroize) - Restore the system (configuration, log files, etc.) to a - factory default state. This is the equivalent of the - C(request system zeroize) CLI command. - required: true - default: none - type: str - choices: - - shutdown - - halt - - reboot - - zeroize - - 'off' - - power-off - - power_off - at: - description: - - The time at which to shutdown, halt, or reboot the system. - - > - The value may be specified in one of the following ways: - - B(now) - The action takes effect immediately. - - B(+minutes) — The action takes effect in C(minutes) minutes from now. - - B(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute - time, specified as year, month, day, hour, and minute. - - B(hh:mm) — The action takes effect at C(hh:mm) absolute time on the - current day, specified in 24-hour time. - - The I(at) option can not be used when the I(action) option has a - value of C(zeroize). The I(at) option is mutually exclusive with the - I(in_min) option. - required: false - default: none - type: str - in_min: - description: - - Specify a delay, in minutes, before the shutdown, halt, or reboot. - - The I(in_min) option can not be used when the I(action) option has a - value of C(zeroize). The I(in_min) option is mutually exclusive with - the I(at) option. - required: false - default: none - type: int - all_re: - description: - - If the system has multiple Routing Engines and this option is C(true), - then the action is performed on all REs in the system. If the system - does not have multiple Routing Engines, then this option has no effect. - - This option applies to all I(action) values. - - The I(all_re) option is mutually exclusive with the I(other_re) option. - required: false - default: true - type: bool - other_re: - description: - - If the system has dual Routing Engines and this option is C(true), - then the action is performed on the other REs in the system. If the - system does not have dual Routing Engines, then this option has no - effect. - - The I(other_re) option can not be used when the I(action) option has a - value of C(zeroize). - - The I(other_re) option is mutually exclusive with the I(all_re) option. - required: false - default: false - type: bool - media: - description: - - Overwrite media when performing the zeroize operation. This option is - only valid when the I(action) option has a value of C(zeroize). - required: false - default: false - type: bool -notes: - - This module only B(INITIATES) the action. It does B(NOT) wait for the - action to complete. - - Some Junos devices are effected by a Junos defect which causes this Ansible - module to hang indefinitely when connected to the Junos device via - the console. This problem is not seen when connecting to the Junos device - using the normal NETCONF over SSH transport connection. Therefore, it is - recommended to use this module only with a NETCONF over SSH transport - connection. However, this module does still permit connecting to Junos - devices via the console port and this functionality may still be used for - Junos devices running Junos versions less than 15.1. -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_system - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: Reboot all REs of the device - juniper_junos_system: - action: "reboot" - - - name: Power off the other RE of the device. - juniper_junos_system: - action: "shutdown" - othe_re: True - - - name: Reboot this RE at 8pm today. - juniper_junos_system: - action: "reboot" - all_re: False - at: "20:00" - - - name: Halt the system on 25 January 2018 at 4pm. - juniper_junos_system: - action: "halt" - at: "1801251600" - - - name: Reboot the system in 30 minutes. - juniper_junos_system: - action: "reboot" - in_min: 30 - - - name: Reboot the system in 30 minutes. - juniper_junos_system: - action: "reboot" - at: "+30m" - - - name: Zeroize the local RE only. - juniper_junos_system: - action: "zeroize" - all_re: False - - - name: Zeroize all REs and overwrite medea. - juniper_junos_system: - action: "zeroize" - media: True -''' - -RETURN = ''' -action: - description: - - The value of the I(action) option. - returned: always - type: str -all_re: - description: - - The value of the I(all_re) option. - returned: always - type: str -changed: - description: - - Indicates if the device's state has changed. If the action is performed - (or if it would have been performed when in check mode) then the value - will be C(true). If there was an error before the action, then the value - will be C(false). - returned: always - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -media: - description: - - The value of the I(media) option. - returned: always - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -other_re: - description: - - The value of the I(other_re) option. - returned: always - type: str -''' - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - action=dict(type='str', - required=True, - choices=['shutdown', 'off', 'power-off', 'power_off', - 'halt', 'reboot', 'zeroize'], - default=None), - at=dict(type='str', - required=False, - default=None), - in_min=dict(type='int', - required=False, - aliases=['in'], - default=None), - all_re=dict(type='bool', - required=False, - default=True), - other_re=dict(type='bool', - required=False, - default=False), - media=dict(type='bool', - required=False, - default=False), - ), - mutually_exclusive=[['at', 'in_min'], ['all_re', 'other_re']], - supports_check_mode=True - ) - - # We're going to be using params a lot - params = junos_module.params - - action = params['action'] - # Synonymns for shutdown - if action == 'off' or action == 'power_off' or action == 'power-off': - action = 'shutdown' - - # at option only applies to reboot, shutdown, or halt actions. - if (params.get('at') is not None and - action != 'reboot' and - action != 'shutdown' and - action != 'halt'): - junos_module.fail_json(msg='The at option can only be used when ' - 'the action option has the value "reboot", ' - '"shutdown", or "halt".') - - # in_min option only applies to reboot, shutdown, or halt actions. - if (params.get('in_min') is not None and - action != 'reboot' and - action != 'shutdown' and - action != 'halt'): - junos_module.fail_json(msg='The in_min option can only be used when ' - 'the action option has the value "reboot", ' - '"shutdown", or "halt".') - - # other_re option only applies to reboot, shutdown, or halt actions. - if (params.get('other_re') is True and - action != 'reboot' and - action != 'shutdown' and - action != 'halt'): - junos_module.fail_json(msg='The other_re option can only be used when ' - 'the action option has the value "reboot", ' - '"shutdown", or "halt".') - - # media option only applies to zeroize action. - if params['media'] is True and action != 'zeroize': - junos_module.fail_json(msg='The media option can only be used when ' - 'the action option has the value ' - '"zeroize".') - - # If other_re, then we should turn off all_re - if params['other_re'] is True: - params['all_re'] = False - - # Set initial results values. Assume failure until we know it's success. - # Assume we haven't changed the state until we do. - results = {'changed': False, - 'msg': '', - 'reboot': bool(action == 'reboot'), - 'action': action, - 'all_re': params.get('all_re'), - 'other_re': params.get('other_re'), - 'media': params.get('media'), - 'failed': True} - - # Map the action to an RPC. - rpc = None - xpath_list = [] - if action == 'reboot': - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-reboot') - else: - rpc = junos_module.etree.Element('request-reboot') - xpath_list.append('.//request-reboot-status') - elif action == 'shutdown': - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-power-off') - else: - rpc = junos_module.etree.Element('request-power-off') - xpath_list.append('.//request-reboot-status') - elif action == 'halt': - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-halt') - else: - rpc = junos_module.etree.Element('request-halt') - xpath_list.append('.//request-reboot-status') - elif action == 'zeroize': - rpc = junos_module.etree.Element('request-system-zeroize') - else: - results['msg'] = 'No RPC found for the %s action.' % (action) - junos_module.fail_json(**results) - - # Add the arguments - if action == 'zeroize': - if params['all_re'] is False: - if junos_module.dev.facts['2RE']: - junos_module.etree.SubElement(rpc, 'local') - if params['media'] is True: - junos_module.etree.SubElement(rpc, 'media') - else: - if params['in_min'] is not None: - junos_module.etree.SubElement(rpc, - 'in').text = str(params['in_min']) - elif params['at'] is not None: - junos_module.etree.SubElement(rpc, - 'at').text = params['at'] - if params['other_re'] is True: - if junos_module.dev.facts['2RE']: - junos_module.etree.SubElement(rpc, 'other-routing-engine') - # At least on some platforms stopping/rebooting the other RE - # just produces messages. - xpath_list.append('..//output') - elif params['all_re'] is True: - junos_module.add_sw() - if (junos_module.sw._multi_RE is True and - junos_module.sw._multi_VC is False): - junos_module.etree.SubElement(rpc, 'both-routing-engines') - # At least on some platforms stopping/rebooting both REs - # produces messages and - # messages. - xpath_list.append('..//output') - elif junos_module.sw._mixed_VC is True: - junos_module.etree.SubElement(rpc, 'all-members') - - # OK, we're ready to do something. Set changed and log the RPC. - results['changed'] = True - junos_module.logger.debug("Ready to execute RPC: %s", - junos_module.etree.tostring(rpc, - pretty_print=True)) - - if not junos_module.check_mode: - if action != 'zeroize': - # If we're going to do a shutdown, reboot, or halt right away then - # try to deal with the fact that we might not get the closing - # and therefore might get an RpcTimeout. - # (This is a known Junos bug.) Set the timeout low so this happens - # relatively quickly. - if (params['at'] == 'now' or params['in_min'] == 0 or - (params['at'] is None and params['in_min'] is None)): - if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") - junos_module.dev.timeout = 5 - - # Execute the RPC. - try: - junos_module.logger.debug( - "Executing RPC: %s", - junos_module.etree.tostring(rpc, pretty_print=True)) - resp = junos_module.dev.rpc(rpc, - ignore_warning=True, - normalize=True) - junos_module.logger.debug("RPC executed cleanly.") - if len(xpath_list) > 0: - for xpath in xpath_list: - if junos_module.dev.facts['_is_linux']: - got = resp.text - else: - got = resp.findtext(xpath) - if got is not None: - results['msg'] = '%s successfully initiated.' % \ - (action) - results['failed'] = False - break - else: - # This is the else clause of the for loop. - # It only gets executed if the loop finished without - # hitting the break. - results['msg'] = 'Did not find expected RPC response.' - results['changed'] = False - else: - results['msg'] = '%s successfully initiated.' % (action) - results['failed'] = False - except (junos_module.pyez_exception.RpcTimeoutError) as ex: - # This might be OK. It might just indicate the device didn't - # send the closing (known Junos bug). - # Try to close the device. If it closes cleanly, then it was - # still reachable, which probably indicates there was a problem. - try: - junos_module.close(raise_exceptions=True) - # This means the device wasn't already disconnected. - results['changed'] = False - results['msg'] = '%s failed. %s may not have been ' \ - 'initiated.' % (action, action) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError): - # This is expected. The device has already disconnected. - results['msg'] = '%s succeeded.' % (action) - results['failed'] = False - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['changed'] = False - results['msg'] = '%s failed. Error: %s' % (action, str(ex)) - - # Return results. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/library/juniper_junos_table.py b/library/juniper_junos_table.py deleted file mode 100644 index 5d74f70a..00000000 --- a/library/juniper_junos_table.py +++ /dev/null @@ -1,477 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright 2016 Jason Edelman -# Network to Code, LLC -# -# Copyright (c) 2017-2018, Juniper Networks Inc. -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_table -version_added: "2.0.0" # of Juniper.junos role -author: - - Jason Edelman (@jedelman8) - - Updated by Juniper Networks - Stacy Smith (@stacywsmith) -short_description: Retrieve data from a Junos device using a PyEZ table/view -description: - - Retrieve data from a Junos device using PyEZ's operational table/views. - This module may be used with the tables/views which are included in the - PyEZ distribution or it may be used with user-defined tables/views. -options: - file: - description: - - Name of the YAML file, relative to the I(path) option, that contains - the table/view definition. The file name must end with the C(.yml) or - C(.yaml) extension. - required: true - default: none - type: path - kwargs: - description: - - Optional keyword arguments and values to the table's get() method. The - value of this option is a dictionary of keywords and values which are - used to refine the data return from performing a get() on the table. - The exact keywords and values which are supported are specific to the - table's definition and the underlying RPC which the table invokes. - required: false - default: none - type: dict - aliases: - - kwarg - - args - - arg - path: - description: - - The directory containing the YAML table/view definition file as - specified by the I(file) option. The default value is the C(op) - directory in C(jnpr.junos.op). This is the directory containing the - table/view definitions which are included in the PyEZ distribution. - required: false - default: C(op) directory in C(jnpr.junos.op) - type: path - aliases: - - directory - - dir - response_type: - description: - - Defines the format of data returned by the module. See RETURN. - The value of the I(resource) key in the module's response is either - a list of dictionaries C(list_of_dicts) or PyEZ's native return - format C(juniper_items). Because Ansible module's may only return JSON - data, PyEZ's native return format C(juniper_items) is translated into - a list of lists. - required: false - default: list_of_dicts - choices: - - list_of_dicts - - juniper_items - type: str - table: - description: - - Name of the PyEZ table used to retrieve data. If not specified, - defaults to the name of the table defined in the I(file) option. Any - table names in I(file) which begin with C(_) are ignored. If more than - one table is defined in I(file), the module fails with an error - message. In this case, you must manually specify the name of the table - by setting this option. - required: false - default: The name of the table defined in the I(file) option. - type: str -notes: - - This module only works with operational tables/views; it does not work with - configuration tables/views. -''' - -EXAMPLES = ''' ---- -- name: Retrieve data from a Junos device using a PyEZ table/view. - hosts: junos-all - connection: local - gather_facts: no - roles: - - Juniper.junos - - tasks: - - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table - juniper_junos_table: - file: "lldp.yml" - register: response - - name: Print response - debug: - var: response - - - name: Retrieve routes within 192.68.1/8 - juniper_junos_table: - file: "routes.yml" - table: "RouteTable" - kwargs: - destination: "192.68.1.0/8" - response_type: "juniper_items" - register: response - - name: Print response - debug: - var: response - - - name: Retrieve from custom table in playbook directory - juniper_junos_table: - file: "fpc.yaml" - path: "." - register: response - - name: Print response - debug: - var: response -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's configuration has changed. Since this - module does not change the operational or configuration state of the - device, the value is always set to C(false). - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -msg: - description: - - A human-readable message indicating a summary of the result. - returned: always - type: str -resource: - description: - - The items retrieved by the table/view. - returned: success - type: list of dicts if I(response_type) is C(list_of_dicts) or list of - lists if I(respsonse_type) is C(juniper_items). - sample: | - # when response_type == 'list_of_dicts' - [ - { - "local_int": "ge-0/0/3", - "local_parent": "-", - "remote_chassis_id": "00:05:86:08:d4:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/0", - "remote_sysname": "r5", - "remote_type": "Mac address" - }, - { - "local_int": "ge-0/0/0", - "local_parent": "-", - "remote_chassis_id": "00:05:86:18:f3:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/2", - "remote_sysname": "r4", - "remote_type": "Mac address" - } - ] - # when response_type == 'juniper_items' - [ - [ - "ge-0/0/3", - [ - [ - "local_parent", - "-" - ], - [ - "remote_port_id", - "ge-0/0/0" - ], - [ - "remote_chassis_id", - "00:05:86:08:d4:c0" - ], - [ - "remote_port_desc", - null - ], - [ - "remote_type", - "Mac address" - ], - [ - "local_int", - "ge-0/0/3" - ], - [ - "remote_sysname", - "r5" - ] - ] - ], - [ - "ge-0/0/0", - [ - [ - "local_parent", - "-" - ], - [ - "remote_port_id", - "ge-0/0/2" - ], - [ - "remote_chassis_id", - "00:05:86:18:f3:c0" - ], - [ - "remote_port_desc", - null - ], - [ - "remote_type", - "Mac address" - ], - [ - "local_int", - "ge-0/0/0" - ], - [ - "remote_sysname", - "r4" - ] - ] - ] - ] -''' - -# Standard library imports -import os.path - -# Constants -RESPONSE_CHOICES = ['list_of_dicts', 'juniper_items'] - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils import juniper_junos_common - - -def expand_items(module, data): - """Recursively expand any table items - """ - resources = [] - # data.items() is a list of tuples - for table_key, table_fields in data.items(): - # sample: - # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), - # ('neighbor', 'vmx2')] - # table_key - element 0 is the key from the Table - not using at all - # table_fields - element 1 is also a list of tuples - temp = [] - for key, value in table_fields: - # calling it normalized value because YOU/WE created the keys - if value and isinstance(value, module.pyez_factory_table.Table): - value = expand_items(module, value) - temp.append((key, value)) - resources.append((table_key, temp)) - return resources - - -def juniper_items_to_list_of_dicts(module, data): - """Recursively convert Juniper PyEZ Table/View items to list of dicts. - """ - resources = [] - # data.items() is a list of tuples - for table_key, table_fields in data.items(): - # sample: - # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), - # ('neighbor', 'vmx2')] - # table_key - element 0 is the key from the Table - not using at all - # table_fields - element 1 is also a list of tuples - temp = {} - for key, value in table_fields: - if isinstance(value, module.pyez_factory_table.Table): - value = juniper_items_to_list_of_dicts(module, value) - temp[key] = value - resources.append(temp) - return resources - - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - file=dict(type='path', - required=True, - default=None), - table=dict(type='str', - required=False, - default=None), - path=dict(type='path', - required=False, - aliases=['directory', 'dir'], - default=None), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='dict', - default=None), - response_type=dict(choices=RESPONSE_CHOICES, - type='str', - required=False, - default='list_of_dicts'), - ), - # Check mode is implemented. - supports_check_mode=True, - min_yaml_version=juniper_junos_common.MIN_YAML_VERSION, - ) - - # Straight from params - file = junos_module.params.get('file') - table = junos_module.params.get('table') - path = junos_module.params.get('path') - kwargs = junos_module.params.get('kwargs') - response_type = junos_module.params.get('response_type') - - if not file.endswith('.yml') and not file.endswith('.yaml'): - junos_module.fail_json(msg='The value of the file option must end ' - 'with the .yml or .yaml extension') - - # If needed, get the default path - if path is None: - path = os.path.dirname( - os.path.abspath(junos_module.pyez_op_table.__file__)) - - # file_name is path + file - file_name = os.path.join(path, file) - - junos_module.logger.debug("Attempting to open: %s.", file_name) - try: - with open(file_name, 'r') as fp: - try: - junos_module.logger.debug("Attempting to parse YAML from : " - "%s.", file_name) - table_view = junos_module.yaml.load(fp) - junos_module.logger.debug("YAML from %s successfully parsed.", - file_name) - except junos_module.yaml.YAMLError as ex: - junos_module.fail_json(msg='Failed parsing YAML file %s. ' - 'Error: %s' % (file_name, str(ex))) - except IOError: - junos_module.fail_json(msg='The file name %s could not be opened for' - 'reading.' % (file_name)) - junos_module.logger.debug("%s successfully read.", file_name) - - # Initialize the results. Assume failure until we know it's success. - results = {'msg': '', - 'changed': False, - 'failed': True} - - # Default to the table defined in file_name. - # Ignore table names which begin with an underscore. - if table is None: - for key in table_view: - if not key.startswith('_') and 'Table' in key: - if table is not None: - junos_module.fail_json( - msg='The file name %s contains multiple table ' - 'definitions. Specify the desired table with the ' - 'table option.' % (file_name)) - table = key - - if table is None: - junos_module.fail_json( - msg='No table definition was found in the %s file. Specify a ' - 'value for the file option which contains a valid table/view ' - 'definition.' % (file_name)) - junos_module.logger.debug("Table: %s", table) - - try: - loader = \ - junos_module.pyez_factory_loader.FactoryLoader().load(table_view) - junos_module.logger.debug("Loader created successfully.") - except Exception as ex: - junos_module.fail_json(msg='Unable to create a table loader from the ' - '%s file. Error: %s' % (file_name, str(ex))) - try: - data = loader[table](junos_module.dev) - junos_module.logger.debug("Table %s created successfully.", table) - if kwargs is None: - data.get() - else: - data.get(**kwargs) - junos_module.logger.debug("Data retrieved from %s successfully.", - table) - except KeyError: - junos_module.fail_json(msg='Unable to find table %s in the ' - '%s file.' % (table, file_name)) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.fail_json(msg='Unable to retrieve data from table %s. ' - 'Error: %s' % (table, str(ex))) - - if data is not None: - try: - len_data = len(data) - except Exception as ex: - junos_module.fail_json(msg='Unable to parse table %s data into ' - 'items. Error: %s' % (table, str(ex))) - junos_module.logger.debug('Successfully retrieved %d items from %s.', - len_data, table) - results['msg'] = 'Successfully retrieved %d items from %s.' % \ - (len_data, table) - - if response_type == 'list_of_dicts': - junos_module.logger.debug('Converting data to list of dicts.') - resource = juniper_items_to_list_of_dicts(junos_module, data) - else: - resource = expand_items(junos_module, data) - - # If we made it this far, everything was successful. - results['failed'] = False - results['resource'] = resource - - # Return response. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() From e82ae89be3d3ef597530319f879b4c9a86bd142b Mon Sep 17 00:00:00 2001 From: Robin Breathe Date: Wed, 12 Feb 2020 12:44:44 +0100 Subject: [PATCH 301/426] Fix ignore_warning with non-boolean arguments --- .../Juniper/junos/plugins/module_utils/juniper_junos_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py b/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py index d7f0151e..7200ac68 100644 --- a/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py @@ -1201,7 +1201,7 @@ def parse_ignore_warning_option(self): if ignore_warn_list is None: return ignore_warn_list if len(ignore_warn_list) == 1: - bool_val = boolean(ignore_warn_list[0]) + bool_val = boolean(ignore_warn_list[0], strict=False) if bool_val is not None: return bool_val elif isinstance(ignore_warn_list[0], basestring): From c61084e8c534e333f3771f3f4b10fb8db8d4cd20 Mon Sep 17 00:00:00 2001 From: Nitin Kr Date: Mon, 16 Mar 2020 17:03:29 +0530 Subject: [PATCH 302/426] chnages to make sure odd value is not entertained for example `ignore_warning: 4` in old commit will consider ignore_warning as False. Hence changing logic to take care of invalid scenarios. --- .../module_utils/juniper_junos_common.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py b/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py index 7200ac68..5c683569 100644 --- a/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py @@ -1201,16 +1201,17 @@ def parse_ignore_warning_option(self): if ignore_warn_list is None: return ignore_warn_list if len(ignore_warn_list) == 1: - bool_val = boolean(ignore_warn_list[0], strict=False) - if bool_val is not None: - return bool_val - elif isinstance(ignore_warn_list[0], basestring): - return ignore_warn_list[0] - else: - self.fail_json(msg="The value of the ignore_warning option " - "(%s) is invalid. Unexpected type (%s)." % - (ignore_warn_list[0], - type(ignore_warn_list[0]))) + try: + bool_val = boolean(ignore_warn_list[0]) + if bool_val is not None: + return bool_val + except TypeError: + if isinstance(ignore_warn_list[0], basestring): + return ignore_warn_list[0] + self.fail_json(msg="The value of the ignore_warning option " + "(%s) is invalid. Unexpected type (%s)." % + (ignore_warn_list[0], + type(ignore_warn_list[0]))) elif len(ignore_warn_list) > 1: for ignore_warn in ignore_warn_list: if not isinstance(ignore_warn, basestring): From 066be5695d6b2e18d0f956cb84ac5303ef1ded99 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 28 Apr 2020 21:13:29 -0400 Subject: [PATCH 303/426] updated pyez ansible and jsnapy versions Signed-off-by: Stephen Steiner --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index c9d1b985..2ffd45ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -ansible >= 2.4 +ansible >= 2.7 junos-eznc -jsnapy==1.3.2 +jsnapy==1.3.4 jxmlease docker -junos-netconify \ No newline at end of file +junos-netconify From 04b1a56f4224dccd167aea96256a23591b4f3066 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 29 Apr 2020 23:17:36 +0530 Subject: [PATCH 304/426] Clean-up of the master branch for ansible collection as roles moved to roles branch --- README.md | 81 ++++++++--------- action_plugins | 1 - ansible_collection/Juniper/junos/README.md | 3 - .../Juniper/junos_collection/README.md | 1 + .../Juniper/junos_collection}/docs/Makefile | 0 .../docs/_static/juniper-junos-modules.css | 0 .../docs/_static/juniper.png | Bin .../junos_collection}/docs/ansible2rst.py | 2 +- .../Juniper/junos_collection}/docs/conf.py | 0 .../Juniper/junos_collection}/docs/docreq.txt | 0 .../Juniper/junos_collection}/docs/rst.j2 | 0 .../Juniper/junos_collection}/galaxy.yml | 2 +- .../junos_collection}/plugins/README.md | 0 .../plugins/action/juniper_junos_command.py | 0 .../action/juniper_junos_common_action.py | 0 .../plugins/action/juniper_junos_config.py | 0 .../plugins/action/juniper_junos_facts.py | 0 .../plugins/action/juniper_junos_jsnapy.py | 0 .../plugins/action/juniper_junos_ping.py | 0 .../plugins/action/juniper_junos_pmtud.py | 0 .../plugins/action/juniper_junos_rpc.py | 0 .../plugins/action/juniper_junos_software.py | 0 .../action/juniper_junos_srx_cluster.py | 0 .../plugins/action/juniper_junos_system.py | 0 .../plugins/action/juniper_junos_table.py | 0 .../plugins/callback/jsnapy.py | 3 + .../plugins/module_utils/__init__.py | 0 .../module_utils/juniper_junos_common.py | 0 .../plugins/modules/juniper_junos_command.py | 7 +- .../plugins/modules/juniper_junos_config.py | 7 +- .../plugins/modules/juniper_junos_facts.py | 7 +- .../plugins/modules/juniper_junos_jsnapy.py | 7 +- .../plugins/modules/juniper_junos_ping.py | 7 +- .../plugins/modules/juniper_junos_pmtud.py | 6 +- .../plugins/modules/juniper_junos_rpc.py | 11 ++- .../plugins/modules/juniper_junos_software.py | 7 +- .../modules/juniper_junos_srx_cluster.py | 7 +- .../plugins/modules/juniper_junos_system.py | 7 +- .../plugins/modules/juniper_junos_table.py | 7 +- callback_plugins | 1 - docs | 1 - library | 1 - module_utils | 1 - setup.py | 3 + tests/ansible.cfg | 3 +- tests/pb.juniper_junos_config.yml | 27 ++---- tests/pb.juniper_junos_facts.yml | 8 +- tests/pb.juniper_junos_jsnapy.yml | 44 +-------- tests/pb.juniper_junos_ping.yml | 24 +---- tests/pb.juniper_junos_pmtud.yml | 8 +- tests/pb.juniper_junos_rpc.yml | 36 +------- tests/pb.rav.token.app_stop.yml | 34 ------- tests/pb.rav.token.create-deploy.yml | 85 ------------------ tests/pb.rav.token.fqdn_get.yml | 54 ----------- tests/ravello.ini | 18 ---- version.py | 4 +- 56 files changed, 108 insertions(+), 417 deletions(-) delete mode 120000 action_plugins delete mode 100644 ansible_collection/Juniper/junos/README.md create mode 120000 ansible_collections/Juniper/junos_collection/README.md rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/docs/Makefile (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/docs/_static/juniper-junos-modules.css (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/docs/_static/juniper.png (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/docs/ansible2rst.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/docs/conf.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/docs/docreq.txt (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/docs/rst.j2 (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/galaxy.yml (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/README.md (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_command.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_common_action.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_config.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_facts.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_jsnapy.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_ping.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_pmtud.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_rpc.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_software.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_srx_cluster.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_system.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/action/juniper_junos_table.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/callback/jsnapy.py (97%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/module_utils/__init__.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/module_utils/juniper_junos_common.py (100%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_command.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_config.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_facts.py (98%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_jsnapy.py (98%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_ping.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_pmtud.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_rpc.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_software.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_srx_cluster.py (98%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_system.py (99%) rename {ansible_collection/Juniper/junos => ansible_collections/Juniper/junos_collection}/plugins/modules/juniper_junos_table.py (99%) delete mode 120000 callback_plugins delete mode 120000 docs delete mode 120000 library delete mode 120000 module_utils delete mode 100644 tests/pb.rav.token.app_stop.yml delete mode 100644 tests/pb.rav.token.create-deploy.yml delete mode 100644 tests/pb.rav.token.fqdn_get.yml delete mode 100644 tests/ravello.ini diff --git a/README.md b/README.md index b3e92250..cd22e178 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,39 @@ -# Juniper Ansible roles for Junos +[![Documentation Status](https://readthedocs.org/projects/junos-ansible-modules/badge/?version=stable)](https://junos-ansible-modules.readthedocs.io/en/2.3.0/) + +# Juniper Ansible collection for Junos + +The repo is under active development. If you take a clone, you are getting the latest, and perhaps not entirely stable code. ## About -Juniper Networks supports Ansible for managing devices running -the Junos operating system (Junos OS). This role is hosted on the Ansible Galaxy website under -the role [Juniper.junos](https://galaxy.ansible.com/Juniper/junos/). The Juniper.junos role includes a set of Ansible -modules that perform specific operational and configuration tasks on devices running Junos OS. These tasks include: +Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS). +This collection is hosted on the Ansible Galaxy website under the collection +[Juniper.junos_collection](https://galaxy.ansible.com/Juniper/junos/). The Juniper.junos_collection collection includes +a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. +These tasks include: installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to the -[INSTALLATION](#installation) section for instructions on installing this role. +[INSTALLATION](#installation) section for instructions on installing this collection. + +#Juniper.junos roles by Juniper Networks + +Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have +Juniper.junos_collection support. Juniper.junos roles have been moved to roles branch. Roles will be supported for now. +For more information for roles, check: +https://github.com/Juniper/ansible-junos-stdlib/tree/roles ## Two Sets of Ansible Modules for Junos devices Since Ansible version >= 2.1, Ansible also natively includes [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included -in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.junos role -have names which begin with the prefix `juniper_junos_`. These two sets of Junos modules can coexist on the same +in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.junos_collection +collection have names which begin with the prefix `juniper_junos_`. These two sets of Junos modules can coexist on the same Ansible control machine, and an Ansible play may invoke a module from either (or both) sets. Juniper Networks recommends -using the modules in this role when writing new playbooks that manage Junos devices. +using the modules in this collection when writing new playbooks that manage Junos devices. ## Overview of Modules -This Juniper.junos role includes the following modules: +This Juniper.junos_collection collection includes the following modules: - **juniper_junos_command** — Execute one or more CLI commands on a Junos device. - **juniper_junos_config** — Manipulate the configuration of a Junos device. @@ -35,17 +47,6 @@ This Juniper.junos role includes the following modules: - **juniper_junos_system** — Initiate operational actions on the Junos system. - **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. -### Important Changes - -Significant changes to the modules in the Juniper.junos role were made between versions 1.4.3 and 2.0.0. -In versions <= 1.4.3 of the Juniper.junos role, the modules used different module and argument names. Versions >= 2.0.0 -of the Juniper.junos role provide backwards compatibility with playbooks written to prior versions of the Juniper.junos -role. If a playbook worked with a prior version of the Juniper.junos role, it should -continue to work on the current version without requiring modifications to the playbook. However, these older module and -argument names are no longer present in the current documentation. You may reference previous module and argument names -by referring directly to the -[1.4.3 version of the Juniper.junos role documentation](http://junos-ansible-modules.readthedocs.io/en/1.4.3/). - ### Overview of Plugins In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `juniper_junos_jsnapy`. @@ -89,9 +90,11 @@ You must have the [DEPENDENCIES](#dependencies) installed on your system. #### Ubuntu 14.04 -If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy install Juniper.junos -c` +If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python +which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process +by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy collection install Juniper.junos_collection -c` - [WARNING]: - Juniper.junos was NOT installed successfully: Failed to get data + [WARNING]: - Juniper.junos_collection was NOT installed successfully: Failed to get data from the API server (https://galaxy.ansible.com/api/): Failed to validate the SSL certificate for galaxy.ansible.com:443. Make sure your managed systems have a valid CA certificate installed. If the website serving the url uses SNI you @@ -106,7 +109,7 @@ If you're dealing with Ubuntu 14.04 and faced following error during the install u'galaxy.ansible.com' doesn't match either of '*.c1e4.galaxy.openshiftapps.com', 'c1e4.galaxy.openshiftapps.com'. - ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list. + ERROR! - you can use --ignore-errors to skip failed collections and finish processing the list. ### MacOS Mojave and newer @@ -116,24 +119,20 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge - Check existing keys: `head -n1 ~/.ssh/some_private_key` RSA keys will be `-----BEGIN RSA PRIVATE KEY-----` and OPENSSH keys will be `-----BEGIN OPENSSH PRIVATE KEY-----` - Convert an OPENSSH key to an RSA key: `ssh-keygen -p -m PEM -f ~/.ssh/some_key` -### Ansible Galaxy Role +### Ansible Galaxy collection -To download the latest released version of the junos role to the Ansible -server, execute the ansible-galaxy install command, and specify **Juniper.junos**. +To download the latest released version of the junos collection to the Ansible +server, execute the ansible-galaxy collection install command, and specify **Juniper.junos_collection**. ```bash -[root@ansible-cm]# ansible-galaxy install Juniper.junos -- downloading role 'junos', owned by Juniper -- downloading role from https://github.com/Juniper/ansible-junos-stdlib/archive/1.3.1.tar.gz -- extracting Juniper.junos to /usr/local/etc/ansible/roles/Juniper.junos -- Juniper.junos was installed successfully +[root@ansible-cm]# ansible-galaxy collection install Juniper.junos_collection ``` You can also use the ansible-galaxy install command to install the latest -development version of the junos role directly from GitHub. +development version of the junos collection directly from GitHub. ```bash -sudo ansible-galaxy install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos +sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos_collection ``` ### Git clone @@ -196,8 +195,8 @@ This example outlines how to use Ansible to install or upgrade the software imag --- - name: Install Junos OS hosts: dc1 - roles: - - Juniper.junos + collections: + - Juniper.junos_collection connection: local gather_facts: no vars: @@ -241,8 +240,8 @@ Apache 2.0 ## SUPPORT -Support for this Juniper.junos role is provided by the community and Juniper Networks. If you have an -issue with a module in the Juniper.junos role, you may: +Support for this Juniper.junos_collection collection is provided by the community and Juniper Networks. If you have an +issue with a module in the Juniper.junos_collection collection, you may: - Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). - Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) @@ -258,8 +257,10 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c [jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) for any queries. *Contributors:* -[Nitin Kumar](https://github.com/vnitinv), [Stacy W Smith](https://github.com/stacywsmith), [Stephen Steiner](https://github.com/ntwrkguru) +[Nitin Kumar](https://github.com/vnitinv), [Rahul Kumar](https://github.com/rahkumar651991) + +* v0.1.0: [Rahul Kumar](https://github.com/rahkumar651991) *Former Contributors:* -[Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) +[Stacy W Smith](https://github.com/stacywsmith), [Stephen Steiner](https://github.com/ntwrkguru), [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) \ No newline at end of file diff --git a/action_plugins b/action_plugins deleted file mode 120000 index 3836d008..00000000 --- a/action_plugins +++ /dev/null @@ -1 +0,0 @@ -ansible_collection/Juniper/junos/plugins/action \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/README.md b/ansible_collection/Juniper/junos/README.md deleted file mode 100644 index 7ee43d2e..00000000 --- a/ansible_collection/Juniper/junos/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ansible Collection - juniper.junos - -Documentation for the collection. \ No newline at end of file diff --git a/ansible_collections/Juniper/junos_collection/README.md b/ansible_collections/Juniper/junos_collection/README.md new file mode 120000 index 00000000..8a33348c --- /dev/null +++ b/ansible_collections/Juniper/junos_collection/README.md @@ -0,0 +1 @@ +../../../README.md \ No newline at end of file diff --git a/ansible_collection/Juniper/junos/docs/Makefile b/ansible_collections/Juniper/junos_collection/docs/Makefile similarity index 100% rename from ansible_collection/Juniper/junos/docs/Makefile rename to ansible_collections/Juniper/junos_collection/docs/Makefile diff --git a/ansible_collection/Juniper/junos/docs/_static/juniper-junos-modules.css b/ansible_collections/Juniper/junos_collection/docs/_static/juniper-junos-modules.css similarity index 100% rename from ansible_collection/Juniper/junos/docs/_static/juniper-junos-modules.css rename to ansible_collections/Juniper/junos_collection/docs/_static/juniper-junos-modules.css diff --git a/ansible_collection/Juniper/junos/docs/_static/juniper.png b/ansible_collections/Juniper/junos_collection/docs/_static/juniper.png similarity index 100% rename from ansible_collection/Juniper/junos/docs/_static/juniper.png rename to ansible_collections/Juniper/junos_collection/docs/_static/juniper.png diff --git a/ansible_collection/Juniper/junos/docs/ansible2rst.py b/ansible_collections/Juniper/junos_collection/docs/ansible2rst.py similarity index 99% rename from ansible_collection/Juniper/junos/docs/ansible2rst.py rename to ansible_collections/Juniper/junos_collection/docs/ansible2rst.py index a5809813..ec4531a3 100755 --- a/ansible_collection/Juniper/junos/docs/ansible2rst.py +++ b/ansible_collections/Juniper/junos_collection/docs/ansible2rst.py @@ -410,7 +410,7 @@ def main(): index_file_path = os.path.join(OUTPUTDIR, "index.rst") index_file = open(index_file_path, "w") - index_file.write('Juniper.junos Ansible Modules\n') + index_file.write('Juniper.junos_collection Ansible Modules\n') index_file.write('=================================================\n') index_file.write('\n') index_file.write('Contents:\n') diff --git a/ansible_collection/Juniper/junos/docs/conf.py b/ansible_collections/Juniper/junos_collection/docs/conf.py similarity index 100% rename from ansible_collection/Juniper/junos/docs/conf.py rename to ansible_collections/Juniper/junos_collection/docs/conf.py diff --git a/ansible_collection/Juniper/junos/docs/docreq.txt b/ansible_collections/Juniper/junos_collection/docs/docreq.txt similarity index 100% rename from ansible_collection/Juniper/junos/docs/docreq.txt rename to ansible_collections/Juniper/junos_collection/docs/docreq.txt diff --git a/ansible_collection/Juniper/junos/docs/rst.j2 b/ansible_collections/Juniper/junos_collection/docs/rst.j2 similarity index 100% rename from ansible_collection/Juniper/junos/docs/rst.j2 rename to ansible_collections/Juniper/junos_collection/docs/rst.j2 diff --git a/ansible_collection/Juniper/junos/galaxy.yml b/ansible_collections/Juniper/junos_collection/galaxy.yml similarity index 99% rename from ansible_collection/Juniper/junos/galaxy.yml rename to ansible_collections/Juniper/junos_collection/galaxy.yml index 264702e4..9e62164b 100644 --- a/ansible_collection/Juniper/junos/galaxy.yml +++ b/ansible_collections/Juniper/junos_collection/galaxy.yml @@ -6,7 +6,7 @@ namespace: Juniper # The name of the collection. Has the same character restrictions as 'namespace' -name: junos +name: junos_collection # The version of the collection. Must be compatible with semantic versioning version: 0.1.0 diff --git a/ansible_collection/Juniper/junos/plugins/README.md b/ansible_collections/Juniper/junos_collection/plugins/README.md similarity index 100% rename from ansible_collection/Juniper/junos/plugins/README.md rename to ansible_collections/Juniper/junos_collection/plugins/README.md diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_command.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_command.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_command.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_command.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_common_action.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_common_action.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_common_action.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_common_action.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_config.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_config.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_config.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_config.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_facts.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_facts.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_facts.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_facts.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_jsnapy.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_jsnapy.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_jsnapy.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_jsnapy.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_ping.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_ping.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_ping.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_ping.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_pmtud.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_pmtud.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_pmtud.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_pmtud.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_rpc.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_rpc.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_rpc.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_rpc.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_software.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_software.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_software.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_software.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_srx_cluster.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_srx_cluster.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_system.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_system.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_system.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_system.py diff --git a/ansible_collection/Juniper/junos/plugins/action/juniper_junos_table.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_table.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/action/juniper_junos_table.py rename to ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_table.py diff --git a/ansible_collection/Juniper/junos/plugins/callback/jsnapy.py b/ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py similarity index 97% rename from ansible_collection/Juniper/junos/plugins/callback/jsnapy.py rename to ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py index 66f1dec9..3c5b5462 100644 --- a/ansible_collection/Juniper/junos/plugins/callback/jsnapy.py +++ b/ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py @@ -20,6 +20,9 @@ class CallbackModule(CallbackBase): CALLBACK_TYPE = 'aggregate' CALLBACK_NAME = 'jsnapy' + # only needed if you ship it and don't want to enable by default + CALLBACK_NEEDS_WHITELIST = True + ## useful links regarding Callback ## https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py diff --git a/ansible_collection/Juniper/junos/plugins/module_utils/__init__.py b/ansible_collections/Juniper/junos_collection/plugins/module_utils/__init__.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/module_utils/__init__.py rename to ansible_collections/Juniper/junos_collection/plugins/module_utils/__init__.py diff --git a/ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py b/ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py similarity index 100% rename from ansible_collection/Juniper/junos/plugins/module_utils/juniper_junos_common.py rename to ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_command.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_command.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py index 259ef687..bd80278c 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_command.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py @@ -46,7 +46,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_command -version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Execute one or more CLI commands on a Junos device description: @@ -152,8 +151,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Execute single "show version" command. @@ -309,7 +308,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_config.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_config.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py index 6cbeba61..f6ca827c 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_config.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py @@ -47,7 +47,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_config -version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Manipulate the configuration of a Junos device description: @@ -521,8 +520,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Retrieve the committed configuration juniper_junos_config: @@ -740,7 +739,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): # Choices which are defined in the common module. diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_facts.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_facts.py similarity index 98% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_facts.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_facts.py index 75d167fd..86700ae7 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_facts.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_facts.py @@ -46,7 +46,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_facts -version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Retrieve facts from a Junos device description: @@ -90,8 +89,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Gather Junos facts with no configuration juniper_junos_facts: @@ -177,7 +176,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common from ansible.module_utils._text import to_bytes def get_facts_dict(junos_module): diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_jsnapy.py similarity index 98% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_jsnapy.py index 581d454b..967b02e8 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_jsnapy.py @@ -46,7 +46,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_jsnapy -version_added: "2.0.0" # of Juniper.junos role author: - Juniper Networks - Roslan Zaki @@ -115,8 +114,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: JUNOS Post Checklist @@ -209,7 +208,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_ping.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_ping.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_ping.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_ping.py index 230031ca..cfcb477a 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_ping.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_ping.py @@ -46,7 +46,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_ping -version_added: "2.0.0" # of Juniper.junos role author: Juniper Networks - Stacy Smith (@stacywsmith) short_description: Execute ping from a Junos device description: @@ -146,8 +145,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. @@ -386,7 +385,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): # The argument spec for the module. diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_pmtud.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_pmtud.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_pmtud.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_pmtud.py index c5f12dd8..ca05bb95 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_pmtud.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_pmtud.py @@ -132,8 +132,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Perform PMTUD to 192.68.1.1 with default parameters. @@ -248,7 +248,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): # Constants for MTU size diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_rpc.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_rpc.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py index f23e310b..a0487758 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_rpc.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py @@ -47,7 +47,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_rpc -version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Execute one or more NETCONF RPCs on a Junos device description: @@ -190,8 +189,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Execute single get-software-information RPC. @@ -246,8 +245,8 @@ # and use it with rpc_reply - name: Get Device Configuration hosts: all - roles: - - Juniper.junos + collections: + - Juniper.junos_collection connection: local gather_facts: no tasks: @@ -376,7 +375,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_software.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_software.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py index 8fd2bfce..66408908 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py @@ -46,7 +46,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_software -version_added: "2.0.0" # of Juniper.junos role author: - Jeremy Schulman - "Juniper Networks - Stacy Smith (@stacywsmith)" @@ -322,8 +321,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Execute a basic Junos software upgrade. @@ -391,7 +390,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def parse_version_from_filename(filename): """Attempts to parse a version string from the filename of a Junos package. diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_srx_cluster.py similarity index 98% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_srx_cluster.py index acfd5a42..a651b81c 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_srx_cluster.py @@ -47,7 +47,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_srx_cluster -version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Add or remove SRX chassis cluster configuration description: @@ -95,8 +94,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Enable an SRX cluster juniper_junos_srx_cluster: @@ -150,7 +149,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_system.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_system.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py index d9a2ea18..2503844d 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py @@ -46,7 +46,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_system -version_added: "2.0.0" # of Juniper.junos role author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Initiate operational actions on the Junos system description: @@ -157,8 +156,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Reboot all REs of the device @@ -250,7 +249,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_table.py similarity index 99% rename from ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py rename to ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_table.py index ded0bcca..bdce4c07 100644 --- a/ansible_collection/Juniper/junos/plugins/modules/juniper_junos_table.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_table.py @@ -48,7 +48,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_table -version_added: "2.0.0" # of Juniper.junos role author: - Jason Edelman (@jedelman8) - Updated by Juniper Networks - Stacy Smith (@stacywsmith) @@ -128,8 +127,8 @@ hosts: junos-all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table @@ -292,7 +291,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common def expand_items(module, data): """Recursively expand any table items diff --git a/callback_plugins b/callback_plugins deleted file mode 120000 index 672f918b..00000000 --- a/callback_plugins +++ /dev/null @@ -1 +0,0 @@ -ansible_collection/Juniper/junos/plugins/callback \ No newline at end of file diff --git a/docs b/docs deleted file mode 120000 index 0a437da1..00000000 --- a/docs +++ /dev/null @@ -1 +0,0 @@ -ansible_collection/Juniper/junos/docs \ No newline at end of file diff --git a/library b/library deleted file mode 120000 index de06a658..00000000 --- a/library +++ /dev/null @@ -1 +0,0 @@ -ansible_collection/Juniper/junos/plugins/modules \ No newline at end of file diff --git a/module_utils b/module_utils deleted file mode 120000 index 356a3b32..00000000 --- a/module_utils +++ /dev/null @@ -1 +0,0 @@ -ansible_collection/Juniper/junos/plugins/module_utils \ No newline at end of file diff --git a/setup.py b/setup.py index 62585c2b..419afdbc 100755 --- a/setup.py +++ b/setup.py @@ -23,6 +23,9 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/tests/ansible.cfg b/tests/ansible.cfg index 7435948f..ef7c4505 100644 --- a/tests/ansible.cfg +++ b/tests/ansible.cfg @@ -1,4 +1,5 @@ [defaults] hash_behaviour=merge -roles_path = /etc/ansible/roles +roles_path = ~/.ansible/roles + diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 5ecbc4cf..e41657e6 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -3,8 +3,8 @@ hosts: all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: ################# - name: Retrieve the committed configuration @@ -21,7 +21,6 @@ assert: that: - test1.config - - "'host-name choc-qfx-a;' in test1.config" ################# - name: Append .foo to the hostname using private config mode. juniper_junos_config: @@ -37,7 +36,6 @@ assert: that: - test2.diff_lines - - "'+ host-name choc-qfx-a.foo;' in test2.diff_lines" ################# - name: Rollback to the previous config. juniper_junos_config: @@ -51,7 +49,6 @@ assert: that: - test3.diff_lines - - "'- host-name choc-qfx-a.foo;' in test3.diff_lines" ################# - name: Creates directory file: @@ -80,18 +77,13 @@ - name: Rollback to the rescue config. juniper_junos_config: rollback: 'rescue' - - - name: Check out/choc-qfx-a.diff exists - stat: - path: out/choc-qfx-a.diff - register: stat_result_1 - - - name: Check TEST 4 - assert: - that: - - stat_result_1.stat.exists == True - - test4.diff_lines - - "'+ interface ge-1/1/1;' in test4.diff_lines" +# +# - name: Check TEST 4 +# assert: +# that: +# - stat_result_1.stat.exists == True +# - test4.diff_lines +# - "'+ interface ge-1/1/1;' in test4.diff_lines" - name: Clean up TEST 4 file: @@ -115,4 +107,3 @@ - test5.failed == False - "'system {' in test5.config_lines" ################# -#TODO: Add tests for commit check and commit confirmed workflows diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index c585b025..1c22713f 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -3,15 +3,11 @@ hosts: all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: "TEST 1 - Gather Facts" juniper_junos_facts: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" ignore_errors: True register: test1 diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index 4318c76f..bcf4d3b4 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -3,18 +3,14 @@ hosts: all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: ################################################## #### TEST 1 ## ################################################## - name: "TEST 1 - Execute SNAPCHECK with 1 test file / no dir" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: junos_jsnapy/test_junos_storage.yml action: snapcheck register: test1 @@ -35,10 +31,6 @@ ################################################## - name: "TEST 2 - Execute SNAPCHECK with 2 test file & dir" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: - test_junos_storage.yml - test_version.yml @@ -62,10 +54,6 @@ ################################################## - name: "TEST 3 - Wrong test file" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: file_that_doesnt_exist.yml action: snapcheck register: test3 @@ -84,10 +72,6 @@ ################################################## - name: "TEST 4 - SNAP_PRE" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: junos_jsnapy/test_loopback.yml action: snap_pre register: test4 @@ -107,10 +91,6 @@ ################################################## - name: "TEST 5 - SNAP_POST" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: junos_jsnapy/test_loopback.yml action: snap_post register: test5 @@ -130,10 +110,6 @@ ################################################## - name: "TEST 6 - CHECK" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: junos_jsnapy/test_loopback.yml action: check register: test6 @@ -154,10 +130,6 @@ ################################################## - name: "PRE-TEST 7 - Add loopback address" juniper_junos_config: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" load: 'merge' file: junos_jsnapy/add_loopback.set register: test7_1 @@ -169,10 +141,6 @@ - name: "TEST 7 - SNAP_POST with additional loopback" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: junos_jsnapy/test_loopback.yml action: snap_post register: test7_2 @@ -182,10 +150,6 @@ - name: "TEST 7 - CHECK" juniper_junos_jsnapy: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" test_files: junos_jsnapy/test_loopback.yml action: check register: test7 @@ -195,10 +159,6 @@ - name: "TEST 7 - Cleanup" juniper_junos_config: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" file: junos_jsnapy/delete_loopback.set load: 'merge' register: test7_3 diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index c8fb77c2..7b6e12f4 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -3,15 +3,11 @@ hosts: all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: "TEST 1 - Ping Google DNS" juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" dest_ip: 8.8.8.8 register: test1 ignore_errors: True @@ -26,10 +22,6 @@ - name: "TEST 2 - Ping Wrong IP" juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" dest_ip: 8.8.1.1 register: test2 ignore_errors: True @@ -43,10 +35,6 @@ - name: "TEST 3 - Change nbr packets" juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" dest_ip: 8.8.8.8 count: 3 register: test3 @@ -62,10 +50,6 @@ - name: "TEST 4 - Ping with DF-bit set" juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" dest_ip: 8.8.8.8 count: 3 do_not_fragment: True @@ -83,10 +67,6 @@ - name: "TEST 5 - Ping with DF-bit set and size that well exceeds jumbo sizes" juniper_junos_ping: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" dest_ip: 8.8.8.8 count: 3 do_not_fragment: True diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index cdf3fb41..8ee7985a 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -3,15 +3,11 @@ hosts: all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: - name: "TEST 1 - Check path MTU to Google DNS" juniper_junos_pmtud: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" dest_ip: 8.8.8.8 register: test1 ignore_errors: True diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 22a195da..166598d1 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -3,17 +3,13 @@ hosts: all connection: local gather_facts: no - roles: - - Juniper.junos + collections: + - Juniper.junos_collection tasks: ################# - name: "Execute single RPC get-software-information without any kwargs" juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpcs: - "get-software-information" register: test1 @@ -29,10 +25,6 @@ ################# - name: "Get Device Configuration with dest" juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpc: get-config dest: get_config.conf register: test2 @@ -59,10 +51,6 @@ - name: "Get Device Configuration in text" juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpc: get-interface-information kwargs: "interface_name=em0" format: text @@ -80,10 +68,6 @@ - name: "Execute multiple RPCs without any kwargs" juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpcs: - "get-software-information" - "get-interface-information" @@ -108,10 +92,6 @@ - name: "Execute multiple RPCs with multiple kwargs" juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpcs: - "get-software-information" - "get-interface-information" @@ -137,10 +117,6 @@ - name: "Execute multiple RPCs with multiple kwargs and dest-dir" juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpcs: - "get-software-information" - "get-interface-information" @@ -179,10 +155,6 @@ ################# - name: Get Device Configuration for interface juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpc: get-config filter_xml: "" register: test7 @@ -198,10 +170,6 @@ ################# - name: "Execute wrong RPC to generate RPC error" juniper_junos_rpc: - host: "{{ ansible_ssh_host }}" - port: "{{ ansible_ssh_port }}" - user: "{{ ansible_ssh_user }}" - passwd: "{{ ansible_ssh_pass }}" rpcs: - "wrong-rpc" register: test8 diff --git a/tests/pb.rav.token.app_stop.yml b/tests/pb.rav.token.app_stop.yml deleted file mode 100644 index e1d8fadc..00000000 --- a/tests/pb.rav.token.app_stop.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -- name: Get FQDN for all VM on Ravello - connection: local - hosts: all - gather_facts: no - roles: - - ravello.lib - - tasks: -############################### -## Get VM ID ### -############################### - - name: Get App ID from Ravello - ravello_get_id: - resource_name: "{{ ravello_ci_app_name }}" - resource_type: applications - token: "{{ ravello_ci_token }}" - register: app - run_once: true - - -############################### -## Delete the application ## -############################### - - name: Stop Application on Ravello - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/stop" - method: POST - status_code: 202 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - run_once: true - changed_when: true diff --git a/tests/pb.rav.token.create-deploy.yml b/tests/pb.rav.token.create-deploy.yml deleted file mode 100644 index 4f789e35..00000000 --- a/tests/pb.rav.token.create-deploy.yml +++ /dev/null @@ -1,85 +0,0 @@ ---- -- name: Create Application on Ravello for CI - connection: local - hosts: all - gather_facts: no - roles: - - ravello.lib - vars: - ravello_deploy_topology_cloud: AMAZON - ravello_deploy_topology_region: Oregon - ravello_deploy_topology_optimization: PERFORMANCE_OPTIMIZED - ravello_deploy_topology_start_all: true - - tasks: - - name: Create Application from Blueprint for CI - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/" - method: POST - status_code: 201 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - body: - name: "{{ ravello_ci_app_name }}" - description: "App created by Travis CI" - baseBlueprintId: "{{ ravello_ci_blueprint }}" - body_format: json - run_once: true - changed_when: true - - - name: Get App ID from Ravello - ravello_get_id: - resource_name: "{{ ravello_ci_app_name }}" - resource_type: applications - token: "{{ ravello_ci_token }}" - register: app - run_once: true - - - debug: var=app - run_once: true - -####################################################### -## Deploy Application(s) ## -####################################################### - - name: Deploy Application On Ravello - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/publish" - method: POST - status_code: 202 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - body: > - { - "preferredCloud": "{{ ravello_deploy_topology_cloud }}", - "preferredRegion": "{{ ravello_deploy_topology_region }}", - "optimizationLevel": "{{ ravello_deploy_topology_optimization }}", - "startAllVms": "{{ ravello_deploy_topology_start_all }}" - } - body_format: json - run_once: true - -######################################## -## Set application Expiration time ## -######################################## - - name: Set Application Expiration time - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/setExpiration" - method: POST - status_code: 200 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - body: '{ "expirationFromNowSeconds": {{ ravello_ci_expiration_time_min * 60 }} }' - body_format: json - run_once: true - - - name: Wait for devices to come up - pause: minutes=5 - - - name: Wait for devices to come up - pause: minutes=5 - - - name: Wait for devices to come up - pause: minutes=2 diff --git a/tests/pb.rav.token.fqdn_get.yml b/tests/pb.rav.token.fqdn_get.yml deleted file mode 100644 index 6304c2cf..00000000 --- a/tests/pb.rav.token.fqdn_get.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -- name: Get FQDN for all VM on Ravello - connection: local - hosts: junos - gather_facts: no - roles: - - ravello.lib - - tasks: - -############################### -## Get VM ID ### -############################### - - name: Get App ID from Ravello - ravello_get_id: - resource_name: "{{ ravello_ci_app_name }}" - resource_type: applications - token: "{{ ravello_ci_token }}" - register: app - run_once: true - - # - debug: var=app - - - name: Get VM ID from Ravello - ravello_get_id: - application_id: "{{ app.json.id }}" - resource_type: vms - resource_name: "{{ inventory_hostname }}" - token: "{{ ravello_ci_token }}" - failed_if_not_found: true - register: vm - - # - debug: var=vm - - - name: Get VM public FQDN - uri: - url: "https://cloud.ravellosystems.com/api/v1/applications/{{ app.json.id }}/vms/{{ vm.json.id }}/fqdn;deployment" - method: GET - status_code: 200 - HEADER_Content-Type: 'application/json' - HEADER_Accept: 'application/json' - HEADER_X-Ephemeral-Token-Authorization: "{{ ravello_ci_token }}" - register: ravello_public_ip - - - name: Delete previous file - file: - path: "host_vars/{{ inventory_hostname}}/fqdn.yaml" - state: absent - - - name: Populate ansible_ssh_host Variable based on FQDN - lineinfile: - create: yes - dest: "host_vars/{{ inventory_hostname}}/fqdn.yaml" - line: "ansible_ssh_host: {{ ravello_public_ip.json.value }}" diff --git a/tests/ravello.ini b/tests/ravello.ini deleted file mode 100644 index 051bbefb..00000000 --- a/tests/ravello.ini +++ /dev/null @@ -1,18 +0,0 @@ -[all:children] -junos - -[junos] -vqfx-01 -vqfx-02 - -################################### -### Define variables per groups ### -################################### -[all:vars] -ansible_ssh_user=root -ansible_ssh_pass=Juniper -ansible_ssh_port=22 -ravello_ci_app_name="Ansible-junos-stdlib Ansible_{{ lookup('env','ANSIBLE_VERSION') }} Travis_{{ lookup('env','TRAVIS_JOB_ID') }} {{ lookup('env','TRAVIS_COMMIT') }}" -ravello_ci_blueprint="75695295" -ravello_ci_token="GtHFbCOuKgD1pcfkvCCIgenj6DOtn3VgRLjaYipdideCsiPC1NxJitt1UHfhF0Bf" -ravello_ci_expiration_time_min=50 diff --git a/version.py b/version.py index dfff5cd8..bdb3ef47 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "2.3.0" -DATE = "2019-Dec-13" +VERSION = "0.1.0" +DATE = "2020-Apr-30" From 54d76f0e9cd4ddb3c8c9937d81d764c7412473e9 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 8 May 2020 15:51:32 +0530 Subject: [PATCH 305/426] changes for ansible actions of reboot shutdown to be handled in Pyez --- .../plugins/modules/juniper_junos_software.py | 74 ++------ .../plugins/modules/juniper_junos_system.py | 171 ++++-------------- 2 files changed, 55 insertions(+), 190 deletions(-) diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py index 66408908..9655f03f 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py @@ -708,67 +708,23 @@ def main(): "to 5 seconds.") junos_module.dev.timeout = 5 junos_module.logger.debug('Initiating reboot.') - xpath_list = ['.//request-reboot-status'] - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-reboot') - elif install_params.get('vmhost'): - rpc = junos_module.etree.Element('request-vmhost-reboot') - # RPC reply can contain multiple output tags - xpath_list.append('output') - else: - rpc = junos_module.etree.Element('request-reboot') - - if all_re is True: - if (junos_module.sw._multi_RE is True and - junos_module.sw._multi_VC is False): - junos_module.etree.SubElement(rpc, - 'both-routing-engines') - # At least on some platforms stopping/rebooting both - # REs produces messages and - # messages. - xpath_list.append('output') - elif junos_module.sw._mixed_VC is True: - junos_module.etree.SubElement(rpc, 'all-members') - resp = junos_module.dev.rpc(rpc, - ignore_warning=True, - normalize=True) - junos_module.logger.debug("Reboot RPC executed cleanly.") - if len(xpath_list) > 0: - obj = resp.getparent() - for xpath in xpath_list: - if junos_module.dev.facts['_is_linux']: - got = resp.text - else: - # there are cases where rpc-reply will have multiple - # child element, hence lets work on parent. - # for ex: - # Rebooting fpc1 - # - # - # Shutdown at Mon Jun 24 10:16:35 2019. - # [pid 1949] - # - # - if xpath == 'output': - got = '\n'.join([i.text for i in obj.findall('output') - if i.text is not None]) - else: - got = obj.findtext(xpath) - if got is not None: - results['msg'] += ' Reboot successfully initiated. ' \ + + got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) + + junos_module.logger.debug("Reboot RPC executed.") + + if got is not None: + results['msg'] += ' Reboot successfully initiated. ' \ 'Reboot message: %s' % got - break - else: - # This is the else clause of the for loop. - # It only gets executed if the loop finished without - # hitting the break. - results['msg'] += ' Did not find expected response ' \ - 'from reboot RPC. RPC response is ' \ - '%s' % \ - junos_module.etree.tostring(resp) - junos_module.fail_json(**results) else: - results['msg'] += ' Reboot successfully initiated.' + # This is the else clause of the for loop. + # It only gets executed if the loop finished without + # hitting the break. + results['msg'] += ' Did not find expected response ' \ + 'from reboot RPC. RPC response is ' \ + '%s' % \ + junos_module.etree.tostring(resp) + junos_module.fail_json(**results) except (junos_module.pyez_exception.RpcTimeoutError) as ex: # This might be OK. It might just indicate the device didn't # send the closing (known Junos bug). diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py index 2503844d..b878f34f 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py @@ -285,123 +285,39 @@ def main(): params = junos_module.params action = params['action'] - # Synonymns for shutdown + at = params.get('at') + in_min = params.get('in_min') + all_re = params.get('all_re') + other_re = params.get('other_re') + media = params.get('media') + + # Synonymn for shutdown if action == 'off' or action == 'power_off' or action == 'power-off': action = 'shutdown' - # at option only applies to reboot, shutdown, or halt actions. - if (params.get('at') is not None and - action != 'reboot' and - action != 'shutdown' and - action != 'halt'): - junos_module.fail_json(msg='The at option can only be used when ' - 'the action option has the value "reboot", ' - '"shutdown", or "halt".') - - # in_min option only applies to reboot, shutdown, or halt actions. - if (params.get('in_min') is not None and - action != 'reboot' and - action != 'shutdown' and - action != 'halt'): - junos_module.fail_json(msg='The in_min option can only be used when ' - 'the action option has the value "reboot", ' - '"shutdown", or "halt".') - - # other_re option only applies to reboot, shutdown, or halt actions. - if (params.get('other_re') is True and - action != 'reboot' and - action != 'shutdown' and - action != 'halt'): - junos_module.fail_json(msg='The other_re option can only be used when ' - 'the action option has the value "reboot", ' - '"shutdown", or "halt".') + #Four actions are expected - reboot, shutdown, halt and zeroize + if action not in {'reboot', 'shutdown', 'halt'}: + # at, in_min and other_re option only applies to reboot, shutdown, or halt action. + for str,val in {"at":at,"in_min":in_min,"other_re":other_re}: + if val is not None: + junos_module.fail_json(msg='The %s option can only be used when ' + 'the action option has the value "reboot", ' + '"shutdown", or "halt".' % str) - # media option only applies to zeroize action. - if params['media'] is True and action != 'zeroize': + elif media is True: # media option only applies to zeroize action. junos_module.fail_json(msg='The media option can only be used when ' - 'the action option has the value ' - '"zeroize".') - - # If other_re, then we should turn off all_re - if params['other_re'] is True: - params['all_re'] = False + 'the action option has the value "zeroize".') # Set initial results values. Assume failure until we know it's success. - # Assume we haven't changed the state until we do. - results = {'changed': False, + results = {'changed': True, 'msg': '', 'reboot': bool(action == 'reboot'), 'action': action, - 'all_re': params.get('all_re'), - 'other_re': params.get('other_re'), - 'media': params.get('media'), + 'all_re': all_re, + 'other_re': other_re, + 'media': media, 'failed': True} - # Map the action to an RPC. - rpc = None - xpath_list = [] - if action == 'reboot': - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-reboot') - else: - rpc = junos_module.etree.Element('request-reboot') - xpath_list.append('.//request-reboot-status') - elif action == 'shutdown': - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-power-off') - else: - rpc = junos_module.etree.Element('request-power-off') - xpath_list.append('.//request-reboot-status') - elif action == 'halt': - if junos_module.dev.facts['_is_linux']: - rpc = junos_module.etree.Element('request-shutdown-halt') - else: - rpc = junos_module.etree.Element('request-halt') - xpath_list.append('.//request-reboot-status') - elif action == 'zeroize': - rpc = junos_module.etree.Element('request-system-zeroize') - else: - results['msg'] = 'No RPC found for the %s action.' % (action) - junos_module.fail_json(**results) - - # Add the arguments - if action == 'zeroize': - if params['all_re'] is False: - if junos_module.dev.facts['2RE']: - junos_module.etree.SubElement(rpc, 'local') - if params['media'] is True: - junos_module.etree.SubElement(rpc, 'media') - else: - if params['in_min'] is not None: - junos_module.etree.SubElement(rpc, - 'in').text = str(params['in_min']) - elif params['at'] is not None: - junos_module.etree.SubElement(rpc, - 'at').text = params['at'] - if params['other_re'] is True: - if junos_module.dev.facts['2RE']: - junos_module.etree.SubElement(rpc, 'other-routing-engine') - # At least on some platforms stopping/rebooting the other RE - # just produces messages. - xpath_list.append('..//output') - elif params['all_re'] is True: - junos_module.add_sw() - if (junos_module.sw._multi_RE is True and - junos_module.sw._multi_VC is False): - junos_module.etree.SubElement(rpc, 'both-routing-engines') - # At least on some platforms stopping/rebooting both REs - # produces messages and - # messages. - xpath_list.append('..//output') - elif junos_module.sw._mixed_VC is True: - junos_module.etree.SubElement(rpc, 'all-members') - - # OK, we're ready to do something. Set changed and log the RPC. - results['changed'] = True - junos_module.logger.debug("Ready to execute RPC: %s", - junos_module.etree.tostring(rpc, - pretty_print=True)) - if not junos_module.check_mode: if action != 'zeroize': # If we're going to do a shutdown, reboot, or halt right away then @@ -409,8 +325,8 @@ def main(): # and therefore might get an RpcTimeout. # (This is a known Junos bug.) Set the timeout low so this happens # relatively quickly. - if (params['at'] == 'now' or params['in_min'] == 0 or - (params['at'] is None and params['in_min'] is None)): + if (at == 'now' or in_min == 0 or + (at is None and in_min is None)): if junos_module.dev.timeout > 5: junos_module.logger.debug("Decreasing device RPC timeout " "to 5 seconds.") @@ -418,30 +334,23 @@ def main(): # Execute the RPC. try: - junos_module.logger.debug( - "Executing RPC: %s", - junos_module.etree.tostring(rpc, pretty_print=True)) - resp = junos_module.dev.rpc(rpc, - ignore_warning=True, - normalize=True) - junos_module.logger.debug("RPC executed cleanly.") - if len(xpath_list) > 0: - for xpath in xpath_list: - if junos_module.dev.facts['_is_linux']: - got = resp.text - else: - got = resp.findtext(xpath) - if got is not None: - results['msg'] = '%s successfully initiated.' % \ - (action) - results['failed'] = False - break - else: - # This is the else clause of the for loop. - # It only gets executed if the loop finished without - # hitting the break. - results['msg'] = 'Did not find expected RPC response.' - results['changed'] = False + junos_module.logger.debug("Executing RPC") + + if action == 'reboot': + got = junos_module.sw.reboot(in_min, at, all_re, None, False, other_re) + elif action == 'shutdown': + got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) + elif action == 'halt': + got = junos_module.sw.halt(in_min, at, all_re, other_re) + elif action == 'zeroize': + got = junos_module.sw.halt(all_re, media) + else: + junos_module.fail_json(msg='Relevant action not found') + + junos_module.logger.debug("RPC executed") + if got is None: + results['msg'] = 'Did not find expected RPC response.' + results['changed'] = False else: results['msg'] = '%s successfully initiated.' % (action) results['failed'] = False From 9127619840f98ecb42575b6f63e1a24ac4553e30 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 8 May 2020 21:53:13 +0530 Subject: [PATCH 306/426] Call zeroize function on that action --- .../junos_collection/plugins/modules/juniper_junos_system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py index b878f34f..41a992c8 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py @@ -343,7 +343,7 @@ def main(): elif action == 'halt': got = junos_module.sw.halt(in_min, at, all_re, other_re) elif action == 'zeroize': - got = junos_module.sw.halt(all_re, media) + got = junos_module.sw.zeroize(all_re, media) else: junos_module.fail_json(msg='Relevant action not found') From e3f9ea279b69d7f37d2dcc2196436cf8387b9906 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Tue, 12 May 2020 15:25:27 +0530 Subject: [PATCH 307/426] Adding testcases --- tests/pb.juniper_junos_config.yml | 69 ++++++++++++++++++------------- tests/pb.juniper_junos_facts.yml | 10 +++++ tests/pb.juniper_junos_ping.yml | 22 ++-------- tests/pb.juniper_junos_pmtud.yml | 17 +++++++- 4 files changed, 70 insertions(+), 48 deletions(-) diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index e41657e6..932574c7 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -28,6 +28,7 @@ load: 'merge' lines: - "set system host-name {{ inventory_hostname }}.foo" + comment: "Append .foo to the hostname" register: test2 ignore_errors: True tags: [ test2 ] @@ -55,28 +56,28 @@ path: out state: directory - - name: Configure LLDP - juniper_junos_config: - load: 'merge' - lines: - - "set protocols lldp advertisement-interval 30" - - "set protocols lldp transmit-delay 2" - - "set protocols lldp hold-multiplier 4" - - "set protocols lldp ptopo-configuration-trap-interval 30" - - "set protocols lldp ptopo-configuration-maximum-hold-time 300" - - "set protocols lldp lldp-configuration-notification-interval 30" - - "set protocols lldp interface all disable" - - "set protocols lldp interface ge-1/1/1" - format: 'set' - comment: 'Start LLDP with given options' - dest_dir: './out' - register: test4 - ignore_errors: True - tags: [ test4 ] - - - name: Rollback to the rescue config. - juniper_junos_config: - rollback: 'rescue' +# - name: Configure LLDP +# juniper_junos_config: +# load: 'merge' +# lines: +# - "set protocols lldp advertisement-interval 30" +# - "set protocols lldp transmit-delay 2" +# - "set protocols lldp hold-multiplier 4" +# - "set protocols lldp ptopo-configuration-trap-interval 30" +# - "set protocols lldp ptopo-configuration-maximum-hold-time 300" +# - "set protocols lldp lldp-configuration-notification-interval 30" +# - "set protocols lldp interface all disable" +# - "set protocols lldp interface ge-1/1/1" +# format: 'set' +# comment: 'Start LLDP with given options' +# dest_dir: './out' +# register: test4 +# ignore_errors: True +# tags: [ test4 ] +# +# - name: Rollback to the rescue config. +# juniper_junos_config: +# rollback: 'rescue' # # - name: Check TEST 4 # assert: @@ -84,12 +85,12 @@ # - stat_result_1.stat.exists == True # - test4.diff_lines # - "'+ interface ge-1/1/1;' in test4.diff_lines" - - - name: Clean up TEST 4 - file: - path: out - state: absent -################# +# +# - name: Clean up TEST 4 +# file: +# path: out +# state: absent +################ - name: Retrieve [edit system services] of current committed config. juniper_junos_config: retrieve: 'committed' @@ -107,3 +108,15 @@ - test5.failed == False - "'system {' in test5.config_lines" ################# + + - name: Confirm the previous commit with a commit check (but no commit) + juniper_junos_config: + check: true + diff: false + commit: false + register: test6 + + - name: Check TEST 6 + assert: + that: + - test6.changed == False \ No newline at end of file diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 1c22713f..86de1afc 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -20,3 +20,13 @@ - test1.facts.serialnumber - test1.facts.model - test1.facts.fqdn + + - name: TEST 2 - get facts in xml format + juniper_junos_facts: + config_format: xml + register: test2 + + - name: Check TEST 2 + assert: + that: + - "' Date: Mon, 22 Jun 2020 13:57:12 +0530 Subject: [PATCH 308/426] Adding vmhost support in juniper_junos_system --- .../plugins/modules/juniper_junos_system.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py index 41a992c8..d138ee38 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py @@ -137,6 +137,12 @@ required: false default: false type: bool + vmhost: + description: + - Whether or not this is a vmhost reboot. + required: false + default: false + type: bool notes: - This module only B(INITIATES) the action. It does B(NOT) wait for the action to complete. @@ -273,6 +279,9 @@ def main(): other_re=dict(type='bool', required=False, default=False), + vmhost=dict(required=False, + type='bool', + default=False), media=dict(type='bool', required=False, default=False), @@ -290,19 +299,24 @@ def main(): all_re = params.get('all_re') other_re = params.get('other_re') media = params.get('media') + vmhost = params.get('vmhost') # Synonymn for shutdown if action == 'off' or action == 'power_off' or action == 'power-off': action = 'shutdown' + if action == 'reboot' and vmhost is True: + junos_module.fail_json(msg='The vmhost option can only be used when ' + 'the action option has the value "reboot".') + #Four actions are expected - reboot, shutdown, halt and zeroize - if action not in {'reboot', 'shutdown', 'halt'}: + if action not in ['reboot', 'shutdown', 'halt']: # at, in_min and other_re option only applies to reboot, shutdown, or halt action. - for str,val in {"at":at,"in_min":in_min,"other_re":other_re}: - if val is not None: + for arg_type,arg_val in {"at":at,"in_min":in_min,"other_re":other_re}: + if arg_val is not None: junos_module.fail_json(msg='The %s option can only be used when ' 'the action option has the value "reboot", ' - '"shutdown", or "halt".' % str) + '"shutdown", or "halt".' % arg_type) elif media is True: # media option only applies to zeroize action. junos_module.fail_json(msg='The media option can only be used when ' @@ -316,6 +330,7 @@ def main(): 'all_re': all_re, 'other_re': other_re, 'media': media, + 'vmhost': vmhost, 'failed': True} if not junos_module.check_mode: @@ -337,7 +352,7 @@ def main(): junos_module.logger.debug("Executing RPC") if action == 'reboot': - got = junos_module.sw.reboot(in_min, at, all_re, None, False, other_re) + got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) elif action == 'shutdown': got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) elif action == 'halt': From aaf6c451000fcd5c68a5e1ee8bf14a33f6e5b24d Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 24 Jun 2020 18:22:16 +0530 Subject: [PATCH 309/426] Add error handling for timeout during RPC --- .../junos_collection/plugins/modules/juniper_junos_software.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py index 9655f03f..f7dfbd45 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py @@ -737,6 +737,7 @@ def main(): 'initiated.' junos_module.fail_json(**results) except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.RpcTimeoutError, junos_module.pyez_exception.ConnectError): # This is expected. The device has already disconnected. results['msg'] += ' Reboot succeeded.' From 7c6283bad5933ad7a0a2224f35804238f44dfd23 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Sun, 5 Jul 2020 15:10:11 +0530 Subject: [PATCH 310/426] Update collection with recent changes done for roles. --- README.md | 12 +++---- .../plugins/callback/jsnapy.py | 7 ++-- .../module_utils/juniper_junos_common.py | 36 +++++++++++++------ .../plugins/modules/juniper_junos_command.py | 8 ++++- .../plugins/modules/juniper_junos_config.py | 2 +- .../plugins/modules/juniper_junos_rpc.py | 4 ++- .../plugins/modules/juniper_junos_software.py | 32 ++++++++++++----- .../plugins/modules/juniper_junos_system.py | 2 +- requirements.txt | 4 +-- 9 files changed, 69 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index cd22e178..1efb9cb1 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,9 @@ This Juniper.junos_collection collection includes the following modules: - **juniper_junos_system** — Initiate operational actions on the Junos system. - **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. +### PyEZ Version Requirement +For ansible roles 2.4.0 we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. + ### Overview of Plugins In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `juniper_junos_jsnapy`. @@ -121,14 +124,7 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge ### Ansible Galaxy collection -To download the latest released version of the junos collection to the Ansible -server, execute the ansible-galaxy collection install command, and specify **Juniper.junos_collection**. - -```bash -[root@ansible-cm]# ansible-galaxy collection install Juniper.junos_collection -``` - -You can also use the ansible-galaxy install command to install the latest +You can use the ansible-galaxy install command to install the latest development version of the junos collection directly from GitHub. ```bash diff --git a/ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py b/ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py index 3c5b5462..7b07a9d2 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py +++ b/ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py @@ -20,7 +20,7 @@ class CallbackModule(CallbackBase): CALLBACK_TYPE = 'aggregate' CALLBACK_NAME = 'jsnapy' - # only needed if you ship it and don't want to enable by default + # callback needs to be enabled with config-file to use jsnapy callback during execution CALLBACK_NEEDS_WHITELIST = True ## useful links regarding Callback @@ -64,11 +64,10 @@ def v2_playbook_on_stats(self, stats): for result in results: # self._pp.pprint(result.__dict__) res = result._result - if res['final_result'] == "Failed": + if res.get('final_result') == "Failed": for test_name, test_results in iteritems(res['test_results']): for testlet in test_results: - if testlet['count']['fail'] != 0: - + if ('count' in testlet) and testlet['count']['fail'] != 0: if not has_printed_banner: self._display.banner("JSNAPy Results for: " + str(host)) has_printed_banner = True diff --git a/ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py b/ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py index 5c683569..769f8332 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py @@ -84,6 +84,12 @@ except ImportError: HAS_PYEZ_EXCEPTIONS = False +try: + import ncclient.operations.errors as ncclient_exception + HAS_NCCLIENT_EXCEPTIONS = True +except ImportError: + HAS_NCCLIENT_EXCEPTIONS = False + try: import jnpr.jsnapy HAS_JSNAPY_VERSION = jnpr.jsnapy.__version__ @@ -565,6 +571,9 @@ class ModuleDocFragment(object): '_module_name': dict(type='str', required=True, default=None), + '_inventory_hostname': dict(type='str', + required=True, + default=None), } # Known RPC output formats @@ -576,7 +585,7 @@ class ModuleDocFragment(object): CONFIG_DATABASE_CHOICES = ['candidate', 'committed'] # Known configuration actions CONFIG_ACTION_CHOICES = ['set', 'merge', 'update', - 'replace', 'override', 'overwrite'] + 'replace', 'override', 'overwrite', 'patch'] # Supported configuration modes CONFIG_MODE_CHOICES = ['exclusive', 'private'] # Supported configuration models @@ -681,6 +690,7 @@ def __init__(self, mutually_exclusive=mutually_exclusive, **kwargs) self.module_name = self.params.get('_module_name') + self.inventory_hostname = self.params.get('_inventory_hostname') # Remove any arguments in internal_spec for arg_name in internal_spec: self.params.pop(arg_name) @@ -744,6 +754,7 @@ def __init__(self, self.pyez_factory_table = jnpr.junos.factory.table self.pyez_op_table = jnpr.junos.op self.pyez_exception = pyez_exception + self.ncclient_exception = ncclient_exception # Check LXML Etree. self.check_lxml_etree(min_lxml_etree_version) self.etree = etree @@ -1034,6 +1045,9 @@ def check_pyez(self, minimum=None, self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' 'the jnpr.junos.exception module could not ' 'be imported.') + if HAS_NCCLIENT_EXCEPTIONS is False: + self.fail_json(msg='ncclient.operations.errors module could not ' + 'be imported.') def check_jsnapy(self, minimum=None): """Check jsnapy is available and version is >= minimum. @@ -1209,9 +1223,9 @@ def parse_ignore_warning_option(self): if isinstance(ignore_warn_list[0], basestring): return ignore_warn_list[0] self.fail_json(msg="The value of the ignore_warning option " - "(%s) is invalid. Unexpected type (%s)." % - (ignore_warn_list[0], - type(ignore_warn_list[0]))) + "(%s) is invalid. Unexpected type (%s)." % + (ignore_warn_list[0], + type(ignore_warn_list[0]))) elif len(ignore_warn_list) > 1: for ignore_warn in ignore_warn_list: if not isinstance(ignore_warn, basestring): @@ -1550,7 +1564,7 @@ def check_configuration(self): self.fail_json(msg='Failure checking the configuraton: %s' % (str(ex))) - def diff_configuration(self): + def diff_configuration(self, ignore_warning=False): """Diff the candidate and committed configurations. Diff the candidate and committed configurations. @@ -1566,7 +1580,7 @@ def diff_configuration(self): self.logger.debug("Diffing candidate and committed configurations.") try: - diff = self.config.diff(rb_id=0) + diff = self.config.diff(rb_id=0, ignore_warning=ignore_warning) self.logger.debug("Configuration diff completed.") return diff except (self.pyez_exception.RpcError, @@ -1591,7 +1605,7 @@ def load_configuration(self, Args: action - The type of load to perform: 'merge', 'replace', 'set', 'override', 'overwrite', and - 'update' + 'update', 'patch' lines - A list of strings containing the configuration. src - The file path on the local Ansible control machine to the configuration to be loaded. @@ -1621,6 +1635,8 @@ def load_configuration(self, load_args['overwrite'] = True if action == 'update': load_args['update'] = True + if action == 'patch': + load_args['patch'] = True if lines is not None: config = '\n'.join(map(lambda line: line.rstrip('\n'), lines)) self.logger.debug("Loading the supplied configuration.") @@ -1846,8 +1862,7 @@ def save_text_output(self, name, format, text): file_path = os.path.normpath(self.params.get('diffs_file')) elif self.params.get('dest_dir') is not None: dest_dir = self.params.get('dest_dir') - hostname = self.params.get('host') - file_name = '%s.diff' % (hostname) + file_name = '%s.diff' % (self.inventory_hostname,) file_path = os.path.normpath(os.path.join(dest_dir, file_name)) else: if self.params.get('dest') is not None: @@ -1858,13 +1873,12 @@ def save_text_output(self, name, format, text): mode = 'ab' elif self.params.get('dest_dir') is not None: dest_dir = self.params.get('dest_dir') - hostname = self.params.get('host') # Substitute underscore for spaces. name = name.replace(' ', '_') # Substitute underscore for pipe name = name.replace('|', '_') name = '' if name == 'config' else '_' + name - file_name = '%s%s.%s' % (hostname, name, format) + file_name = '%s%s.%s' % (self.inventory_hostname, name, format) file_path = os.path.normpath(os.path.join(dest_dir, file_name)) if file_path is not None: try: diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py index bd80278c..d18b96a4 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py @@ -330,6 +330,9 @@ def main(): type='path', aliases=['destination_dir', 'destdir'], default=None), + ignore_warning=dict(required=False, + type='list', + default=None), return_output=dict(required=False, type='bool', default=True) @@ -343,6 +346,9 @@ def main(): min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, ) + # Parse ignore_warning value + ignore_warning = junos_module.parse_ignore_warning_option() + # Check over commands commands = junos_module.params.get('commands') # Ansible allows users to specify a commands argument with no value. @@ -411,7 +417,7 @@ def main(): command) rpc = junos_module.etree.Element('command', format=format) rpc.text = command - resp = junos_module.dev.rpc(rpc, normalize=bool(format == 'xml')) + resp = junos_module.dev.rpc(rpc, ignore_warning=ignore_warning, normalize=bool(format == 'xml')) result['msg'] = 'The command executed successfully.' junos_module.logger.debug('Command "%s" executed successfully.', command) diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py index f6ca827c..ddc95fe8 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py @@ -1070,7 +1070,7 @@ def main(): "candidate and committed configuration " "databases.") if diff is True or junos_module._diff: - diff = junos_module.diff_configuration() + diff = junos_module.diff_configuration(ignore_warning) if diff is not None: results['changed'] = True if return_output is True or junos_module._diff: diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py index a0487758..51555eea 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py @@ -502,6 +502,9 @@ def main(): # Execute the RPC try: + #for get-config in case of exception handling it will not display + #filters and arguments. To be added in future. + rpc = junos_module.etree.Element(rpc_string, format=format) if rpc_string == 'get-config': filter = junos_module.params.get('filter') if attr is None: @@ -520,7 +523,6 @@ def main(): junos_module.logger.debug('The "get-config" RPC executed ' 'successfully.') else: - rpc = junos_module.etree.Element(rpc_string, format=format) if kwarg is not None: # Add kwarg for (key, value) in iteritems(kwarg): diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py index f7dfbd45..e794e153 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py @@ -684,13 +684,14 @@ def main(): junos_module.logger.debug("Install parameters are: %s", str(install_params)) junos_module.add_sw() - ok = junos_module.sw.install(**install_params) + ok, msg_ret = junos_module.sw.install(**install_params) if ok is not True: - results['msg'] = 'Unable to install the software' + results['msg'] = 'Unable to install the software %s', msg_ret junos_module.fail_json(**results) - msg = 'Package %s successfully installed.' % ( + msg = 'Package %s successfully installed. Response from device is: %s' % ( install_params.get('package') or - install_params.get('pkg_set')) + install_params.get('pkg_set'), + msg_ret) results['msg'] = msg junos_module.logger.debug(msg) except (junos_module.pyez_exception.ConnectError, @@ -703,14 +704,19 @@ def main(): # and therefore might get an RpcTimeout. # (This is a known Junos bug.) Set the timeout low so this # happens relatively quickly. + restore_timeout = junos_module.dev.timeout if junos_module.dev.timeout > 5: junos_module.logger.debug("Decreasing device RPC timeout " "to 5 seconds.") junos_module.dev.timeout = 5 junos_module.logger.debug('Initiating reboot.') + try: - got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) - + got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) + junos_module.dev.timeout = restore_timeout + except Exception: # pylint: disable=broad-except + junos_module.dev.timeout = restore_timeout + raise junos_module.logger.debug("Reboot RPC executed.") if got is not None: @@ -721,9 +727,7 @@ def main(): # It only gets executed if the loop finished without # hitting the break. results['msg'] += ' Did not find expected response ' \ - 'from reboot RPC. RPC response is ' \ - '%s' % \ - junos_module.etree.tostring(resp) + 'from reboot RPC. ' junos_module.fail_json(**results) except (junos_module.pyez_exception.RpcTimeoutError) as ex: # This might be OK. It might just indicate the device didn't @@ -741,10 +745,20 @@ def main(): junos_module.pyez_exception.ConnectError): # This is expected. The device has already disconnected. results['msg'] += ' Reboot succeeded.' + except (junos_module.ncclient_exception.TimeoutExpiredError): + # This is not really expected. Still consider reboot success as + # Looks like rpc was consumed but no response as its rebooting. + results['msg'] += ' Reboot succeeded. Ignoring close error.' except (junos_module.pyez_exception.RpcError, junos_module.pyez_exception.ConnectError) as ex: results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) junos_module.fail_json(**results) + else: + try: + junos_module.close() + except (junos_module.ncclient_exception.TimeoutExpiredError): + junos_module.logger.debug("Ignoring TimeoutError for close call") + junos_module.logger.debug("Reboot RPC successfully initiated.") if reboot_pause > 0: junos_module.logger.debug("Sleeping for %d seconds", diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py index d138ee38..f4e45a43 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py @@ -367,7 +367,7 @@ def main(): results['msg'] = 'Did not find expected RPC response.' results['changed'] = False else: - results['msg'] = '%s successfully initiated.' % (action) + results['msg'] = '%s successfully initiated. Response got %s' % (action, got) results['failed'] = False except (junos_module.pyez_exception.RpcTimeoutError) as ex: # This might be OK. It might just indicate the device didn't diff --git a/requirements.txt b/requirements.txt index c9d1b985..858bae37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ ansible >= 2.4 -junos-eznc -jsnapy==1.3.2 +junos-eznc >= 2.5.0 +jsnapy>=1.3.2 jxmlease docker junos-netconify \ No newline at end of file From a88ef63f79456eb48494a5d9e8dfbbc1ddb112fd Mon Sep 17 00:00:00 2001 From: rahkumar651991 <58289625+rahkumar651991@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:33:05 +0530 Subject: [PATCH 311/426] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1efb9cb1..c576ca00 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ This Juniper.junos_collection collection includes the following modules: - **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. ### PyEZ Version Requirement -For ansible roles 2.4.0 we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. +For ansible collection junos_collection we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. ### Overview of Plugins @@ -259,4 +259,4 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c *Former Contributors:* -[Stacy W Smith](https://github.com/stacywsmith), [Stephen Steiner](https://github.com/ntwrkguru), [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) \ No newline at end of file +[Stacy W Smith](https://github.com/stacywsmith), [Stephen Steiner](https://github.com/ntwrkguru), [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) From 39658ec6d32c0d6ab117912be9c2098c6fb397e4 Mon Sep 17 00:00:00 2001 From: routenull0 Date: Mon, 6 Jul 2020 12:47:07 -0500 Subject: [PATCH 312/426] Update README.md Fixed Juniper.junos roles by Juniper Networks Heading. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c576ca00..b2c02637 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ installing and upgrading Junos OS, provisioning new Junos devices in the network retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to the [INSTALLATION](#installation) section for instructions on installing this collection. -#Juniper.junos roles by Juniper Networks +## Juniper.junos roles by Juniper Networks Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have Juniper.junos_collection support. Juniper.junos roles have been moved to roles branch. Roles will be supported for now. From 09dfe3086283fa4c427ee0917bef1e5556fb050a Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Tue, 7 Jul 2020 13:19:18 +0530 Subject: [PATCH 313/426] Juniper collection renamed to juniper.junos --- README.md | 25 ++-- .../{junos_collection => junos}/README.md | 0 .../{junos_collection => junos}/docs/Makefile | 0 .../docs/_static/juniper-junos-modules.css | 0 .../docs/_static/juniper.png | Bin .../docs/ansible2rst.py | 2 +- .../{junos_collection => junos}/docs/conf.py | 0 .../docs/docreq.txt | 0 .../{junos_collection => junos}/docs/rst.j2 | 0 .../{junos_collection => junos}/galaxy.yml | 2 +- .../plugins/README.md | 0 .../plugins/action/juniper_junos_command.py | 1 + .../action/juniper_junos_common_action.py | 2 + .../plugins/action/juniper_junos_config.py | 1 + .../plugins/action/juniper_junos_facts.py | 1 + .../plugins/action/juniper_junos_jsnapy.py | 1 + .../plugins/action/juniper_junos_ping.py | 1 + .../plugins/action/juniper_junos_pmtud.py | 1 + .../junos/plugins/action/juniper_junos_rpc.py | 1 + .../plugins/action/juniper_junos_software.py | 1 + .../action/juniper_junos_srx_cluster.py | 1 + .../plugins/action/juniper_junos_system.py | 1 + .../plugins/action/juniper_junos_table.py | 1 + .../plugins/callback/jsnapy.py | 0 .../plugins/module_utils/__init__.py | 0 .../module_utils/juniper_junos_common.py | 0 .../plugins/modules/juniper_junos_command.py | 4 +- .../plugins/modules/juniper_junos_config.py | 4 +- .../plugins/modules/juniper_junos_facts.py | 4 +- .../plugins/modules/juniper_junos_jsnapy.py | 4 +- .../plugins/modules/juniper_junos_ping.py | 4 +- .../plugins/modules/juniper_junos_pmtud.py | 4 +- .../plugins/modules/juniper_junos_rpc.py | 6 +- .../plugins/modules/juniper_junos_software.py | 4 +- .../modules/juniper_junos_srx_cluster.py | 4 +- .../plugins/modules/juniper_junos_system.py | 4 +- .../plugins/modules/juniper_junos_table.py | 4 +- .../plugins/action/juniper_junos_command.py | 116 ------------------ .../plugins/action/juniper_junos_config.py | 116 ------------------ .../plugins/action/juniper_junos_facts.py | 116 ------------------ .../plugins/action/juniper_junos_jsnapy.py | 116 ------------------ .../plugins/action/juniper_junos_ping.py | 116 ------------------ .../plugins/action/juniper_junos_pmtud.py | 116 ------------------ .../plugins/action/juniper_junos_rpc.py | 116 ------------------ .../plugins/action/juniper_junos_software.py | 116 ------------------ .../action/juniper_junos_srx_cluster.py | 116 ------------------ .../plugins/action/juniper_junos_system.py | 116 ------------------ .../plugins/action/juniper_junos_table.py | 116 ------------------ tests/pb.juniper_junos_config.yml | 2 +- tests/pb.juniper_junos_facts.yml | 2 +- tests/pb.juniper_junos_jsnapy.yml | 2 +- tests/pb.juniper_junos_ping.yml | 2 +- tests/pb.juniper_junos_pmtud.yml | 2 +- tests/pb.juniper_junos_rpc.yml | 2 +- 54 files changed, 57 insertions(+), 1319 deletions(-) rename ansible_collections/Juniper/{junos_collection => junos}/README.md (100%) rename ansible_collections/Juniper/{junos_collection => junos}/docs/Makefile (100%) rename ansible_collections/Juniper/{junos_collection => junos}/docs/_static/juniper-junos-modules.css (100%) rename ansible_collections/Juniper/{junos_collection => junos}/docs/_static/juniper.png (100%) rename ansible_collections/Juniper/{junos_collection => junos}/docs/ansible2rst.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/docs/conf.py (100%) rename ansible_collections/Juniper/{junos_collection => junos}/docs/docreq.txt (100%) rename ansible_collections/Juniper/{junos_collection => junos}/docs/rst.j2 (100%) rename ansible_collections/Juniper/{junos_collection => junos}/galaxy.yml (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/README.md (100%) create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_command.py rename ansible_collections/Juniper/{junos_collection => junos}/plugins/action/juniper_junos_common_action.py (97%) create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_config.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_facts.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_jsnapy.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_ping.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_pmtud.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_rpc.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_software.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_system.py create mode 120000 ansible_collections/Juniper/junos/plugins/action/juniper_junos_table.py rename ansible_collections/Juniper/{junos_collection => junos}/plugins/callback/jsnapy.py (100%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/module_utils/__init__.py (100%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/module_utils/juniper_junos_common.py (100%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_command.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_config.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_facts.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_jsnapy.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_ping.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_pmtud.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_rpc.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_software.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_srx_cluster.py (98%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_system.py (99%) rename ansible_collections/Juniper/{junos_collection => junos}/plugins/modules/juniper_junos_table.py (99%) delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_command.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_config.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_facts.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_jsnapy.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_ping.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_pmtud.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_rpc.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_software.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_srx_cluster.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_system.py delete mode 100755 ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_table.py diff --git a/README.md b/README.md index c576ca00..27858b80 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,17 @@ The repo is under active development. If you take a clone, you are getting the Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS). This collection is hosted on the Ansible Galaxy website under the collection -[Juniper.junos_collection](https://galaxy.ansible.com/Juniper/junos/). The Juniper.junos_collection collection includes +[Juniper.junos](https://galaxy.ansible.com/Juniper/junos/). The Juniper.junos collection includes a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. These tasks include: installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to the [INSTALLATION](#installation) section for instructions on installing this collection. -#Juniper.junos roles by Juniper Networks +## Juniper.junos roles by Juniper Networks Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have -Juniper.junos_collection support. Juniper.junos roles have been moved to roles branch. Roles will be supported for now. +Juniper.junos collection support. Juniper.junos roles have been moved to roles branch. For more information for roles, check: https://github.com/Juniper/ansible-junos-stdlib/tree/roles @@ -26,14 +26,14 @@ https://github.com/Juniper/ansible-junos-stdlib/tree/roles Since Ansible version >= 2.1, Ansible also natively includes [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included -in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.junos_collection +in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.junos collection have names which begin with the prefix `juniper_junos_`. These two sets of Junos modules can coexist on the same Ansible control machine, and an Ansible play may invoke a module from either (or both) sets. Juniper Networks recommends using the modules in this collection when writing new playbooks that manage Junos devices. ## Overview of Modules -This Juniper.junos_collection collection includes the following modules: +This Juniper.junos collection includes the following modules: - **juniper_junos_command** — Execute one or more CLI commands on a Junos device. - **juniper_junos_config** — Manipulate the configuration of a Junos device. @@ -48,7 +48,7 @@ This Juniper.junos_collection collection includes the following modules: - **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. ### PyEZ Version Requirement -For ansible collection junos_collection we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. +For ansible collection junos we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. ### Overview of Plugins @@ -95,9 +95,9 @@ You must have the [DEPENDENCIES](#dependencies) installed on your system. If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process -by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy collection install Juniper.junos_collection -c` +by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy collection install Juniper.junos -c` - [WARNING]: - Juniper.junos_collection was NOT installed successfully: Failed to get data + [WARNING]: - Juniper.junos was NOT installed successfully: Failed to get data from the API server (https://galaxy.ansible.com/api/): Failed to validate the SSL certificate for galaxy.ansible.com:443. Make sure your managed systems have a valid CA certificate installed. If the website serving the url uses SNI you @@ -127,8 +127,9 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge You can use the ansible-galaxy install command to install the latest development version of the junos collection directly from GitHub. + ```bash -sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos_collection +sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos ``` ### Git clone @@ -192,7 +193,7 @@ This example outlines how to use Ansible to install or upgrade the software imag - name: Install Junos OS hosts: dc1 collections: - - Juniper.junos_collection + - Juniper.junos connection: local gather_facts: no vars: @@ -236,8 +237,8 @@ Apache 2.0 ## SUPPORT -Support for this Juniper.junos_collection collection is provided by the community and Juniper Networks. If you have an -issue with a module in the Juniper.junos_collection collection, you may: +Support for this Juniper.junos collection is provided by the community and Juniper Networks. If you have an +issue with a module in the Juniper.junos collection, you may: - Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). - Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) diff --git a/ansible_collections/Juniper/junos_collection/README.md b/ansible_collections/Juniper/junos/README.md similarity index 100% rename from ansible_collections/Juniper/junos_collection/README.md rename to ansible_collections/Juniper/junos/README.md diff --git a/ansible_collections/Juniper/junos_collection/docs/Makefile b/ansible_collections/Juniper/junos/docs/Makefile similarity index 100% rename from ansible_collections/Juniper/junos_collection/docs/Makefile rename to ansible_collections/Juniper/junos/docs/Makefile diff --git a/ansible_collections/Juniper/junos_collection/docs/_static/juniper-junos-modules.css b/ansible_collections/Juniper/junos/docs/_static/juniper-junos-modules.css similarity index 100% rename from ansible_collections/Juniper/junos_collection/docs/_static/juniper-junos-modules.css rename to ansible_collections/Juniper/junos/docs/_static/juniper-junos-modules.css diff --git a/ansible_collections/Juniper/junos_collection/docs/_static/juniper.png b/ansible_collections/Juniper/junos/docs/_static/juniper.png similarity index 100% rename from ansible_collections/Juniper/junos_collection/docs/_static/juniper.png rename to ansible_collections/Juniper/junos/docs/_static/juniper.png diff --git a/ansible_collections/Juniper/junos_collection/docs/ansible2rst.py b/ansible_collections/Juniper/junos/docs/ansible2rst.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/docs/ansible2rst.py rename to ansible_collections/Juniper/junos/docs/ansible2rst.py index ec4531a3..a5809813 100755 --- a/ansible_collections/Juniper/junos_collection/docs/ansible2rst.py +++ b/ansible_collections/Juniper/junos/docs/ansible2rst.py @@ -410,7 +410,7 @@ def main(): index_file_path = os.path.join(OUTPUTDIR, "index.rst") index_file = open(index_file_path, "w") - index_file.write('Juniper.junos_collection Ansible Modules\n') + index_file.write('Juniper.junos Ansible Modules\n') index_file.write('=================================================\n') index_file.write('\n') index_file.write('Contents:\n') diff --git a/ansible_collections/Juniper/junos_collection/docs/conf.py b/ansible_collections/Juniper/junos/docs/conf.py similarity index 100% rename from ansible_collections/Juniper/junos_collection/docs/conf.py rename to ansible_collections/Juniper/junos/docs/conf.py diff --git a/ansible_collections/Juniper/junos_collection/docs/docreq.txt b/ansible_collections/Juniper/junos/docs/docreq.txt similarity index 100% rename from ansible_collections/Juniper/junos_collection/docs/docreq.txt rename to ansible_collections/Juniper/junos/docs/docreq.txt diff --git a/ansible_collections/Juniper/junos_collection/docs/rst.j2 b/ansible_collections/Juniper/junos/docs/rst.j2 similarity index 100% rename from ansible_collections/Juniper/junos_collection/docs/rst.j2 rename to ansible_collections/Juniper/junos/docs/rst.j2 diff --git a/ansible_collections/Juniper/junos_collection/galaxy.yml b/ansible_collections/Juniper/junos/galaxy.yml similarity index 99% rename from ansible_collections/Juniper/junos_collection/galaxy.yml rename to ansible_collections/Juniper/junos/galaxy.yml index 9e62164b..264702e4 100644 --- a/ansible_collections/Juniper/junos_collection/galaxy.yml +++ b/ansible_collections/Juniper/junos/galaxy.yml @@ -6,7 +6,7 @@ namespace: Juniper # The name of the collection. Has the same character restrictions as 'namespace' -name: junos_collection +name: junos # The version of the collection. Must be compatible with semantic versioning version: 0.1.0 diff --git a/ansible_collections/Juniper/junos_collection/plugins/README.md b/ansible_collections/Juniper/junos/plugins/README.md similarity index 100% rename from ansible_collections/Juniper/junos_collection/plugins/README.md rename to ansible_collections/Juniper/junos/plugins/README.md diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_command.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_command.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_command.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_common_action.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_common_action.py similarity index 97% rename from ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_common_action.py rename to ansible_collections/Juniper/junos/plugins/action/juniper_junos_common_action.py index a6b8336f..61437b86 100755 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_common_action.py +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_common_action.py @@ -111,6 +111,8 @@ def run(self, tmp=None, task_vars=None): self._task.args['_module_utils_path'] = module_utils_path # Pass the hidden _module_name option self._task.args['_module_name'] = self._task.action + # Pass the hidden _inventory_hostname option + self._task.args['_inventory_hostname'] = task_vars['inventory_hostname'] # Call the parent action module. return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_config.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_config.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_config.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_facts.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_facts.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_facts.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_jsnapy.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_jsnapy.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_jsnapy.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_ping.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_ping.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_ping.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_pmtud.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_pmtud.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_pmtud.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_rpc.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_rpc.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_rpc.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_software.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_software.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_software.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_system.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_system.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_system.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_table.py b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_table.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/Juniper/junos/plugins/action/juniper_junos_table.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py b/ansible_collections/Juniper/junos/plugins/callback/jsnapy.py similarity index 100% rename from ansible_collections/Juniper/junos_collection/plugins/callback/jsnapy.py rename to ansible_collections/Juniper/junos/plugins/callback/jsnapy.py diff --git a/ansible_collections/Juniper/junos_collection/plugins/module_utils/__init__.py b/ansible_collections/Juniper/junos/plugins/module_utils/__init__.py similarity index 100% rename from ansible_collections/Juniper/junos_collection/plugins/module_utils/__init__.py rename to ansible_collections/Juniper/junos/plugins/module_utils/__init__.py diff --git a/ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py b/ansible_collections/Juniper/junos/plugins/module_utils/juniper_junos_common.py similarity index 100% rename from ansible_collections/Juniper/junos_collection/plugins/module_utils/juniper_junos_common.py rename to ansible_collections/Juniper/junos/plugins/module_utils/juniper_junos_common.py diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_command.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_command.py index d18b96a4..c7b3090b 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_command.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_command.py @@ -152,7 +152,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Execute single "show version" command. @@ -308,7 +308,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_config.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_config.py index ddc95fe8..b008b4bd 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_config.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_config.py @@ -521,7 +521,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Retrieve the committed configuration juniper_junos_config: @@ -739,7 +739,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Choices which are defined in the common module. diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_facts.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_facts.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_facts.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_facts.py index 86700ae7..4d018e6d 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_facts.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_facts.py @@ -90,7 +90,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Gather Junos facts with no configuration juniper_junos_facts: @@ -176,7 +176,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common from ansible.module_utils._text import to_bytes def get_facts_dict(junos_module): diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_jsnapy.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_jsnapy.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py index 967b02e8..a56b10b5 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_jsnapy.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py @@ -115,7 +115,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: JUNOS Post Checklist @@ -208,7 +208,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_ping.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_ping.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_ping.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_ping.py index cfcb477a..d4153c75 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_ping.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_ping.py @@ -146,7 +146,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. @@ -385,7 +385,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # The argument spec for the module. diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_pmtud.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_pmtud.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_pmtud.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_pmtud.py index ca05bb95..e01c1388 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_pmtud.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_pmtud.py @@ -133,7 +133,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Perform PMTUD to 192.68.1.1 with default parameters. @@ -248,7 +248,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Constants for MTU size diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_rpc.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_rpc.py index 51555eea..f8d7ed9a 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_rpc.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_rpc.py @@ -190,7 +190,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Execute single get-software-information RPC. @@ -246,7 +246,7 @@ - name: Get Device Configuration hosts: all collections: - - Juniper.junos_collection + - Juniper.junos connection: local gather_facts: no tasks: @@ -375,7 +375,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_software.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_software.py index e794e153..9baf1858 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_software.py @@ -322,7 +322,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Execute a basic Junos software upgrade. @@ -390,7 +390,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def parse_version_from_filename(filename): """Attempts to parse a version string from the filename of a Junos package. diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_srx_cluster.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py similarity index 98% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_srx_cluster.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py index a651b81c..758b62a8 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_srx_cluster.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py @@ -95,7 +95,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Enable an SRX cluster juniper_junos_srx_cluster: @@ -149,7 +149,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_system.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_system.py index f4e45a43..0cf0dea5 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_system.py @@ -163,7 +163,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Reboot all REs of the device @@ -255,7 +255,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_table.py b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_table.py similarity index 99% rename from ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_table.py rename to ansible_collections/Juniper/junos/plugins/modules/juniper_junos_table.py index bdce4c07..51aa2176 100644 --- a/ansible_collections/Juniper/junos_collection/plugins/modules/juniper_junos_table.py +++ b/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_table.py @@ -128,7 +128,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table @@ -291,7 +291,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos_collection.plugins.module_utils import juniper_junos_common +from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common def expand_items(module, data): """Recursively expand any table items diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_command.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_command.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_command.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_config.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_config.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_config.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_facts.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_facts.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_facts.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_jsnapy.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_jsnapy.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_jsnapy.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_ping.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_ping.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_ping.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_pmtud.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_pmtud.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_pmtud.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_rpc.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_rpc.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_rpc.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_software.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_software.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_software.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_srx_cluster.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_srx_cluster.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_system.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_system.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_system.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_table.py b/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_table.py deleted file mode 100755 index a6b8336f..00000000 --- a/ansible_collections/Juniper/junos_collection/plugins/action/juniper_junos_table.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from ansible.plugins.action.normal import ActionModule as ActionNormal -import os - - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. -# The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. - - All juniper_junos_* modules share common behavior which is implemented in - this class. This includes specific option fallback/default behavior and - passing the "hidden" _module_utils_path option to the module. - - """ - def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - - # Call the parent action module. - return super(ActionModule, self).run(tmp, task_vars) diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 932574c7..91741cbb 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: ################# - name: Retrieve the committed configuration diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 86de1afc..966fbc3d 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: "TEST 1 - Gather Facts" juniper_junos_facts: diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index bcf4d3b4..2db42400 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: ################################################## #### TEST 1 ## diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index bf0e4afc..4a2a6c41 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: "TEST 1 - Ping Host DNS" juniper_junos_ping: diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index 3520844a..eef4d9f1 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: - name: "TEST 1 - Check path MTU to host DNS" juniper_junos_pmtud: diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 166598d1..5298b950 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos_collection + - Juniper.junos tasks: ################# From ac7cb6acd3ceee96e7e2cf8b56638e9bee7f259a Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 9 Jul 2020 13:34:32 +0530 Subject: [PATCH 314/426] Modifying collection name to junipernetworks.device --- Dockerfile | 2 +- ISSUE_TEMPLATE.md | 4 +- README.md | 50 +++++------------- ansible-junos-awx/Makefile | 2 +- ansible-junos-awx/README.md | 12 ++--- .../device}/README.md | 0 .../device}/docs/Makefile | 0 .../docs/_static/juniper-junos-modules.css | 0 .../device}/docs/_static/juniper.png | Bin .../device}/docs/ansible2rst.py | 0 .../device}/docs/conf.py | 0 .../device}/docs/docreq.txt | 0 .../device}/docs/rst.j2 | 0 .../device}/galaxy.yml | 4 +- .../device}/plugins/README.md | 0 .../device/plugins/action/command.py} | 0 .../device/plugins/action/config.py} | 0 .../device/plugins/action/facts.py} | 0 .../device/plugins/action/jsnapy.py} | 0 .../plugins/action/juniper_junos_command.py} | 0 .../action/juniper_junos_common_action.py | 0 .../plugins/action/juniper_junos_config.py} | 0 .../plugins/action/juniper_junos_facts.py} | 0 .../plugins/action/juniper_junos_jsnapy.py} | 0 .../plugins/action/juniper_junos_ping.py} | 0 .../plugins/action/juniper_junos_pmtud.py} | 0 .../plugins/action/juniper_junos_rpc.py} | 0 .../plugins/action/juniper_junos_software.py | 1 + .../action/juniper_junos_srx_cluster.py | 1 + .../plugins/action/juniper_junos_system.py | 1 + .../plugins/action/juniper_junos_table.py | 1 + .../device/plugins/action/ping.py | 1 + .../device/plugins/action/pmtud.py | 1 + .../device/plugins/action/rpc.py | 1 + .../device/plugins/action/software.py | 1 + .../device/plugins/action/srx_cluster.py | 1 + .../device/plugins/action/system.py | 1 + .../device/plugins/action/table.py | 1 + .../device}/plugins/callback/jsnapy.py | 0 .../device}/plugins/module_utils/__init__.py | 0 .../module_utils/juniper_junos_common.py | 7 ++- .../device/plugins/modules/command.py | 1 + .../device/plugins/modules/config.py | 1 + .../device/plugins/modules/facts.py | 1 + .../device/plugins/modules/jsnapy.py | 1 + .../plugins/modules/juniper_junos_command.py | 5 +- .../plugins/modules/juniper_junos_config.py | 5 +- .../plugins/modules/juniper_junos_facts.py | 6 +-- .../plugins/modules/juniper_junos_jsnapy.py | 4 +- .../plugins/modules/juniper_junos_ping.py | 4 +- .../plugins/modules/juniper_junos_pmtud.py | 5 +- .../plugins/modules/juniper_junos_rpc.py | 6 +-- .../plugins/modules/juniper_junos_software.py | 4 +- .../modules/juniper_junos_srx_cluster.py | 4 +- .../plugins/modules/juniper_junos_system.py | 4 +- .../plugins/modules/juniper_junos_table.py | 4 +- .../device/plugins/modules/ping.py | 1 + .../device/plugins/modules/pmtud.py | 1 + .../device/plugins/modules/rpc.py | 1 + .../device/plugins/modules/software.py | 1 + .../device/plugins/modules/srx_cluster.py | 1 + .../device/plugins/modules/system.py | 1 + .../device/plugins/modules/table.py | 1 + tests/pb.juniper_junos_config.yml | 2 +- tests/pb.juniper_junos_facts.yml | 2 +- tests/pb.juniper_junos_jsnapy.yml | 2 +- tests/pb.juniper_junos_ping.yml | 2 +- tests/pb.juniper_junos_pmtud.yml | 2 +- tests/pb.juniper_junos_rpc.yml | 2 +- 69 files changed, 80 insertions(+), 86 deletions(-) rename ansible_collections/{Juniper/junos => junipernetworks/device}/README.md (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/docs/Makefile (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/docs/_static/juniper-junos-modules.css (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/docs/_static/juniper.png (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/docs/ansible2rst.py (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/docs/conf.py (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/docs/docreq.txt (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/docs/rst.j2 (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/galaxy.yml (98%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/README.md (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_command.py => junipernetworks/device/plugins/action/command.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_config.py => junipernetworks/device/plugins/action/config.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_facts.py => junipernetworks/device/plugins/action/facts.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_jsnapy.py => junipernetworks/device/plugins/action/jsnapy.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_ping.py => junipernetworks/device/plugins/action/juniper_junos_command.py} (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/action/juniper_junos_common_action.py (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_pmtud.py => junipernetworks/device/plugins/action/juniper_junos_config.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_rpc.py => junipernetworks/device/plugins/action/juniper_junos_facts.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_software.py => junipernetworks/device/plugins/action/juniper_junos_jsnapy.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_srx_cluster.py => junipernetworks/device/plugins/action/juniper_junos_ping.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_system.py => junipernetworks/device/plugins/action/juniper_junos_pmtud.py} (100%) rename ansible_collections/{Juniper/junos/plugins/action/juniper_junos_table.py => junipernetworks/device/plugins/action/juniper_junos_rpc.py} (100%) create mode 120000 ansible_collections/junipernetworks/device/plugins/action/juniper_junos_software.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/juniper_junos_srx_cluster.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/juniper_junos_system.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/juniper_junos_table.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/ping.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/pmtud.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/rpc.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/software.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/srx_cluster.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/system.py create mode 120000 ansible_collections/junipernetworks/device/plugins/action/table.py rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/callback/jsnapy.py (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/module_utils/__init__.py (100%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/module_utils/juniper_junos_common.py (99%) create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/command.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/config.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/facts.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/jsnapy.py rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_command.py (99%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_config.py (99%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_facts.py (98%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_jsnapy.py (99%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_ping.py (99%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_pmtud.py (98%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_rpc.py (99%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_software.py (99%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_srx_cluster.py (98%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_system.py (99%) rename ansible_collections/{Juniper/junos => junipernetworks/device}/plugins/modules/juniper_junos_table.py (99%) create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/ping.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/pmtud.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/rpc.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/software.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/srx_cluster.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/system.py create mode 120000 ansible_collections/junipernetworks/device/plugins/modules/table.py diff --git a/Dockerfile b/Dockerfile index 86b1c52f..c624307b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apk del -r --purge gcc make g++ &&\ rm -rf /var/cache/apk/* &&\ rm -rf /tmp/* -WORKDIR /etc/ansible/roles/Juniper.junos +WORKDIR /etc/ansible/collections/ansible_collections/junipernetworks.device COPY action_plugins action_plugins COPY callback_plugins callback_plugins COPY library library diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 255b3d93..f95990d3 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -13,9 +13,9 @@ Module Name -Juniper.Junos role and Python libraries version +junipernetworks.device role and Python libraries version +Also provide the version of junipernetworks.device role--> ``` ``` diff --git a/README.md b/README.md index 27858b80..2220ca7a 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,17 @@ The repo is under active development. If you take a clone, you are getting the Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS). This collection is hosted on the Ansible Galaxy website under the collection -[Juniper.junos](https://galaxy.ansible.com/Juniper/junos/). The Juniper.junos collection includes +[junipernetworks.device](https://galaxy.ansible.com/Juniper/junos/). The junipernetworks.device collection includes a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. These tasks include: installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to the [INSTALLATION](#installation) section for instructions on installing this collection. -## Juniper.junos roles by Juniper Networks +## juniper.junos roles by Juniper Networks Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have -Juniper.junos collection support. Juniper.junos roles have been moved to roles branch. +junipernetworks.device collection support. Juniper.junos roles have been moved to roles branch. For more information for roles, check: https://github.com/Juniper/ansible-junos-stdlib/tree/roles @@ -33,7 +33,7 @@ using the modules in this collection when writing new playbooks that manage Juno ## Overview of Modules -This Juniper.junos collection includes the following modules: +This junipernetworks.device collection includes the following modules: - **juniper_junos_command** — Execute one or more CLI commands on a Junos device. - **juniper_junos_config** — Manipulate the configuration of a Junos device. @@ -89,31 +89,6 @@ should be added to the Ansible configuration file in order to allow the jsnapy c You must have the [DEPENDENCIES](#dependencies) installed on your system. -### NOTICES - -#### Ubuntu 14.04 - -If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python -which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process -by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy collection install Juniper.junos -c` - - [WARNING]: - Juniper.junos was NOT installed successfully: Failed to get data - from the API server (https://galaxy.ansible.com/api/): Failed to validate the - SSL certificate for galaxy.ansible.com:443. Make sure your managed systems have - a valid CA certificate installed. If the website serving the url uses SNI you - need python >= 2.7.9 on your managed machine (the python executable used - (/usr/bin/python) is version: 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC - 4.8.4]) or you can install the `urllib3`, `pyOpenSSL`, `ndg-httpsclient`, and - `pyasn1` python modules to perform SNI verification in python >= 2.6. You can - use validate_certs=False if you do not need to confirm the servers identity but - this is unsafe and not recommended. Paths checked for this platform: - /etc/ssl/certs, /etc/pki/ca-trust/extracted/pem, /etc/pki/tls/certs, /usr/share - /ca-certificates/cacert.org, /etc/ansible. The exception msg was: hostname - u'galaxy.ansible.com' doesn't match either of - '*.c1e4.galaxy.openshiftapps.com', 'c1e4.galaxy.openshiftapps.com'. - - ERROR! - you can use --ignore-errors to skip failed collections and finish processing the list. - ### MacOS Mojave and newer In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keygen` are created using the newer 'OPENSSH' key format, even when specifying `-t rsa` during creation. This directly affects the usage of ssh keys, particularly when using the `ssh_private_key_file`. To create/convert/check keys, follow these steps: @@ -125,11 +100,10 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge ### Ansible Galaxy collection You can use the ansible-galaxy install command to install the latest -development version of the junos collection directly from GitHub. - +version of the junipernetworks.device collection. ```bash -sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos +sudo ansible-galaxy collection install junipernetworks.device ``` ### Git clone @@ -193,7 +167,7 @@ This example outlines how to use Ansible to install or upgrade the software imag - name: Install Junos OS hosts: dc1 collections: - - Juniper.junos + - junipernetworks.device connection: local gather_facts: no vars: @@ -226,9 +200,9 @@ This example outlines how to use Ansible to install or upgrade the software imag This modules requires the following to be installed on the Ansible control machine: -- Python >= 2.7 -- [Ansible](http://www.ansible.com) 2.3 or later -- Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.1.7 or later +- Python >= 3.5 +- [Ansible](http://www.ansible.com) 2.9 or later +- Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.5.0 or later - [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later ## LICENSE @@ -237,8 +211,8 @@ Apache 2.0 ## SUPPORT -Support for this Juniper.junos collection is provided by the community and Juniper Networks. If you have an -issue with a module in the Juniper.junos collection, you may: +Support for this junipernetworks.device collection is provided by the community and Juniper Networks. If you have an +issue with a module in the junipernetworks.device collection, you may: - Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). - Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) diff --git a/ansible-junos-awx/Makefile b/ansible-junos-awx/Makefile index 203297a5..626e5434 100644 --- a/ansible-junos-awx/Makefile +++ b/ansible-junos-awx/Makefile @@ -58,7 +58,7 @@ endif .PHONY: docker-exec docker-exec: docker exec -it awx_task pip install jsnapy jxmlease junos-eznc - docker exec -it awx_task ansible-galaxy install Juniper.junos,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles + docker exec -it awx_task ansible-galaxy install junipernetworks.device,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' ifneq '$(HOST_FILE)' '' curl -u admin:password --noproxy '*' http://localhost/api/v2/inventories/ --header "Content-Type: application/json" -x POST -d '{"name":"$(INVENTORY_NAME)" , "organization": 1}' diff --git a/ansible-junos-awx/README.md b/ansible-junos-awx/README.md index e1f08d94..1e24a14c 100644 --- a/ansible-junos-awx/README.md +++ b/ansible-junos-awx/README.md @@ -26,8 +26,8 @@ This will do the following operations: - Clone AWX repository into the Juniper-awx/awx folder - Change AWX inventory file to include user specifications.Refer [Makefile.variable](#makefilevariable). - Launch AWX conatiners. -- Install Juniper.junos role with user specified version.Refer [Makefile.variable](#makefilevariable). -- Install python modules required for Juniper.junos role in awx_task container: jxmlease,junos-eznc,jsnappy. +- Install junipernetworks.device role with user specified version.Refer [Makefile.variable](#makefilevariable). +- Install python modules required for junipernetworks.device role in awx_task container: jxmlease,junos-eznc,jsnappy. - Change roles_path in ansible.cfg for awx_task container. - If HOST_FILE is mentioned, an inventory with name INVENTORY_NAME is created and host's loaded into it.Refer [Makefile.variable](#makefilevariable). @@ -508,11 +508,11 @@ Installing collected packages: lxml, ncclient, scp, pyserial, netaddr, junos-ezn Successfully installed colorama-0.3.9 configparser-3.5.0 future-0.16.0 icdiff-1.9.1 jsnapy-1.3.1 junos-eznc-2.1.7 jxmlease-1.0.1 lxml-4.1.1 ncclient-0.5.3 netaddr-0.7.19 pyparsing-2.2.0 pyserial-3.4 scp-0.10.2 You are using pip version 8.1.2, however version 9.0.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. -docker exec -it awx_task ansible-galaxy install Juniper.junos, -p /etc/ansible/roles +docker exec -it awx_task ansible-galaxy install junipernetworks.device, -p /etc/ansible/roles - downloading role 'junos', owned by Juniper - downloading role from https://github.com/Juniper/ansible-junos-stdlib/archive/2.0.2.tar.gz -- extracting Juniper.junos to /etc/ansible/roles/Juniper.junos -- Juniper.junos (2.0.2) was installed successfully +- extracting junipernetworks.device to /etc/ansible/roles/junipernetworks.device +- junipernetworks.device (2.0.2) was installed successfully docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' @@ -580,7 +580,7 @@ INVENTORY_NAME = Junos 1. `PROJECT_DATA_DIR` : Provide absolute path to directory where the ansible projects reside.If the directory is not present Makefile will create the path. 2. `AWX_TASK_TAG`: Mention the awx_task tag to be installed.For available versions refer [Dockerhub](https://hub.docker.com/r/ansible/awx_task/tags/). 3. `POSTGRES_DATA_DIR`: Provide absolute path to postgres directory.If the directory is not present Makefile will create the path and create folders required for postgres to run. -4. `ANSIBLE_JUNOS_VERSION`: Mention the Juniper.junos version to be installed.By default, it installs the latest version. +4. `ANSIBLE_JUNOS_VERSION`: Mention the junipernetworks.device version to be installed.By default, it installs the latest version. 5. `HOST_FILE`: Provide the absolute path to the host file.This option can be only used if PROJECT_DATA_DIR is mentioned. By default, it doesnot load any host file.Please ensure that a unique INVENTORY_NAME is mentioned to avoid errors e.g Hosts. 6. `INVENTORY_NAME`: The name of the inventory to which HOST_FILE is to be loaded. diff --git a/ansible_collections/Juniper/junos/README.md b/ansible_collections/junipernetworks/device/README.md similarity index 100% rename from ansible_collections/Juniper/junos/README.md rename to ansible_collections/junipernetworks/device/README.md diff --git a/ansible_collections/Juniper/junos/docs/Makefile b/ansible_collections/junipernetworks/device/docs/Makefile similarity index 100% rename from ansible_collections/Juniper/junos/docs/Makefile rename to ansible_collections/junipernetworks/device/docs/Makefile diff --git a/ansible_collections/Juniper/junos/docs/_static/juniper-junos-modules.css b/ansible_collections/junipernetworks/device/docs/_static/juniper-junos-modules.css similarity index 100% rename from ansible_collections/Juniper/junos/docs/_static/juniper-junos-modules.css rename to ansible_collections/junipernetworks/device/docs/_static/juniper-junos-modules.css diff --git a/ansible_collections/Juniper/junos/docs/_static/juniper.png b/ansible_collections/junipernetworks/device/docs/_static/juniper.png similarity index 100% rename from ansible_collections/Juniper/junos/docs/_static/juniper.png rename to ansible_collections/junipernetworks/device/docs/_static/juniper.png diff --git a/ansible_collections/Juniper/junos/docs/ansible2rst.py b/ansible_collections/junipernetworks/device/docs/ansible2rst.py similarity index 100% rename from ansible_collections/Juniper/junos/docs/ansible2rst.py rename to ansible_collections/junipernetworks/device/docs/ansible2rst.py diff --git a/ansible_collections/Juniper/junos/docs/conf.py b/ansible_collections/junipernetworks/device/docs/conf.py similarity index 100% rename from ansible_collections/Juniper/junos/docs/conf.py rename to ansible_collections/junipernetworks/device/docs/conf.py diff --git a/ansible_collections/Juniper/junos/docs/docreq.txt b/ansible_collections/junipernetworks/device/docs/docreq.txt similarity index 100% rename from ansible_collections/Juniper/junos/docs/docreq.txt rename to ansible_collections/junipernetworks/device/docs/docreq.txt diff --git a/ansible_collections/Juniper/junos/docs/rst.j2 b/ansible_collections/junipernetworks/device/docs/rst.j2 similarity index 100% rename from ansible_collections/Juniper/junos/docs/rst.j2 rename to ansible_collections/junipernetworks/device/docs/rst.j2 diff --git a/ansible_collections/Juniper/junos/galaxy.yml b/ansible_collections/junipernetworks/device/galaxy.yml similarity index 98% rename from ansible_collections/Juniper/junos/galaxy.yml rename to ansible_collections/junipernetworks/device/galaxy.yml index 264702e4..c8c7de65 100644 --- a/ansible_collections/Juniper/junos/galaxy.yml +++ b/ansible_collections/junipernetworks/device/galaxy.yml @@ -3,10 +3,10 @@ # The namespace of the collection. This can be a company/brand/organization or product namespace under which all # content lives. May only contain alphanumeric characters and underscores. Additionally namespaces cannot start with # underscores or numbers and cannot contain consecutive underscores -namespace: Juniper +namespace: junipernetworks # The name of the collection. Has the same character restrictions as 'namespace' -name: junos +name: device # The version of the collection. Must be compatible with semantic versioning version: 0.1.0 diff --git a/ansible_collections/Juniper/junos/plugins/README.md b/ansible_collections/junipernetworks/device/plugins/README.md similarity index 100% rename from ansible_collections/Juniper/junos/plugins/README.md rename to ansible_collections/junipernetworks/device/plugins/README.md diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_command.py b/ansible_collections/junipernetworks/device/plugins/action/command.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_command.py rename to ansible_collections/junipernetworks/device/plugins/action/command.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_config.py b/ansible_collections/junipernetworks/device/plugins/action/config.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_config.py rename to ansible_collections/junipernetworks/device/plugins/action/config.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_facts.py b/ansible_collections/junipernetworks/device/plugins/action/facts.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_facts.py rename to ansible_collections/junipernetworks/device/plugins/action/facts.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_jsnapy.py b/ansible_collections/junipernetworks/device/plugins/action/jsnapy.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_jsnapy.py rename to ansible_collections/junipernetworks/device/plugins/action/jsnapy.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_ping.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_command.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_ping.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_command.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_common_action.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_common_action.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_common_action.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_common_action.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_pmtud.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_config.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_pmtud.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_config.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_rpc.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_facts.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_rpc.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_facts.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_software.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_jsnapy.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_software.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_jsnapy.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_ping.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_srx_cluster.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_ping.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_system.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_pmtud.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_system.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_pmtud.py diff --git a/ansible_collections/Juniper/junos/plugins/action/juniper_junos_table.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_rpc.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/action/juniper_junos_table.py rename to ansible_collections/junipernetworks/device/plugins/action/juniper_junos_rpc.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_software.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_software.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_software.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_srx_cluster.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_srx_cluster.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_system.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_system.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_system.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_table.py b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_table.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_table.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/ping.py b/ansible_collections/junipernetworks/device/plugins/action/ping.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/ping.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/pmtud.py b/ansible_collections/junipernetworks/device/plugins/action/pmtud.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/pmtud.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/rpc.py b/ansible_collections/junipernetworks/device/plugins/action/rpc.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/rpc.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/software.py b/ansible_collections/junipernetworks/device/plugins/action/software.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/software.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/srx_cluster.py b/ansible_collections/junipernetworks/device/plugins/action/srx_cluster.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/srx_cluster.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/system.py b/ansible_collections/junipernetworks/device/plugins/action/system.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/system.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/action/table.py b/ansible_collections/junipernetworks/device/plugins/action/table.py new file mode 120000 index 00000000..7470277b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/action/table.py @@ -0,0 +1 @@ +juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/callback/jsnapy.py b/ansible_collections/junipernetworks/device/plugins/callback/jsnapy.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/callback/jsnapy.py rename to ansible_collections/junipernetworks/device/plugins/callback/jsnapy.py diff --git a/ansible_collections/Juniper/junos/plugins/module_utils/__init__.py b/ansible_collections/junipernetworks/device/plugins/module_utils/__init__.py similarity index 100% rename from ansible_collections/Juniper/junos/plugins/module_utils/__init__.py rename to ansible_collections/junipernetworks/device/plugins/module_utils/__init__.py diff --git a/ansible_collections/Juniper/junos/plugins/module_utils/juniper_junos_common.py b/ansible_collections/junipernetworks/device/plugins/module_utils/juniper_junos_common.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/module_utils/juniper_junos_common.py rename to ansible_collections/junipernetworks/device/plugins/module_utils/juniper_junos_common.py index 769f8332..e22d125d 100644 --- a/ansible_collections/Juniper/junos/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/junipernetworks/device/plugins/module_utils/juniper_junos_common.py @@ -126,7 +126,7 @@ # Constants # Minimum PyEZ version required by shared code. -MIN_PYEZ_VERSION = "2.2.0" +MIN_PYEZ_VERSION = "2.4.0" # Installation URL for PyEZ. PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" # Minimum lxml version required by shared code. @@ -134,7 +134,7 @@ # Installation URL for LXML. LXML_ETREE_INSTALLATION_URL = "http://lxml.de/installation.html" # Minimum JSNAPy version required by shared code. -MIN_JSNAPY_VERSION = "1.2.1" +MIN_JSNAPY_VERSION = "1.3.4" # Installation URL for JSNAPy. JSNAPY_INSTALLATION_URL = "https://github.com/Juniper/jsnapy#installation" # Minimum jxmlease version required by shared code. @@ -146,7 +146,6 @@ MIN_YAML_VERSION = "3.08" YAML_INSTALLATION_URL = "http://pyyaml.org/wiki/PyYAMLDocumentation" - class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -448,7 +447,7 @@ class ModuleDocFragment(object): suboptions:''' + _SUB_CONNECT_DOCUMENTATION + ''' requirements: - U(junos-eznc|https://github.com/Juniper/py-junos-eznc) >= ''' + MIN_PYEZ_VERSION + ''' - - Python >= 2.7 + - Python >= 3.5 notes: - The NETCONF system service must be enabled on the target Junos device. ''' diff --git a/ansible_collections/junipernetworks/device/plugins/modules/command.py b/ansible_collections/junipernetworks/device/plugins/modules/command.py new file mode 120000 index 00000000..cc8aaf8f --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/command.py @@ -0,0 +1 @@ +juniper_junos_command.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/config.py b/ansible_collections/junipernetworks/device/plugins/modules/config.py new file mode 120000 index 00000000..5383605d --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/config.py @@ -0,0 +1 @@ +juniper_junos_config.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/facts.py b/ansible_collections/junipernetworks/device/plugins/modules/facts.py new file mode 120000 index 00000000..cbdd3b96 --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/facts.py @@ -0,0 +1 @@ +juniper_junos_facts.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/jsnapy.py b/ansible_collections/junipernetworks/device/plugins/modules/jsnapy.py new file mode 120000 index 00000000..e14ff8d8 --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/jsnapy.py @@ -0,0 +1 @@ +juniper_junos_jsnapy.py \ No newline at end of file diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_command.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_command.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_command.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_command.py index c7b3090b..c1b3c127 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_command.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_command.py @@ -50,6 +50,7 @@ short_description: Execute one or more CLI commands on a Junos device description: - Execute one or more CLI commands on a Junos device. + - Alias command - This module does NOT use the Junos CLI to execute the CLI command. Instead, it uses the C() RPC over a NETCONF channel. The C() RPC takes a CLI command as it's input and is very similar to @@ -152,7 +153,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Execute single "show version" command. @@ -308,7 +309,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_config.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_config.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_config.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_config.py index b008b4bd..cc979df8 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_config.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_config.py @@ -56,7 +56,6 @@ committing the configuration of a Junos device. It performs the following steps in order: - #. Open a candidate configuration database. * If the I(config_mode) option has a value of C(exclusive), the default, @@ -521,7 +520,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Retrieve the committed configuration juniper_junos_config: @@ -739,7 +738,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): # Choices which are defined in the common module. diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_facts.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_facts.py similarity index 98% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_facts.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_facts.py index 4d018e6d..597ca4c1 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_facts.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_facts.py @@ -90,7 +90,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Gather Junos facts with no configuration juniper_junos_facts: @@ -176,7 +176,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common from ansible.module_utils._text import to_bytes def get_facts_dict(junos_module): @@ -240,7 +240,7 @@ def save_facts(junos_module, facts): file_path = os.path.normpath(os.path.join(save_dir, file_name)) junos_module.logger.debug("Saving facts to: %s.", file_path) try: - # TODO: Verify does thsi work with Python3 + # TODO: Verify does this work with Python3 with open(file_path, 'w') as fact_file: json.dump(facts, fact_file) junos_module.logger.debug("Facts saved to: %s.", file_path) diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_jsnapy.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_jsnapy.py index a56b10b5..4d61441b 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_jsnapy.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_jsnapy.py @@ -115,7 +115,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: JUNOS Post Checklist @@ -208,7 +208,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_ping.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_ping.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_ping.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_ping.py index d4153c75..c08f3d9d 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_ping.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_ping.py @@ -146,7 +146,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. @@ -385,7 +385,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): # The argument spec for the module. diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_pmtud.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_pmtud.py similarity index 98% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_pmtud.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_pmtud.py index e01c1388..a74fe9a3 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_pmtud.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_pmtud.py @@ -46,7 +46,6 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: juniper_junos_pmtud -version_added: "2.0.0" # of Juniper.junos role author: - Martin Komon (@mkomon) - Juniper Networks - Stacy Smith (@stacywsmith) @@ -133,7 +132,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Perform PMTUD to 192.68.1.1 with default parameters. @@ -248,7 +247,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): # Constants for MTU size diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_rpc.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_rpc.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_rpc.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_rpc.py index f8d7ed9a..2fbb1af9 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_rpc.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_rpc.py @@ -190,7 +190,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Execute single get-software-information RPC. @@ -246,7 +246,7 @@ - name: Get Device Configuration hosts: all collections: - - Juniper.junos + - junipernetworks.device connection: local gather_facts: no tasks: @@ -375,7 +375,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_software.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_software.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_software.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_software.py index 9baf1858..1c523fcd 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_software.py @@ -322,7 +322,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Execute a basic Junos software upgrade. @@ -390,7 +390,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def parse_version_from_filename(filename): """Attempts to parse a version string from the filename of a Junos package. diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_srx_cluster.py similarity index 98% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_srx_cluster.py index 758b62a8..1480e7b6 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_srx_cluster.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_srx_cluster.py @@ -95,7 +95,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Enable an SRX cluster juniper_junos_srx_cluster: @@ -149,7 +149,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_system.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_system.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_system.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_system.py index 0cf0dea5..949d9889 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_system.py @@ -163,7 +163,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Reboot all REs of the device @@ -255,7 +255,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_table.py b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_table.py similarity index 99% rename from ansible_collections/Juniper/junos/plugins/modules/juniper_junos_table.py rename to ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_table.py index 51aa2176..1db2736b 100644 --- a/ansible_collections/Juniper/junos/plugins/modules/juniper_junos_table.py +++ b/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_table.py @@ -128,7 +128,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table @@ -291,7 +291,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.Juniper.junos.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common def expand_items(module, data): """Recursively expand any table items diff --git a/ansible_collections/junipernetworks/device/plugins/modules/ping.py b/ansible_collections/junipernetworks/device/plugins/modules/ping.py new file mode 120000 index 00000000..3ca2aa75 --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/ping.py @@ -0,0 +1 @@ +juniper_junos_ping.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/pmtud.py b/ansible_collections/junipernetworks/device/plugins/modules/pmtud.py new file mode 120000 index 00000000..b7bdfe9b --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/pmtud.py @@ -0,0 +1 @@ +juniper_junos_pmtud.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/rpc.py b/ansible_collections/junipernetworks/device/plugins/modules/rpc.py new file mode 120000 index 00000000..9e5b8b3f --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/rpc.py @@ -0,0 +1 @@ +juniper_junos_rpc.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/software.py b/ansible_collections/junipernetworks/device/plugins/modules/software.py new file mode 120000 index 00000000..0a63f359 --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/software.py @@ -0,0 +1 @@ +juniper_junos_software.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/srx_cluster.py b/ansible_collections/junipernetworks/device/plugins/modules/srx_cluster.py new file mode 120000 index 00000000..511a3de3 --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/srx_cluster.py @@ -0,0 +1 @@ +juniper_junos_srx_cluster.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/system.py b/ansible_collections/junipernetworks/device/plugins/modules/system.py new file mode 120000 index 00000000..95f32974 --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/system.py @@ -0,0 +1 @@ +juniper_junos_system.py \ No newline at end of file diff --git a/ansible_collections/junipernetworks/device/plugins/modules/table.py b/ansible_collections/junipernetworks/device/plugins/modules/table.py new file mode 120000 index 00000000..32df1a46 --- /dev/null +++ b/ansible_collections/junipernetworks/device/plugins/modules/table.py @@ -0,0 +1 @@ +juniper_junos_table.py \ No newline at end of file diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 91741cbb..c2b58198 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: ################# - name: Retrieve the committed configuration diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 966fbc3d..869919fc 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: "TEST 1 - Gather Facts" juniper_junos_facts: diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index 2db42400..9545056f 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: ################################################## #### TEST 1 ## diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index 4a2a6c41..ee441ec6 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: "TEST 1 - Ping Host DNS" juniper_junos_ping: diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index eef4d9f1..e6f16104 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: - name: "TEST 1 - Check path MTU to host DNS" juniper_junos_pmtud: diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 5298b950..befd0062 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - Juniper.junos + - junipernetworks.device tasks: ################# From 9534d41eea02c7babc932299e07ac0ea7ec9f5d8 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 9 Jul 2020 13:52:14 +0530 Subject: [PATCH 315/426] modifying the namespace --- Dockerfile | 2 +- ISSUE_TEMPLATE.md | 4 ++-- README.md | 16 ++++++++-------- ansible-junos-awx/Makefile | 2 +- ansible-junos-awx/README.md | 12 ++++++------ .../{device => devices}/README.md | 0 .../{device => devices}/docs/Makefile | 0 .../docs/_static/juniper-junos-modules.css | 0 .../docs/_static/juniper.png | Bin .../{device => devices}/docs/ansible2rst.py | 0 .../{device => devices}/docs/conf.py | 0 .../{device => devices}/docs/docreq.txt | 0 .../{device => devices}/docs/rst.j2 | 0 .../{device => devices}/galaxy.yml | 0 .../{device => devices}/plugins/README.md | 0 .../plugins/action/command.py | 0 .../plugins/action/config.py | 0 .../{device => devices}/plugins/action/facts.py | 0 .../plugins/action/jsnapy.py | 0 .../plugins/action/juniper_junos_command.py | 0 .../action/juniper_junos_common_action.py | 0 .../plugins/action/juniper_junos_config.py | 0 .../plugins/action/juniper_junos_facts.py | 0 .../plugins/action/juniper_junos_jsnapy.py | 0 .../plugins/action/juniper_junos_ping.py | 0 .../plugins/action/juniper_junos_pmtud.py | 0 .../plugins/action/juniper_junos_rpc.py | 0 .../plugins/action/juniper_junos_software.py | 0 .../plugins/action/juniper_junos_srx_cluster.py | 0 .../plugins/action/juniper_junos_system.py | 0 .../plugins/action/juniper_junos_table.py | 0 .../{device => devices}/plugins/action/ping.py | 0 .../{device => devices}/plugins/action/pmtud.py | 0 .../{device => devices}/plugins/action/rpc.py | 0 .../plugins/action/software.py | 0 .../plugins/action/srx_cluster.py | 0 .../plugins/action/system.py | 0 .../{device => devices}/plugins/action/table.py | 0 .../plugins/callback/jsnapy.py | 0 .../plugins/module_utils/__init__.py | 0 .../module_utils/juniper_junos_common.py | 2 +- .../plugins/modules/command.py | 0 .../plugins/modules/config.py | 0 .../plugins/modules/facts.py | 0 .../plugins/modules/jsnapy.py | 0 .../plugins/modules/juniper_junos_command.py | 4 ++-- .../plugins/modules/juniper_junos_config.py | 4 ++-- .../plugins/modules/juniper_junos_facts.py | 4 ++-- .../plugins/modules/juniper_junos_jsnapy.py | 4 ++-- .../plugins/modules/juniper_junos_ping.py | 4 ++-- .../plugins/modules/juniper_junos_pmtud.py | 4 ++-- .../plugins/modules/juniper_junos_rpc.py | 6 +++--- .../plugins/modules/juniper_junos_software.py | 4 ++-- .../modules/juniper_junos_srx_cluster.py | 4 ++-- .../plugins/modules/juniper_junos_system.py | 4 ++-- .../plugins/modules/juniper_junos_table.py | 4 ++-- .../{device => devices}/plugins/modules/ping.py | 0 .../plugins/modules/pmtud.py | 0 .../{device => devices}/plugins/modules/rpc.py | 0 .../plugins/modules/software.py | 0 .../plugins/modules/srx_cluster.py | 0 .../plugins/modules/system.py | 0 .../plugins/modules/table.py | 0 tests/pb.juniper_junos_config.yml | 2 +- tests/pb.juniper_junos_facts.yml | 2 +- tests/pb.juniper_junos_jsnapy.yml | 2 +- tests/pb.juniper_junos_ping.yml | 2 +- tests/pb.juniper_junos_pmtud.yml | 2 +- tests/pb.juniper_junos_rpc.yml | 2 +- 69 files changed, 48 insertions(+), 48 deletions(-) rename ansible_collections/junipernetworks/{device => devices}/README.md (100%) rename ansible_collections/junipernetworks/{device => devices}/docs/Makefile (100%) rename ansible_collections/junipernetworks/{device => devices}/docs/_static/juniper-junos-modules.css (100%) rename ansible_collections/junipernetworks/{device => devices}/docs/_static/juniper.png (100%) rename ansible_collections/junipernetworks/{device => devices}/docs/ansible2rst.py (100%) rename ansible_collections/junipernetworks/{device => devices}/docs/conf.py (100%) rename ansible_collections/junipernetworks/{device => devices}/docs/docreq.txt (100%) rename ansible_collections/junipernetworks/{device => devices}/docs/rst.j2 (100%) rename ansible_collections/junipernetworks/{device => devices}/galaxy.yml (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/README.md (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/command.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/config.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/facts.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/jsnapy.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_command.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_common_action.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_config.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_facts.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_jsnapy.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_ping.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_pmtud.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_rpc.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_software.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_srx_cluster.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_system.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/juniper_junos_table.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/ping.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/pmtud.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/rpc.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/software.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/srx_cluster.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/system.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/action/table.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/callback/jsnapy.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/module_utils/__init__.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/module_utils/juniper_junos_common.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/command.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/config.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/facts.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/jsnapy.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_command.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_config.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_facts.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_jsnapy.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_ping.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_pmtud.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_rpc.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_software.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_srx_cluster.py (98%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_system.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/juniper_junos_table.py (99%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/ping.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/pmtud.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/rpc.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/software.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/srx_cluster.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/system.py (100%) rename ansible_collections/junipernetworks/{device => devices}/plugins/modules/table.py (100%) diff --git a/Dockerfile b/Dockerfile index c624307b..9364cdea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apk del -r --purge gcc make g++ &&\ rm -rf /var/cache/apk/* &&\ rm -rf /tmp/* -WORKDIR /etc/ansible/collections/ansible_collections/junipernetworks.device +WORKDIR /etc/ansible/collections/ansible_collections/junipernetworks.devices COPY action_plugins action_plugins COPY callback_plugins callback_plugins COPY library library diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index f95990d3..23532e11 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -13,9 +13,9 @@ Module Name -junipernetworks.device role and Python libraries version +junipernetworks.devices collection and Python libraries version +Also provide the version of junipernetworks.devices collection--> ``` ``` diff --git a/README.md b/README.md index 2220ca7a..c501f062 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The repo is under active development. If you take a clone, you are getting the Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS). This collection is hosted on the Ansible Galaxy website under the collection -[junipernetworks.device](https://galaxy.ansible.com/Juniper/junos/). The junipernetworks.device collection includes +[junipernetworks.devices](https://galaxy.ansible.com/Juniper/junos/). The junipernetworks.devices collection includes a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. These tasks include: installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, @@ -18,7 +18,7 @@ retrieving information, and resetting, rebooting, or shutting down managed devic ## juniper.junos roles by Juniper Networks Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have -junipernetworks.device collection support. Juniper.junos roles have been moved to roles branch. +junipernetworks.devices collection support. Juniper.junos roles have been moved to roles branch. For more information for roles, check: https://github.com/Juniper/ansible-junos-stdlib/tree/roles @@ -33,7 +33,7 @@ using the modules in this collection when writing new playbooks that manage Juno ## Overview of Modules -This junipernetworks.device collection includes the following modules: +This junipernetworks.devices collection includes the following modules: - **juniper_junos_command** — Execute one or more CLI commands on a Junos device. - **juniper_junos_config** — Manipulate the configuration of a Junos device. @@ -100,10 +100,10 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge ### Ansible Galaxy collection You can use the ansible-galaxy install command to install the latest -version of the junipernetworks.device collection. +version of the junipernetworks.devices collection. ```bash -sudo ansible-galaxy collection install junipernetworks.device +sudo ansible-galaxy collection install junipernetworks.devices ``` ### Git clone @@ -167,7 +167,7 @@ This example outlines how to use Ansible to install or upgrade the software imag - name: Install Junos OS hosts: dc1 collections: - - junipernetworks.device + - junipernetworks.devices connection: local gather_facts: no vars: @@ -211,8 +211,8 @@ Apache 2.0 ## SUPPORT -Support for this junipernetworks.device collection is provided by the community and Juniper Networks. If you have an -issue with a module in the junipernetworks.device collection, you may: +Support for this junipernetworks.devices collection is provided by the community and Juniper Networks. If you have an +issue with a module in the junipernetworks.devices collection, you may: - Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). - Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) diff --git a/ansible-junos-awx/Makefile b/ansible-junos-awx/Makefile index 626e5434..cccd514e 100644 --- a/ansible-junos-awx/Makefile +++ b/ansible-junos-awx/Makefile @@ -58,7 +58,7 @@ endif .PHONY: docker-exec docker-exec: docker exec -it awx_task pip install jsnapy jxmlease junos-eznc - docker exec -it awx_task ansible-galaxy install junipernetworks.device,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles + docker exec -it awx_task ansible-galaxy install junipernetworks.devices,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' ifneq '$(HOST_FILE)' '' curl -u admin:password --noproxy '*' http://localhost/api/v2/inventories/ --header "Content-Type: application/json" -x POST -d '{"name":"$(INVENTORY_NAME)" , "organization": 1}' diff --git a/ansible-junos-awx/README.md b/ansible-junos-awx/README.md index 1e24a14c..12211f38 100644 --- a/ansible-junos-awx/README.md +++ b/ansible-junos-awx/README.md @@ -26,8 +26,8 @@ This will do the following operations: - Clone AWX repository into the Juniper-awx/awx folder - Change AWX inventory file to include user specifications.Refer [Makefile.variable](#makefilevariable). - Launch AWX conatiners. -- Install junipernetworks.device role with user specified version.Refer [Makefile.variable](#makefilevariable). -- Install python modules required for junipernetworks.device role in awx_task container: jxmlease,junos-eznc,jsnappy. +- Install junipernetworks.devices role with user specified version.Refer [Makefile.variable](#makefilevariable). +- Install python modules required for junipernetworks.devices role in awx_task container: jxmlease,junos-eznc,jsnappy. - Change roles_path in ansible.cfg for awx_task container. - If HOST_FILE is mentioned, an inventory with name INVENTORY_NAME is created and host's loaded into it.Refer [Makefile.variable](#makefilevariable). @@ -508,11 +508,11 @@ Installing collected packages: lxml, ncclient, scp, pyserial, netaddr, junos-ezn Successfully installed colorama-0.3.9 configparser-3.5.0 future-0.16.0 icdiff-1.9.1 jsnapy-1.3.1 junos-eznc-2.1.7 jxmlease-1.0.1 lxml-4.1.1 ncclient-0.5.3 netaddr-0.7.19 pyparsing-2.2.0 pyserial-3.4 scp-0.10.2 You are using pip version 8.1.2, however version 9.0.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. -docker exec -it awx_task ansible-galaxy install junipernetworks.device, -p /etc/ansible/roles +docker exec -it awx_task ansible-galaxy install junipernetworks.devices, -p /etc/ansible/roles - downloading role 'junos', owned by Juniper - downloading role from https://github.com/Juniper/ansible-junos-stdlib/archive/2.0.2.tar.gz -- extracting junipernetworks.device to /etc/ansible/roles/junipernetworks.device -- junipernetworks.device (2.0.2) was installed successfully +- extracting junipernetworks.devices to /etc/ansible/roles/junipernetworks.devices +- junipernetworks.devices (2.0.2) was installed successfully docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' @@ -580,7 +580,7 @@ INVENTORY_NAME = Junos 1. `PROJECT_DATA_DIR` : Provide absolute path to directory where the ansible projects reside.If the directory is not present Makefile will create the path. 2. `AWX_TASK_TAG`: Mention the awx_task tag to be installed.For available versions refer [Dockerhub](https://hub.docker.com/r/ansible/awx_task/tags/). 3. `POSTGRES_DATA_DIR`: Provide absolute path to postgres directory.If the directory is not present Makefile will create the path and create folders required for postgres to run. -4. `ANSIBLE_JUNOS_VERSION`: Mention the junipernetworks.device version to be installed.By default, it installs the latest version. +4. `ANSIBLE_JUNOS_VERSION`: Mention the junipernetworks.devices version to be installed.By default, it installs the latest version. 5. `HOST_FILE`: Provide the absolute path to the host file.This option can be only used if PROJECT_DATA_DIR is mentioned. By default, it doesnot load any host file.Please ensure that a unique INVENTORY_NAME is mentioned to avoid errors e.g Hosts. 6. `INVENTORY_NAME`: The name of the inventory to which HOST_FILE is to be loaded. diff --git a/ansible_collections/junipernetworks/device/README.md b/ansible_collections/junipernetworks/devices/README.md similarity index 100% rename from ansible_collections/junipernetworks/device/README.md rename to ansible_collections/junipernetworks/devices/README.md diff --git a/ansible_collections/junipernetworks/device/docs/Makefile b/ansible_collections/junipernetworks/devices/docs/Makefile similarity index 100% rename from ansible_collections/junipernetworks/device/docs/Makefile rename to ansible_collections/junipernetworks/devices/docs/Makefile diff --git a/ansible_collections/junipernetworks/device/docs/_static/juniper-junos-modules.css b/ansible_collections/junipernetworks/devices/docs/_static/juniper-junos-modules.css similarity index 100% rename from ansible_collections/junipernetworks/device/docs/_static/juniper-junos-modules.css rename to ansible_collections/junipernetworks/devices/docs/_static/juniper-junos-modules.css diff --git a/ansible_collections/junipernetworks/device/docs/_static/juniper.png b/ansible_collections/junipernetworks/devices/docs/_static/juniper.png similarity index 100% rename from ansible_collections/junipernetworks/device/docs/_static/juniper.png rename to ansible_collections/junipernetworks/devices/docs/_static/juniper.png diff --git a/ansible_collections/junipernetworks/device/docs/ansible2rst.py b/ansible_collections/junipernetworks/devices/docs/ansible2rst.py similarity index 100% rename from ansible_collections/junipernetworks/device/docs/ansible2rst.py rename to ansible_collections/junipernetworks/devices/docs/ansible2rst.py diff --git a/ansible_collections/junipernetworks/device/docs/conf.py b/ansible_collections/junipernetworks/devices/docs/conf.py similarity index 100% rename from ansible_collections/junipernetworks/device/docs/conf.py rename to ansible_collections/junipernetworks/devices/docs/conf.py diff --git a/ansible_collections/junipernetworks/device/docs/docreq.txt b/ansible_collections/junipernetworks/devices/docs/docreq.txt similarity index 100% rename from ansible_collections/junipernetworks/device/docs/docreq.txt rename to ansible_collections/junipernetworks/devices/docs/docreq.txt diff --git a/ansible_collections/junipernetworks/device/docs/rst.j2 b/ansible_collections/junipernetworks/devices/docs/rst.j2 similarity index 100% rename from ansible_collections/junipernetworks/device/docs/rst.j2 rename to ansible_collections/junipernetworks/devices/docs/rst.j2 diff --git a/ansible_collections/junipernetworks/device/galaxy.yml b/ansible_collections/junipernetworks/devices/galaxy.yml similarity index 100% rename from ansible_collections/junipernetworks/device/galaxy.yml rename to ansible_collections/junipernetworks/devices/galaxy.yml diff --git a/ansible_collections/junipernetworks/device/plugins/README.md b/ansible_collections/junipernetworks/devices/plugins/README.md similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/README.md rename to ansible_collections/junipernetworks/devices/plugins/README.md diff --git a/ansible_collections/junipernetworks/device/plugins/action/command.py b/ansible_collections/junipernetworks/devices/plugins/action/command.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/command.py rename to ansible_collections/junipernetworks/devices/plugins/action/command.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/config.py b/ansible_collections/junipernetworks/devices/plugins/action/config.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/config.py rename to ansible_collections/junipernetworks/devices/plugins/action/config.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/facts.py b/ansible_collections/junipernetworks/devices/plugins/action/facts.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/facts.py rename to ansible_collections/junipernetworks/devices/plugins/action/facts.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/jsnapy.py b/ansible_collections/junipernetworks/devices/plugins/action/jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/jsnapy.py rename to ansible_collections/junipernetworks/devices/plugins/action/jsnapy.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_command.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_command.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_command.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_command.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_common_action.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_common_action.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_common_action.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_common_action.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_config.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_config.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_config.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_config.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_facts.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_facts.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_facts.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_facts.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_jsnapy.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_jsnapy.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_jsnapy.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_ping.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_ping.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_ping.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_ping.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_pmtud.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_pmtud.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_pmtud.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_pmtud.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_rpc.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_rpc.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_rpc.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_rpc.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_software.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_software.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_software.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_software.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_srx_cluster.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_srx_cluster.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_srx_cluster.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_system.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_system.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_system.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_system.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/juniper_junos_table.py b/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_table.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/juniper_junos_table.py rename to ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_table.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/ping.py b/ansible_collections/junipernetworks/devices/plugins/action/ping.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/ping.py rename to ansible_collections/junipernetworks/devices/plugins/action/ping.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/pmtud.py b/ansible_collections/junipernetworks/devices/plugins/action/pmtud.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/pmtud.py rename to ansible_collections/junipernetworks/devices/plugins/action/pmtud.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/rpc.py b/ansible_collections/junipernetworks/devices/plugins/action/rpc.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/rpc.py rename to ansible_collections/junipernetworks/devices/plugins/action/rpc.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/software.py b/ansible_collections/junipernetworks/devices/plugins/action/software.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/software.py rename to ansible_collections/junipernetworks/devices/plugins/action/software.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/srx_cluster.py b/ansible_collections/junipernetworks/devices/plugins/action/srx_cluster.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/srx_cluster.py rename to ansible_collections/junipernetworks/devices/plugins/action/srx_cluster.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/system.py b/ansible_collections/junipernetworks/devices/plugins/action/system.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/system.py rename to ansible_collections/junipernetworks/devices/plugins/action/system.py diff --git a/ansible_collections/junipernetworks/device/plugins/action/table.py b/ansible_collections/junipernetworks/devices/plugins/action/table.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/action/table.py rename to ansible_collections/junipernetworks/devices/plugins/action/table.py diff --git a/ansible_collections/junipernetworks/device/plugins/callback/jsnapy.py b/ansible_collections/junipernetworks/devices/plugins/callback/jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/callback/jsnapy.py rename to ansible_collections/junipernetworks/devices/plugins/callback/jsnapy.py diff --git a/ansible_collections/junipernetworks/device/plugins/module_utils/__init__.py b/ansible_collections/junipernetworks/devices/plugins/module_utils/__init__.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/module_utils/__init__.py rename to ansible_collections/junipernetworks/devices/plugins/module_utils/__init__.py diff --git a/ansible_collections/junipernetworks/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/module_utils/juniper_junos_common.py rename to ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py index e22d125d..94dc6505 100644 --- a/ansible_collections/junipernetworks/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py @@ -126,7 +126,7 @@ # Constants # Minimum PyEZ version required by shared code. -MIN_PYEZ_VERSION = "2.4.0" +MIN_PYEZ_VERSION = "2.5.0" # Installation URL for PyEZ. PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" # Minimum lxml version required by shared code. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/command.py b/ansible_collections/junipernetworks/devices/plugins/modules/command.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/command.py rename to ansible_collections/junipernetworks/devices/plugins/modules/command.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/config.py b/ansible_collections/junipernetworks/devices/plugins/modules/config.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/config.py rename to ansible_collections/junipernetworks/devices/plugins/modules/config.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/facts.py b/ansible_collections/junipernetworks/devices/plugins/modules/facts.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/facts.py rename to ansible_collections/junipernetworks/devices/plugins/modules/facts.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/jsnapy.py b/ansible_collections/junipernetworks/devices/plugins/modules/jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/jsnapy.py rename to ansible_collections/junipernetworks/devices/plugins/modules/jsnapy.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_command.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_command.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_command.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_command.py index c1b3c127..0a4cb04e 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_command.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_command.py @@ -153,7 +153,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Execute single "show version" command. @@ -309,7 +309,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_config.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_config.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_config.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_config.py index cc979df8..c59d02f4 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_config.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_config.py @@ -520,7 +520,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Retrieve the committed configuration juniper_junos_config: @@ -738,7 +738,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): # Choices which are defined in the common module. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_facts.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_facts.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_facts.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_facts.py index 597ca4c1..198e2d86 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_facts.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_facts.py @@ -90,7 +90,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Gather Junos facts with no configuration juniper_junos_facts: @@ -176,7 +176,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common from ansible.module_utils._text import to_bytes def get_facts_dict(junos_module): diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_jsnapy.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_jsnapy.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_jsnapy.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_jsnapy.py index 4d61441b..c4b07aff 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_jsnapy.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_jsnapy.py @@ -115,7 +115,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: JUNOS Post Checklist @@ -208,7 +208,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_ping.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_ping.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_ping.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_ping.py index c08f3d9d..ad22f345 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_ping.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_ping.py @@ -146,7 +146,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. @@ -385,7 +385,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): # The argument spec for the module. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_pmtud.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_pmtud.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_pmtud.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_pmtud.py index a74fe9a3..05694a5c 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_pmtud.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_pmtud.py @@ -132,7 +132,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Perform PMTUD to 192.68.1.1 with default parameters. @@ -247,7 +247,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): # Constants for MTU size diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_rpc.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_rpc.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_rpc.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_rpc.py index 2fbb1af9..d5355745 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_rpc.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_rpc.py @@ -190,7 +190,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Execute single get-software-information RPC. @@ -246,7 +246,7 @@ - name: Get Device Configuration hosts: all collections: - - junipernetworks.device + - junipernetworks.devices connection: local gather_facts: no tasks: @@ -375,7 +375,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_software.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_software.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_software.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_software.py index 1c523fcd..7bf307cc 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_software.py @@ -322,7 +322,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Execute a basic Junos software upgrade. @@ -390,7 +390,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def parse_version_from_filename(filename): """Attempts to parse a version string from the filename of a Junos package. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_srx_cluster.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_srx_cluster.py similarity index 98% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_srx_cluster.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_srx_cluster.py index 1480e7b6..1ee29327 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_srx_cluster.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_srx_cluster.py @@ -95,7 +95,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Enable an SRX cluster juniper_junos_srx_cluster: @@ -149,7 +149,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_system.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_system.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_system.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_system.py index 949d9889..f20717d3 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_system.py @@ -163,7 +163,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Reboot all REs of the device @@ -255,7 +255,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_table.py b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_table.py similarity index 99% rename from ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_table.py rename to ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_table.py index 1db2736b..eb3c8475 100644 --- a/ansible_collections/junipernetworks/device/plugins/modules/juniper_junos_table.py +++ b/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_table.py @@ -128,7 +128,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table @@ -291,7 +291,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.device.plugins.module_utils import juniper_junos_common +from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common def expand_items(module, data): """Recursively expand any table items diff --git a/ansible_collections/junipernetworks/device/plugins/modules/ping.py b/ansible_collections/junipernetworks/devices/plugins/modules/ping.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/ping.py rename to ansible_collections/junipernetworks/devices/plugins/modules/ping.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/pmtud.py b/ansible_collections/junipernetworks/devices/plugins/modules/pmtud.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/pmtud.py rename to ansible_collections/junipernetworks/devices/plugins/modules/pmtud.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/rpc.py b/ansible_collections/junipernetworks/devices/plugins/modules/rpc.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/rpc.py rename to ansible_collections/junipernetworks/devices/plugins/modules/rpc.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/software.py b/ansible_collections/junipernetworks/devices/plugins/modules/software.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/software.py rename to ansible_collections/junipernetworks/devices/plugins/modules/software.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/srx_cluster.py b/ansible_collections/junipernetworks/devices/plugins/modules/srx_cluster.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/srx_cluster.py rename to ansible_collections/junipernetworks/devices/plugins/modules/srx_cluster.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/system.py b/ansible_collections/junipernetworks/devices/plugins/modules/system.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/system.py rename to ansible_collections/junipernetworks/devices/plugins/modules/system.py diff --git a/ansible_collections/junipernetworks/device/plugins/modules/table.py b/ansible_collections/junipernetworks/devices/plugins/modules/table.py similarity index 100% rename from ansible_collections/junipernetworks/device/plugins/modules/table.py rename to ansible_collections/junipernetworks/devices/plugins/modules/table.py diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index c2b58198..0c26fe5e 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: ################# - name: Retrieve the committed configuration diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 869919fc..6c074bf1 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: "TEST 1 - Gather Facts" juniper_junos_facts: diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index 9545056f..a88d19b3 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: ################################################## #### TEST 1 ## diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index ee441ec6..23dd4fe4 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: "TEST 1 - Ping Host DNS" juniper_junos_ping: diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index e6f16104..c3c9ca0b 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: - name: "TEST 1 - Check path MTU to host DNS" juniper_junos_pmtud: diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index befd0062..ca030930 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.device + - junipernetworks.devices tasks: ################# From 17cc87685ce14b482286e9503cc9ee9c6f48204f Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 9 Jul 2020 14:07:35 +0530 Subject: [PATCH 316/426] modifying the namespace --- ansible_collections/junipernetworks/devices/galaxy.yml | 2 +- .../devices/plugins/module_utils/juniper_junos_common.py | 2 +- requirements.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible_collections/junipernetworks/devices/galaxy.yml b/ansible_collections/junipernetworks/devices/galaxy.yml index c8c7de65..865b237a 100644 --- a/ansible_collections/junipernetworks/devices/galaxy.yml +++ b/ansible_collections/junipernetworks/devices/galaxy.yml @@ -6,7 +6,7 @@ namespace: junipernetworks # The name of the collection. Has the same character restrictions as 'namespace' -name: device +name: devices # The version of the collection. Must be compatible with semantic versioning version: 0.1.0 diff --git a/ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py b/ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py index 94dc6505..66ba92eb 100644 --- a/ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py @@ -126,7 +126,7 @@ # Constants # Minimum PyEZ version required by shared code. -MIN_PYEZ_VERSION = "2.5.0" +MIN_PYEZ_VERSION = "2.4.1" # Installation URL for PyEZ. PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" # Minimum lxml version required by shared code. diff --git a/requirements.txt b/requirements.txt index 858bae37..b67c556b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -ansible >= 2.4 +ansible >= 2.9 junos-eznc >= 2.5.0 -jsnapy>=1.3.2 +jsnapy>=1.3.4 jxmlease docker junos-netconify \ No newline at end of file From ec154ffa9d3e2aad1323a460c0624c3ab0634203 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 9 Jul 2020 16:29:04 +0530 Subject: [PATCH 317/426] MOdified namespace to juniper.device --- Dockerfile | 2 +- ISSUE_TEMPLATE.md | 4 ++-- README.md | 16 ++++++++-------- ansible-junos-awx/Makefile | 2 +- ansible-junos-awx/README.md | 12 ++++++------ .../devices => juniper/device}/README.md | 0 .../devices => juniper/device}/docs/Makefile | 0 .../docs/_static/juniper-junos-modules.css | 0 .../device}/docs/_static/juniper.png | Bin .../device}/docs/ansible2rst.py | 0 .../devices => juniper/device}/docs/conf.py | 0 .../devices => juniper/device}/docs/docreq.txt | 0 .../devices => juniper/device}/docs/rst.j2 | 0 .../devices => juniper/device}/galaxy.yml | 4 ++-- .../device}/plugins/README.md | 0 .../device}/plugins/action/command.py | 0 .../device}/plugins/action/config.py | 0 .../device}/plugins/action/facts.py | 0 .../device}/plugins/action/jsnapy.py | 0 .../plugins/action/juniper_junos_command.py | 0 .../action/juniper_junos_common_action.py | 0 .../plugins/action/juniper_junos_config.py | 0 .../plugins/action/juniper_junos_facts.py | 0 .../plugins/action/juniper_junos_jsnapy.py | 0 .../plugins/action/juniper_junos_ping.py | 0 .../plugins/action/juniper_junos_pmtud.py | 0 .../device}/plugins/action/juniper_junos_rpc.py | 0 .../plugins/action/juniper_junos_software.py | 0 .../plugins/action/juniper_junos_srx_cluster.py | 0 .../plugins/action/juniper_junos_system.py | 0 .../plugins/action/juniper_junos_table.py | 0 .../device}/plugins/action/ping.py | 0 .../device}/plugins/action/pmtud.py | 0 .../device}/plugins/action/rpc.py | 0 .../device}/plugins/action/software.py | 0 .../device}/plugins/action/srx_cluster.py | 0 .../device}/plugins/action/system.py | 0 .../device}/plugins/action/table.py | 0 .../device}/plugins/callback/jsnapy.py | 0 .../device}/plugins/module_utils/__init__.py | 0 .../module_utils/juniper_junos_common.py | 0 .../device}/plugins/modules/command.py | 0 .../device}/plugins/modules/config.py | 0 .../device}/plugins/modules/facts.py | 0 .../device}/plugins/modules/jsnapy.py | 0 .../plugins/modules/juniper_junos_command.py | 4 ++-- .../plugins/modules/juniper_junos_config.py | 4 ++-- .../plugins/modules/juniper_junos_facts.py | 4 ++-- .../plugins/modules/juniper_junos_jsnapy.py | 4 ++-- .../plugins/modules/juniper_junos_ping.py | 4 ++-- .../plugins/modules/juniper_junos_pmtud.py | 4 ++-- .../plugins/modules/juniper_junos_rpc.py | 6 +++--- .../plugins/modules/juniper_junos_software.py | 4 ++-- .../modules/juniper_junos_srx_cluster.py | 4 ++-- .../plugins/modules/juniper_junos_system.py | 4 ++-- .../plugins/modules/juniper_junos_table.py | 4 ++-- .../device}/plugins/modules/ping.py | 0 .../device}/plugins/modules/pmtud.py | 0 .../device}/plugins/modules/rpc.py | 0 .../device}/plugins/modules/software.py | 0 .../device}/plugins/modules/srx_cluster.py | 0 .../device}/plugins/modules/system.py | 0 .../device}/plugins/modules/table.py | 0 tests/pb.juniper_junos_config.yml | 2 +- tests/pb.juniper_junos_facts.yml | 2 +- tests/pb.juniper_junos_jsnapy.yml | 2 +- tests/pb.juniper_junos_ping.yml | 2 +- tests/pb.juniper_junos_pmtud.yml | 2 +- tests/pb.juniper_junos_rpc.yml | 2 +- 69 files changed, 49 insertions(+), 49 deletions(-) rename ansible_collections/{junipernetworks/devices => juniper/device}/README.md (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/docs/Makefile (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/docs/_static/juniper-junos-modules.css (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/docs/_static/juniper.png (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/docs/ansible2rst.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/docs/conf.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/docs/docreq.txt (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/docs/rst.j2 (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/galaxy.yml (98%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/README.md (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/command.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/config.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/facts.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/jsnapy.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_command.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_common_action.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_config.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_facts.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_jsnapy.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_ping.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_pmtud.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_rpc.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_software.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_srx_cluster.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_system.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/juniper_junos_table.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/ping.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/pmtud.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/rpc.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/software.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/srx_cluster.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/system.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/action/table.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/callback/jsnapy.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/module_utils/__init__.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/module_utils/juniper_junos_common.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/command.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/config.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/facts.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/jsnapy.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_command.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_config.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_facts.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_jsnapy.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_ping.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_pmtud.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_rpc.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_software.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_srx_cluster.py (98%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_system.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/juniper_junos_table.py (99%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/ping.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/pmtud.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/rpc.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/software.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/srx_cluster.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/system.py (100%) rename ansible_collections/{junipernetworks/devices => juniper/device}/plugins/modules/table.py (100%) diff --git a/Dockerfile b/Dockerfile index 9364cdea..ccb6c4a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apk del -r --purge gcc make g++ &&\ rm -rf /var/cache/apk/* &&\ rm -rf /tmp/* -WORKDIR /etc/ansible/collections/ansible_collections/junipernetworks.devices +WORKDIR /etc/ansible/collections/ansible_collections/juniper.device COPY action_plugins action_plugins COPY callback_plugins callback_plugins COPY library library diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 23532e11..f5f0b51d 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -13,9 +13,9 @@ Module Name -junipernetworks.devices collection and Python libraries version +juniper.device collection and Python libraries version +Also provide the version of juniper.device collection--> ``` ``` diff --git a/README.md b/README.md index c501f062..5e466850 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The repo is under active development. If you take a clone, you are getting the Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS). This collection is hosted on the Ansible Galaxy website under the collection -[junipernetworks.devices](https://galaxy.ansible.com/Juniper/junos/). The junipernetworks.devices collection includes +[juniper.device](https://galaxy.ansible.com/Juniper/junos/). The juniper.device collection includes a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. These tasks include: installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, @@ -18,7 +18,7 @@ retrieving information, and resetting, rebooting, or shutting down managed devic ## juniper.junos roles by Juniper Networks Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have -junipernetworks.devices collection support. Juniper.junos roles have been moved to roles branch. +juniper.device collection support. Juniper.junos roles have been moved to roles branch. For more information for roles, check: https://github.com/Juniper/ansible-junos-stdlib/tree/roles @@ -33,7 +33,7 @@ using the modules in this collection when writing new playbooks that manage Juno ## Overview of Modules -This junipernetworks.devices collection includes the following modules: +This juniper.device collection includes the following modules: - **juniper_junos_command** — Execute one or more CLI commands on a Junos device. - **juniper_junos_config** — Manipulate the configuration of a Junos device. @@ -100,10 +100,10 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge ### Ansible Galaxy collection You can use the ansible-galaxy install command to install the latest -version of the junipernetworks.devices collection. +version of the juniper.device collection. ```bash -sudo ansible-galaxy collection install junipernetworks.devices +sudo ansible-galaxy collection install juniper.device ``` ### Git clone @@ -167,7 +167,7 @@ This example outlines how to use Ansible to install or upgrade the software imag - name: Install Junos OS hosts: dc1 collections: - - junipernetworks.devices + - juniper.device connection: local gather_facts: no vars: @@ -211,8 +211,8 @@ Apache 2.0 ## SUPPORT -Support for this junipernetworks.devices collection is provided by the community and Juniper Networks. If you have an -issue with a module in the junipernetworks.devices collection, you may: +Support for this juniper.device collection is provided by the community and Juniper Networks. If you have an +issue with a module in the juniper.device collection, you may: - Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). - Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) diff --git a/ansible-junos-awx/Makefile b/ansible-junos-awx/Makefile index cccd514e..cd273ff3 100644 --- a/ansible-junos-awx/Makefile +++ b/ansible-junos-awx/Makefile @@ -58,7 +58,7 @@ endif .PHONY: docker-exec docker-exec: docker exec -it awx_task pip install jsnapy jxmlease junos-eznc - docker exec -it awx_task ansible-galaxy install junipernetworks.devices,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles + docker exec -it awx_task ansible-galaxy install juniper.junos,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' ifneq '$(HOST_FILE)' '' curl -u admin:password --noproxy '*' http://localhost/api/v2/inventories/ --header "Content-Type: application/json" -x POST -d '{"name":"$(INVENTORY_NAME)" , "organization": 1}' diff --git a/ansible-junos-awx/README.md b/ansible-junos-awx/README.md index 12211f38..70afd815 100644 --- a/ansible-junos-awx/README.md +++ b/ansible-junos-awx/README.md @@ -26,8 +26,8 @@ This will do the following operations: - Clone AWX repository into the Juniper-awx/awx folder - Change AWX inventory file to include user specifications.Refer [Makefile.variable](#makefilevariable). - Launch AWX conatiners. -- Install junipernetworks.devices role with user specified version.Refer [Makefile.variable](#makefilevariable). -- Install python modules required for junipernetworks.devices role in awx_task container: jxmlease,junos-eznc,jsnappy. +- Install juniper.junos role with user specified version.Refer [Makefile.variable](#makefilevariable). +- Install python modules required for juniper.junos role in awx_task container: jxmlease,junos-eznc,jsnappy. - Change roles_path in ansible.cfg for awx_task container. - If HOST_FILE is mentioned, an inventory with name INVENTORY_NAME is created and host's loaded into it.Refer [Makefile.variable](#makefilevariable). @@ -508,11 +508,11 @@ Installing collected packages: lxml, ncclient, scp, pyserial, netaddr, junos-ezn Successfully installed colorama-0.3.9 configparser-3.5.0 future-0.16.0 icdiff-1.9.1 jsnapy-1.3.1 junos-eznc-2.1.7 jxmlease-1.0.1 lxml-4.1.1 ncclient-0.5.3 netaddr-0.7.19 pyparsing-2.2.0 pyserial-3.4 scp-0.10.2 You are using pip version 8.1.2, however version 9.0.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. -docker exec -it awx_task ansible-galaxy install junipernetworks.devices, -p /etc/ansible/roles +docker exec -it awx_task ansible-galaxy install juniper.junos, -p /etc/ansible/roles - downloading role 'junos', owned by Juniper - downloading role from https://github.com/Juniper/ansible-junos-stdlib/archive/2.0.2.tar.gz -- extracting junipernetworks.devices to /etc/ansible/roles/junipernetworks.devices -- junipernetworks.devices (2.0.2) was installed successfully +- extracting juniper.junos to /etc/ansible/roles/juniper.junos +- juniper.junos (2.0.2) was installed successfully docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' @@ -580,7 +580,7 @@ INVENTORY_NAME = Junos 1. `PROJECT_DATA_DIR` : Provide absolute path to directory where the ansible projects reside.If the directory is not present Makefile will create the path. 2. `AWX_TASK_TAG`: Mention the awx_task tag to be installed.For available versions refer [Dockerhub](https://hub.docker.com/r/ansible/awx_task/tags/). 3. `POSTGRES_DATA_DIR`: Provide absolute path to postgres directory.If the directory is not present Makefile will create the path and create folders required for postgres to run. -4. `ANSIBLE_JUNOS_VERSION`: Mention the junipernetworks.devices version to be installed.By default, it installs the latest version. +4. `ANSIBLE_JUNOS_VERSION`: Mention the juniper.junos version to be installed.By default, it installs the latest version. 5. `HOST_FILE`: Provide the absolute path to the host file.This option can be only used if PROJECT_DATA_DIR is mentioned. By default, it doesnot load any host file.Please ensure that a unique INVENTORY_NAME is mentioned to avoid errors e.g Hosts. 6. `INVENTORY_NAME`: The name of the inventory to which HOST_FILE is to be loaded. diff --git a/ansible_collections/junipernetworks/devices/README.md b/ansible_collections/juniper/device/README.md similarity index 100% rename from ansible_collections/junipernetworks/devices/README.md rename to ansible_collections/juniper/device/README.md diff --git a/ansible_collections/junipernetworks/devices/docs/Makefile b/ansible_collections/juniper/device/docs/Makefile similarity index 100% rename from ansible_collections/junipernetworks/devices/docs/Makefile rename to ansible_collections/juniper/device/docs/Makefile diff --git a/ansible_collections/junipernetworks/devices/docs/_static/juniper-junos-modules.css b/ansible_collections/juniper/device/docs/_static/juniper-junos-modules.css similarity index 100% rename from ansible_collections/junipernetworks/devices/docs/_static/juniper-junos-modules.css rename to ansible_collections/juniper/device/docs/_static/juniper-junos-modules.css diff --git a/ansible_collections/junipernetworks/devices/docs/_static/juniper.png b/ansible_collections/juniper/device/docs/_static/juniper.png similarity index 100% rename from ansible_collections/junipernetworks/devices/docs/_static/juniper.png rename to ansible_collections/juniper/device/docs/_static/juniper.png diff --git a/ansible_collections/junipernetworks/devices/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py similarity index 100% rename from ansible_collections/junipernetworks/devices/docs/ansible2rst.py rename to ansible_collections/juniper/device/docs/ansible2rst.py diff --git a/ansible_collections/junipernetworks/devices/docs/conf.py b/ansible_collections/juniper/device/docs/conf.py similarity index 100% rename from ansible_collections/junipernetworks/devices/docs/conf.py rename to ansible_collections/juniper/device/docs/conf.py diff --git a/ansible_collections/junipernetworks/devices/docs/docreq.txt b/ansible_collections/juniper/device/docs/docreq.txt similarity index 100% rename from ansible_collections/junipernetworks/devices/docs/docreq.txt rename to ansible_collections/juniper/device/docs/docreq.txt diff --git a/ansible_collections/junipernetworks/devices/docs/rst.j2 b/ansible_collections/juniper/device/docs/rst.j2 similarity index 100% rename from ansible_collections/junipernetworks/devices/docs/rst.j2 rename to ansible_collections/juniper/device/docs/rst.j2 diff --git a/ansible_collections/junipernetworks/devices/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml similarity index 98% rename from ansible_collections/junipernetworks/devices/galaxy.yml rename to ansible_collections/juniper/device/galaxy.yml index 865b237a..59168ad5 100644 --- a/ansible_collections/junipernetworks/devices/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -3,10 +3,10 @@ # The namespace of the collection. This can be a company/brand/organization or product namespace under which all # content lives. May only contain alphanumeric characters and underscores. Additionally namespaces cannot start with # underscores or numbers and cannot contain consecutive underscores -namespace: junipernetworks +namespace: juniper # The name of the collection. Has the same character restrictions as 'namespace' -name: devices +name: device # The version of the collection. Must be compatible with semantic versioning version: 0.1.0 diff --git a/ansible_collections/junipernetworks/devices/plugins/README.md b/ansible_collections/juniper/device/plugins/README.md similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/README.md rename to ansible_collections/juniper/device/plugins/README.md diff --git a/ansible_collections/junipernetworks/devices/plugins/action/command.py b/ansible_collections/juniper/device/plugins/action/command.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/command.py rename to ansible_collections/juniper/device/plugins/action/command.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/config.py b/ansible_collections/juniper/device/plugins/action/config.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/config.py rename to ansible_collections/juniper/device/plugins/action/config.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/facts.py b/ansible_collections/juniper/device/plugins/action/facts.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/facts.py rename to ansible_collections/juniper/device/plugins/action/facts.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/jsnapy.py b/ansible_collections/juniper/device/plugins/action/jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/jsnapy.py rename to ansible_collections/juniper/device/plugins/action/jsnapy.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_command.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_command.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_command.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_command.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_common_action.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_common_action.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_config.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_config.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_config.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_config.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_facts.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_facts.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_facts.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_facts.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_jsnapy.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_jsnapy.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_jsnapy.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_ping.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_ping.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_ping.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_ping.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_pmtud.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_pmtud.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_pmtud.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_pmtud.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_rpc.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_rpc.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_rpc.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_rpc.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_software.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_software.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_software.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_software.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_srx_cluster.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_srx_cluster.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_srx_cluster.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_system.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_system.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_system.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_system.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_table.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_table.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/juniper_junos_table.py rename to ansible_collections/juniper/device/plugins/action/juniper_junos_table.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/ping.py b/ansible_collections/juniper/device/plugins/action/ping.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/ping.py rename to ansible_collections/juniper/device/plugins/action/ping.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/pmtud.py b/ansible_collections/juniper/device/plugins/action/pmtud.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/pmtud.py rename to ansible_collections/juniper/device/plugins/action/pmtud.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/rpc.py b/ansible_collections/juniper/device/plugins/action/rpc.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/rpc.py rename to ansible_collections/juniper/device/plugins/action/rpc.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/software.py b/ansible_collections/juniper/device/plugins/action/software.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/software.py rename to ansible_collections/juniper/device/plugins/action/software.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/srx_cluster.py b/ansible_collections/juniper/device/plugins/action/srx_cluster.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/srx_cluster.py rename to ansible_collections/juniper/device/plugins/action/srx_cluster.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/system.py b/ansible_collections/juniper/device/plugins/action/system.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/system.py rename to ansible_collections/juniper/device/plugins/action/system.py diff --git a/ansible_collections/junipernetworks/devices/plugins/action/table.py b/ansible_collections/juniper/device/plugins/action/table.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/action/table.py rename to ansible_collections/juniper/device/plugins/action/table.py diff --git a/ansible_collections/junipernetworks/devices/plugins/callback/jsnapy.py b/ansible_collections/juniper/device/plugins/callback/jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/callback/jsnapy.py rename to ansible_collections/juniper/device/plugins/callback/jsnapy.py diff --git a/ansible_collections/junipernetworks/devices/plugins/module_utils/__init__.py b/ansible_collections/juniper/device/plugins/module_utils/__init__.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/module_utils/__init__.py rename to ansible_collections/juniper/device/plugins/module_utils/__init__.py diff --git a/ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/module_utils/juniper_junos_common.py rename to ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/command.py rename to ansible_collections/juniper/device/plugins/modules/command.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/config.py rename to ansible_collections/juniper/device/plugins/modules/config.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/facts.py rename to ansible_collections/juniper/device/plugins/modules/facts.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/jsnapy.py rename to ansible_collections/juniper/device/plugins/modules/jsnapy.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_command.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_command.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_command.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_command.py index 0a4cb04e..8766edd2 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_command.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_command.py @@ -153,7 +153,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Execute single "show version" command. @@ -309,7 +309,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_config.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_config.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_config.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_config.py index c59d02f4..193a7200 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_config.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_config.py @@ -520,7 +520,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Retrieve the committed configuration juniper_junos_config: @@ -738,7 +738,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): # Choices which are defined in the common module. diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_facts.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_facts.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_facts.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_facts.py index 198e2d86..4383b4df 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_facts.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_facts.py @@ -90,7 +90,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Gather Junos facts with no configuration juniper_junos_facts: @@ -176,7 +176,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common from ansible.module_utils._text import to_bytes def get_facts_dict(junos_module): diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_jsnapy.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_jsnapy.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_jsnapy.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_jsnapy.py index c4b07aff..0d7a587a 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_jsnapy.py @@ -115,7 +115,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: JUNOS Post Checklist @@ -208,7 +208,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_ping.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_ping.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_ping.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_ping.py index ad22f345..872b6826 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_ping.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_ping.py @@ -146,7 +146,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. @@ -385,7 +385,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): # The argument spec for the module. diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_pmtud.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_pmtud.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_pmtud.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_pmtud.py index 05694a5c..6add19a1 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_pmtud.py @@ -132,7 +132,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Perform PMTUD to 192.68.1.1 with default parameters. @@ -247,7 +247,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): # Constants for MTU size diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_rpc.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_rpc.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_rpc.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_rpc.py index d5355745..dcbcc18c 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_rpc.py @@ -190,7 +190,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Execute single get-software-information RPC. @@ -246,7 +246,7 @@ - name: Get Device Configuration hosts: all collections: - - junipernetworks.devices + - juniper.device connection: local gather_facts: no tasks: @@ -375,7 +375,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_software.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_software.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_software.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_software.py index 7bf307cc..e8bff25a 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_software.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_software.py @@ -322,7 +322,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Execute a basic Junos software upgrade. @@ -390,7 +390,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def parse_version_from_filename(filename): """Attempts to parse a version string from the filename of a Junos package. diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_srx_cluster.py similarity index 98% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_srx_cluster.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_srx_cluster.py index 1ee29327..fd505f5c 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_srx_cluster.py @@ -95,7 +95,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Enable an SRX cluster juniper_junos_srx_cluster: @@ -149,7 +149,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_system.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_system.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_system.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_system.py index f20717d3..3dff9907 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_system.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_system.py @@ -163,7 +163,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Reboot all REs of the device @@ -255,7 +255,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def main(): # Create the module instance. diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_table.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_table.py similarity index 99% rename from ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_table.py rename to ansible_collections/juniper/device/plugins/modules/juniper_junos_table.py index eb3c8475..b0c5d7f9 100644 --- a/ansible_collections/junipernetworks/devices/plugins/modules/juniper_junos_table.py +++ b/ansible_collections/juniper/device/plugins/modules/juniper_junos_table.py @@ -128,7 +128,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table @@ -291,7 +291,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.junipernetworks.devices.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common def expand_items(module, data): """Recursively expand any table items diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/ping.py rename to ansible_collections/juniper/device/plugins/modules/ping.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/pmtud.py rename to ansible_collections/juniper/device/plugins/modules/pmtud.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/rpc.py rename to ansible_collections/juniper/device/plugins/modules/rpc.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/software.py rename to ansible_collections/juniper/device/plugins/modules/software.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/srx_cluster.py rename to ansible_collections/juniper/device/plugins/modules/srx_cluster.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/system.py rename to ansible_collections/juniper/device/plugins/modules/system.py diff --git a/ansible_collections/junipernetworks/devices/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py similarity index 100% rename from ansible_collections/junipernetworks/devices/plugins/modules/table.py rename to ansible_collections/juniper/device/plugins/modules/table.py diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 0c26fe5e..c0e4f8f9 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: ################# - name: Retrieve the committed configuration diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 6c074bf1..db5b12c1 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: "TEST 1 - Gather Facts" juniper_junos_facts: diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index a88d19b3..a7f86240 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: ################################################## #### TEST 1 ## diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index 23dd4fe4..dfc4293c 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: "TEST 1 - Ping Host DNS" juniper_junos_ping: diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index c3c9ca0b..61e74c4f 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: - name: "TEST 1 - Check path MTU to host DNS" juniper_junos_pmtud: diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index ca030930..32553dca 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -4,7 +4,7 @@ connection: local gather_facts: no collections: - - junipernetworks.devices + - juniper.device tasks: ################# From 43fd15fcc36385bdb8aeaebe6643a5648accd967 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 14 Jul 2020 09:09:33 -0400 Subject: [PATCH 318/426] updated requirements.txt Signed-off-by: Stephen Steiner --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2ffd45ba..515c1299 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ ansible >= 2.7 -junos-eznc -jsnapy==1.3.4 +junos-eznc>=2.5.0 +jsnapy>=1.3.4 jxmlease docker junos-netconify From 41e33c5a3a6730a0f3e2f78dfccf97e91112f825 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 14 Jul 2020 13:11:50 -0400 Subject: [PATCH 319/426] updated Dockerfile and added entrypoint Signed-off-by: Stephen Steiner --- Dockerfile | 20 ++++++++++------ entrypoint.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 +--- 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 86b1c52f..3b270a55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,8 @@ LABEL net.juniper.image.maintainer="Stephen Steiner " WORKDIR /tmp -RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make +RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make \ + bash COPY requirements.txt . RUN pip3 install -r requirements.txt @@ -14,13 +15,18 @@ RUN apk del -r --purge gcc make g++ &&\ rm -rf /var/cache/apk/* &&\ rm -rf /tmp/* -WORKDIR /etc/ansible/roles/Juniper.junos -COPY action_plugins action_plugins -COPY callback_plugins callback_plugins -COPY library library -COPY meta meta -COPY module_utils module_utils +WORKDIR /usr/share/ansible/collections/ +COPY ansible_collections/ . + +WORKDIR /usr/bin +COPY entrypoint.sh . +RUN chmod +x entrypoint.sh + +# Also install the roles, until collections is ready for prime-time +RUN ansible-galaxy role install Juniper.junos WORKDIR /playbooks VOLUME /playbooks + +ENTRYPOINT ["/usr/bin/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000..868563bb --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set +e + +## Functions +function apk_add { + echo "Installing additional OS packages" + while IFS= read -r pkg + do + echo "Installing ${pkg}" + apk add --no-cache -q "${pkg}" + done < "$1" +} + +function pip_install { + echo "Installing Python packages" + pip install -r "$1" +} + +function galaxy_install { + echo "Install Ansible roles" + ansible-galaxy install -r "$1" +} + +function run_command { + echo "Executing given commands" + bash -c "$*" +} + + +if [ "$APK" ]; then APK=$APK +elif [ -f "/extras/apk.txt" ]; then APK="/extras/apk.txt" +else APK='' +fi + +if [ "$REQ" ]; then REQ=$REQ +elif [ -f "/extras/requirements.txt" ];then REQ="/extras/requirements.txt" +else REQ='' +fi + +if [ "$ROLES" ]; then ROLES=$ROLES +elif [ -f "/extras/requirements.yml" ]; then ROLES="/extras/requirements.yml" +else ROLES='' +fi + + +[[ -z "$APK" ]] || apk_add "$APK" + +[[ -z "$REQ" ]] || pip_install "$REQ" + +[[ -z "$ROLES" ]] || galaxy_install "$ROLES" + +if [ -z "$1" ] +then + echo "Starting an interactive Bash session" + /bin/bash +else run_command "$*" +fi + diff --git a/requirements.txt b/requirements.txt index 515c1299..757ac535 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ -ansible >= 2.7 junos-eznc>=2.5.0 +ansible >= 2.7 jsnapy>=1.3.4 jxmlease -docker -junos-netconify From 487d2feaf9cd14ee7b322bbf7ad6f2841aa730c1 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 14 Jul 2020 14:04:42 -0400 Subject: [PATCH 320/426] updated README and Dockerfile Signed-off-by: Stephen Steiner --- Dockerfile | 16 ++++++------- README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3b270a55..03070954 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ -FROM juniper/pyez:latest +FROM juniper/pyez -LABEL net.juniper.image.maintainer="Stephen Steiner " +LABEL net.juniper.image.maintainer="Stephen Steiner " \ + net.junier.image.description="Lightweight image with Ansible and the Junos roles" -WORKDIR /tmp - -RUN apk add --no-cache ca-certificates openssh-client build-base gcc g++ make \ - bash +RUN apk add --no-cache build-base python3-dev py3-pip \ + openssl-dev curl ca-certificates bash +WORKDIR /tmp COPY requirements.txt . RUN pip3 install -r requirements.txt @@ -25,8 +25,8 @@ RUN chmod +x entrypoint.sh # Also install the roles, until collections is ready for prime-time RUN ansible-galaxy role install Juniper.junos -WORKDIR /playbooks +WORKDIR /project -VOLUME /playbooks +VOLUME /project ENTRYPOINT ["/usr/bin/entrypoint.sh"] diff --git a/README.md b/README.md index b2c02637..1f023315 100644 --- a/README.md +++ b/README.md @@ -151,13 +151,13 @@ $ echo $ANSIBLE_LIBRARY To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. ```bash -docker run -it --rm juniper/pyez-ansible ash +docker run -it --rm juniper/pyez-ansible ``` Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. ```bash -docker run -it --rm -v $PWD:/project juniper/pyez-ansible ash +docker run -it --rm -v $PWD:/project juniper/pyez-ansible ``` You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: @@ -175,13 +175,74 @@ We can move to the example directory and run the playbook with the following com cd example/ docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml ``` +You can pass any valid command string after the container name and it will be passed to Bash for execution. You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. -```bash +```text alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" pb-ansible -i hosts playbook.yml ``` +### Extending the container with additional packages + +It's possible to install additional OS (Alpine) packages, Python packages (via pip), and Ansible roles or collections at container instantiation. This can be done by passing in environment variables or bind mounting files. + +__OS Packages__ + +Environment Variable: `$APK` +Bind Mount: `/extras/apk.txt` +File Format: list of valid Alpine packages, one per line +Examples: + +As an environment variable, where the file containing a list of packages is in the current directory. + +```text +docker run -it --rm -v $PWD:/project -e APK="apk.txt" juniper/pyez-ansible +``` + +As a bind mount. + +```text +docker run -it --rm -v $PWD/apk.txt:/extras/apk.txt juniper/pyez-ansible +``` + +__Python Packages__ + +Environment Variable: `$REQ` +Bind Mount: `/extras/requirements.txt` +File Format: pip [requirements](https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format) file + +Examples: + +```text +docker run -it --rm -v $PWD:/project -e REQ="requirements.txt" juniper/pyez-ansible +``` + +As a bind mount. + +```text +docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.txt juniper/pyez-ansible +``` + +__Ansible Packages__ + +Environment Variable: `$ROLES` +Bind Mount: `/extras/requirements.yml` +File Format: Ansible [requirements](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html#install-multiple-collections-with-a-requirements-file) file + +_NOTE:_ This works for collections as well as roles. + +Examples: + +```text +docker run -it --rm -v $PWD:/project -e REQ="requirements.yml" juniper/pyez-ansible +``` + +As a bind mount. + +```text +docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.yml juniper/pyez-ansible +``` ## Example Playbook From 7ce036f294bc22474cdbfc1357fb4da3e9b344eb Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 27 Aug 2020 11:12:15 +0530 Subject: [PATCH 321/426] Add message for collection under active development --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e466850..29cd0edc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ [![Documentation Status](https://readthedocs.org/projects/junos-ansible-modules/badge/?version=stable)](https://junos-ansible-modules.readthedocs.io/en/2.3.0/) +NOTE : The collection for Ansible is under development and changes are expected in the namespace/module implementation. +One may use it but it is recommended to currently use juniper.junos roles for professional implementation. +Refer - https://github.com/Juniper/ansible-junos-stdlib/tree/roles for more info. + # Juniper Ansible collection for Junos -The repo is under active development. If you take a clone, you are getting the latest, and perhaps not entirely stable code. +The repo is under active development. If you take a clone, you are getting the latest, and perhaps not entirely stable code. ## About From 9216ec941b6cd6fc2de9b4589b95ba0480ae9043 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 27 Aug 2020 13:04:23 +0530 Subject: [PATCH 322/426] Modified collection name in documentation --- README.md | 2 +- ansible_collections/juniper/device/docs/ansible2rst.py | 9 ++++----- .../device/plugins/module_utils/juniper_junos_common.py | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6c4c2c50..1cddb69c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ https://github.com/Juniper/ansible-junos-stdlib/tree/roles Since Ansible version >= 2.1, Ansible also natively includes [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included -in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.junos +in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.device collection have names which begin with the prefix `juniper_junos_`. These two sets of Junos modules can coexist on the same Ansible control machine, and an Ansible play may invoke a module from either (or both) sets. Juniper Networks recommends using the modules in this collection when writing new playbooks that manage Junos devices. diff --git a/ansible_collections/juniper/device/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py index a5809813..b6375483 100755 --- a/ansible_collections/juniper/device/docs/ansible2rst.py +++ b/ansible_collections/juniper/device/docs/ansible2rst.py @@ -42,7 +42,7 @@ try: from html import escape as html_escape except ImportError: - # Python-3.2 or later + # Python-3.5 or later import cgi def html_escape(text, quote=True): @@ -67,7 +67,7 @@ def html_escape(text, quote=True): DEPRECATED = b" (D)" MODULE_NAME_STARTS_WITH = "juniper_junos_" -MODULEDIR = "../library/" +MODULEDIR = "../plugins/modules/" OUTPUTDIR = "./" ##################################################################################### @@ -199,7 +199,7 @@ def add_fragments(doc, filename): else: fragment_name, fragment_var = fragment_slug, 'DOCUMENTATION' - fragment_loader.add_directory('../module_utils/') + fragment_loader.add_directory('../plugins/module_utils/') fragment_class = fragment_loader.get(fragment_name) assert fragment_class is not None @@ -230,7 +230,6 @@ def add_fragments(doc, filename): doc[key] = value - def get_docstring(filename, verbose=False): """ DOCUMENTATION can be extended using documentation fragments loaded by the PluginLoader from the module_docs_fragments directory. @@ -410,7 +409,7 @@ def main(): index_file_path = os.path.join(OUTPUTDIR, "index.rst") index_file = open(index_file_path, "w") - index_file.write('Juniper.junos Ansible Modules\n') + index_file.write('juniper.device Ansible Modules\n') index_file.write('=================================================\n') index_file.write('\n') index_file.write('Contents:\n') diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 66ba92eb..9dd9aacf 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -126,7 +126,7 @@ # Constants # Minimum PyEZ version required by shared code. -MIN_PYEZ_VERSION = "2.4.1" +MIN_PYEZ_VERSION = "2.5.2" # Installation URL for PyEZ. PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" # Minimum lxml version required by shared code. From 70c57d9c787484007620f36016ebecbabe8d5b9b Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 6 Oct 2020 08:00:39 -0400 Subject: [PATCH 323/426] adding ssh-client Signed-off-by: Stephen Steiner --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03070954..983a7f1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM juniper/pyez +FROM juniper/pyez:2.4.0 LABEL net.juniper.image.maintainer="Stephen Steiner " \ - net.junier.image.description="Lightweight image with Ansible and the Junos roles" + net.juniper.image.description="Lightweight image with Ansible and the Junos roles" RUN apk add --no-cache build-base python3-dev py3-pip \ openssl-dev curl ca-certificates bash From 9d1c2712f9bf952a05cf012783813288f3e811a1 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 6 Oct 2020 08:05:30 -0400 Subject: [PATCH 324/426] updated README and added openssh-client Signed-off-by: Stephen Steiner --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 983a7f1c..f719266e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ LABEL net.juniper.image.maintainer="Stephen Steiner " \ net.juniper.image.description="Lightweight image with Ansible and the Junos roles" RUN apk add --no-cache build-base python3-dev py3-pip \ - openssl-dev curl ca-certificates bash + openssl-dev curl ca-certificates bash openssh-client WORKDIR /tmp COPY requirements.txt . From 95d55c5195e2f4d6725cfa173fc93fd9f176fc29 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 6 Oct 2020 08:15:22 -0400 Subject: [PATCH 325/426] cleaned up README per markdown guidelines Signed-off-by: Stephen Steiner --- README.md | 101 ++++++++++++++---------------------------------------- 1 file changed, 25 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 1f023315..44bd7ac3 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,7 @@ retrieving information, and resetting, rebooting, or shutting down managed devic ## Juniper.junos roles by Juniper Networks -Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have -Juniper.junos_collection support. Juniper.junos roles have been moved to roles branch. Roles will be supported for now. -For more information for roles, check: -https://github.com/Juniper/ansible-junos-stdlib/tree/roles +Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have Juniper.junos_collection support. Juniper.junos roles have been moved to roles branch. Roles will be supported for now. For more information for roles, check: [https://github.com/Juniper/ansible-junos-stdlib/tree/roles](https://github.com/Juniper/ansible-junos-stdlib/tree/roles) ## Two Sets of Ansible Modules for Junos devices @@ -48,6 +45,7 @@ This Juniper.junos_collection collection includes the following modules: - **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. ### PyEZ Version Requirement + For ansible collection junos_collection we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. ### Overview of Plugins @@ -91,29 +89,6 @@ You must have the [DEPENDENCIES](#dependencies) installed on your system. ### NOTICES -#### Ubuntu 14.04 - -If you're dealing with Ubuntu 14.04 and faced following error during the installation, it's because the system python -which used by Ubuntu 14.04 is locked to 2.7.6 till EOL, as a result, please consider to skip galaxy certification process -by appending `-c` option of ansible-galaxy. i.e. `ansible-galaxy collection install Juniper.junos_collection -c` - - [WARNING]: - Juniper.junos_collection was NOT installed successfully: Failed to get data - from the API server (https://galaxy.ansible.com/api/): Failed to validate the - SSL certificate for galaxy.ansible.com:443. Make sure your managed systems have - a valid CA certificate installed. If the website serving the url uses SNI you - need python >= 2.7.9 on your managed machine (the python executable used - (/usr/bin/python) is version: 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC - 4.8.4]) or you can install the `urllib3`, `pyOpenSSL`, `ndg-httpsclient`, and - `pyasn1` python modules to perform SNI verification in python >= 2.6. You can - use validate_certs=False if you do not need to confirm the servers identity but - this is unsafe and not recommended. Paths checked for this platform: - /etc/ssl/certs, /etc/pki/ca-trust/extracted/pem, /etc/pki/tls/certs, /usr/share - /ca-certificates/cacert.org, /etc/ansible. The exception msg was: hostname - u'galaxy.ansible.com' doesn't match either of - '*.c1e4.galaxy.openshiftapps.com', 'c1e4.galaxy.openshiftapps.com'. - - ERROR! - you can use --ignore-errors to skip failed collections and finish processing the list. - ### MacOS Mojave and newer In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keygen` are created using the newer 'OPENSSH' key format, even when specifying `-t rsa` during creation. This directly affects the usage of ssh keys, particularly when using the `ssh_private_key_file`. To create/convert/check keys, follow these steps: @@ -127,38 +102,28 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge You can use the ansible-galaxy install command to install the latest development version of the junos collection directly from GitHub. -```bash -sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos_collection -``` + sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos_collection ### Git clone For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: -```bash -user@ansible-junos-stdlib> source env-setup -``` + user@ansible-junos-stdlib> source env-setup This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the installed Ansible library path. For example: -```bash -$ echo $ANSIBLE_LIBRARY -/home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible -``` + $ echo $ANSIBLE_LIBRARY + /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible ### Docker To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. -```bash -docker run -it --rm juniper/pyez-ansible -``` + docker run -it --rm juniper/pyez-ansible Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. -```bash -docker run -it --rm -v $PWD:/project juniper/pyez-ansible -``` + docker run -it --rm -v $PWD:/project juniper/pyez-ansible You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: @@ -171,23 +136,21 @@ You can also use the container as an executable to run your playbooks. Let's ass We can move to the example directory and run the playbook with the following command: -```bash -cd example/ -docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml -``` + cd example/ + docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml + You can pass any valid command string after the container name and it will be passed to Bash for execution. You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. -```text -alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" -pb-ansible -i hosts playbook.yml -``` + alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" + pb-ansible -i hosts playbook.yml + ### Extending the container with additional packages It's possible to install additional OS (Alpine) packages, Python packages (via pip), and Ansible roles or collections at container instantiation. This can be done by passing in environment variables or bind mounting files. -__OS Packages__ +#### OS Packages Environment Variable: `$APK` Bind Mount: `/extras/apk.txt` @@ -196,17 +159,13 @@ Examples: As an environment variable, where the file containing a list of packages is in the current directory. -```text -docker run -it --rm -v $PWD:/project -e APK="apk.txt" juniper/pyez-ansible -``` + docker run -it --rm -v $PWD:/project -e APK="apk.txt" juniper/pyez-ansible As a bind mount. -```text -docker run -it --rm -v $PWD/apk.txt:/extras/apk.txt juniper/pyez-ansible -``` + docker run -it --rm -v $PWD/apk.txt:/extras/apk.txt juniper/pyez-ansible -__Python Packages__ +#### Python Packages Environment Variable: `$REQ` Bind Mount: `/extras/requirements.txt` @@ -214,17 +173,13 @@ File Format: pip [requirements](https://pip.pypa.io/en/stable/reference/pip_inst Examples: -```text -docker run -it --rm -v $PWD:/project -e REQ="requirements.txt" juniper/pyez-ansible -``` + docker run -it --rm -v $PWD:/project -e REQ="requirements.txt" juniper/pyez-ansible As a bind mount. -```text -docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.txt juniper/pyez-ansible -``` + docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.txt juniper/pyez-ansible -__Ansible Packages__ +#### Ansible Packages Environment Variable: `$ROLES` Bind Mount: `/extras/requirements.yml` @@ -234,15 +189,11 @@ _NOTE:_ This works for collections as well as roles. Examples: -```text -docker run -it --rm -v $PWD:/project -e REQ="requirements.yml" juniper/pyez-ansible -``` + docker run -it --rm -v $PWD:/project -e REQ="requirements.yml" juniper/pyez-ansible As a bind mount. -```text -docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.yml juniper/pyez-ansible -``` + docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.yml juniper/pyez-ansible ## Example Playbook @@ -314,10 +265,8 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c [jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) for any queries. *Contributors:* -[Nitin Kumar](https://github.com/vnitinv), [Rahul Kumar](https://github.com/rahkumar651991) - -* v0.1.0: [Rahul Kumar](https://github.com/rahkumar651991) +[Nitin Kumar](https://github.com/vnitinv), [Rahul Kumar](https://github.com/rahkumar651991), [Stephen Steiner](https://github.com/ntwrkguru) *Former Contributors:* -[Stacy W Smith](https://github.com/stacywsmith), [Stephen Steiner](https://github.com/ntwrkguru), [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) +[Stacy W Smith](https://github.com/stacywsmith), [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) From 2f621c326d5ca41686379b17aa6c015919fe2d9f Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 6 Oct 2020 08:18:48 -0400 Subject: [PATCH 326/426] changed maintainer to community netdev Signed-off-by: Stephen Steiner --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index f719266e..cbc41de7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM juniper/pyez:2.4.0 +FROM juniper/pyez:2.5.3 -LABEL net.juniper.image.maintainer="Stephen Steiner " \ - net.juniper.image.description="Lightweight image with Ansible and the Junos roles" +LABEL net.juniper.image.maintainer="Juniper Networks " \ + net.junier.image.description="Lightweight image with Ansible and the Junos roles" RUN apk add --no-cache build-base python3-dev py3-pip \ openssl-dev curl ca-certificates bash openssh-client From 6864c53339140484a85f56378c11dc4beaca8c48 Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Tue, 6 Oct 2020 08:00:39 -0400 Subject: [PATCH 327/426] adding ssh-client Signed-off-by: Stephen Steiner --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cbc41de7..bc968bcc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM juniper/pyez:2.5.3 LABEL net.juniper.image.maintainer="Juniper Networks " \ - net.junier.image.description="Lightweight image with Ansible and the Junos roles" + net.juniper.image.description="Lightweight image with Ansible and the Junos roles" RUN apk add --no-cache build-base python3-dev py3-pip \ openssl-dev curl ca-certificates bash openssh-client From e45bc6f253c3ac18b7c02794255c9de0f9bf2a9c Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 5 Nov 2020 10:22:41 +0530 Subject: [PATCH 328/426] Changes being done for persistent connection in collections --- .../device/plugins/action/extract_data.py | 96 ++ .../plugins/action/juniper_junos_command.py | 1 - .../action/juniper_junos_common_action.py | 74 +- .../plugins/action/juniper_junos_config.py | 1 - .../plugins/action/juniper_junos_facts.py | 1 - .../plugins/action/juniper_junos_jsnapy.py | 1 - .../plugins/action/juniper_junos_ping.py | 1 - .../plugins/action/juniper_junos_pmtud.py | 1 - .../plugins/action/juniper_junos_rpc.py | 1 - .../plugins/action/juniper_junos_software.py | 1 - .../action/juniper_junos_srx_cluster.py | 1 - .../plugins/action/juniper_junos_system.py | 1 - .../plugins/action/juniper_junos_table.py | 1 - .../juniper/device/plugins/connection/pyez.py | 417 ++++++ .../plugins/module_utils/configuration.py | 244 ++++ .../module_utils/juniper_junos_common.py | 565 +++----- .../juniper/device/plugins/modules/command.py | 513 +++++++- .../juniper/device/plugins/modules/config.py | 1142 ++++++++++++++++- .../juniper/device/plugins/modules/facts.py | 356 ++++- .../juniper/device/plugins/modules/jsnapy.py | 361 +++++- .../plugins/modules/juniper_junos_command.py | 507 -------- .../plugins/modules/juniper_junos_config.py | 1140 ---------------- .../plugins/modules/juniper_junos_facts.py | 342 ----- .../plugins/modules/juniper_junos_jsnapy.py | 356 ----- .../plugins/modules/juniper_junos_ping.py | 502 -------- .../plugins/modules/juniper_junos_pmtud.py | 408 ------ .../plugins/modules/juniper_junos_rpc.py | 624 --------- .../plugins/modules/juniper_junos_software.py | 775 ----------- .../modules/juniper_junos_srx_cluster.py | 293 ----- .../plugins/modules/juniper_junos_system.py | 398 ------ .../plugins/modules/juniper_junos_table.py | 475 ------- .../juniper/device/plugins/modules/ping.py | 503 +++++++- .../juniper/device/plugins/modules/pmtud.py | 409 +++++- .../juniper/device/plugins/modules/rpc.py | 652 +++++++++- .../device/plugins/modules/software.py | 786 +++++++++++- .../device/plugins/modules/srx_cluster.py | 294 ++++- .../juniper/device/plugins/modules/system.py | 404 +++++- .../juniper/device/plugins/modules/table.py | 483 ++++++- requirements.txt | 1 + 39 files changed, 6842 insertions(+), 6289 deletions(-) create mode 100644 ansible_collections/juniper/device/plugins/action/extract_data.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_command.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_config.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_facts.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_jsnapy.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_ping.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_pmtud.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_rpc.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_software.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_srx_cluster.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_system.py delete mode 120000 ansible_collections/juniper/device/plugins/action/juniper_junos_table.py create mode 100644 ansible_collections/juniper/device/plugins/connection/pyez.py create mode 100644 ansible_collections/juniper/device/plugins/module_utils/configuration.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/command.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/config.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/facts.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/jsnapy.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_command.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_config.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_facts.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_jsnapy.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_ping.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_pmtud.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_rpc.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_software.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_srx_cluster.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_system.py delete mode 100644 ansible_collections/juniper/device/plugins/modules/juniper_junos_table.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/ping.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/pmtud.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/rpc.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/software.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/srx_cluster.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/system.py mode change 120000 => 100644 ansible_collections/juniper/device/plugins/modules/table.py diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py new file mode 100644 index 00000000..e1b4cecc --- /dev/null +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +import os + +connection_spec_fallbacks = { + 'host': ['ansible_host', 'inventory_hostname'], + 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['ansible_ssh_pass', 'ansible_pass'], + 'port': ['ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ansible_ssh_private_key_file', + 'ansible_private_key_file'] +} + +class ExtractData: + def extract(self, tmp=None, task_vars=None): + # The new connection arguments based on fallback/defaults. + new_connection_args = dict() + + connection_args = self._task.args + + self._task.args['_connection'] = self._play_context.connection + new_connection_args['_connection'] = self._play_context.connection + + # The environment variables used by Ansible Tower + if 'user' not in connection_args: + net_user = os.getenv('ANSIBLE_NET_USERNAME') + if net_user is not None: + new_connection_args['user'] = net_user + connection_args['user'] = net_user + if 'passwd' not in connection_args: + net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + if net_passwd is not None: + new_connection_args['passwd'] = net_passwd + connection_args['passwd'] = net_passwd + if 'ssh_private_key_file' not in connection_args: + net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + if net_key is not None: + new_connection_args['ssh_private_key_file'] = net_key + connection_args['ssh_private_key_file'] = net_key + + # The values set by Ansible command line arguments, configuration + # settings, or environment variables. + for key in connection_spec_fallbacks: + if key not in connection_args: + for task_var_key in connection_spec_fallbacks[key]: + if task_var_key in task_vars: + new_connection_args[key] = task_vars[task_var_key] + break + + # Backwards compatible behavior to fallback to USER env. variable. + if 'user' not in connection_args and 'user' not in new_connection_args: + user = os.getenv('USER') + if user is not None: + new_connection_args['user'] = user + + self._task.args.update(new_connection_args) + + # Pass the hidden _module_utils_path option + module_utils_path = os.path.normpath(os.path.dirname(__file__)) + self._task.args['_module_utils_path'] = module_utils_path + # Pass the hidden _module_name option + self._task.args['_module_name'] = self._task.action + # Pass the hidden _inventory_hostname option + self._task.args['_inventory_hostname'] = task_vars['inventory_hostname'] diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_command.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_command.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_command.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py index 61437b86..4f918b2a 100755 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py +++ b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py @@ -31,25 +31,12 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -from __future__ import absolute_import, division, print_function from ansible.plugins.action.normal import ActionModule as ActionNormal +from ansible_collections.juniper.device.plugins.action.extract_data import ExtractData import os - -connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] -} - - -# Moved the defintion from module_utils/juniper_junos_common.py to action_plugins/juniper_junos_common_action.py -# Use the custom behavior defined below as our ActionModule. # The Ansible core engine will call ActionModule.run() -class ActionModule(ActionNormal): +class ActionModule(ExtractData, ActionNormal): """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. All juniper_junos_* modules share common behavior which is implemented in @@ -58,61 +45,6 @@ class ActionModule(ActionNormal): """ def run(self, tmp=None, task_vars=None): - # The new connection arguments based on fallback/defaults. - new_connection_args = dict() - - # Get the current connection args from either provider or the top-level - if 'provider' in self._task.args: - connection_args = self._task.args['provider'] - else: - connection_args = self._task.args - - # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') - if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') - if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') - if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key - - # The values set by Ansible command line arguments, configuration - # settings, or environment variables. - for key in connection_spec_fallbacks: - if key not in connection_args: - for task_var_key in connection_spec_fallbacks[key]: - if task_var_key in task_vars: - new_connection_args[key] = task_vars[task_var_key] - break - - # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') - if user is not None: - new_connection_args['user'] = user - - # Copy the new connection arguments back into either top-level or - # the provider dictionary. - if 'provider' in self._task.args: - self._task.args['provider'].update(new_connection_args) - else: - self._task.args.update(new_connection_args) - - # Pass the hidden _module_utils_path option - module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path - # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action - # Pass the hidden _inventory_hostname option - self._task.args['_inventory_hostname'] = task_vars['inventory_hostname'] - + super().extract(tmp,task_vars) # Call the parent action module. return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_config.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_config.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_config.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_facts.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_facts.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_facts.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_jsnapy.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_jsnapy.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_jsnapy.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_ping.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_ping.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_ping.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_pmtud.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_pmtud.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_pmtud.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_rpc.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_rpc.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_rpc.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_software.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_software.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_software.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_srx_cluster.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_srx_cluster.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_srx_cluster.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_system.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_system.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_system.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_table.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_table.py deleted file mode 120000 index 7470277b..00000000 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_table.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_common_action.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py new file mode 100644 index 00000000..6998e7d8 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -0,0 +1,417 @@ +# (c) 2016 Red Hat Inc. +# (c) 2017 Ansible Project +# 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 +import xmltodict + +__metaclass__ = type + +DOCUMENTATION = """author: Ansible Networking Team +connection: pyez +short_description: Use pyez to run command on JUNOS appliances +description: +- This connection plugin provides a connection to remote devices over the junos-pyez library. +options: + host: + description: + - Specifies the remote device FQDN or IP address to establish the SSH connection + to. + default: inventory_hostname + vars: + - name: ansible_host + port: + type: int + description: + - Specifies the port on the remote device that listens for connections when establishing + the SSH connection. + ini: + - section: defaults + key: remote_port + env: + - name: ANSIBLE_REMOTE_PORT + vars: + - name: ansible_port + remote_user: + description: + - The username used to authenticate to the remote device when the SSH connection + is first established. If the remote_user is not specified, the connection will + use the username of the logged in user. + - Can be configured from the CLI via the C(--user) or C(-u) options. + ini: + - section: defaults + key: remote_user + env: + - name: ANSIBLE_REMOTE_USER + vars: + - name: ansible_user + password: + description: + - Configures the user password used to authenticate to the remote device when + first establishing the SSH connection. + vars: + - name: ansible_password + - name: ansible_ssh_pass + - name: ansible_ssh_password + pyez_console: + description: + - console option. + ini: + - section: pyez_connection + key: console + env: + - name: ANSIBLE_PYEZ_CONSOLE + vars: + - name: ansible_pyez_console + private_key_file: + description: + - The private SSH key or certificate file used to authenticate to the remote device + when first establishing the SSH connection. + ini: + - section: defaults + key: private_key_file + env: + - name: ANSIBLE_PRIVATE_KEY_FILE + vars: + - name: ansible_private_key_file + host_key_auto_add: + type: boolean + description: + - By default, Ansible will prompt the user before adding SSH keys to the known + hosts file. Since persistent connections such as network_cli run in background + processes, the user will never be prompted. By enabling this option, unknown + host keys will automatically be added to the known hosts file. + - Be sure to fully understand the security implications of enabling this option + on production systems as it could create a security vulnerability. + default: false + ini: + - section: pyez_connection + key: host_key_auto_add + env: + - name: ANSIBLE_HOST_KEY_AUTO_ADD + persistent_connect_timeout: + type: int + description: + - Configures, in seconds, the amount of time to wait when trying to initially + establish a persistent connection. If this value expires before the connection + to the remote device is completed, the connection will fail. + default: 30 + ini: + - section: persistent_connection + key: connect_timeout + env: + - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT + vars: + - name: ansible_connect_timeout + persistent_command_timeout: + type: int + description: + - Configures, in seconds, the amount of time to wait for a command to return from + the remote device. If this timer is exceeded before the command returns, the + connection plugin will raise an exception and close. + default: 30 + ini: + - section: persistent_connection + key: command_timeout + env: + - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT + vars: + - name: ansible_command_timeout + persistent_log_messages: + type: boolean + description: + - This flag will enable logging the command executed and response received from + target device in the ansible log file. For this option to work 'log_path' ansible + configuration option is required to be set to a file path with write access. + - Be sure to fully understand the security implications of enabling this option + as it could create a security vulnerability by logging sensitive information + in log file. + default: false + ini: + - section: persistent_connection + key: log_messages + env: + - name: ANSIBLE_PERSISTENT_LOG_MESSAGES + vars: + - name: ansible_persistent_log_messages + pyez_ssh_config: + description: + - This variable is used to enable bastion/jump host with netconf connection. If + set to True the bastion/jump host ssh settings should be present in ~/.ssh/config + file, alternatively it can be set to custom ssh configuration file path to read + the bastion/jump host settings. + ini: + - section: pyez_connection + key: ssh_config + env: + - name: ANSIBLE_PYEZ_SSH_CONFIG + vars: + - name: ansible_pyez_ssh_config +""" +import pickle + +from ansible.errors import AnsibleConnectionFailure, AnsibleError +from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.plugins.connection import NetworkConnectionBase, ensure_connect + +import json +import logging + +# Non-standard library imports and checks +try: + from jnpr.junos.version import VERSION + + HAS_PYEZ_VERSION = VERSION +except ImportError: + HAS_PYEZ_VERSION = None + +try: + import jnpr.junos.device + + HAS_PYEZ_DEVICE = True +except ImportError: + HAS_PYEZ_DEVICE = False + +try: + import jnpr.junos.utils.sw + + HAS_PYEZ_SW = True +except ImportError: + HAS_PYEZ_SW = False + +try: + import jnpr.junos.utils.config + + HAS_PYEZ_CONFIG = True +except ImportError: + HAS_PYEZ_CONFIG = False + +try: + import jnpr.junos.op + import jnpr.junos.factory.factory_loader + import jnpr.junos.factory.table + + HAS_PYEZ_OP_TABLE = True +except ImportError: + HAS_PYEZ_OP_TABLE = False + +try: + import jnpr.junos.exception as pyez_exception + + HAS_PYEZ_EXCEPTIONS = True +except ImportError: + HAS_PYEZ_EXCEPTIONS = False + +try: + from jnpr.jsnapy import SnapAdmin, __version__ + + HAS_JSNAPY_VERSION = __version__ +except ImportError: + HAS_JSNAPY_VERSION = None +# Most likely JSNAPy 1.2.0 with https://github.com/Juniper/jsnapy/issues/263 +except TypeError: + HAS_JSNAPY_VERSION = "possibly 1.2.0" + +try: + from lxml import etree + + HAS_LXML_ETREE_VERSION = ".".join(map(str, etree.LXML_VERSION)) +except ImportError: + HAS_LXML_ETREE_VERSION = None + +try: + import jxmlease + + HAS_JXMLEASE_VERSION = jxmlease.__version__ +except ImportError: + HAS_JXMLEASE_VERSION = None + +try: + import yaml + + HAS_YAML_VERSION = yaml.__version__ +except ImportError: + HAS_YAML_VERSION = None + +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + +#import q +logging.getLogger("ncclient").setLevel(logging.INFO) + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) + +# Supported configuration modes +CONFIG_MODE_CHOICES = ['exclusive', 'private'] + + +class Connection(NetworkConnectionBase): + """NetConf connections""" + + transport = "junipernetworks.pyez.pyez" + has_pipelining = False + + def __init__(self, play_context, new_stdin, *args, **kwargs): + super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) + self.dev = None + + @property + @ensure_connect + def manager(self): + return self.dev + + def _connect(self): + + self.queue_message("log", "ssh connection done, starting junos-eznc") + self.open() + if not self.dev.connected: + return 1, b"", b"not connected" + + self._connected = True + + super(Connection, self)._connect() + + self._sub_plugin = {"name": "pyez", "obj": self.dev} + self.queue_message( + "vvvv", + "created pyez connection type" + ) + return ( + 0, + to_bytes(self.dev._conn.session_id, errors="surrogate_or_strict"), + b"", + ) + + def open(self): + """Open the self.dev PyEZ Device instance. + Failures: + - ConnectError: When unable to make a PyEZ connection. + """ + # Move all of the connection arguments into connect_args + connect_args = {} + connect_args['host'] = self.get_option('host') + connect_args['user'] = self.get_option('remote_user') + connect_args['passwd'] = self.get_option('password') + connect_args['ssh_private_key_file'] = self.get_option('private_key_file') + connect_args['ssh_config'] = self.get_option('pyez_ssh_config') + connect_args['timeout'] = self.get_option('persistent_connect_timeout') + try: + log_connect_args = dict(connect_args) + log_connect_args["passwd"] = "NOT_LOGGING_PARAMETER" + + self.queue_message("vvvv", "Creating device parameters: %s" % log_connect_args) + timeout = connect_args.pop("timeout") + self.dev = jnpr.junos.device.Device(**connect_args) + self.queue_message("vvvv", "Opening device.") + self.dev.open() + self.queue_message("vvvv", "Device opened.") + + self.dev.timeout = self.get_option('persistent_command_timeout') + self.queue_message("vvvv", "Setting default device timeout to %d." % timeout) + # Exceptions raised by close() or open() are all sub-classes of + # ConnectError, so this should catch all connection-related exceptions + # raised from PyEZ. + except pyez_exception.ConnectError as ex: + raise AnsibleError(msg="Unable to make a PyEZ connection: %s" % (str(ex))) + + def close(self): + """Close the self.dev PyEZ Device instance. + """ + if self.dev is not None: + try: + # Because self.fail_json() calls self.close(), we must set + # self.dev = None BEFORE calling dev.close() in order to avoid + # the infinite recursion which would occur if dev.close() + # raised a ConnectError. + dev = self.dev + self.dev = None + dev.close() + # Exceptions raised by close() are all sub-classes of + # ConnectError or RpcError, so this should catch all + # exceptions raised from PyEZ. + except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: + # Ignore exceptions from closing. We're about to exit + # anyway and they will just mask the real error that + # happened. + pass + super(Connection, self).close() + + @ensure_connect + def get_capabilities(self): + return json.dumps({'network_api': 'pyez'}) + + def get_config(self, filter_xml=None, options=None, model=None, + namespace=None, remove_ns=True, **kwarg): + resp = self.dev.rpc.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) + return etree.tostring(resp) + + def get_rpc_resp(self,rpc, ignore_warning=None): + # data comes in JSON format, needs to be converted + rpc_val = xmltodict.unparse(rpc) + rpc_val = rpc_val.encode('utf-8') + parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8') + rpc_etree = etree.fromstring(rpc_val, parser=parser) + resp = self.dev.rpc(rpc_etree, normalize=bool(format == 'xml'), ignore_warning=ignore_warning) + return etree.tostring(resp) + + def get_facts(self): + return dict(self.dev.facts) + + def ping_device(self, normalize=True, **params): + resp = self.dev.rpc.ping(normalize, **params) + rpc_str = etree.tostring(resp) + return rpc_str + + def get_chassis_inventory(self): + resp = self.dev.rpc.get_chassis_inventory() + return etree.tostring(resp) + + def invoke_jsnapy(self, data, action): + try: + self.queue_message("vvvv", "Creating jnpr.jsnapy.SnapAdmin instance.") + jsa = jnpr.jsnapy.SnapAdmin() + self.queue_message("vvvv", 'Executing %s action.' % action) + if action == 'check': + responses = jsa.check(data=data, + dev=self.dev, + pre_file='PRE', + post_file='POST') + elif action == 'snapcheck': + responses = jsa.snapcheck(data=data, + dev=self.dev) + elif action == 'snap_pre': + responses = jsa.snap(data=data, + dev=self.dev, + file_name='PRE') + elif action == 'snap_post': + responses = jsa.snap(data=data, + dev=self.dev, + file_name='POST') + else: + raise AnsibleError("Unexpected action: %s." % (action)) + self.queue_message("vvvv", 'The %s action executed successfully' % action) + except (pyez_exception.RpcError, pyez_exception.ConnectError) as ex: + raise AnsibleError("Error communicating with the device: %s" % str(ex)) + + if isinstance(responses, list) and len(responses) == 1: + if action in ('snapcheck', 'check'): + results = [] + for response in to_list(responses): + result = {} + result['device'] = response.device + result['result'] = response.result + result['no_passed'] = response.no_passed + result['no_failed'] = response.no_failed + result['test_results'] = response.test_results + results.append(result) + else: + results = [to_text(responses)] + else: + results = responses + return json.dumps(results) diff --git a/ansible_collections/juniper/device/plugins/module_utils/configuration.py b/ansible_collections/juniper/device/plugins/module_utils/configuration.py new file mode 100644 index 00000000..105ab48a --- /dev/null +++ b/ansible_collections/juniper/device/plugins/module_utils/configuration.py @@ -0,0 +1,244 @@ +from distutils.version import LooseVersion +import logging +import os + +# Non-standard library imports and checks +try: + from jnpr.junos.version import VERSION + HAS_PYEZ_VERSION = VERSION +except ImportError: + HAS_PYEZ_VERSION = None + +try: + import jnpr.junos.op + import jnpr.junos.factory.factory_loader + import jnpr.junos.factory.table + HAS_PYEZ_OP_TABLE = True +except ImportError: + HAS_PYEZ_OP_TABLE = False + +try: + import ncclient.operations.errors as ncclient_exception + HAS_NCCLIENT_EXCEPTIONS = True +except ImportError: + HAS_NCCLIENT_EXCEPTIONS = False + +try: + import jnpr.jsnapy + HAS_JSNAPY_VERSION = jnpr.jsnapy.__version__ +except ImportError: + HAS_JSNAPY_VERSION = None +# Most likely JSNAPy 1.2.0 with https://github.com/Juniper/jsnapy/issues/263 +except TypeError: + HAS_JSNAPY_VERSION = 'possibly 1.2.0' + +try: + from lxml import etree + HAS_LXML_ETREE_VERSION = '.'.join(map(str, etree.LXML_VERSION)) +except ImportError: + HAS_LXML_ETREE_VERSION = None + +try: + import jxmlease + HAS_JXMLEASE_VERSION = jxmlease.__version__ +except ImportError: + HAS_JXMLEASE_VERSION = None + +try: + import yaml + HAS_YAML_VERSION = yaml.__version__ +except ImportError: + HAS_YAML_VERSION = None + +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + +# Constants +# Minimum PyEZ version required by shared code. +MIN_PYEZ_VERSION = "2.5.2" +# Installation URL for PyEZ. +PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" +# Minimum lxml version required by shared code. +MIN_LXML_ETREE_VERSION = "3.2.4" +# Installation URL for LXML. +LXML_ETREE_INSTALLATION_URL = "http://lxml.de/installation.html" +# Minimum JSNAPy version required by shared code. +MIN_JSNAPY_VERSION = "1.3.4" +# Installation URL for JSNAPy. +JSNAPY_INSTALLATION_URL = "https://github.com/Juniper/jsnapy#installation" +# Minimum jxmlease version required by shared code. +MIN_JXMLEASE_VERSION = "1.0.1" +# Installation URL for jxmlease. +JXMLEASE_INSTALLATION_URL = \ + "http://jxmlease.readthedocs.io/en/stable/install.html" +# Minimum yaml version required by shared code. +MIN_YAML_VERSION = "3.08" +YAML_INSTALLATION_URL = "http://pyyaml.org/wiki/PyYAMLDocumentation" + + +def _check_library( + library_name, + installed_version, + installation_url, + minimum=None, + library_nickname=None): + """Check if library_name is installed and version is >= minimum. + + Args: + library_name: The name of the library to check. + installed_version: The currently installed version, or None if it's + not installed. + installation_url: The URL with instructions on installing + library_name + minimum: The minimum version required. + Default = None which means no version check. + library_nickname: The library name with any nickname. + Default = library_name. + Failures: + - library_name not installed (unable to import). + - library_name installed_version < minimum. + """ + if library_nickname is None: + library_nickname = library_name + if installed_version is None: + if minimum is not None: + return('%s >= %s is required for this module. ' + 'However, %s does not appear to be ' + 'currently installed. See %s for ' + 'details on installing %s.' % + (library_nickname, minimum, library_name, + installation_url, library_name)) + else: + return('%s is required for this module. However, ' + '%s does not appear to be currently ' + 'installed. See %s for details on ' + 'installing %s.' % + (library_nickname, library_name, + installation_url, library_name)) + elif installed_version is not None and minimum is not None: + if not LooseVersion(installed_version) >= LooseVersion(minimum): + return( + '%s >= %s is required for this module. Version %s of ' + '%s is currently installed. See %s for details on ' + 'upgrading %s.' % + (library_nickname, minimum, installed_version, + library_name, installation_url, library_name)) + return "success" + +def check_pyez(minimum=None): + """Check PyEZ is available and version is >= minimum. + + Args: + minimum: The minimum PyEZ version required. + Default = None which means no version check. + check_device: Indicates whether to check for PyEZ Device object. + check_exception: Indicates whether to check for PyEZ exceptions. + + Failures: + - PyEZ not installed (unable to import). + - PyEZ version < minimum. + """ + if HAS_NCCLIENT_EXCEPTIONS is False: + return('ncclient.operations.errors module could not ' + 'be imported.') + return _check_library('junos-eznc', HAS_PYEZ_VERSION, + PYEZ_INSTALLATION_URL, minimum=minimum, + library_nickname='junos-eznc (aka PyEZ)') + +def check_jsnapy(minimum=None): + """Check jsnapy is available and version is >= minimum. + + Args: + minimum: The minimum jsnapy version required. + Default = None which means no version check. + + Failures: + - jsnapy not installed. + - jsnapy version < minimum. + """ + return _check_library('jsnapy', HAS_JSNAPY_VERSION, + JSNAPY_INSTALLATION_URL, minimum=minimum) + +def check_jxmlease(minimum=None): + """Check jxmlease is available and version is >= minimum. + + Args: + minimum: The minimum jxmlease version required. + Default = None which means no version check. + + Failures: + - jxmlease not installed. + - jxmlease version < minimum. + """ + return _check_library('jxmlease', HAS_JXMLEASE_VERSION, + JXMLEASE_INSTALLATION_URL, minimum=minimum) + +def check_lxml_etree(minimum=None): + """Check lxml etree is available and version is >= minimum. + + Args: + minimum: The minimum lxml version required. + Default = None which means no version check. + + Failures: + - lxml not installed. + - lxml version < minimum. + """ + return _check_library('lxml Etree', HAS_LXML_ETREE_VERSION, + LXML_ETREE_INSTALLATION_URL, minimum=minimum) + +def check_yaml(minimum=None): + """Check yaml is available and version is >= minimum. + + Args: + minimum: The minimum PyYAML version required. + Default = None which means no version check. + + Failures: + - yaml not installed. + - yaml version < minimum. + """ + return _check_library('yaml', HAS_YAML_VERSION, + YAML_INSTALLATION_URL, minimum=minimum) + +def check_sw_compatibility(min_pyez_version, + min_lxml_etree_version, + min_jsnapy_version=None, + min_jxmlease_version=None, + min_yaml_version=None): + """Check yaml is available and version is >= minimum. + + Args: + minimum: The minimum PyYAML version required. + Default = None which means no version check. + Returns: + string as success or the error + """ + ret_output = check_pyez(min_pyez_version) + if ret_output != "success": + return ret_output + + ret_output = check_lxml_etree(min_lxml_etree_version) + if ret_output != "success": + return ret_output + + if min_jsnapy_version is not None: + ret_output = check_jsnapy(min_jsnapy_version) + if ret_output != "success": + return ret_output + + if min_jxmlease_version is not None: + ret_output = check_jxmlease(min_jxmlease_version) + if ret_output != "success": + return ret_output + + if min_yaml_version is not None: + ret_output = check_yaml(min_yaml_version) + + return ret_output + + diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 9dd9aacf..a1773017 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -34,117 +34,22 @@ from __future__ import absolute_import, division, print_function # Ansible imports +from ansible.module_utils.connection import Connection from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import boolean -from ansible.module_utils._text import to_bytes +from ansible.module_utils._text import to_bytes, to_text +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +import jnpr +from jnpr.junos import exception as pyez_exception # Standard library imports from argparse import ArgumentParser from distutils.version import LooseVersion import json +import xmltodict import logging import os -# Non-standard library imports and checks -try: - from jnpr.junos.version import VERSION - HAS_PYEZ_VERSION = VERSION -except ImportError: - HAS_PYEZ_VERSION = None - -try: - import jnpr.junos.device - HAS_PYEZ_DEVICE = True -except ImportError: - HAS_PYEZ_DEVICE = False - -try: - import jnpr.junos.utils.sw - HAS_PYEZ_SW = True -except ImportError: - HAS_PYEZ_SW = False - -try: - import jnpr.junos.utils.config - HAS_PYEZ_CONFIG = True -except ImportError: - HAS_PYEZ_CONFIG = False - -try: - import jnpr.junos.op - import jnpr.junos.factory.factory_loader - import jnpr.junos.factory.table - HAS_PYEZ_OP_TABLE = True -except ImportError: - HAS_PYEZ_OP_TABLE = False - -try: - import jnpr.junos.exception as pyez_exception - HAS_PYEZ_EXCEPTIONS = True -except ImportError: - HAS_PYEZ_EXCEPTIONS = False - -try: - import ncclient.operations.errors as ncclient_exception - HAS_NCCLIENT_EXCEPTIONS = True -except ImportError: - HAS_NCCLIENT_EXCEPTIONS = False - -try: - import jnpr.jsnapy - HAS_JSNAPY_VERSION = jnpr.jsnapy.__version__ -except ImportError: - HAS_JSNAPY_VERSION = None -# Most likely JSNAPy 1.2.0 with https://github.com/Juniper/jsnapy/issues/263 -except TypeError: - HAS_JSNAPY_VERSION = 'possibly 1.2.0' - -try: - from lxml import etree - HAS_LXML_ETREE_VERSION = '.'.join(map(str, etree.LXML_VERSION)) -except ImportError: - HAS_LXML_ETREE_VERSION = None - -try: - import jxmlease - HAS_JXMLEASE_VERSION = jxmlease.__version__ -except ImportError: - HAS_JXMLEASE_VERSION = None - -try: - import yaml - HAS_YAML_VERSION = yaml.__version__ -except ImportError: - HAS_YAML_VERSION = None - -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str - -# Constants -# Minimum PyEZ version required by shared code. -MIN_PYEZ_VERSION = "2.5.2" -# Installation URL for PyEZ. -PYEZ_INSTALLATION_URL = "https://github.com/Juniper/py-junos-eznc#installation" -# Minimum lxml version required by shared code. -MIN_LXML_ETREE_VERSION = "3.2.4" -# Installation URL for LXML. -LXML_ETREE_INSTALLATION_URL = "http://lxml.de/installation.html" -# Minimum JSNAPy version required by shared code. -MIN_JSNAPY_VERSION = "1.3.4" -# Installation URL for JSNAPy. -JSNAPY_INSTALLATION_URL = "https://github.com/Juniper/jsnapy#installation" -# Minimum jxmlease version required by shared code. -MIN_JXMLEASE_VERSION = "1.0.1" -# Installation URL for jxmlease. -JXMLEASE_INSTALLATION_URL = \ - "http://jxmlease.readthedocs.io/en/stable/install.html" -# Minimum yaml version required by shared code. -MIN_YAML_VERSION = "3.08" -YAML_INSTALLATION_URL = "http://pyyaml.org/wiki/PyYAMLDocumentation" class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -446,7 +351,7 @@ class ModuleDocFragment(object): type: dict suboptions:''' + _SUB_CONNECT_DOCUMENTATION + ''' requirements: - - U(junos-eznc|https://github.com/Juniper/py-junos-eznc) >= ''' + MIN_PYEZ_VERSION + ''' + - U(junos-eznc|https://github.com/Juniper/py-junos-eznc) >= ''' + cfg.MIN_PYEZ_VERSION + ''' - Python >= 3.5 notes: - The NETCONF system service must be enabled on the target Junos device. @@ -456,14 +361,14 @@ class ModuleDocFragment(object): # The common argument specification for connecting to Junos devices. connection_spec = { 'host': dict(type='str', - # Required either in provider or at top-level. + # Required at top-level. required=False, aliases=['hostname', 'ip'], # See documentation for real default behavior. # Default behavior coded in JuniperJunosActionModule.run() default=None), 'user': dict(type='str', - # Required either in provider or at top-level. + # Required at top-level. required=False, aliases=['username'], # See documentation for real default behavior. @@ -477,14 +382,14 @@ class ModuleDocFragment(object): default=None, no_log=True), 'cs_user': dict(type='str', - aliases=['console_username'], - required=False, - default=None), + aliases=['console_username'], + required=False, + default=None), 'cs_passwd': dict(type='str', - aliases=['console_password'], - required=False, - default=None, - no_log=True), + aliases=['console_password'], + required=False, + default=None, + no_log=True), 'ssh_private_key_file': dict(type='path', required=False, aliases=['ssh_keyfile'], @@ -493,8 +398,8 @@ class ModuleDocFragment(object): # JuniperJunosActionModule.run() default=None), 'ssh_config': dict(type='path', - required=False, - default=None), + required=False, + default=None), 'mode': dict(choices=[None, 'telnet', 'serial'], default=None), 'console': dict(type='str', @@ -524,22 +429,10 @@ class ModuleDocFragment(object): connection_spec_mutually_exclusive = [['mode', 'console'], ['port', 'console'], ['baud', 'console'], - ['attempts','console'], + ['attempts', 'console'], ['cs_user', 'console'], ['cs_passwd', 'console']] -# Specify the provider spec with options matching connection_spec. -provider_spec = { - 'provider': dict(type='dict', - options=connection_spec) -} - -# The provider option is mutually exclusive with all top-level connection -# options. -provider_spec_mutually_exclusive = [] -for key in connection_spec: - provider_spec_mutually_exclusive.append(['provider', key]) - # Specify the logging spec. logging_spec = { 'logfile': dict(type='path', required=False, default=None), @@ -553,12 +446,11 @@ class ModuleDocFragment(object): # Other logging names which should be logged to the logfile additional_logger_names = ['ncclient', 'paramiko'] -# top_spec is connection_spec + provider_spec + logging_spec +# top_spec is connection_spec + logging_spec top_spec = connection_spec -top_spec.update(provider_spec) + top_spec.update(logging_spec) top_spec_mutually_exclusive = connection_spec_mutually_exclusive -top_spec_mutually_exclusive += provider_spec_mutually_exclusive top_spec_mutually_exclusive += logging_spec_mutually_exclusive # "Hidden" arguments which are passed between the action plugin and the @@ -573,6 +465,8 @@ class ModuleDocFragment(object): '_inventory_hostname': dict(type='str', required=True, default=None), + '_connection': dict(type='str', + default=None), } # Known RPC output formats @@ -590,7 +484,6 @@ class ModuleDocFragment(object): # Supported configuration models CONFIG_MODEL_CHOICES = ['openconfig', 'custom', 'ietf', 'True'] - class JuniperJunosModule(AnsibleModule): """A subclass of AnsibleModule used by all juniper_junos_* modules. @@ -631,8 +524,8 @@ class JuniperJunosModule(AnsibleModule): def __init__(self, argument_spec={}, mutually_exclusive=[], - min_pyez_version=MIN_PYEZ_VERSION, - min_lxml_etree_version=MIN_LXML_ETREE_VERSION, + min_pyez_version=cfg.MIN_PYEZ_VERSION, + min_lxml_etree_version=cfg.MIN_LXML_ETREE_VERSION, min_jsnapy_version=None, min_jxmlease_version=None, min_yaml_version=None, @@ -641,8 +534,7 @@ def __init__(self, Combines module-specific parameters with the common parameters shared by all juniper_junos_* modules. Performs additional checks on options. - Collapses any provider options to be top-level options. Checks the - minimum PyEZ version. Creates and opens the PyEZ Device instance. + Checks the minimum PyEZ version. Creates and opens the PyEZ Device instance. Args: agument_spec: Module-specific argument_spec added to top_spec. @@ -673,10 +565,7 @@ def __init__(self, Returns: A JuniperJunosModule instance object. """ - # Initialize the dev attribute - self.dev = None - # Initialize the config attribute - self.config = None + # Update argument_spec with the internal_spec argument_spec.update(internal_spec) # Update argument_spec with the top_spec @@ -688,20 +577,68 @@ def __init__(self, argument_spec=argument_spec, mutually_exclusive=mutually_exclusive, **kwargs) - self.module_name = self.params.get('_module_name') - self.inventory_hostname = self.params.get('_inventory_hostname') + + # initialize the parameters + self.initialize_params() + # Remove any arguments in internal_spec for arg_name in internal_spec: self.params.pop(arg_name) - # Promote any provider arg_name into params - if 'provider' in self.params and self.params['provider'] is not None: - for arg_name, arg_value in self.params['provider'].items(): - if arg_name in self.aliases: - arg_name = self.aliases[arg_name] - self.params[arg_name] = arg_value - self.params.pop('provider') + + # check software compatibility for various 3rd party tools used + ret_output = cfg.check_sw_compatibility(min_pyez_version, + min_lxml_etree_version, + min_jsnapy_version, + min_jxmlease_version, + min_yaml_version) + + if ret_output != 'success': + self.fail_json(msg="%s" % ret_output) + + self.pyez_factory_loader = jnpr.junos.factory.factory_loader + self.pyez_factory_table = jnpr.junos.factory.table + self.pyez_op_table = jnpr.junos.op + self.pyez_exception = pyez_exception + self.ncclient_exception = cfg.ncclient_exception + self.etree = cfg.etree + self.jsnapy = jnpr.jsnapy + self.jxmlease = cfg.jxmlease + self.yaml = cfg.yaml + + # Setup logging. + self.logger = self._setup_logging() + + # Open the PyEZ connection + if self.conn_type == "local": + self.open() + else: + self._pyez_conn = self.get_connection() + + def initialize_params(self): + """ + Initalize the parameters in common module + """ + # priority for arguments is inventory < tasks < console + + self.module_name = self.params.get('_module_name') + self.inventory_hostname = self.params.get('_inventory_hostname') + self.conn_type = self.params.get('_connection') + # Parse the console option self._parse_console_options() + + # Initialize the dev attribute + self.dev = None + # Initialize the config attribute + self.config = None + + # Check that we have a user and host + if not self.params.get('host'): + self.fail_json(msg="missing required arguments: host") + if not self.params.get('user'): + self.fail_json(msg="missing required arguments: user") + + # Default port based on mode. if self.params.get('port') is None: if self.params.get('mode') == 'telnet': @@ -718,64 +655,51 @@ def __init__(self, self.fail_json(msg="The port option (%s) must be an " "integer value." % (self.params['port'])) - # Default baud if serial or telnet mode - if self.params.get('baud') is None: - if (self.params.get('mode') == 'telnet' or - self.params.get('mode') == 'serial'): - self.params['baud'] = 9600 - # Default attempts if serial or telnet mode - if self.params.get('attemps') is None: - if (self.params.get('mode') == 'telnet' or - self.params.get('mode') == 'serial'): - self.params['attempts'] = 10 - # baud and attempts are only valid if mode != None - if (self.params.get('baud') is not None and - self.params.get('mode') is None): - self.fail_json(msg="The baud option (%s) is not valid when " - "mode == none." % (self.params.get('baud'))) - if (self.params.get('attempts') is not None and - self.params.get('mode') is None): - self.fail_json(msg="The attempts option (%s) is not valid when " - "mode == none." % (self.params.get('attempts'))) - # Check that we have a user and host - if not self.params.get('host'): - self.fail_json(msg="missing required arguments: host") - if not self.params.get('user'): - self.fail_json(msg="missing required arguments: user") - # Check PyEZ version and add attributes to reach PyEZ components. - self.check_pyez(min_pyez_version, - check_device=True, - check_sw=True, - check_config=True, - check_op_table=True, - check_exception=True) - self.pyez_factory_loader = jnpr.junos.factory.factory_loader - self.pyez_factory_table = jnpr.junos.factory.table - self.pyez_op_table = jnpr.junos.op - self.pyez_exception = pyez_exception - self.ncclient_exception = ncclient_exception - # Check LXML Etree. - self.check_lxml_etree(min_lxml_etree_version) - self.etree = etree - # Check jsnapy if needed. - if min_jsnapy_version is not None: - self.check_jsnapy(min_jsnapy_version) - if hasattr(jnpr, 'jsnapy'): - self.jsnapy = jnpr.jsnapy else: - self.fail_json("JSNAPy not available.") - # Check jxmlease if needed. - if min_jxmlease_version is not None: - self.check_jxmlease(min_jxmlease_version) - self.jxmlease = jxmlease - # Check yaml if needed. - if min_yaml_version is not None: - self.check_yaml(min_yaml_version) - self.yaml = yaml - # Setup logging. - self.logger = self._setup_logging() - # Open the PyEZ connection - self.open() + self.params['port'] = self.params['port'] + + if (self.params.get('mode') == 'telnet' or + self.params.get('mode') == 'serial'): + if self.params.get('baud') is None: + # Default baud if serial or telnet mode + self.params['baud'] = 9600 + if self.params.get('attempts') is None: + # Default attempts if serial or telnet mode + self.params['attempts'] = 10 + else: + if self.params.get('baud') is not None: + self.fail_json(msg="The baud option (%s) is not valid when " + "mode == none." % (self.params.get('baud'))) + if self.params.get('attempts') is not None: + self.fail_json(msg="The attempts option (%s) is not valid when " + "mode == none." % (self.params.get('attempts'))) + + def get_connection(self): + if hasattr(self, "_pyez_connection"): + return self._pyez_connection + try: + capabilities = self.get_capabilities() + except ConnectionError as exc: + self.logger.debug("Connection might be local") + return + # module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + network_api = capabilities.get("network_api") + if network_api == "pyez": + self._pyez_connection = Connection(self._socket_path) + else: + self.fail_json(msg="Invalid connection type %s" % network_api) + return self._pyez_connection + + def get_capabilities(self): + if hasattr(self, "_pyez_junos_capabilities"): + return self._pyez_junos_capabilities + try: + capabilities = Connection(self._socket_path).get_capabilities() + except ConnectionError as exc: + raise exc + # module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + self._pyez_junos_capabilities = json.loads(capabilities) + return self._pyez_junos_capabilities def exit_json(self, **kwargs): """Close self.dev and call parent's exit_json(). @@ -785,7 +709,8 @@ def exit_json(self, **kwargs): AnsibleModule.exit_json(). """ # Close the connection. - self.close() + if self.conn_type == "local": + self.close() self.logger.debug("Exit JSON: %s", kwargs) # Call the parent's exit_json() super(JuniperJunosModule, self).exit_json(**kwargs) @@ -800,6 +725,7 @@ def fail_json(self, **kwargs): # Close the configuration self.close_configuration() # Close the connection. + # if self.conn_type == "local": self.close() if hasattr(self, 'logger'): self.logger.debug("Fail JSON: %s", kwargs) @@ -852,12 +778,14 @@ def error(self, message): port = con_params.get('port', None) baud = con_params.get('baud', None) attempts = con_params.get('attempts', None) - timeout = con_params.get('timeout', None) + timeout = con_params.get('timeout', None) # not used self.params['mode'] = 'serial' if port is not None: self.params['port'] = port if baud is not None: self.params['baud'] = baud + if attempts is not None: + self.params['attempts'] = attempts # Remove the console option. self.params.pop('console') @@ -890,10 +818,12 @@ def _setup_logging(self): Returns: Logger instance object for the name jnpr.ansible_module.. """ + class CustomAdapter(logging.LoggerAdapter): """ Prepend the hostname, in brackets, to the log message. """ + def process(self, msg, kwargs): return '[%s] %s' % (self.extra['host'], msg), kwargs @@ -948,162 +878,6 @@ def process(self, msg, kwargs): # Use the CustomAdapter to add host information. return CustomAdapter(logger, {'host': self.params.get('host')}) - def _check_library(self, - library_name, - installed_version, - installation_url, - minimum=None, - library_nickname=None): - """Check if library_name is installed and version is >= minimum. - - Args: - library_name: The name of the library to check. - installed_version: The currently installed version, or None if it's - not installed. - installation_url: The URL with instructions on installing - library_name - minimum: The minimum version required. - Default = None which means no version check. - library_nickname: The library name with any nickname. - Default = library_name. - Failures: - - library_name not installed (unable to import). - - library_name installed_version < minimum. - """ - if library_nickname is None: - library_nickname = library_name - if installed_version is None: - if minimum is not None: - self.fail_json(msg='%s >= %s is required for this module. ' - 'However, %s does not appear to be ' - 'currently installed. See %s for ' - 'details on installing %s.' % - (library_nickname, minimum, library_name, - installation_url, library_name)) - else: - self.fail_json(msg='%s is required for this module. However, ' - '%s does not appear to be currently ' - 'installed. See %s for details on ' - 'installing %s.' % - (library_nickname, library_name, - installation_url, library_name)) - elif installed_version is not None and minimum is not None: - if not LooseVersion(installed_version) >= LooseVersion(minimum): - self.fail_json( - msg='%s >= %s is required for this module. Version %s of ' - '%s is currently installed. See %s for details on ' - 'upgrading %s.' % - (library_nickname, minimum, installed_version, - library_name, installation_url, library_name)) - - def check_pyez(self, minimum=None, - check_device=False, - check_sw=False, - check_config=False, - check_op_table=False, - check_exception=False): - """Check PyEZ is available and version is >= minimum. - - Args: - minimum: The minimum PyEZ version required. - Default = None which means no version check. - check_device: Indicates whether to check for PyEZ Device object. - check_exception: Indicates whether to check for PyEZ exceptions. - - Failures: - - PyEZ not installed (unable to import). - - PyEZ version < minimum. - - check_device and PyEZ Device object can't be imported - - check_exception and PyEZ exceptions can't be imported - """ - self._check_library('junos-eznc', HAS_PYEZ_VERSION, - PYEZ_INSTALLATION_URL, minimum=minimum, - library_nickname='junos-eznc (aka PyEZ)') - if check_device is True: - if HAS_PYEZ_DEVICE is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' - 'the jnpr.junos.device.Device class could ' - 'not be imported.') - if check_sw is True: - if HAS_PYEZ_SW is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' - 'the jnpr.junos.utils.sw class could ' - 'not be imported.') - if check_config is True: - if HAS_PYEZ_CONFIG is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' - 'the jnpr.junos.utils.config class could ' - 'not be imported.') - if check_op_table is True: - if HAS_PYEZ_OP_TABLE is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' - 'the jnpr.junos.op class could not be ' - 'imported.') - if check_exception is True: - if HAS_PYEZ_EXCEPTIONS is False: - self.fail_json(msg='junos-eznc (aka PyEZ) is installed, but ' - 'the jnpr.junos.exception module could not ' - 'be imported.') - if HAS_NCCLIENT_EXCEPTIONS is False: - self.fail_json(msg='ncclient.operations.errors module could not ' - 'be imported.') - - def check_jsnapy(self, minimum=None): - """Check jsnapy is available and version is >= minimum. - - Args: - minimum: The minimum jsnapy version required. - Default = None which means no version check. - - Failures: - - jsnapy not installed. - - jsnapy version < minimum. - """ - self._check_library('jsnapy', HAS_JSNAPY_VERSION, - JSNAPY_INSTALLATION_URL, minimum=minimum) - - def check_jxmlease(self, minimum=None): - """Check jxmlease is available and version is >= minimum. - - Args: - minimum: The minimum jxmlease version required. - Default = None which means no version check. - - Failures: - - jxmlease not installed. - - jxmlease version < minimum. - """ - self._check_library('jxmlease', HAS_JXMLEASE_VERSION, - JXMLEASE_INSTALLATION_URL, minimum=minimum) - - def check_lxml_etree(self, minimum=None): - """Check lxml etree is available and version is >= minimum. - - Args: - minimum: The minimum lxml version required. - Default = None which means no version check. - - Failures: - - lxml not installed. - - lxml version < minimum. - """ - self._check_library('lxml Etree', HAS_LXML_ETREE_VERSION, - LXML_ETREE_INSTALLATION_URL, minimum=minimum) - - def check_yaml(self, minimum=None): - """Check yaml is available and version is >= minimum. - - Args: - minimum: The minimum PyYAML version required. - Default = None which means no version check. - - Failures: - - yaml not installed. - - yaml version < minimum. - """ - self._check_library('yaml', HAS_YAML_VERSION, - YAML_INSTALLATION_URL, minimum=minimum) - def parse_arg_to_list_of_dicts(self, option_name, string_val, @@ -1222,9 +996,9 @@ def parse_ignore_warning_option(self): if isinstance(ignore_warn_list[0], basestring): return ignore_warn_list[0] self.fail_json(msg="The value of the ignore_warning option " - "(%s) is invalid. Unexpected type (%s)." % - (ignore_warn_list[0], - type(ignore_warn_list[0]))) + "(%s) is invalid. Unexpected type (%s)." % + (ignore_warn_list[0], + type(ignore_warn_list[0]))) elif len(ignore_warn_list) > 1: for ignore_warn in ignore_warn_list: if not isinstance(ignore_warn, basestring): @@ -1285,12 +1059,12 @@ def open(self): connect_args[key] = self.params.get(key) try: - self.close() + #self.close() log_connect_args = dict(connect_args) log_connect_args['passwd'] = 'NOT_LOGGING_PARAMETER' - if 'cs_passwd' in log_connect_args: + if 'cs_passwd' in log_connect_args: log_connect_args['cs_passwd'] = 'NOT_LOGGING_PARAMETER' - + self.logger.debug("Creating device parameters: %s", log_connect_args) timeout = connect_args.pop('timeout') @@ -1348,7 +1122,7 @@ def open_configuration(self, mode, ignore_warning=None): config or an already opened private config. """ - ignore_warn=['uncommitted changes will be discarded on exit'] + ignore_warn = ['uncommitted changes will be discarded on exit'] # if ignore_warning is a bool, pass the bool # if ignore_warning is a string add to the list # if ignore_warning is a list, merge them @@ -1361,6 +1135,13 @@ def open_configuration(self, mode, ignore_warning=None): # Already have an open configuration? if self.config is None: + # configuration check and loading should not be done as peristent connection + # with other modules like rpc and command. The mode of connection + # will be different along with other additional parameter. + # if connection mode is pyez, close the connection. + if self.conn_type != "local": + self._pyez_conn.close() + if mode not in CONFIG_MODE_CHOICES: self.fail_json(msg='Invalid configuration mode: %s' % (mode)) if self.dev is None: @@ -1447,14 +1228,22 @@ def get_configuration(self, database='committed', format='text', options.update({'database': database, 'format': format}) - if self.dev is None: - self.open() + if self.conn_type == "local": + if self.dev is None: + self.open() self.logger.debug("Retrieving device configuration. Options: %s " "Filter %s", str(options), str(filter)) config = None try: - config = self.dev.rpc.get_config(options=options, + if self.conn_type == "local": + config = self.dev.rpc.get_config(options=options, + filter_xml=filter, + model=model, + remove_ns=remove_ns, + namespace=namespace) + else: + self.get_config(options=options, filter_xml=filter, model=model, remove_ns=remove_ns, @@ -1736,7 +1525,11 @@ def ping(self, params, acceptable_percent_loss=0, results={}): try: self.logger.debug("Executing ping with parameters: %s", str(params)) - resp = self.dev.rpc.ping(normalize=True, **params) + if self.conn_type == "local": + resp = self.dev.rpc.ping(normalize=True, **params) + else: + response = self._pyez_conn.ping_device(normalize=True, **params) + resp = self.etree.fromstring(response) self.logger.debug("Ping executed.") except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: @@ -1749,7 +1542,7 @@ def ping(self, params, acceptable_percent_loss=0, results={}): # Fail if any errors in the results errors = resp.findall( - "rpc-error[error-severity='error']/error-message") + "rpc-error[error-severity='error']/error-message") if len(errors) != 0: # Create a comma-plus-space-seperated string of the errors. # Calls the text attribute of each element in the errors list. @@ -1759,7 +1552,7 @@ def ping(self, params, acceptable_percent_loss=0, results={}): # Add any warnings into the results warnings = resp.findall( - "rpc-error[error-severity='warning']/error-message") + "rpc-error[error-severity='warning']/error-message") if len(warnings) != 0: # Create list of the text attributes of each element in the # warnings list. @@ -1777,7 +1570,7 @@ def ping(self, params, acceptable_percent_loss=0, results={}): r_fields['packet_loss'] = probe_summary.findtext('packet-loss') r_fields['packets_sent'] = probe_summary.findtext('probes-sent') r_fields['packets_received'] = probe_summary.findtext( - 'responses-received') + 'responses-received') o_fields = {} o_fields['rtt_minimum'] = probe_summary.findtext('rtt-minimum') o_fields['rtt_maximum'] = probe_summary.findtext('rtt-maximum') @@ -1889,3 +1682,23 @@ def save_text_output(self, name, format, text): except IOError: self.fail_json(msg="Unable to save output. Failed to " "open the %s file." % (file_path)) + + def get_config(self, filter_xml=None, options=None, model=None, + namespace=None, remove_ns=True, **kwarg): + response = self._pyez_conn.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) + return self.etree.fromstring(response) + + def get_rpc(self, rpc, ignore_warning=None): + rpc_1 = self.etree.tostring(rpc) + rpc_str = xmltodict.parse(rpc_1) + #json.dumps(rpc_str) + response = self._pyez_conn.get_rpc_resp(rpc_str, ignore_warning=ignore_warning) + return self.etree.fromstring(response) + + def get_facts(self): + facts = self._pyez_conn.get_facts() + return facts + + def get_chassis_inventory(self): + chassis = self._pyez_conn.get_chassis_inventory() + return chassis \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py deleted file mode 120000 index cc8aaf8f..00000000 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_command.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py new file mode 100644 index 00000000..0fdb9937 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -0,0 +1,512 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_command +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Execute one or more CLI commands on a Junos device +description: + - Execute one or more CLI commands on a Junos device. + - Alias command + - This module does NOT use the Junos CLI to execute the CLI command. + Instead, it uses the C() RPC over a NETCONF channel. The + C() RPC takes a CLI command as it's input and is very similar to + executing the command on the CLI, but you can NOT include any pipe modifies + (i.e. C(| match), C(| count), etc.) with the CLI commands executed by this + module. +options: + commands: + description: + - A list of one or more CLI commands to execute on the Junos device. + required: true + default: none + type: list + aliases: + - cli + - command + - cmd + - cmds + dest: + description: + - The path to a file, on the Ansible control machine, where the output of + the cli command will be saved. + - The file must be writeable. If the file already exists, it is + overwritten. + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the value of the I(dest) option. It is + the user's responsibility to ensure this value is unique per target + host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine, where + the output of the cli command will be saved. The output will be logged + to a file named C({{ inventory_hostname }}_)I(command)C(.)I(format) + in the directory specified by the value of the I(dest_dir) option. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: None + type: path + aliases: + - destination_dir + - destdir + formats: + description: + - The format of the reply for the CLI command(s) specified by the + I(commands) option. The specified format(s) must be supported by the + target Junos device. The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all command(s) specified by the I(commands) option. If a + list of formats are specified, there must be one value in the list for + each command specified by the I(commands) option. Specifying the value + C(xml) for the I(formats) option is similar to appending + C(| display xml) to a CLI command, and specifying the value C(json) + for the I(formats) option is similar to appending C(| display json) to + a CLI command. + required: false + default: text + type: str or list of str + choices: + - text + - xml + - json + aliases: + - format + - display + - output + return_output: + description: + - Indicates if the output of the command should be returned in the + module's response. You might want to set this option to C(false), + and set the I(dest_dir) option, if the command output is very large + and you only need to save the output rather than using it's content in + subsequent tasks/plays of your playbook. + required: false + default: true + type: bool +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_command + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Execute single "show version" command. + juniper_junos_command: + commands: "show version" + register: response + + - name: Print the command output + debug: + var: response.stdout + + - name: Execute three commands. + juniper_junos_command: + commands: + - "show version" + - "show system uptime" + - "show interface terse" + register: response + + - name: Print the command output of each. + debug: + var: item.stdout + with_items: "{{ response.results }}" + + - name: Two commands with XML output. + juniper_junos_command: + commands: + - "show route" + - "show lldp neighbors" + format: xml + + - name: show route with XML output - show version with JSON output + juniper_junos_command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + + - name: save outputs in dest_dir + juniper_junos_command: + commands: + - "show route" + - "show version" + dest_dir: "./output" + + - name: save output to dest + juniper_junos_command: + command: "show system uptime" + dest: "/tmp/{{ inventory_hostname }}.uptime.output" + + - name: save output to dest + juniper_junos_command: + command: + - "show route" + - "show lldp neighbors" + dest: "/tmp/{{ inventory_hostname }}.commands.output" + + - name: Multiple commands, save outputs, but don't return them + juniper_junos_command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + dest_dir: "/tmp/outputs/" + return_output: false +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's state has changed. Since this module does not + change the operational or configuration state of the device, the value + is always set to false. + - You could use this module to execute a command which + changes the operational state of the the device. For example, + C(clear ospf neighbors). Beware, this module is unable to detect + this situation, and will still return the value C(false) for I(changed) + in this case. + returned: success + type: bool + sample: false +command: + description: + - The CLI command which was executed. + returned: always + type: str +failed: + description: + - Indicates if the task failed. See the I(results) key for additional + details. + returned: always + type: bool +format: + description: + - The format of the command response. + returned: always + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +parsed_output: + description: + - The command reply from the Junos device parsed into a JSON data structure. + For XML replies, the response is parsed into JSON using the + U(jxmlease|https://github.com/Juniper/jxmlease) + library. For JSON the response is parsed using the Python + U(json|https://docs.python.org/2/library/json.html) library. + - When Ansible converts the jxmlease or native Python data structure + into JSON, it does not guarantee that the order of dictionary/object keys + are maintained. + returned: when command executed successfully, I(return_output) is true, + and the value of the I(formats) option is C(xml) or C(json). + type: dict +results: + description: + - The other keys are returned when a single command is specified for the + I(commands) option. When the value of the I(commands) option is a list + of commands, this key is returned instead. The value of this key is a + list of dictionaries. Each element in the list corresponds to the + commands in the I(commands) option. The keys for each element in the list + include all of the other keys listed. The I(failed) key indicates if the + individual command failed. In this case, there is also a top-level + I(failed) key. The top-level I(failed) key will have a value of C(false) + if ANY of the commands ran successfully. In this case, check the value + of the I(failed) key for each element in the I(results) list for the + results of individual commands. + returned: when the I(commands) option is a list value. + type: list of dict +stdout: + description: + - The command reply from the Junos device as a single multi-line string. + returned: when command executed successfully and I(return_output) is C(true). + type: str +stdout_lines: + description: + - The command reply from the Junos device as a list of single-line strings. + returned: when command executed successfully and I(return_output) is C(true). + type: list of str +''' + +import sys + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + commands=dict(required=True, + type='list', + aliases=['cli', 'command', 'cmd', 'cmds'], + default=None), + formats=dict(required=False, + type='list', + aliases=['format', 'display', 'output'], + default=None), + dest=dict(required=False, + type='path', + aliases=['destination'], + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir'], + default=None), + ignore_warning=dict(required=False, + type='list', + default=None), + return_output=dict(required=False, + type='bool', + default=True) + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. Well, that's not completely true. It does depend on the + # command executed. See the I(changed) key in the RETURN documentation + # for more details. + supports_check_mode=True, + min_jxmlease_version=cfg.MIN_JXMLEASE_VERSION, + ) + + # Parse ignore_warning value + ignore_warning = junos_module.parse_ignore_warning_option() + + # Check over commands + commands = junos_module.params.get('commands') + # Ansible allows users to specify a commands argument with no value. + if commands is None: + junos_module.fail_json(msg="The commands option must have a value.") + # Make sure the commands don't include any pipe modifiers. + for command in commands: + pipe_index = command.find('|') + if (pipe_index != -1 and + command[pipe_index:].strip() != 'display xml rpc'): + # Allow "show configuration | display set" + if ('show configuration' in command and + 'display set' in command[pipe_index:] and + '|' not in command[pipe_index+1:]): + continue + # Any other "| display " should use the format option instead. + for valid_format in juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES: + if 'display ' + valid_format in command[pipe_index:]: + junos_module.fail_json( + msg='The pipe modifier (%s) in the command ' + '(%s) is not supported. Use format: "%s" ' + 'instead.' % + (command[pipe_index:], command, valid_format)) + # Any other "| " is going to produce an error anyway, so fail + # with a meaningful message. + junos_module.fail_json(msg='The pipe modifier (%s) in the command ' + '(%s) is not supported.' % + (command[pipe_index:], command)) + + # Check over formats + formats = junos_module.params.get('formats') + if formats is None: + # Default to text format + formats = ['text'] + valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES + # Check format values + for format in formats: + # Is it a valid format? + if format not in valid_formats: + junos_module.fail_json(msg="The value %s in formats is invalid. " + "Must be one of: %s" % + (format, ', '.join(map(str, + valid_formats)))) + # Correct number of format values? + if len(formats) != 1 and len(formats) != len(commands): + junos_module.fail_json(msg="The formats option must have a single " + "value, or one value per command. There " + "are %d commands and %d formats." % + (len(commands), len(formats))) + # Same format for all commands + elif len(formats) == 1 and len(commands) > 1: + formats = formats * len(commands) + + results = list() + for (command, format) in zip(commands, formats): + # Set initial result values. Assume failure until we know it's success. + result = {'msg': '', + 'command': command, + 'format': format, + 'changed': False, + 'failed': True} + + # Execute the CLI command + try: + junos_module.logger.debug('Executing command "%s".', + command) + rpc = junos_module.etree.Element('command', format=format) + rpc.text = command + if junos_module.conn_type == "local": + resp = junos_module.dev.rpc(rpc, ignore_warning=ignore_warning, normalize=bool(format == 'xml')) + else: + resp = junos_module.get_rpc(rpc, + ignore_warning=ignore_warning) + result['msg'] = 'The command executed successfully.' + junos_module.logger.debug('Command "%s" executed successfully.', + command) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Unable to execute "%s". Error: %s', + command, str(ex)) + result['msg'] = 'Unable to execute the command: %s. Error: %s' % \ + (command, str(ex)) + results.append(result) + continue + + text_output = None + parsed_output = None + if resp is True: + text_output = '' + elif (resp, junos_module.etree._Element): + # Handle the output based on format + if format == 'text': + if resp.tag in ['output', 'rpc-reply']: + text_output = resp.text + junos_module.logger.debug('Text output set.') + elif resp.tag == 'configuration-information': + text_output = resp.findtext('configuration-output') + junos_module.logger.debug('Text configuration output set.') + else: + result['msg'] = 'Unexpected text response tag: %s.' % ( + (resp.tag)) + results.append(result) + junos_module.logger.debug('Unexpected text response tag ' + '%s.', resp.tag) + continue + elif format == 'xml': + encode = None if sys.version < '3' else 'unicode' + text_output = junos_module.etree.tostring(resp, + pretty_print=True, + encoding=encode) + parsed_output = junos_module.jxmlease.parse_etree(resp) + junos_module.logger.debug('XML output set.') + elif format == 'json': + text_output = str(resp) + parsed_output = resp + junos_module.logger.debug('JSON output set.') + else: + result['msg'] = 'Unexpected format %s.' % (format) + results.append(result) + junos_module.logger.debug('Unexpected format %s.', format) + continue + else: + result['msg'] = 'Unexpected response type %s.' % (type(resp)) + results.append(result) + junos_module.logger.debug('Unexpected response type %s.', + type(resp)) + continue + + # Set the output keys + if junos_module.params['return_output'] is True: + if text_output is not None: + result['stdout'] = text_output + result['stdout_lines'] = text_output.splitlines() + if parsed_output is not None: + result['parsed_output'] = parsed_output + # Save the output + junos_module.save_text_output(command, format, text_output) + # This command succeeded. + result['failed'] = False + # Append to the list of results + results.append(result) + + # Return response. + if len(results) == 1: + junos_module.exit_json(**results[0]) + else: + # Calculate the overall failed. Only failed if all commands failed. + failed = True + for result in results: + if result.get('failed') is False: + failed = False + break + junos_module.exit_json(results=results, + changed=False, + failed=failed) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py deleted file mode 120000 index 5383605d..00000000 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_config.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py new file mode 100644 index 00000000..180056f5 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -0,0 +1,1141 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# 2015, Rick Sherman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_config +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Manipulate the configuration of a Junos device +description: + - > + Manipulate the configuration of a Junos device. This module allows a + combination of loading or rolling back, checking, diffing, retrieving, and + committing the configuration of a Junos device. It performs the following + steps in order: + + #. Open a candidate configuration database. + + * If the I(config_mode) option has a value of C(exclusive), the default, + take a lock on the candidate configuration database. If the lock fails + the module fails and reports an error. + * If the I(config_mode) option has a value of C(private), open a private + candidate configuration database. If opening the private configuration + database fails the module fails and reports an error. + #. Load configuration data into the candidate configuration database. + + * Configuration data may be loaded using the I(load) or I(rollback) + options. If either of these options are specified, new configuration + data is loaded. If neither option is specified, this step is skipped. + * If the I(rollback) option is specified, replace the candidate + configuration with the previous configuration specified by the value + of the I(rollback) option. + * If the I(load) option is specified, load new configuration data. + * The value of the I(load) option defines the type of load which is + performed. + * The source of the new configuration data is one of the following: + + * I(src) - A file path on the local Ansible control machine. + * I(lines) - A list of strings containing the configuration data. + * I(template) - A file path to a Jinja2 template on the local + Ansible control machine. This template is rendered with the variables + specified by the I(vars) option. If the I(template) option is + specified, the I(vars) option must also be specified. + * I(url) - A URL reachable from the target Junos device. + * If the I(format) option is specified, the configuration file being + loaded is in the specified format, rather than the format determined + from the file name. + #. Check the validity of the candidate configuration database. + + * If the I(check) option is C(true), the default, check the validity + of the configuration by performing a "commit check" operation. + * This option may be specified with I(diff) C(false) and I(commit) + C(false) to confirm a previous "commit confirmed " operation + without actually performing an additional commit. + * If the configuration check fails, further processing stops, the module + fails, and an error is reported. + #. Determine differences between the candidate and committed configuration + databases. + + * If step 2 was not skipped, and the I(diff) option is C(true), + the default, perform a diff between the candidate and committed + configuration databases. + * If the I(diffs_file) or I(dest_dir) option is specified, save the + generated configuration differences. + * If the I(return_output) option is C(true), the default, include the + generated configuration difference in the I(diff) and I(diff_lines) + keys of the module's response. + #. Retrieve the configuration database from the Junos device. + + * If the I(retrieve) option is specified, retrieve the configuration + database specified by the I(retrieve) value from the target Junos + device to the local Ansible control machine. + * The format in which the configuration is retrieved is specified by the + value of the I(format) option. + * The optional I(filter) controls which portions of the configuration + are retrieved. + * If I(options) are specified, they control the content of the + configuration retrieved. + * If the I(dest) or I(dest_dir) option is specified, save the + retrieved configuration to a file on the local Ansible control + machine. + * If the I(return_output) option is C(true), the default, include the + retrieved configuration in the I(config), I(config_lines), and + I(config_parsed) keys of the module's response. + #. Commit the configuration changes. + + * If the I(commit) option is C(true), the default, commit the + configuration changes. + * This option may be specified with I(diff) C(false) and I(check) + C(false) to confirm a previous "commit confirmed " operation. + * If the I(comment) option is specified, add the comment to the commit. + * If the I(confirmed) option is specified, perform a + C(commit confirmed) I(min) operation where I(min) is the value of the + I(confirmed) option. + * If the I(check) option is C(true) and the I(check_commit_wait) + option is specified, wait I(check_commit_wait) seconds before + performing the commit. + #. Close the candidate configuration database. + + * Close and discard the candidate configuration database. + * If the I(config_mode) option has a value of C(exclusive), the default, + unlock the candidate configuration database. +options: + check: + description: + - Perform a commit check operation. + required: false + default: true (false if retrieve is set and load and rollback are not set) + type: bool + aliases: + - check_commit + - commit_check + check_commit_wait: + description: + - The number of seconds to wait between check and commit operations. + - This option is only valid if I(check) is C(true) and I(commit) is + C(true). + - This option should not normally be needed. It works around an issue in + some versions of Junos. + required: false + default: none + type: int + comment: + description: + - Provide a comment to be used with the commit operation. + - This option is only valid if the I(commit) option is true. + required: false + default: none + type: str + commit: + description: + - Perform a commit operation. + required: false + default: true (false if retrieve is set and load and rollback are not set) + type: bool + commit_empty_changes: + description: + - Perform a commit operation, even if there are no changes between the + candidate configuration and the committed configuration. + required: false + default: false + type: bool + config_mode: + description: + - The mode used to access the candidate configuration database. + required: false + default: exclusive + type: str + choices: + - exclusive + - private + aliases: + - config_access + - edit_mode + - edit_access + confirmed: + description: + - Provide a confirmed timeout, in minutes, to be used with the commit + operation. + - This option is only valid if the I(commit) option is C(true). + - The value of this option is the number of minutes to wait for another + commit operation before automatically rolling back the configuration + change performed by this task. In other words, this option causes the + module to perform a C(commit confirmed )I(min) where I(min) is the + value of the I(confirmed) option. This option DOES NOT confirm a + previous C(commit confirmed )I(min) operation. To confirm a previous + commit operation, invoke this module with the I(check) or I(commit) + option set to C(true). + required: false + default: none + type: int + aliases: + - confirm + dest: + description: + - The path to a file, on the local Ansible control machine, where the + configuration will be saved if the I(retrieve) option is specified. + - The file must be writeable. If the file already exists, it is + overwritten. + - This option is only valid if the I(retrieve) option is not C(none). + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(dest) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: none + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine. This is the + directory where the configuration will be saved if the I(retrieve) + option is specified. It is also the directory where the configuration + diff will be specified if the I(diff) option is C(true). + - This option is only valid if the I(retrieve) option is not C(none) or + the I(diff) option is C(true). + - The retrieved configuration will be saved to a file named + C({{ inventory_hostname }}.)I(format_extension) in the I(dest_dir) + directory. Where I(format_extension) is C(conf) for text format, C(xml) + for XML format, C(json) for JSON format, and C(set) for set format. + - If the I(diff) option is C(true), the configuration diff will be saved + to a file named C({{ inventory_hostname }}.diff) in the I(dest_dir) + directory. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + - The I(dest_dir) and I(diff_file) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: none + type: path + aliases: + - destination_dir + - destdir + - savedir + - save_dir + diff: + description: + - Perform a configuration compare (aka diff) operation. + required: false + default: true (false if retrieve is set and load and rollback are not set) + type: bool + aliases: + - compare + - diffs + diffs_file: + description: + - The path to a file, on the Ansible control machine, where the + configuration differences will be saved if the I(diff) option is + specified. + - The file must be writeable. If the file already exists, it is + overwritten. + - This option is only valid if the I(diff) option is C(true). + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(diffs_file) value. It is the + user's responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + - The I(diffs_file) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + format: + description: + - Specifies the format of the configuration retrieved, if I(retrieve) + is not C(none). + - Specifies the format of the configuration to be loaded, if I(load) is + not C(none). + - The specified format must be supported by the target Junos device. + required: false + default: none (auto-detect on load, text on retrieve) + type: str + choices: + - xml + - set + - text + - json + filter: + description: + - A string of XML, or '/'-separated configuration hierarchies, + which specifies a filter used to restrict the portions of the + configuration which are retrieved. See + U(PyEZ's get_config method documentation|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + for details on the value of this option. + required: false + default: none + type: 'str' + aliases: + - filter_xml + ignore_warning: + description: + - A boolean, string or list of strings. If the value is C(true), + ignore all warnings regardless of the warning message. If the value + is a string, it will ignore warning(s) if the message of each warning + matches the string. If the value is a list of strings, ignore + warning(s) if the message of each warning matches at least one of the + strings in the list. The value of the I(ignore_warning) option is + applied to the load and commit operations performed by this module. + required: false + default: none + type: bool, str, or list of str + lines: + description: + - Used with the I(load) option. Specifies a list of list of + configuration strings containing the configuration to be loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is auto-dectected by + the content of the first line in the I(lines) list. + - If the I(format) option is specified, the I(format) value overrides the + format auto-detection. + required: false + default: none + type: list + load: + description: + - Specifies the type of load operation to be performed. + - The I(load) and I(rollback) options are mutually exclusive. + - > + The choices have the following meanings: + - B(none) - Do not perform a load operation. + - B(merge) - Combine the new configuration with the existing + configuration. If statements in the new configuration conflict with + statements in the existing configuration, the statements in + the new configuration replace those in the existing + configuration. + - B(replace) - This option is a superset of the B(merge) option. It + combines the new configuration with the existing configuration. If the + new configuration is in text format and a hierarchy level in the new + configuartion is prefixed with the string C(replace:), then the + hierarchy level in the new configuration replaces the entire + corresponding hierarchy level in the existing configuration, regardles + of the existence or content of that hierarchy level in the existing + configuration. If the configuration is in XML format, the XML attribute + C(replace = "replace") is equivalent to the text format's C(replace:) + prefix. If a configuration hierarchy in the new configuration is not + prefixed with C(replace:), then the B(merge) behavior is used. + Specifically, for any statements in the new configuration which + conflict with statements in the existing configuration, the statements + in the new configuration replace those in the existing configuration. + - B(override) - Discard the entire existing configuration and replace it + with the new configuration. When the configuration is later committed, + all system processes are notified and the entire new configuration is + marked as 'changed' even if some statements previously existed in the + configuration. The value B(overwrite) is a synonym for B(override). + - B(update) - This option is similar to the B(override) option. The new + configuration completely replaces the existing configuration. The + difference comes when the configuration is later committed. This option + performs a 'diff' between the new candidate configuration and the + existing committed configuration. It then only notifies system + processes repsonsible for the changed portions of the configuration, + and only marks the actual configuration changes as 'changed'. + - B(set) - This option is used when the new configuration data is in set + format (a series of configuration mode commands). The new configuration + data is loaded line by line and may contain any configuration mode + commands, such as set, delete, edit, or deactivate. This value must be + specified if the new configuration is in set format. + required: false + default: none + choices: + - none + - set + - merge + - update + - replace + - override + - overwrite + type: str + options: + description: + - Additional options, specified as a dictionary of key/value pairs, used + when retrieving the configuration. See the + U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) + for information on available options. + required: false + default: None + type: dict + retrieve: + description: + - The configuration database to be retrieved. + required: false + default: none + choices: + - none + - candidate + - committed + type: str + return_output: + description: + - Indicates if the output of the I(diff) and I(retreive) options should + be returned in the module's response. You might want to set this option + to C(false), and set the I(dest_dir) option, if the configuration or + diff output is very large and you only need to save the output rather + than using it's content in subsequent tasks/plays of your playbook. + required: false + default: true + type: bool + rollback: + description: + - Populate the candidate configuration from a previously committed + configuration. This value can be a configuration number between 0 and + 49, or the keyword C(rescue) to load the previously saved rescue + configuration. + - By default, some Junos platforms store fewer than 50 previous + configurations. Specifying a value greater than the number + of previous configurations available, or specifying C(rescue) when no + rescue configuration has been saved, will result in an error when the + module attempts to perform the rollback. + - The I(rollback) and I(load) options are mutually exclusive. + required: false + default: none + choices: + - 0-49 + - rescue + type: int or str + src: + description: + - Used with the I(load) option. Specifies the path to a file, on the + local Ansible control machine, containing the configuration to be + loaded. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - By default, the format of the configuration data is determined by the + file extension of this path name. If the file has a C(.conf) + extension, the content is treated as text format. If the file has a + C(.xml) extension, the content is treated as XML format. If the file + has a C(.set) extension, the content is treated as Junos B(set) + commands. + - If the I(format) option is specified, the I(format) value overrides the + file-extension based format detection. + required: false + default: none + type: 'path' + aliases: + - source + - file + template: + description: + - The path to a Jinja2 template file, on the local Ansible control + machine. This template file, along with the I(vars) option, is used to + generate the configuration to be loaded on the target Junos device. + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. + required: false + default: none + type: path + aliases: + - template_path + url: + description: + - A URL which specifies the configuration data to load on the target + Junos device. + - The Junos device uses this URL to load the configuration, therefore + this URL must be reachable by the target Junos device. + - The possible formats of this value are documented in the 'url' section + of the + U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). + - The I(src), I(lines), I(template), and I(url) options are mutually + exclusive. + required: false + default: none + type: str + vars: + description: + - A dictionary of keys and values used to render the Jinja2 template + specified by the I(template) option. + - The I(template) and I(vars) options are required together. If one is + specified, the other must be specified. + required: false + default: none + type: dict + aliases: + - template_vars +''' + +EXAMPLES = ''' +--- +- name: Manipulate the configuration of Junos devices + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + tasks: + - name: Retrieve the committed configuration + juniper_junos_config: + retrieve: 'committed' + diff: false + check: false + commit: false + register: response + - name: Print the lines in the config. + debug: + var: response.config_lines + + - name: Append .foo to the hostname using private config mode. + juniper_junos_config: + config_mode: 'private' + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + register: response + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the previous config. + juniper_junos_config: + config_mode: 'private' + rollback: 1 + register: response + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the rescue config. + juniper_junos_config: + rollback: 'rescue' + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load override from a file. + juniper_junos_config: + load: 'override' + src: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a Jinja2 template. + juniper_junos_config: + load: 'merge' + format: 'xml' + template: "{{ inventory_hostname }}.j2" + vars: + host: "{{ inventory_hostname }}" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device. + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device, skip the commit check + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + check: false + register: response + - name: Print the msg. + debug: + var: response.msg + + - name: Print diff between current and rollback 10. No check. No commit. + juniper_junos_config: + rollback: 11 + diff: true + check: false + commit: false + register: response + - name: Print the msg. + debug: + var: response + + - name: Retrieve [edit system services] of current committed config. + juniper_junos_config: + retrieve: 'committed' + filter: 'system/services' + diff: true + check: false + commit: false + register: response + - name: Print the resulting config lines. + debug: + var: response.config_lines + + - name: Enable NETCONF SSH and traceoptions, save config, and diffs. + juniper_junos_config: + load: 'merge' + lines: + - 'set system services netconf ssh' + - 'set system services netconf traceoptions flag all' + - 'set system services netconf traceoptions file netconf.log' + format: 'set' + retrieve: 'candidate' + filter: 'system/services' + comment: 'Enable NETCONF with traceoptions' + dest_dir: './output' + register: response + - name: Print the complete response + debug: + var: response + + - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit + juniper_junos_config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + confirm: 5 + check_commit_wait: 3 + register: response + - name: Print the complete response + debug: + var: response + - name: Confirm the previous commit with a commit check (but no commit) + juniper_junos_config: + check: true + diff: false + commit: false + register: response + - name: Print the complete response + debug: + var: response +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed, or would have + changed when in check mode. + returned: success + type: bool +config: + description: + - The retrieved configuration. The value is a single multi-line + string in the format specified by the I(format) option. + returned: when I(retrieved) is not C(none) and I(return_output) is C(true). + type: str +config_lines: + description: + - The retrieved configuration. The value is a list of single-line + strings in the format specified by the I(format) option. + returned: when I(retrieved) is not C(none) and I(return_output) is C(true). + type: list +config_parsed: + description: + - The retrieved configuration parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the + jxmlease library. For JSON the response is parsed using the + Python json library. + - When Ansible converts the jxmlease or native Python data + structure into JSON, it does not guarantee that the order of + dictionary/object keys are maintained. + returned: when I(retrieved) is not C(none), the I(format) option is C(xml) or + C(json) and I(return_output) is C(true). + type: dict +diff: + description: + - The configuration differences between the previous and new + configurations. The value is a dict that contains a single key named + "prepared". Value associated with that key is a single multi-line string + in "diff" format. + returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and + I(return_output) is C(true). + type: dict +diff_lines: + description: + - The configuration differences between the previous and new + configurations. The value is a list of single-line strings in "diff" + format. + returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and + I(return_output) is C(true). + type: list +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +file: + description: + - The value of the I(src) option. + returned: when I(load) is not C(none) and I(src) is not C(none) + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +''' + + +# Standard library imports +import time + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg + +def main(): + # Choices which are defined in the common module. + config_format_choices = juniper_junos_common.CONFIG_FORMAT_CHOICES + config_database_choices = [None] + \ + juniper_junos_common.CONFIG_DATABASE_CHOICES + config_action_choices = [None] + juniper_junos_common.CONFIG_ACTION_CHOICES + config_mode_choices = juniper_junos_common.CONFIG_MODE_CHOICES + config_model_choices = juniper_junos_common.CONFIG_MODEL_CHOICES + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + ignore_warning=dict(required=False, + type='list', + default=None), + config_mode=dict(choices=config_mode_choices, + type='str', + required=False, + aliases=['config_access', 'edit_mode', + 'edit_access'], + default='exclusive'), + rollback=dict(type='str', + required=False, + default=None), + load=dict(choices=config_action_choices, + type='str', + required=False, + default=None), + src=dict(type='path', + required=False, + aliases=['source', 'file'], + default=None), + lines=dict(type='list', + required=False, + default=None), + template=dict(type='path', + required=False, + aliases=['template_path'], + default=None), + vars=dict(type='dict', + required=False, + aliases=['template_vars'], + default=None), + url=dict(type='str', + required=False, + default=None), + format=dict(choices=config_format_choices, + type='str', + required=False, + default=None), + model=dict(required=False, + choices=config_model_choices, + type='str', + default=None), + remove_ns=dict(required=False, + type='bool', + default=None), + namespace=dict(required=False, + type='str', + default=None), + check=dict(required=False, + type='bool', + aliases=['check_commit', 'commit_check'], + default=None), + diff=dict(required=False, + type='bool', + aliases=['compare', 'diffs'], + default=None), + diffs_file=dict(type='path', + required=False, + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir', 'savedir', + 'save_dir'], + default=None), + return_output=dict(required=False, + type='bool', + default=True), + retrieve=dict(choices=config_database_choices, + type='str', + required=False, + default=None), + options=dict(type='dict', + required=False, + default={}), + filter=dict(required=False, + type='str', + aliases=['filter_xml'], + default=None), + dest=dict(type='path', + required=False, + aliases=['destination'], + default=None), + commit=dict(required=False, + type='bool', + default=None), + commit_empty_changes=dict(required=False, + type='bool', + default=False), + confirmed=dict(required=False, + type='int', + aliases=['confirm'], + default=None), + comment=dict(required=False, + type='str', + default=None), + check_commit_wait=dict(required=False, + type='int', + default=None) + ), + # Mutually exclusive options. + mutually_exclusive=[['load', 'rollback'], + ['src', 'lines', 'template', 'url'], + ['diffs_file', 'dest_dir'], + ['dest', 'dest_dir']], + # Required together options. + required_together=[['template', 'vars']], + # Check mode is implemented. + supports_check_mode=True, + min_jxmlease_version=cfg.MIN_JXMLEASE_VERSION, + ) + # Do additional argument verification. + + # Parse ignore_warning value + ignore_warning = junos_module.parse_ignore_warning_option() + + # Straight from params + config_mode = junos_module.params.get('config_mode') + + # Parse rollback value + rollback = junos_module.parse_rollback_option() + + # Straight from params + load = junos_module.params.get('load') + src = junos_module.params.get('src') + lines = junos_module.params.get('lines') + template = junos_module.params.get('template') + vars = junos_module.params.get('vars') + url = junos_module.params.get('url') + format = junos_module.params.get('format') + check = junos_module.params.get('check') + diff = junos_module.params.get('diff') + diffs_file = junos_module.params.get('diffs_file') + dest_dir = junos_module.params.get('dest_dir') + return_output = junos_module.params.get('return_output') + retrieve = junos_module.params.get('retrieve') + options = junos_module.params.get('options') + filter = junos_module.params.get('filter') + dest = junos_module.params.get('dest') + commit = junos_module.params.get('commit') + commit_empty_changes = junos_module.params.get('commit_empty_changes') + confirmed = junos_module.params.get('confirmed') + comment = junos_module.params.get('comment') + check_commit_wait = junos_module.params.get('check_commit_wait') + model = junos_module.params.get('model') + remove_ns = junos_module.params.get('remove_ns') + namespace = junos_module.params.get('namespace') + + + # If retrieve is set and load and rollback are not set, then + # check, diff, and commit default to False. + if retrieve is not None and load is None and rollback is None: + if diff is None: + diff = False + if check is None: + check = False + if commit is None: + commit = False + # Otherwise, diff, check, and commit default to True. + else: + if diff is None: + diff = True + if check is None: + check = True + if commit is None: + commit = True + + # If load is not None, must have one of src, template, url, lines + if load is not None: + for option in ['src', 'lines', 'template', 'url']: + if junos_module.params.get(option) is not None: + break + # for/else only executed if we didn't break out of the loop. + else: + junos_module.fail_json(msg="The load option (%s) is specified, " + "but none of 'src', 'lines', " + "'template', or 'url' are specified. " + "Must specify one of the 'src', " + "'lines', 'template', or 'url' options." + % (load)) + + # format is valid if retrieve is not None or load is not None. + if format is not None: + if load is None and retrieve is None: + junos_module.fail_json(msg="The format option (%s) is specified, " + "but neither 'load' or 'retrieve' are " + "specified. Must specify one of " + "'load' or 'retrieve' options." + % (format)) + + # dest_dir is valid if retrieve is not None or diff is True. + if dest_dir is not None: + if retrieve is None and diff is False: + junos_module.fail_json(msg="The dest_dir option (%s) is specified," + " but neither 'retrieve' or 'diff' " + "are specified. Must specify one of " + "'retrieve' or 'diff' options." + % (dest_dir)) + + # dest is valid if retrieve is not None + if dest is not None: + if retrieve is None: + junos_module.fail_json(msg="The dest option (%s) is specified," + " but 'retrieve' is not specified. " + "Must specify the 'retrieve' option." + % (dest)) + + # diffs_file is valid if diff is True + if diffs_file is not None: + if diff is False: + junos_module.fail_json(msg="The diffs_file option (%s) is " + "specified, but 'diff' is false." + % (diffs_file)) + + # commit_empty_changes is valid if commit is True + if commit_empty_changes is True: + if commit is False: + junos_module.fail_json(msg="The commit_empty_changes option " + "is true, but 'commit' is false. " + "The commit_empty_changes option " + "may only be specified when " + "'commit' is true.") + + # comment is valid if commit is True + if comment is not None: + if commit is False: + junos_module.fail_json(msg="The comment option (%s) is " + "specified, but 'commit' is false." + % (comment)) + + # confirmed is valid if commit is True + if confirmed is not None: + if commit is False: + junos_module.fail_json(msg="The confirmed option (%s) is " + "specified, but 'commit' is false." + % (confirmed)) + # Must be greater >= 1. + if confirmed < 1: + junos_module.fail_json(msg="The confirmed option (%s) must have a " + "positive integer value." % (confirmed)) + + # check_commit_wait is valid if check is True and commit is True + if check_commit_wait is not None: + if commit is False: + junos_module.fail_json(msg="The check_commit_wait option (%s) is " + "specified, but 'commit' is false." + % (check_commit_wait)) + if check is False: + junos_module.fail_json(msg="The check_commit_wait option (%s) is " + "specified, but 'check' is false." + % (check_commit_wait)) + # Must be greater >= 1. + if check_commit_wait < 1: + junos_module.fail_json(msg="The check_commit_wait option (%s) " + "must have a positive integer value." % + (check_commit_wait)) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': 'Configuration has been: ', + 'changed': False, + 'failed': True} + + junos_module.logger.debug("Step 1 - Open a candidate configuration " + "database.") + junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning) + results['msg'] += 'opened' + + junos_module.logger.debug("Step 2 - Load configuration data into the " + "candidate configuration database.") + if rollback is not None: + junos_module.rollback_configuration(id=rollback) + # Assume configuration changed in case we don't perform a diff later. + # If diff is set, we'll check for actual differences later. + results['changed'] = True + results['msg'] += ', rolled back' + elif load is not None: + if src is not None: + junos_module.load_configuration(action=load, + src=src, + ignore_warning=ignore_warning, + format=format) + results['file'] = src + elif lines is not None: + junos_module.load_configuration(action=load, + lines=lines, + ignore_warning=ignore_warning, + format=format) + elif template is not None: + junos_module.load_configuration(action=load, + template=template, + vars=vars, + ignore_warning=ignore_warning, + format=format) + elif url is not None: + junos_module.load_configuration(action=load, + url=url, + ignore_warning=ignore_warning, + format=format) + else: + junos_module.fail_json(msg="The load option was set to: %s, but " + "no 'src', 'lines', 'template', or " + "'url' option was set." % + (load)) + # Assume configuration changed in case we don't perform a diff later. + # If diff is set, we'll check for actual differences later. + results['changed'] = True + results['msg'] += ', loaded' + + junos_module.logger.debug("Step 3 - Check the validity of the candidate " + "configuration database.") + if check is True: + junos_module.check_configuration() + results['msg'] += ', checked' + + junos_module.logger.debug("Step 4 - Determine differences between the " + "candidate and committed configuration " + "databases.") + if diff is True or junos_module._diff: + diff = junos_module.diff_configuration(ignore_warning) + if diff is not None: + results['changed'] = True + if return_output is True or junos_module._diff: + results['diff'] = {'prepared': diff} + results['diff_lines'] = diff.splitlines() + # Save the diff output + junos_module.save_text_output('diff', 'diff', diff) + else: + results['changed'] = False + results['msg'] += ', diffed' + + junos_module.logger.debug("Step 5 - Retrieve the configuration database " + "from the Junos device.") + if retrieve is not None: + if format is None: + format = 'text' + (config, config_parsed) = junos_module.get_configuration( + database=retrieve, + format=format, + options=options, + filter=filter, + model=model, + namespace=namespace, + remove_ns=remove_ns) + if return_output is True: + if config is not None: + results['config'] = config + results['config_lines'] = config.splitlines() + if config_parsed is not None: + results['config_parsed'] = config_parsed + # Save the output + format_extension = 'config' if format == 'text' else format + junos_module.save_text_output('config', format_extension, config) + results['msg'] += ', retrieved' + + junos_module.logger.debug("Step 6 - Commit the configuration changes.") + if commit is True and not junos_module.check_mode: + # Perform the commit if: + # 1) commit_empty_changes is True + # 2) Neither rollback or load is set. i.e. confirming a previous commit + # 3) rollback or load is set, and there were actual changes. + if (commit_empty_changes is True or + (rollback is None and load is None) or + ((rollback is not None or load is not None) and + results['changed'] is True)): + if check_commit_wait is not None: + time.sleep(check_commit_wait) + junos_module.commit_configuration(ignore_warning=ignore_warning, + comment=comment, + confirmed=confirmed) + results['msg'] += ', committed' + else: + junos_module.logger.debug("Skipping commit. Nothing changed.") + + junos_module.logger.debug("Step 7 - Close the candidate configuration " + "database.") + junos_module.close_configuration() + results['msg'] += ', closed.' + + # If we made it this far, everything was successful. + results['failed'] = False + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py deleted file mode 120000 index cbdd3b96..00000000 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_facts.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py new file mode 100644 index 00000000..db8866ed --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -0,0 +1,355 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_facts +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Retrieve facts from a Junos device +description: + - Retrieve facts from a Junos device using the + U(PyEZ fact gathering system|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html). + - Also returns the committed configuration of the Junos device if the + I(config_format) option has a value other than C(none). +options: + config_format: + description: + - The format of the configuration returned. The specified format must be + supported by the target Junos device. + required: false + default: none + choices: + - none + - xml + - set + - text + - json + savedir: + description: + - A path to a directory, on the Ansible control machine, where facts + will be stored in a JSON file. + - The resulting JSON file is saved in + I(savedir)C(/)I(hostname)C(-facts.json). + - The I(savedir) directory is the value of the I(savedir) option. + - The I(hostname)C(-facts.json) filename begins with the value of the + C(hostname) fact returned from the Junos device, which might be + different than the value of the I(host) option passed to the module. + - If the value of the I(savedir) option is C(none), the default, then + facts are NOT saved to a file. + required: false + default: none + type: path +''' + +EXAMPLES = ''' +--- +- name: Gather facts from Junos devices + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + tasks: + - name: Gather Junos facts with no configuration + juniper_junos_facts: + +# Print a fact + +# Using config_format option + +# Print the config + +# Using savedir option + +# Print the saved JSON file +''' + +RETURN = ''' +ansible_facts.junos: + description: + - Facts collected from the Junos device. This dictionary contains the + keys listed in the I(contains) section of this documentation PLUS all + of the keys returned from PyEZ's fact gathering system. See + U(PyEZ facts|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) + for a complete list of these keys and their meaning. + returned: success + type: complex + contains: + config: + description: + - The device's committed configuration, in the format specified by + I(config_format), as a single multi-line string. + returned: when I(config_format) is not C(none). + type: str + has_2RE: + description: + - Indicates if the device has more than one Routing Engine installed. + Because Ansible does not allow keys to begin with a number, this fact + is returned in place of PyEZ's C(2RE) fact. + returned: success + type: bool + re_name: + description: + - The name of the current Routing Engine to which Ansible is connected. + returned: success + type: str + master_state: + description: + - The mastership state of the Routing Engine to which Ansible is + connected. C(true) if the RE is the master Routing Engine. C(false) + if the RE is not the master Routing Engine. + returned: success + type: bool +changed: + description: + - Indicates if the device's state has changed. Since this module does not + change the operational or configuration state of the device, the value is + always set to C(false). + returned: success + type: bool + sample: false +facts: + description: + - Returned for backwards compatibility. Returns the same keys and values + which are returned under I(ansible_facts.junos). + returned: success + type: dict +failed: + description: + - Indicates if the task failed. + returned: always + type: bool + sample: false +''' + +# Standard library imports +import json +import os.path + + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common +from ansible.module_utils._text import to_bytes +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg + +def get_facts_dict(junos_module): + """Retreive PyEZ facts and convert to a standard dict w/o custom types. + + Ansible >= 2.0 doesn't like custom objects in a modules return value. + Because PyEZ facts are a custom object rather than a true dict they must be + converted to a standard dict. Since facts are read-only, we must begin by + copying facts into a dict. Since PyEZ facts are "on-demand", the + junos_module.dev instance must be an open PyEZ Device instance ojbect + before this function is called. + + Args: + junos_module: An instance of a JuniperJunosModule. + + Returns: + A dict containing the device facts. + """ + if junos_module.conn_type == "local" : + dev = junos_module.dev + # Retrieve all PyEZ-supported facts and copy to a standard dict. + facts = dict(dev.facts) + # Add two useful facts that are implement as PyEZ Device attributes. + facts['re_name'] = dev.re_name + facts['master_state'] = dev.master + else: + facts = junos_module.get_facts() + # Ansible doesn't allow keys starting with numbers. + # Replace the '2RE' key with the 'has_2RE' key. + if '2RE' in facts: + facts['has_2RE'] = facts['2RE'] + del facts['2RE'] + # The value of the 'version_info' key is a custom junos.version_info + # object. Convert this value to a dict. + if 'version_info' in facts and facts['version_info'] is not None: + facts['version_info'] = dict(facts['version_info']) + # The values of the ['junos_info'][re_name]['object'] keys are + # custom junos.version_info objects. Convert all of these to dicts. + if 'junos_info' in facts and facts['junos_info'] is not None: + for key in facts['junos_info']: + facts['junos_info'][key]['object'] = dict( + facts['junos_info'][key]['object']) + return facts + + +def save_facts(junos_module, facts): + """If the savedir option was specified, save the facts into a JSON file. + + If the savedir option was specified, save the facts into a JSON file named + savedir/hostname-facts.json. The filename begins with the value of the + hostname fact returned from the Junos device, which might be different than + the value of the host option passed to the module. + + Args: + junos_module: An instance of a JuniperJunosModule. + facts: The facts dict returned by get_facts_dict(). + + Raises: + IOError: Calls junos_module.fail_json if unable to open the facts + file for writing. + """ + if junos_module.params.get('savedir') is not None: + save_dir = junos_module.params.get('savedir') + file_name = '%s-facts.json' % (facts['hostname']) + file_path = os.path.normpath(os.path.join(save_dir, file_name)) + junos_module.logger.debug("Saving facts to: %s.", file_path) + try: + # TODO: Verify does this work with Python3 + with open(file_path, 'w') as fact_file: + json.dump(facts, fact_file) + junos_module.logger.debug("Facts saved to: %s.", file_path) + except IOError: + junos_module.fail_json(msg="Unable to save facts. Failed to open " + "the %s file." % (file_path)) + + +def save_inventory(junos_module, inventory): + """If the savedir option was specified, save the XML inventory. + + If the savedir option was specified, save the inventory XML output into + an XML file named savedir/hostname-inventory.xml. The filename begins with + the value of the hostname fact returned from the Junos device, which might + be different than the value of the host option passed to the module. + + Args: + junos_module: An instance of a JuniperJunosModule. + inventory: The XML string of inventory to save. + + Raises: + IOError: Calls junos_module.fail_json if unable to open the inventory + file for writing. + """ + if junos_module.conn_type == "local" : + dev = junos_module.dev + file_name = '%s-inventory.xml' % (dev.facts['hostname']) + else: + facts = junos_module._pyez_conn.get_facts() + file_name = '%s-inventory.xml' % (facts['hostname']) + if junos_module.params.get('savedir') is not None: + save_dir = junos_module.params.get('savedir') + file_path = os.path.normpath(os.path.join(save_dir, file_name)) + junos_module.logger.debug("Saving inventory to: %s.", file_path) + try: + with open(file_path, 'wb') as fact_file: + fact_file.write(to_bytes(inventory, encoding='utf-8')) + junos_module.logger.debug("Inventory saved to: %s.", file_path) + except IOError: + junos_module.fail_json(msg="Unable to save inventory. Failed to " + "open the %s file." % (file_path)) + + +def main(): + config_format_choices = [None] + config_format_choices += juniper_junos_common.CONFIG_FORMAT_CHOICES + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + config_format=dict(choices=config_format_choices, + required=False, + default=None), + savedir=dict(type='path', required=False, default=None), + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True, + min_jxmlease_version=cfg.MIN_JXMLEASE_VERSION, + ) + + junos_module.logger.debug("Gathering facts.") + # Get the facts dictionary from the device. + facts = get_facts_dict(junos_module) + junos_module.logger.debug("Facts gathered.") + + if junos_module.params.get('savedir') is not None: + # Save the facts. + save_facts(junos_module, facts) + + # Get and save the inventory + try: + junos_module.logger.debug("Gathering inventory.") + if junos_module.conn_type == "local": + inventory = junos_module.dev.rpc.get_chassis_inventory() + else: + junos_module.get_chassis_inventory() + junos_module.logger.debug("Inventory gathered.") + save_inventory(junos_module, + junos_module.etree.tostring(inventory, + pretty_print=True)) + except junos_module.pyez_exception.RpcError as ex: + junos_module.fail_json(msg='Unable to retrieve hardware ' + 'inventory: %s' % (str(ex))) + + config_format = junos_module.params.get('config_format') + if config_format is not None: + (config, config_parsed) = junos_module.get_configuration( + format=config_format) + if config is not None: + facts.update({'config': config}) + # Need to wait until the ordering issues are figured out before + # using config_parsed. + # if config_parsed is not None: + # facts.update({'config_parsed': config_parsed}) + + # Return response. + junos_module.exit_json( + changed=False, + failed=False, + ansible_facts={'junos': facts}, + facts=facts) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py deleted file mode 120000 index e14ff8d8..00000000 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_jsnapy.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py new file mode 100644 index 00000000..bdf9bff6 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -0,0 +1,360 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2016, Roslan Zaki +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_jsnapy +author: + - Juniper Networks + - Roslan Zaki + - Damien Garros + - Stacy Smith (@stacywsmith)" +short_description: Execute JSNAPy tests on a Junos device +description: + - Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. + JSNAPy is documented on U(Github|https://github.com/Juniper/jsnapy) and + this + U(Day One Book|https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) + - This module only reports C(failed) if the module encounters an error and + fails to execute the JSNAPy tests. If does NOT report C(failed) if one or + more of the JSNAPy tests fail. To check the test results, register the + module's response and use the assert module to verify the expected result + in the response. (See :ref:`juniper_junos_jsnapy-examples-label`.) + - A callback plugin which formats and prints JSNAPy test results for human + consumption is also available. This callback plugin is enabled by adding + C(callback_whitelist = jsnapy) to the Ansible configuration file. +options: + action: + description: + - The JSNAPy action to perform. + required: true + default: none + type: str + choices: + - check + - snapcheck + - snap_pre + - snap_post + config_file: + description: + - The filename of a JSNAPy configuration file (in YAML format). The + I(test_files) option and the I(config_file) option are mutually + exclusive. Either the I(test_files) option or the I(config_file) + option is required. + required: false + type: path + default: none + dir: + description: + - The path to the directory containing the JSNAPy test file(s) specified + by the I(test_files) option or the JSNAPy configuration file specified + by the I(config_file) option. + required: false + type: path + default: /etc/jsnapy/testfiles + aliases: + - directory + test_files: + description: + - The filename of file(s) in the I(dir) directory. Each file contains + JSNAPy test case definitions. The I(test_files) option and the + I(config_file) option are mutually exclusive. Either the I(test_files) + option or the I(config_file) option is required. + required: false + type: list of path + default: none +''' + + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_jsnapy + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: JUNOS Post Checklist + juniper_junos_jsnapy: + action: "snap_post" + config_file: "first_test.yml" + logfile: "migration_post.log" + register: test1 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test1.passPercentage == 100" + - name: Print the full test response + debug: + var: test1 + + - name: Test based on a test_file directly + juniper_junos_jsnapy: + action: "snapcheck" + test_files: "tests/test_junos_interface.yaml" + register: test2 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test2.passPercentage == 100" + - name: Print the full test response + debug: + var: test2 + + - name: "Collect Pre Snapshot" + juniper_junos_jsnapy: + action: "snap_pre" + test_files: "tests/test_loopback.yml" + + - name: "Collect Post Snapshot" + juniper_junos_jsnapy: + action: "snap_post" + test_files: "tests/test_loopback.yml" + + - name: "Check after Pre and Post Snapshots" + juniper_junos_jsnapy: + action: "check" + test_files: "tests/test_loopback.yml" + register: test3 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test3.|succeeded" + - "test3.passPercentage == 100" + - name: Print the full test response + debug: + var: test3 +''' + +RETURN = ''' +action: + description: + - The JSNAPy action performed as specified by the I(action) option. + returned: success + type: str +changed: + description: + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to C(false). + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +# final_result: +msg: + description: + - A human-readable message indicating the result of the JSNAPy tests. + returned: always + type: str +# total_passed: +# total_failed: +''' + +# Standard Library imports +import os.path + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + +def main(): + JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + action=dict(required=True, + choices=JSNAPY_ACTION_CHOICES, + type='str', + default=None), + test_files=dict(required=False, + type='list', + default=None), + config_file=dict(required=False, + type='path', + default=None), + dir=dict(required=False, + type='path', + aliases=['directory'], + default='/etc/jsnapy/testfiles')), + # Mutually exclusive options. + mutually_exclusive=[['test_files', 'config_file']], + # One of test_files or config_file is required. + required_one_of=[['test_files', 'config_file']], + supports_check_mode=True, + min_jsnapy_version=juniper_junos_common.MIN_JSNAPY_VERSION, + ) + + # Straight from params + action = junos_module.params.get('action') + test_files = junos_module.params.get('test_files') + config_file = junos_module.params.get('config_file') + dir = junos_module.params.get('dir') + + # Initialize the results. Assume failure until we know otherwise. + results = {'msg': '', + 'action': action, + 'changed': False, + 'failed': True} + + if config_file is not None: + junos_module.logger.debug('Checking config file: %s.', config_file) + config_file_path = os.path.abspath(config_file) + config_dir_file_path = os.path.abspath(os.path.join(dir, config_file)) + if os.path.isfile(config_file_path): + data = config_file_path + elif os.path.isfile(config_dir_file_path): + data = config_dir_file_path + else: + junos_module.fail_json(msg="Unable to locate the %s config file " + "at %s or %s." % (config_file, + config_file_path, + config_dir_file_path)) + elif test_files is not None and len(test_files) > 0: + data = {'tests': []} + for test_file in test_files: + junos_module.logger.debug('Checking test file: %s.', test_file) + test_file_path = os.path.abspath(test_file) + test_dir_file_path = os.path.abspath(os.path.join(dir, test_file)) + if os.path.isfile(test_file_path): + data['tests'].append(test_file_path) + elif os.path.isfile(test_dir_file_path): + data['tests'].append(test_dir_file_path) + else: + junos_module.fail_json(msg="Unable to locate the %s test file " + "at %s or %s." % + (test_file, + test_file_path, + test_dir_file_path)) + else: + junos_module.fail_json(msg="No config_file or test_files specified.") + + try: + if junos_module.conn_type == "local": + junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') + jsa = junos_module.jsnapy.SnapAdmin() + junos_module.logger.debug('Executing %s action.', action) + if action == 'check': + responses = jsa.check(data=data, + dev=junos_module.dev, + pre_file='PRE', + post_file='POST') + elif action == 'snapcheck': + responses = jsa.snapcheck(data=data, + dev=junos_module.dev) + elif action == 'snap_pre': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='PRE') + elif action == 'snap_post': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='POST') + else: + junos_module.fail_json(msg="Unexpected action: %s." % (action)) + junos_module.logger.debug('The %s action executed successfully.', + action) + else: + conn = junos_module.get_connection() + responses = json.loads(conn.invoke_jsnapy(data, action)) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + junos_module.fail_json(msg="Error communicating with the device: %s" % + (str(ex))) + except Exception as ex: + junos_module.fail_json(msg="Uncaught exception - please report: %s" % + (str(ex))) + + if isinstance(responses, list) and len(responses) == 1: + if action in ('snapcheck', 'check'): + for response in responses: + results['device'] = response.device + results['router'] = response.device + results['final_result'] = response.result + results['total_passed'] = response.no_passed + results['total_failed'] = response.no_failed + results['test_results'] = response.test_results + total_tests = int(response.no_passed) + int(response.no_failed) + results['total_tests'] = total_tests + pass_percentage = 0 + if total_tests > 0: + pass_percentage = ((int(response.no_passed) * 100) // + total_tests) + results['passPercentage'] = pass_percentage + results['pass_percentage'] = pass_percentage + if results['final_result'] == 'Failed': + results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + else: + results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + elif action in ('snap_pre', 'snap_post'): + results['msg'] = "The %s action successfully executed." % (action) + else: + junos_module.fail_json(msg="Unexpected JSNAPy responses. Type: %s." + "Responses: %s" % + (type(responses), str(responses))) + + # If we made it this far, it's success. + results['failed'] = False + + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_command.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_command.py deleted file mode 100644 index 8766edd2..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_command.py +++ /dev/null @@ -1,507 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_command -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Execute one or more CLI commands on a Junos device -description: - - Execute one or more CLI commands on a Junos device. - - Alias command - - This module does NOT use the Junos CLI to execute the CLI command. - Instead, it uses the C() RPC over a NETCONF channel. The - C() RPC takes a CLI command as it's input and is very similar to - executing the command on the CLI, but you can NOT include any pipe modifies - (i.e. C(| match), C(| count), etc.) with the CLI commands executed by this - module. -options: - commands: - description: - - A list of one or more CLI commands to execute on the Junos device. - required: true - default: none - type: list - aliases: - - cli - - command - - cmd - - cmds - dest: - description: - - The path to a file, on the Ansible control machine, where the output of - the cli command will be saved. - - The file must be writeable. If the file already exists, it is - overwritten. - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the value of the I(dest) option. It is - the user's responsibility to ensure this value is unique per target - host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(dest) and I(dest_dir) options are mutually exclusive. - required: false - default: None - type: path - aliases: - - destination - dest_dir: - description: - - The path to a directory, on the Ansible control machine, where - the output of the cli command will be saved. The output will be logged - to a file named C({{ inventory_hostname }}_)I(command)C(.)I(format) - in the directory specified by the value of the I(dest_dir) option. - - The destination file must be writeable. If the file already exists, - it is overwritten. It is the users responsibility to ensure a unique - I(dest_dir) value is provided for each execution of this module - within a playbook. - - The I(dest_dir) and I(dest) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - required: false - default: None - type: path - aliases: - - destination_dir - - destdir - formats: - description: - - The format of the reply for the CLI command(s) specified by the - I(commands) option. The specified format(s) must be supported by the - target Junos device. The value of this option can either be a single - format, or a list of formats. If a single format is specified, it - applies to all command(s) specified by the I(commands) option. If a - list of formats are specified, there must be one value in the list for - each command specified by the I(commands) option. Specifying the value - C(xml) for the I(formats) option is similar to appending - C(| display xml) to a CLI command, and specifying the value C(json) - for the I(formats) option is similar to appending C(| display json) to - a CLI command. - required: false - default: text - type: str or list of str - choices: - - text - - xml - - json - aliases: - - format - - display - - output - return_output: - description: - - Indicates if the output of the command should be returned in the - module's response. You might want to set this option to C(false), - and set the I(dest_dir) option, if the command output is very large - and you only need to save the output rather than using it's content in - subsequent tasks/plays of your playbook. - required: false - default: true - type: bool -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_command - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Execute single "show version" command. - juniper_junos_command: - commands: "show version" - register: response - - - name: Print the command output - debug: - var: response.stdout - - - name: Execute three commands. - juniper_junos_command: - commands: - - "show version" - - "show system uptime" - - "show interface terse" - register: response - - - name: Print the command output of each. - debug: - var: item.stdout - with_items: "{{ response.results }}" - - - name: Two commands with XML output. - juniper_junos_command: - commands: - - "show route" - - "show lldp neighbors" - format: xml - - - name: show route with XML output - show version with JSON output - juniper_junos_command: - commands: - - "show route" - - "show version" - formats: - - "xml" - - "json" - - - name: save outputs in dest_dir - juniper_junos_command: - commands: - - "show route" - - "show version" - dest_dir: "./output" - - - name: save output to dest - juniper_junos_command: - command: "show system uptime" - dest: "/tmp/{{ inventory_hostname }}.uptime.output" - - - name: save output to dest - juniper_junos_command: - command: - - "show route" - - "show lldp neighbors" - dest: "/tmp/{{ inventory_hostname }}.commands.output" - - - name: Multiple commands, save outputs, but don't return them - juniper_junos_command: - commands: - - "show route" - - "show version" - formats: - - "xml" - - "json" - dest_dir: "/tmp/outputs/" - return_output: false -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's state has changed. Since this module does not - change the operational or configuration state of the device, the value - is always set to false. - - You could use this module to execute a command which - changes the operational state of the the device. For example, - C(clear ospf neighbors). Beware, this module is unable to detect - this situation, and will still return the value C(false) for I(changed) - in this case. - returned: success - type: bool - sample: false -command: - description: - - The CLI command which was executed. - returned: always - type: str -failed: - description: - - Indicates if the task failed. See the I(results) key for additional - details. - returned: always - type: bool -format: - description: - - The format of the command response. - returned: always - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -parsed_output: - description: - - The command reply from the Junos device parsed into a JSON data structure. - For XML replies, the response is parsed into JSON using the - U(jxmlease|https://github.com/Juniper/jxmlease) - library. For JSON the response is parsed using the Python - U(json|https://docs.python.org/2/library/json.html) library. - - When Ansible converts the jxmlease or native Python data structure - into JSON, it does not guarantee that the order of dictionary/object keys - are maintained. - returned: when command executed successfully, I(return_output) is true, - and the value of the I(formats) option is C(xml) or C(json). - type: dict -results: - description: - - The other keys are returned when a single command is specified for the - I(commands) option. When the value of the I(commands) option is a list - of commands, this key is returned instead. The value of this key is a - list of dictionaries. Each element in the list corresponds to the - commands in the I(commands) option. The keys for each element in the list - include all of the other keys listed. The I(failed) key indicates if the - individual command failed. In this case, there is also a top-level - I(failed) key. The top-level I(failed) key will have a value of C(false) - if ANY of the commands ran successfully. In this case, check the value - of the I(failed) key for each element in the I(results) list for the - results of individual commands. - returned: when the I(commands) option is a list value. - type: list of dict -stdout: - description: - - The command reply from the Junos device as a single multi-line string. - returned: when command executed successfully and I(return_output) is C(true). - type: str -stdout_lines: - description: - - The command reply from the Junos device as a list of single-line strings. - returned: when command executed successfully and I(return_output) is C(true). - type: list of str -''' - -import sys - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - commands=dict(required=True, - type='list', - aliases=['cli', 'command', 'cmd', 'cmds'], - default=None), - formats=dict(required=False, - type='list', - aliases=['format', 'display', 'output'], - default=None), - dest=dict(required=False, - type='path', - aliases=['destination'], - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir'], - default=None), - ignore_warning=dict(required=False, - type='list', - default=None), - return_output=dict(required=False, - type='bool', - default=True) - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. Well, that's not completely true. It does depend on the - # command executed. See the I(changed) key in the RETURN documentation - # for more details. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - - # Parse ignore_warning value - ignore_warning = junos_module.parse_ignore_warning_option() - - # Check over commands - commands = junos_module.params.get('commands') - # Ansible allows users to specify a commands argument with no value. - if commands is None: - junos_module.fail_json(msg="The commands option must have a value.") - # Make sure the commands don't include any pipe modifiers. - for command in commands: - pipe_index = command.find('|') - if (pipe_index != -1 and - command[pipe_index:].strip() != 'display xml rpc'): - # Allow "show configuration | display set" - if ('show configuration' in command and - 'display set' in command[pipe_index:] and - '|' not in command[pipe_index+1:]): - continue - # Any other "| display " should use the format option instead. - for valid_format in juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES: - if 'display ' + valid_format in command[pipe_index:]: - junos_module.fail_json( - msg='The pipe modifier (%s) in the command ' - '(%s) is not supported. Use format: "%s" ' - 'instead.' % - (command[pipe_index:], command, valid_format)) - # Any other "| " is going to produce an error anyway, so fail - # with a meaningful message. - junos_module.fail_json(msg='The pipe modifier (%s) in the command ' - '(%s) is not supported.' % - (command[pipe_index:], command)) - - # Check over formats - formats = junos_module.params.get('formats') - if formats is None: - # Default to text format - formats = ['text'] - valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES - # Check format values - for format in formats: - # Is it a valid format? - if format not in valid_formats: - junos_module.fail_json(msg="The value %s in formats is invalid. " - "Must be one of: %s" % - (format, ', '.join(map(str, - valid_formats)))) - # Correct number of format values? - if len(formats) != 1 and len(formats) != len(commands): - junos_module.fail_json(msg="The formats option must have a single " - "value, or one value per command. There " - "are %d commands and %d formats." % - (len(commands), len(formats))) - # Same format for all commands - elif len(formats) == 1 and len(commands) > 1: - formats = formats * len(commands) - - results = list() - for (command, format) in zip(commands, formats): - # Set initial result values. Assume failure until we know it's success. - result = {'msg': '', - 'command': command, - 'format': format, - 'changed': False, - 'failed': True} - - # Execute the CLI command - try: - junos_module.logger.debug('Executing command "%s".', - command) - rpc = junos_module.etree.Element('command', format=format) - rpc.text = command - resp = junos_module.dev.rpc(rpc, ignore_warning=ignore_warning, normalize=bool(format == 'xml')) - result['msg'] = 'The command executed successfully.' - junos_module.logger.debug('Command "%s" executed successfully.', - command) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Unable to execute "%s". Error: %s', - command, str(ex)) - result['msg'] = 'Unable to execute the command: %s. Error: %s' % \ - (command, str(ex)) - results.append(result) - continue - - text_output = None - parsed_output = None - if resp is True: - text_output = '' - elif (resp, junos_module.etree._Element): - # Handle the output based on format - if format == 'text': - if resp.tag in ['output', 'rpc-reply']: - text_output = resp.text - junos_module.logger.debug('Text output set.') - elif resp.tag == 'configuration-information': - text_output = resp.findtext('configuration-output') - junos_module.logger.debug('Text configuration output set.') - else: - result['msg'] = 'Unexpected text response tag: %s.' % ( - (resp.tag)) - results.append(result) - junos_module.logger.debug('Unexpected text response tag ' - '%s.', resp.tag) - continue - elif format == 'xml': - encode = None if sys.version < '3' else 'unicode' - text_output = junos_module.etree.tostring(resp, - pretty_print=True, - encoding=encode) - parsed_output = junos_module.jxmlease.parse_etree(resp) - junos_module.logger.debug('XML output set.') - elif format == 'json': - text_output = str(resp) - parsed_output = resp - junos_module.logger.debug('JSON output set.') - else: - result['msg'] = 'Unexpected format %s.' % (format) - results.append(result) - junos_module.logger.debug('Unexpected format %s.', format) - continue - else: - result['msg'] = 'Unexpected response type %s.' % (type(resp)) - results.append(result) - junos_module.logger.debug('Unexpected response type %s.', - type(resp)) - continue - - # Set the output keys - if junos_module.params['return_output'] is True: - if text_output is not None: - result['stdout'] = text_output - result['stdout_lines'] = text_output.splitlines() - if parsed_output is not None: - result['parsed_output'] = parsed_output - # Save the output - junos_module.save_text_output(command, format, text_output) - # This command succeeded. - result['failed'] = False - # Append to the list of results - results.append(result) - - # Return response. - if len(results) == 1: - junos_module.exit_json(**results[0]) - else: - # Calculate the overall failed. Only failed if all commands failed. - failed = True - for result in results: - if result.get('failed') is False: - failed = False - break - junos_module.exit_json(results=results, - changed=False, - failed=failed) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_config.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_config.py deleted file mode 100644 index 193a7200..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_config.py +++ /dev/null @@ -1,1140 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_config -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Manipulate the configuration of a Junos device -description: - - > - Manipulate the configuration of a Junos device. This module allows a - combination of loading or rolling back, checking, diffing, retrieving, and - committing the configuration of a Junos device. It performs the following - steps in order: - - #. Open a candidate configuration database. - - * If the I(config_mode) option has a value of C(exclusive), the default, - take a lock on the candidate configuration database. If the lock fails - the module fails and reports an error. - * If the I(config_mode) option has a value of C(private), open a private - candidate configuration database. If opening the private configuration - database fails the module fails and reports an error. - #. Load configuration data into the candidate configuration database. - - * Configuration data may be loaded using the I(load) or I(rollback) - options. If either of these options are specified, new configuration - data is loaded. If neither option is specified, this step is skipped. - * If the I(rollback) option is specified, replace the candidate - configuration with the previous configuration specified by the value - of the I(rollback) option. - * If the I(load) option is specified, load new configuration data. - * The value of the I(load) option defines the type of load which is - performed. - * The source of the new configuration data is one of the following: - - * I(src) - A file path on the local Ansible control machine. - * I(lines) - A list of strings containing the configuration data. - * I(template) - A file path to a Jinja2 template on the local - Ansible control machine. This template is rendered with the variables - specified by the I(vars) option. If the I(template) option is - specified, the I(vars) option must also be specified. - * I(url) - A URL reachable from the target Junos device. - * If the I(format) option is specified, the configuration file being - loaded is in the specified format, rather than the format determined - from the file name. - #. Check the validity of the candidate configuration database. - - * If the I(check) option is C(true), the default, check the validity - of the configuration by performing a "commit check" operation. - * This option may be specified with I(diff) C(false) and I(commit) - C(false) to confirm a previous "commit confirmed " operation - without actually performing an additional commit. - * If the configuration check fails, further processing stops, the module - fails, and an error is reported. - #. Determine differences between the candidate and committed configuration - databases. - - * If step 2 was not skipped, and the I(diff) option is C(true), - the default, perform a diff between the candidate and committed - configuration databases. - * If the I(diffs_file) or I(dest_dir) option is specified, save the - generated configuration differences. - * If the I(return_output) option is C(true), the default, include the - generated configuration difference in the I(diff) and I(diff_lines) - keys of the module's response. - #. Retrieve the configuration database from the Junos device. - - * If the I(retrieve) option is specified, retrieve the configuration - database specified by the I(retrieve) value from the target Junos - device to the local Ansible control machine. - * The format in which the configuration is retrieved is specified by the - value of the I(format) option. - * The optional I(filter) controls which portions of the configuration - are retrieved. - * If I(options) are specified, they control the content of the - configuration retrieved. - * If the I(dest) or I(dest_dir) option is specified, save the - retrieved configuration to a file on the local Ansible control - machine. - * If the I(return_output) option is C(true), the default, include the - retrieved configuration in the I(config), I(config_lines), and - I(config_parsed) keys of the module's response. - #. Commit the configuration changes. - - * If the I(commit) option is C(true), the default, commit the - configuration changes. - * This option may be specified with I(diff) C(false) and I(check) - C(false) to confirm a previous "commit confirmed " operation. - * If the I(comment) option is specified, add the comment to the commit. - * If the I(confirmed) option is specified, perform a - C(commit confirmed) I(min) operation where I(min) is the value of the - I(confirmed) option. - * If the I(check) option is C(true) and the I(check_commit_wait) - option is specified, wait I(check_commit_wait) seconds before - performing the commit. - #. Close the candidate configuration database. - - * Close and discard the candidate configuration database. - * If the I(config_mode) option has a value of C(exclusive), the default, - unlock the candidate configuration database. -options: - check: - description: - - Perform a commit check operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - aliases: - - check_commit - - commit_check - check_commit_wait: - description: - - The number of seconds to wait between check and commit operations. - - This option is only valid if I(check) is C(true) and I(commit) is - C(true). - - This option should not normally be needed. It works around an issue in - some versions of Junos. - required: false - default: none - type: int - comment: - description: - - Provide a comment to be used with the commit operation. - - This option is only valid if the I(commit) option is true. - required: false - default: none - type: str - commit: - description: - - Perform a commit operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - commit_empty_changes: - description: - - Perform a commit operation, even if there are no changes between the - candidate configuration and the committed configuration. - required: false - default: false - type: bool - config_mode: - description: - - The mode used to access the candidate configuration database. - required: false - default: exclusive - type: str - choices: - - exclusive - - private - aliases: - - config_access - - edit_mode - - edit_access - confirmed: - description: - - Provide a confirmed timeout, in minutes, to be used with the commit - operation. - - This option is only valid if the I(commit) option is C(true). - - The value of this option is the number of minutes to wait for another - commit operation before automatically rolling back the configuration - change performed by this task. In other words, this option causes the - module to perform a C(commit confirmed )I(min) where I(min) is the - value of the I(confirmed) option. This option DOES NOT confirm a - previous C(commit confirmed )I(min) operation. To confirm a previous - commit operation, invoke this module with the I(check) or I(commit) - option set to C(true). - required: false - default: none - type: int - aliases: - - confirm - dest: - description: - - The path to a file, on the local Ansible control machine, where the - configuration will be saved if the I(retrieve) option is specified. - - The file must be writeable. If the file already exists, it is - overwritten. - - This option is only valid if the I(retrieve) option is not C(none). - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the I(dest) value. It is the user's - responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(dest) and I(dest_dir) options are mutually exclusive. - required: false - default: none - type: path - aliases: - - destination - dest_dir: - description: - - The path to a directory, on the Ansible control machine. This is the - directory where the configuration will be saved if the I(retrieve) - option is specified. It is also the directory where the configuration - diff will be specified if the I(diff) option is C(true). - - This option is only valid if the I(retrieve) option is not C(none) or - the I(diff) option is C(true). - - The retrieved configuration will be saved to a file named - C({{ inventory_hostname }}.)I(format_extension) in the I(dest_dir) - directory. Where I(format_extension) is C(conf) for text format, C(xml) - for XML format, C(json) for JSON format, and C(set) for set format. - - If the I(diff) option is C(true), the configuration diff will be saved - to a file named C({{ inventory_hostname }}.diff) in the I(dest_dir) - directory. - - The destination file must be writeable. If the file already exists, - it is overwritten. It is the users responsibility to ensure a unique - I(dest_dir) value is provided for each execution of this module - within a playbook. - - The I(dest_dir) and I(dest) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - - The I(dest_dir) and I(diff_file) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - required: false - default: none - type: path - aliases: - - destination_dir - - destdir - - savedir - - save_dir - diff: - description: - - Perform a configuration compare (aka diff) operation. - required: false - default: true (false if retrieve is set and load and rollback are not set) - type: bool - aliases: - - compare - - diffs - diffs_file: - description: - - The path to a file, on the Ansible control machine, where the - configuration differences will be saved if the I(diff) option is - specified. - - The file must be writeable. If the file already exists, it is - overwritten. - - This option is only valid if the I(diff) option is C(true). - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the I(diffs_file) value. It is the - user's responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - - The I(diffs_file) and I(dest_dir) options are mutually exclusive. - required: false - default: None - type: path - format: - description: - - Specifies the format of the configuration retrieved, if I(retrieve) - is not C(none). - - Specifies the format of the configuration to be loaded, if I(load) is - not C(none). - - The specified format must be supported by the target Junos device. - required: false - default: none (auto-detect on load, text on retrieve) - type: str - choices: - - xml - - set - - text - - json - filter: - description: - - A string of XML, or '/'-separated configuration hierarchies, - which specifies a filter used to restrict the portions of the - configuration which are retrieved. See - U(PyEZ's get_config method documentation|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) - for details on the value of this option. - required: false - default: none - type: 'str' - aliases: - - filter_xml - ignore_warning: - description: - - A boolean, string or list of strings. If the value is C(true), - ignore all warnings regardless of the warning message. If the value - is a string, it will ignore warning(s) if the message of each warning - matches the string. If the value is a list of strings, ignore - warning(s) if the message of each warning matches at least one of the - strings in the list. The value of the I(ignore_warning) option is - applied to the load and commit operations performed by this module. - required: false - default: none - type: bool, str, or list of str - lines: - description: - - Used with the I(load) option. Specifies a list of list of - configuration strings containing the configuration to be loaded. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - By default, the format of the configuration data is auto-dectected by - the content of the first line in the I(lines) list. - - If the I(format) option is specified, the I(format) value overrides the - format auto-detection. - required: false - default: none - type: list - load: - description: - - Specifies the type of load operation to be performed. - - The I(load) and I(rollback) options are mutually exclusive. - - > - The choices have the following meanings: - - B(none) - Do not perform a load operation. - - B(merge) - Combine the new configuration with the existing - configuration. If statements in the new configuration conflict with - statements in the existing configuration, the statements in - the new configuration replace those in the existing - configuration. - - B(replace) - This option is a superset of the B(merge) option. It - combines the new configuration with the existing configuration. If the - new configuration is in text format and a hierarchy level in the new - configuartion is prefixed with the string C(replace:), then the - hierarchy level in the new configuration replaces the entire - corresponding hierarchy level in the existing configuration, regardles - of the existence or content of that hierarchy level in the existing - configuration. If the configuration is in XML format, the XML attribute - C(replace = "replace") is equivalent to the text format's C(replace:) - prefix. If a configuration hierarchy in the new configuration is not - prefixed with C(replace:), then the B(merge) behavior is used. - Specifically, for any statements in the new configuration which - conflict with statements in the existing configuration, the statements - in the new configuration replace those in the existing configuration. - - B(override) - Discard the entire existing configuration and replace it - with the new configuration. When the configuration is later committed, - all system processes are notified and the entire new configuration is - marked as 'changed' even if some statements previously existed in the - configuration. The value B(overwrite) is a synonym for B(override). - - B(update) - This option is similar to the B(override) option. The new - configuration completely replaces the existing configuration. The - difference comes when the configuration is later committed. This option - performs a 'diff' between the new candidate configuration and the - existing committed configuration. It then only notifies system - processes repsonsible for the changed portions of the configuration, - and only marks the actual configuration changes as 'changed'. - - B(set) - This option is used when the new configuration data is in set - format (a series of configuration mode commands). The new configuration - data is loaded line by line and may contain any configuration mode - commands, such as set, delete, edit, or deactivate. This value must be - specified if the new configuration is in set format. - required: false - default: none - choices: - - none - - set - - merge - - update - - replace - - override - - overwrite - type: str - options: - description: - - Additional options, specified as a dictionary of key/value pairs, used - when retrieving the configuration. See the - U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) - for information on available options. - required: false - default: None - type: dict - retrieve: - description: - - The configuration database to be retrieved. - required: false - default: none - choices: - - none - - candidate - - committed - type: str - return_output: - description: - - Indicates if the output of the I(diff) and I(retreive) options should - be returned in the module's response. You might want to set this option - to C(false), and set the I(dest_dir) option, if the configuration or - diff output is very large and you only need to save the output rather - than using it's content in subsequent tasks/plays of your playbook. - required: false - default: true - type: bool - rollback: - description: - - Populate the candidate configuration from a previously committed - configuration. This value can be a configuration number between 0 and - 49, or the keyword C(rescue) to load the previously saved rescue - configuration. - - By default, some Junos platforms store fewer than 50 previous - configurations. Specifying a value greater than the number - of previous configurations available, or specifying C(rescue) when no - rescue configuration has been saved, will result in an error when the - module attempts to perform the rollback. - - The I(rollback) and I(load) options are mutually exclusive. - required: false - default: none - choices: - - 0-49 - - rescue - type: int or str - src: - description: - - Used with the I(load) option. Specifies the path to a file, on the - local Ansible control machine, containing the configuration to be - loaded. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - By default, the format of the configuration data is determined by the - file extension of this path name. If the file has a C(.conf) - extension, the content is treated as text format. If the file has a - C(.xml) extension, the content is treated as XML format. If the file - has a C(.set) extension, the content is treated as Junos B(set) - commands. - - If the I(format) option is specified, the I(format) value overrides the - file-extension based format detection. - required: false - default: none - type: 'path' - aliases: - - source - - file - template: - description: - - The path to a Jinja2 template file, on the local Ansible control - machine. This template file, along with the I(vars) option, is used to - generate the configuration to be loaded on the target Junos device. - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - - The I(template) and I(vars) options are required together. If one is - specified, the other must be specified. - required: false - default: none - type: path - aliases: - - template_path - url: - description: - - A URL which specifies the configuration data to load on the target - Junos device. - - The Junos device uses this URL to load the configuration, therefore - this URL must be reachable by the target Junos device. - - The possible formats of this value are documented in the 'url' section - of the - U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). - - The I(src), I(lines), I(template), and I(url) options are mutually - exclusive. - required: false - default: none - type: str - vars: - description: - - A dictionary of keys and values used to render the Jinja2 template - specified by the I(template) option. - - The I(template) and I(vars) options are required together. If one is - specified, the other must be specified. - required: false - default: none - type: dict - aliases: - - template_vars -''' - -EXAMPLES = ''' ---- -- name: Manipulate the configuration of Junos devices - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - tasks: - - name: Retrieve the committed configuration - juniper_junos_config: - retrieve: 'committed' - diff: false - check: false - commit: false - register: response - - name: Print the lines in the config. - debug: - var: response.config_lines - - - name: Append .foo to the hostname using private config mode. - juniper_junos_config: - config_mode: 'private' - load: 'merge' - lines: - - "set system host-name {{ inventory_hostname }}.foo" - register: response - - name: Print the config changes. - debug: - var: response.diff_lines - - - name: Rollback to the previous config. - juniper_junos_config: - config_mode: 'private' - rollback: 1 - register: response - - name: Print the config changes. - debug: - var: response.diff_lines - - - name: Rollback to the rescue config. - juniper_junos_config: - rollback: 'rescue' - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load override from a file. - juniper_junos_config: - load: 'override' - src: "{{ inventory_hostname }}.conf" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a Jinja2 template. - juniper_junos_config: - load: 'merge' - format: 'xml' - template: "{{ inventory_hostname }}.j2" - vars: - host: "{{ inventory_hostname }}" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a file on the Junos device. - juniper_junos_config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a file on the Junos device, skip the commit check - juniper_junos_config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - check: false - register: response - - name: Print the msg. - debug: - var: response.msg - - - name: Print diff between current and rollback 10. No check. No commit. - juniper_junos_config: - rollback: 11 - diff: true - check: false - commit: false - register: response - - name: Print the msg. - debug: - var: response - - - name: Retrieve [edit system services] of current committed config. - juniper_junos_config: - retrieve: 'committed' - filter: 'system/services' - diff: true - check: false - commit: false - register: response - - name: Print the resulting config lines. - debug: - var: response.config_lines - - - name: Enable NETCONF SSH and traceoptions, save config, and diffs. - juniper_junos_config: - load: 'merge' - lines: - - 'set system services netconf ssh' - - 'set system services netconf traceoptions flag all' - - 'set system services netconf traceoptions file netconf.log' - format: 'set' - retrieve: 'candidate' - filter: 'system/services' - comment: 'Enable NETCONF with traceoptions' - dest_dir: './output' - register: response - - name: Print the complete response - debug: - var: response - - - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit - juniper_junos_config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - confirm: 5 - check_commit_wait: 3 - register: response - - name: Print the complete response - debug: - var: response - - name: Confirm the previous commit with a commit check (but no commit) - juniper_junos_config: - check: true - diff: false - commit: false - register: response - - name: Print the complete response - debug: - var: response -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's configuration has changed, or would have - changed when in check mode. - returned: success - type: bool -config: - description: - - The retrieved configuration. The value is a single multi-line - string in the format specified by the I(format) option. - returned: when I(retrieved) is not C(none) and I(return_output) is C(true). - type: str -config_lines: - description: - - The retrieved configuration. The value is a list of single-line - strings in the format specified by the I(format) option. - returned: when I(retrieved) is not C(none) and I(return_output) is C(true). - type: list -config_parsed: - description: - - The retrieved configuration parsed into a JSON datastructure. - For XML replies, the response is parsed into JSON using the - jxmlease library. For JSON the response is parsed using the - Python json library. - - When Ansible converts the jxmlease or native Python data - structure into JSON, it does not guarantee that the order of - dictionary/object keys are maintained. - returned: when I(retrieved) is not C(none), the I(format) option is C(xml) or - C(json) and I(return_output) is C(true). - type: dict -diff: - description: - - The configuration differences between the previous and new - configurations. The value is a dict that contains a single key named - "prepared". Value associated with that key is a single multi-line string - in "diff" format. - returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and - I(return_output) is C(true). - type: dict -diff_lines: - description: - - The configuration differences between the previous and new - configurations. The value is a list of single-line strings in "diff" - format. - returned: when I(load) or I(rollback) is specified, I(diff) is C(true), and - I(return_output) is C(true). - type: list -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -file: - description: - - The value of the I(src) option. - returned: when I(load) is not C(none) and I(src) is not C(none) - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -''' - - -# Standard library imports -import time - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - # Choices which are defined in the common module. - config_format_choices = juniper_junos_common.CONFIG_FORMAT_CHOICES - config_database_choices = [None] + \ - juniper_junos_common.CONFIG_DATABASE_CHOICES - config_action_choices = [None] + juniper_junos_common.CONFIG_ACTION_CHOICES - config_mode_choices = juniper_junos_common.CONFIG_MODE_CHOICES - config_model_choices = juniper_junos_common.CONFIG_MODEL_CHOICES - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - ignore_warning=dict(required=False, - type='list', - default=None), - config_mode=dict(choices=config_mode_choices, - type='str', - required=False, - aliases=['config_access', 'edit_mode', - 'edit_access'], - default='exclusive'), - rollback=dict(type='str', - required=False, - default=None), - load=dict(choices=config_action_choices, - type='str', - required=False, - default=None), - src=dict(type='path', - required=False, - aliases=['source', 'file'], - default=None), - lines=dict(type='list', - required=False, - default=None), - template=dict(type='path', - required=False, - aliases=['template_path'], - default=None), - vars=dict(type='dict', - required=False, - aliases=['template_vars'], - default=None), - url=dict(type='str', - required=False, - default=None), - format=dict(choices=config_format_choices, - type='str', - required=False, - default=None), - model=dict(required=False, - choices=config_model_choices, - type='str', - default=None), - remove_ns=dict(required=False, - type='bool', - default=None), - namespace=dict(required=False, - type='str', - default=None), - check=dict(required=False, - type='bool', - aliases=['check_commit', 'commit_check'], - default=None), - diff=dict(required=False, - type='bool', - aliases=['compare', 'diffs'], - default=None), - diffs_file=dict(type='path', - required=False, - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir', 'savedir', - 'save_dir'], - default=None), - return_output=dict(required=False, - type='bool', - default=True), - retrieve=dict(choices=config_database_choices, - type='str', - required=False, - default=None), - options=dict(type='dict', - required=False, - default={}), - filter=dict(required=False, - type='str', - aliases=['filter_xml'], - default=None), - dest=dict(type='path', - required=False, - aliases=['destination'], - default=None), - commit=dict(required=False, - type='bool', - default=None), - commit_empty_changes=dict(required=False, - type='bool', - default=False), - confirmed=dict(required=False, - type='int', - aliases=['confirm'], - default=None), - comment=dict(required=False, - type='str', - default=None), - check_commit_wait=dict(required=False, - type='int', - default=None) - ), - # Mutually exclusive options. - mutually_exclusive=[['load', 'rollback'], - ['src', 'lines', 'template', 'url'], - ['diffs_file', 'dest_dir'], - ['dest', 'dest_dir']], - # Required together options. - required_together=[['template', 'vars']], - # Check mode is implemented. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - # Do additional argument verification. - - # Parse ignore_warning value - ignore_warning = junos_module.parse_ignore_warning_option() - - # Straight from params - config_mode = junos_module.params.get('config_mode') - - # Parse rollback value - rollback = junos_module.parse_rollback_option() - - # Straight from params - load = junos_module.params.get('load') - src = junos_module.params.get('src') - lines = junos_module.params.get('lines') - template = junos_module.params.get('template') - vars = junos_module.params.get('vars') - url = junos_module.params.get('url') - format = junos_module.params.get('format') - check = junos_module.params.get('check') - diff = junos_module.params.get('diff') - diffs_file = junos_module.params.get('diffs_file') - dest_dir = junos_module.params.get('dest_dir') - return_output = junos_module.params.get('return_output') - retrieve = junos_module.params.get('retrieve') - options = junos_module.params.get('options') - filter = junos_module.params.get('filter') - dest = junos_module.params.get('dest') - commit = junos_module.params.get('commit') - commit_empty_changes = junos_module.params.get('commit_empty_changes') - confirmed = junos_module.params.get('confirmed') - comment = junos_module.params.get('comment') - check_commit_wait = junos_module.params.get('check_commit_wait') - model = junos_module.params.get('model') - remove_ns = junos_module.params.get('remove_ns') - namespace = junos_module.params.get('namespace') - - - # If retrieve is set and load and rollback are not set, then - # check, diff, and commit default to False. - if retrieve is not None and load is None and rollback is None: - if diff is None: - diff = False - if check is None: - check = False - if commit is None: - commit = False - # Otherwise, diff, check, and commit default to True. - else: - if diff is None: - diff = True - if check is None: - check = True - if commit is None: - commit = True - - # If load is not None, must have one of src, template, url, lines - if load is not None: - for option in ['src', 'lines', 'template', 'url']: - if junos_module.params.get(option) is not None: - break - # for/else only executed if we didn't break out of the loop. - else: - junos_module.fail_json(msg="The load option (%s) is specified, " - "but none of 'src', 'lines', " - "'template', or 'url' are specified. " - "Must specify one of the 'src', " - "'lines', 'template', or 'url' options." - % (load)) - - # format is valid if retrieve is not None or load is not None. - if format is not None: - if load is None and retrieve is None: - junos_module.fail_json(msg="The format option (%s) is specified, " - "but neither 'load' or 'retrieve' are " - "specified. Must specify one of " - "'load' or 'retrieve' options." - % (format)) - - # dest_dir is valid if retrieve is not None or diff is True. - if dest_dir is not None: - if retrieve is None and diff is False: - junos_module.fail_json(msg="The dest_dir option (%s) is specified," - " but neither 'retrieve' or 'diff' " - "are specified. Must specify one of " - "'retrieve' or 'diff' options." - % (dest_dir)) - - # dest is valid if retrieve is not None - if dest is not None: - if retrieve is None: - junos_module.fail_json(msg="The dest option (%s) is specified," - " but 'retrieve' is not specified. " - "Must specify the 'retrieve' option." - % (dest)) - - # diffs_file is valid if diff is True - if diffs_file is not None: - if diff is False: - junos_module.fail_json(msg="The diffs_file option (%s) is " - "specified, but 'diff' is false." - % (diffs_file)) - - # commit_empty_changes is valid if commit is True - if commit_empty_changes is True: - if commit is False: - junos_module.fail_json(msg="The commit_empty_changes option " - "is true, but 'commit' is false. " - "The commit_empty_changes option " - "may only be specified when " - "'commit' is true.") - - # comment is valid if commit is True - if comment is not None: - if commit is False: - junos_module.fail_json(msg="The comment option (%s) is " - "specified, but 'commit' is false." - % (comment)) - - # confirmed is valid if commit is True - if confirmed is not None: - if commit is False: - junos_module.fail_json(msg="The confirmed option (%s) is " - "specified, but 'commit' is false." - % (confirmed)) - # Must be greater >= 1. - if confirmed < 1: - junos_module.fail_json(msg="The confirmed option (%s) must have a " - "positive integer value." % (confirmed)) - - # check_commit_wait is valid if check is True and commit is True - if check_commit_wait is not None: - if commit is False: - junos_module.fail_json(msg="The check_commit_wait option (%s) is " - "specified, but 'commit' is false." - % (check_commit_wait)) - if check is False: - junos_module.fail_json(msg="The check_commit_wait option (%s) is " - "specified, but 'check' is false." - % (check_commit_wait)) - # Must be greater >= 1. - if check_commit_wait < 1: - junos_module.fail_json(msg="The check_commit_wait option (%s) " - "must have a positive integer value." % - (check_commit_wait)) - - # Initialize the results. Assume failure until we know it's success. - results = {'msg': 'Configuration has been: ', - 'changed': False, - 'failed': True} - - junos_module.logger.debug("Step 1 - Open a candidate configuration " - "database.") - junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning) - results['msg'] += 'opened' - - junos_module.logger.debug("Step 2 - Load configuration data into the " - "candidate configuration database.") - if rollback is not None: - junos_module.rollback_configuration(id=rollback) - # Assume configuration changed in case we don't perform a diff later. - # If diff is set, we'll check for actual differences later. - results['changed'] = True - results['msg'] += ', rolled back' - elif load is not None: - if src is not None: - junos_module.load_configuration(action=load, - src=src, - ignore_warning=ignore_warning, - format=format) - results['file'] = src - elif lines is not None: - junos_module.load_configuration(action=load, - lines=lines, - ignore_warning=ignore_warning, - format=format) - elif template is not None: - junos_module.load_configuration(action=load, - template=template, - vars=vars, - ignore_warning=ignore_warning, - format=format) - elif url is not None: - junos_module.load_configuration(action=load, - url=url, - ignore_warning=ignore_warning, - format=format) - else: - junos_module.fail_json(msg="The load option was set to: %s, but " - "no 'src', 'lines', 'template', or " - "'url' option was set." % - (load)) - # Assume configuration changed in case we don't perform a diff later. - # If diff is set, we'll check for actual differences later. - results['changed'] = True - results['msg'] += ', loaded' - - junos_module.logger.debug("Step 3 - Check the validity of the candidate " - "configuration database.") - if check is True: - junos_module.check_configuration() - results['msg'] += ', checked' - - junos_module.logger.debug("Step 4 - Determine differences between the " - "candidate and committed configuration " - "databases.") - if diff is True or junos_module._diff: - diff = junos_module.diff_configuration(ignore_warning) - if diff is not None: - results['changed'] = True - if return_output is True or junos_module._diff: - results['diff'] = {'prepared': diff} - results['diff_lines'] = diff.splitlines() - # Save the diff output - junos_module.save_text_output('diff', 'diff', diff) - else: - results['changed'] = False - results['msg'] += ', diffed' - - junos_module.logger.debug("Step 5 - Retrieve the configuration database " - "from the Junos device.") - if retrieve is not None: - if format is None: - format = 'text' - (config, config_parsed) = junos_module.get_configuration( - database=retrieve, - format=format, - options=options, - filter=filter, - model=model, - namespace=namespace, - remove_ns=remove_ns) - if return_output is True: - if config is not None: - results['config'] = config - results['config_lines'] = config.splitlines() - if config_parsed is not None: - results['config_parsed'] = config_parsed - # Save the output - format_extension = 'config' if format == 'text' else format - junos_module.save_text_output('config', format_extension, config) - results['msg'] += ', retrieved' - - junos_module.logger.debug("Step 6 - Commit the configuration changes.") - if commit is True and not junos_module.check_mode: - # Perform the commit if: - # 1) commit_empty_changes is True - # 2) Neither rollback or load is set. i.e. confirming a previous commit - # 3) rollback or load is set, and there were actual changes. - if (commit_empty_changes is True or - (rollback is None and load is None) or - ((rollback is not None or load is not None) and - results['changed'] is True)): - if check_commit_wait is not None: - time.sleep(check_commit_wait) - junos_module.commit_configuration(ignore_warning=ignore_warning, - comment=comment, - confirmed=confirmed) - results['msg'] += ', committed' - else: - junos_module.logger.debug("Skipping commit. Nothing changed.") - - junos_module.logger.debug("Step 7 - Close the candidate configuration " - "database.") - junos_module.close_configuration() - results['msg'] += ', closed.' - - # If we made it this far, everything was successful. - results['failed'] = False - - # Return response. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_facts.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_facts.py deleted file mode 100644 index 4383b4df..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_facts.py +++ /dev/null @@ -1,342 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_facts -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Retrieve facts from a Junos device -description: - - Retrieve facts from a Junos device using the - U(PyEZ fact gathering system|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html). - - Also returns the committed configuration of the Junos device if the - I(config_format) option has a value other than C(none). -options: - config_format: - description: - - The format of the configuration returned. The specified format must be - supported by the target Junos device. - required: false - default: none - choices: - - none - - xml - - set - - text - - json - savedir: - description: - - A path to a directory, on the Ansible control machine, where facts - will be stored in a JSON file. - - The resulting JSON file is saved in - I(savedir)C(/)I(hostname)C(-facts.json). - - The I(savedir) directory is the value of the I(savedir) option. - - The I(hostname)C(-facts.json) filename begins with the value of the - C(hostname) fact returned from the Junos device, which might be - different than the value of the I(host) option passed to the module. - - If the value of the I(savedir) option is C(none), the default, then - facts are NOT saved to a file. - required: false - default: none - type: path -''' - -EXAMPLES = ''' ---- -- name: Gather facts from Junos devices - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - tasks: - - name: Gather Junos facts with no configuration - juniper_junos_facts: - -# Print a fact - -# Using config_format option - -# Print the config - -# Using savedir option - -# Print the saved JSON file -''' - -RETURN = ''' -ansible_facts.junos: - description: - - Facts collected from the Junos device. This dictionary contains the - keys listed in the I(contains) section of this documentation PLUS all - of the keys returned from PyEZ's fact gathering system. See - U(PyEZ facts|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.facts.html) - for a complete list of these keys and their meaning. - returned: success - type: complex - contains: - config: - description: - - The device's committed configuration, in the format specified by - I(config_format), as a single multi-line string. - returned: when I(config_format) is not C(none). - type: str - has_2RE: - description: - - Indicates if the device has more than one Routing Engine installed. - Because Ansible does not allow keys to begin with a number, this fact - is returned in place of PyEZ's C(2RE) fact. - returned: success - type: bool - re_name: - description: - - The name of the current Routing Engine to which Ansible is connected. - returned: success - type: str - master_state: - description: - - The mastership state of the Routing Engine to which Ansible is - connected. C(true) if the RE is the master Routing Engine. C(false) - if the RE is not the master Routing Engine. - returned: success - type: bool -changed: - description: - - Indicates if the device's state has changed. Since this module does not - change the operational or configuration state of the device, the value is - always set to C(false). - returned: success - type: bool - sample: false -facts: - description: - - Returned for backwards compatibility. Returns the same keys and values - which are returned under I(ansible_facts.junos). - returned: success - type: dict -failed: - description: - - Indicates if the task failed. - returned: always - type: bool - sample: false -''' - -# Standard library imports -import json -import os.path - - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common -from ansible.module_utils._text import to_bytes - -def get_facts_dict(junos_module): - """Retreive PyEZ facts and convert to a standard dict w/o custom types. - - Ansible >= 2.0 doesn't like custom objects in a modules return value. - Because PyEZ facts are a custom object rather than a true dict they must be - converted to a standard dict. Since facts are read-only, we must begin by - copying facts into a dict. Since PyEZ facts are "on-demand", the - junos_module.dev instance must be an open PyEZ Device instance ojbect - before this function is called. - - Args: - junos_module: An instance of a JuniperJunosModule. - - Returns: - A dict containing the device facts. - """ - # Retrieve all PyEZ-supported facts and copy to a standard dict. - facts = dict(junos_module.dev.facts) - # Add two useful facts that are implement as PyEZ Device attributes. - facts['re_name'] = junos_module.dev.re_name - facts['master_state'] = junos_module.dev.master - # Ansible doesn't allow keys starting with numbers. - # Replace the '2RE' key with the 'has_2RE' key. - if '2RE' in facts: - facts['has_2RE'] = facts['2RE'] - del facts['2RE'] - # The value of the 'version_info' key is a custom junos.version_info - # object. Convert this value to a dict. - if 'version_info' in facts and facts['version_info'] is not None: - facts['version_info'] = dict(facts['version_info']) - # The values of the ['junos_info'][re_name]['object'] keys are - # custom junos.version_info objects. Convert all of these to dicts. - if 'junos_info' in facts and facts['junos_info'] is not None: - for key in facts['junos_info']: - facts['junos_info'][key]['object'] = dict( - facts['junos_info'][key]['object']) - return facts - - -def save_facts(junos_module, facts): - """If the savedir option was specified, save the facts into a JSON file. - - If the savedir option was specified, save the facts into a JSON file named - savedir/hostname-facts.json. The filename begins with the value of the - hostname fact returned from the Junos device, which might be different than - the value of the host option passed to the module. - - Args: - junos_module: An instance of a JuniperJunosModule. - facts: The facts dict returned by get_facts_dict(). - - Raises: - IOError: Calls junos_module.fail_json if unable to open the facts - file for writing. - """ - if junos_module.params.get('savedir') is not None: - save_dir = junos_module.params.get('savedir') - file_name = '%s-facts.json' % (facts['hostname']) - file_path = os.path.normpath(os.path.join(save_dir, file_name)) - junos_module.logger.debug("Saving facts to: %s.", file_path) - try: - # TODO: Verify does this work with Python3 - with open(file_path, 'w') as fact_file: - json.dump(facts, fact_file) - junos_module.logger.debug("Facts saved to: %s.", file_path) - except IOError: - junos_module.fail_json(msg="Unable to save facts. Failed to open " - "the %s file." % (file_path)) - - -def save_inventory(junos_module, inventory): - """If the savedir option was specified, save the XML inventory. - - If the savedir option was specified, save the inventory XML output into - an XML file named savedir/hostname-inventory.xml. The filename begins with - the value of the hostname fact returned from the Junos device, which might - be different than the value of the host option passed to the module. - - Args: - junos_module: An instance of a JuniperJunosModule. - inventory: The XML string of inventory to save. - - Raises: - IOError: Calls junos_module.fail_json if unable to open the inventory - file for writing. - """ - if junos_module.params.get('savedir') is not None: - save_dir = junos_module.params.get('savedir') - file_name = '%s-inventory.xml' % (junos_module.dev.facts['hostname']) - file_path = os.path.normpath(os.path.join(save_dir, file_name)) - junos_module.logger.debug("Saving inventory to: %s.", file_path) - try: - with open(file_path, 'wb') as fact_file: - fact_file.write(to_bytes(inventory, encoding='utf-8')) - junos_module.logger.debug("Inventory saved to: %s.", file_path) - except IOError: - junos_module.fail_json(msg="Unable to save inventory. Failed to " - "open the %s file." % (file_path)) - - -def main(): - config_format_choices = [None] - config_format_choices += juniper_junos_common.CONFIG_FORMAT_CHOICES - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - config_format=dict(choices=config_format_choices, - required=False, - default=None), - savedir=dict(type='path', required=False, default=None), - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - - junos_module.logger.debug("Gathering facts.") - # Get the facts dictionary from the device. - facts = get_facts_dict(junos_module) - junos_module.logger.debug("Facts gathered.") - - if junos_module.params.get('savedir') is not None: - # Save the facts. - save_facts(junos_module, facts) - - # Get and save the inventory - try: - junos_module.logger.debug("Gathering inventory.") - inventory = junos_module.dev.rpc.get_chassis_inventory() - junos_module.logger.debug("Inventory gathered.") - save_inventory(junos_module, - junos_module.etree.tostring(inventory, - pretty_print=True)) - except junos_module.pyez_exception.RpcError as ex: - junos_module.fail_json(msg='Unable to retrieve hardware ' - 'inventory: %s' % (str(ex))) - - config_format = junos_module.params.get('config_format') - if config_format is not None: - (config, config_parsed) = junos_module.get_configuration( - format=config_format) - if config is not None: - facts.update({'config': config}) - # Need to wait until the ordering issues are figured out before - # using config_parsed. - # if config_parsed is not None: - # facts.update({'config_parsed': config_parsed}) - - # Return response. - junos_module.exit_json( - changed=False, - failed=False, - ansible_facts={'junos': facts}, - facts=facts) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_jsnapy.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_jsnapy.py deleted file mode 100644 index 0d7a587a..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_jsnapy.py +++ /dev/null @@ -1,356 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Roslan Zaki -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_jsnapy -author: - - Juniper Networks - - Roslan Zaki - - Damien Garros - - Stacy Smith (@stacywsmith)" -short_description: Execute JSNAPy tests on a Junos device -description: - - Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. - JSNAPy is documented on U(Github|https://github.com/Juniper/jsnapy) and - this - U(Day One Book|https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) - - This module only reports C(failed) if the module encounters an error and - fails to execute the JSNAPy tests. If does NOT report C(failed) if one or - more of the JSNAPy tests fail. To check the test results, register the - module's response and use the assert module to verify the expected result - in the response. (See :ref:`juniper_junos_jsnapy-examples-label`.) - - A callback plugin which formats and prints JSNAPy test results for human - consumption is also available. This callback plugin is enabled by adding - C(callback_whitelist = jsnapy) to the Ansible configuration file. -options: - action: - description: - - The JSNAPy action to perform. - required: true - default: none - type: str - choices: - - check - - snapcheck - - snap_pre - - snap_post - config_file: - description: - - The filename of a JSNAPy configuration file (in YAML format). The - I(test_files) option and the I(config_file) option are mutually - exclusive. Either the I(test_files) option or the I(config_file) - option is required. - required: false - type: path - default: none - dir: - description: - - The path to the directory containing the JSNAPy test file(s) specified - by the I(test_files) option or the JSNAPy configuration file specified - by the I(config_file) option. - required: false - type: path - default: /etc/jsnapy/testfiles - aliases: - - directory - test_files: - description: - - The filename of file(s) in the I(dir) directory. Each file contains - JSNAPy test case definitions. The I(test_files) option and the - I(config_file) option are mutually exclusive. Either the I(test_files) - option or the I(config_file) option is required. - required: false - type: list of path - default: none -''' - - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_jsnapy - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: JUNOS Post Checklist - juniper_junos_jsnapy: - action: "snap_post" - config_file: "first_test.yml" - logfile: "migration_post.log" - register: test1 - - name: Verify all JSNAPy tests passed - assert: - that: - - "test1.passPercentage == 100" - - name: Print the full test response - debug: - var: test1 - - - name: Test based on a test_file directly - juniper_junos_jsnapy: - action: "snapcheck" - test_files: "tests/test_junos_interface.yaml" - register: test2 - - name: Verify all JSNAPy tests passed - assert: - that: - - "test2.passPercentage == 100" - - name: Print the full test response - debug: - var: test2 - - - name: "Collect Pre Snapshot" - juniper_junos_jsnapy: - action: "snap_pre" - test_files: "tests/test_loopback.yml" - - - name: "Collect Post Snapshot" - juniper_junos_jsnapy: - action: "snap_post" - test_files: "tests/test_loopback.yml" - - - name: "Check after Pre and Post Snapshots" - juniper_junos_jsnapy: - action: "check" - test_files: "tests/test_loopback.yml" - register: test3 - - name: Verify all JSNAPy tests passed - assert: - that: - - "test3.|succeeded" - - "test3.passPercentage == 100" - - name: Print the full test response - debug: - var: test3 -''' - -RETURN = ''' -action: - description: - - The JSNAPy action performed as specified by the I(action) option. - returned: success - type: str -changed: - description: - - Indicates if the device's state has changed. Since this module doesn't - change the operational or configuration state of the device, the value - is always set to C(false). - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -# final_result: -msg: - description: - - A human-readable message indicating the result of the JSNAPy tests. - returned: always - type: str -# total_passed: -# total_failed: -''' - -# Standard Library imports -import os.path - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - action=dict(required=True, - choices=JSNAPY_ACTION_CHOICES, - type='str', - default=None), - test_files=dict(required=False, - type='list', - default=None), - config_file=dict(required=False, - type='path', - default=None), - dir=dict(required=False, - type='path', - aliases=['directory'], - default='/etc/jsnapy/testfiles')), - # Mutually exclusive options. - mutually_exclusive=[['test_files', 'config_file']], - # One of test_files or config_file is required. - required_one_of=[['test_files', 'config_file']], - supports_check_mode=True, - min_jsnapy_version=juniper_junos_common.MIN_JSNAPY_VERSION, - ) - - # Straight from params - action = junos_module.params.get('action') - test_files = junos_module.params.get('test_files') - config_file = junos_module.params.get('config_file') - dir = junos_module.params.get('dir') - - # Initialize the results. Assume failure until we know otherwise. - results = {'msg': '', - 'action': action, - 'changed': False, - 'failed': True} - - if config_file is not None: - junos_module.logger.debug('Checking config file: %s.', config_file) - config_file_path = os.path.abspath(config_file) - config_dir_file_path = os.path.abspath(os.path.join(dir, config_file)) - if os.path.isfile(config_file_path): - data = config_file_path - elif os.path.isfile(config_dir_file_path): - data = config_dir_file_path - else: - junos_module.fail_json(msg="Unable to locate the %s config file " - "at %s or %s." % (config_file, - config_file_path, - config_dir_file_path)) - elif test_files is not None and len(test_files) > 0: - data = {'tests': []} - for test_file in test_files: - junos_module.logger.debug('Checking test file: %s.', test_file) - test_file_path = os.path.abspath(test_file) - test_dir_file_path = os.path.abspath(os.path.join(dir, test_file)) - if os.path.isfile(test_file_path): - data['tests'].append(test_file_path) - elif os.path.isfile(test_dir_file_path): - data['tests'].append(test_dir_file_path) - else: - junos_module.fail_json(msg="Unable to locate the %s test file " - "at %s or %s." % - (test_file, - test_file_path, - test_dir_file_path)) - else: - junos_module.fail_json(msg="No config_file or test_files specified.") - - try: - junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') - jsa = junos_module.jsnapy.SnapAdmin() - junos_module.logger.debug('Executing %s action.', action) - if action == 'check': - responses = jsa.check(data=data, - dev=junos_module.dev, - pre_file='PRE', - post_file='POST') - elif action == 'snapcheck': - responses = jsa.snapcheck(data=data, - dev=junos_module.dev) - elif action == 'snap_pre': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='PRE') - elif action == 'snap_post': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='POST') - else: - junos_module.fail_json(msg="Unexpected action: %s." % (action)) - junos_module.logger.debug('The %s action executed successfully.', - action) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - junos_module.fail_json(msg="Error communicating with the device: %s" % - (str(ex))) - except Exception as ex: - junos_module.fail_json(msg="Uncaught exception - please report: %s" % - (str(ex))) - - if isinstance(responses, list) and len(responses) == 1: - if action in ('snapcheck', 'check'): - for response in responses: - results['device'] = response.device - results['router'] = response.device - results['final_result'] = response.result - results['total_passed'] = response.no_passed - results['total_failed'] = response.no_failed - results['test_results'] = response.test_results - total_tests = int(response.no_passed) + int(response.no_failed) - results['total_tests'] = total_tests - pass_percentage = 0 - if total_tests > 0: - pass_percentage = ((int(response.no_passed) * 100) // - total_tests) - results['passPercentage'] = pass_percentage - results['pass_percentage'] = pass_percentage - if results['final_result'] == 'Failed': - results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) - else: - results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) - elif action in ('snap_pre', 'snap_post'): - results['msg'] = "The %s action successfully executed." % (action) - else: - junos_module.fail_json(msg="Unexpected JSNAPy responses. Type: %s." - "Responses: %s" % - (type(responses), str(responses))) - - # If we made it this far, it's success. - results['failed'] = False - - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_ping.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_ping.py deleted file mode 100644 index 872b6826..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_ping.py +++ /dev/null @@ -1,502 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Damien Garros -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_ping -author: Juniper Networks - Stacy Smith (@stacywsmith) -short_description: Execute ping from a Junos device -description: - - Execute the ping command from a Junos device to a specified destination in - order to test network reachability from the Junos device . -options: - acceptable_percent_loss: - description: - - Maximum percentage of packets that may be lost and still consider the - task not to have failed. - required: false - default: 0 - type: int - aliases: - - acceptable_packet_loss - count: - description: - - Number of packets to send. - required: false - default: 5 - type: int - dest: - description: - - The IP address, or hostname if DNS is configured on the Junos device, - used as the destination of the ping. - required: true - default: none - type: str - aliases: - - dest_ip - - dest_host - - destination - - destination_ip - - destination_host - do_not_fragment: - description: - - Set Do Not Fragment bit on ping packets. - required: false - default: false - type: bool - interface: - description: - - The source interface from which the the ping is sent. If not - specified, the default Junos algorithm for determining the source - interface is used. - required: false - default: none - type: str - rapid: - description: - - Send ping requests rapidly - required: false - default: true - type: bool - routing_instance: - description: - - Name of the source routing instance from which the ping is - originated. If not specified, the default routing instance is used. - required: false - default: none - type: str - size: - description: - - The size of the ICMP payload of the ping. - - Total size of the IP packet is I(size) + the 20 byte IP header + - the 8 byte ICMP header. Therefore, I(size) of C(1472) generates an IP - packet of size 1500. - required: false - default: none (default size for device) - type: int - source: - description: - - The IP address, or hostname if DNS is configured on the Junos device, - used as the source address of the ping. If not specified, the Junos - default algorithm for determining the source address is used. - required: false - default: none - type: str - aliases: - - source_ip - - source_host - - src - - src_ip - - src_host - ttl: - description: - - Maximum number of IP routers (hops) allowed between source and - destination. - required: false - default: none (default ttl for device) - type: int -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_ping - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. - juniper_junos_ping: - dest: "192.68.1.1" - - - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - acceptable_percent_loss: 50 - register: response - - name: Print all keys in the response. - debug: - var: response - - - name: Ping 192.68.1.1. Send 20 packets. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - count: 20 - register: response - - name: Print packet sent from the response. - debug: - var: response.packets_sent - - - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - count: 10 - rapid: false - register: response - - name: Print the average round-trip-time from the response. - debug: - var: response.rtt_average - - - name: Ping www.juniper.net with ttl 15. Register response. - juniper_junos_ping: - dest: "www.juniper.net" - ttl: 15 - register: response - - name: Print the packet_loss percentage from the response. - debug: - var: response.packet_loss - - - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - size: 1472 - register: response - - name: Print the packets_received from the response. - debug: - var: response.packets_received - - - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - do_not_fragment: true - register: response - - name: Print the maximum round-trip-time from the response. - debug: - var: response.rtt_maximum - - - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. - juniper_junos_ping: - dest: "192.68.1.1" - source: "192.68.1.2" - register: response - - name: Print the source from the response. - debug: - var: response.source - - - name: Ping 192.168.1.1 from the red routing-instance. - juniper_junos_ping: - dest: "192.168.1.1" - routing_instance: "red" - - - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface - juniper_junos_ping: - dest: "224.0.0.1" - interface: "ge-0/0/0.0" -''' - -RETURN = ''' -acceptable_percent_loss: - description: - - The acceptable packet loss (as a percentage) for this task as specified - by the I(acceptable_percent_loss) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -changed: - description: - - Indicates if the device's state has changed. Since this module - doesn't change the operational or configuration state of the - device, the value is always set to C(false). - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: bool -count: - description: - - The number of pings sent, as specified by the I(count) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -do_not_fragment: - description: - - Whether or not the do not fragment bit was set on the pings sent, as - specified by the I(do_not_fragment) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -host: - description: - - The destination IP/host of the pings sent as specified by the I(dest) - option. - - Keys I(dest) and I(dest_ip) are also returned for backwards - compatibility. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -interface: - description: - - The source interface of the pings sent as specified by the - I(interface) option. - returned: when ping successfully executed and the I(interface) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -packet_loss: - description: - - The percentage of packets lost. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -packets_sent: - description: - - The number of packets sent. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -packets_received: - description: - - The number of packets received. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -rapid: - description: - - Whether or not the pings were sent rapidly, as specified by the - I(rapid) option. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: bool -routing_instance: - description: - - The routing-instance from which the pings were sent as specified by - the I(routing_instance) option. - returned: when ping successfully executed and the I(routing_instance) - option was specified, even if the I(acceptable_percent_loss) was - exceeded. - type: str -rtt_average: - description: - - The average round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -rtt_maximum: - description: - - The maximum round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -rtt_minimum: - description: - - The minimum round-trip-time, in microseconds, of all ping responses - received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -rtt_stddev: - description: - - The standard deviation of round-trip-time, in microseconds, of all ping - responses received. - returned: when ping successfully executed, and I(packet_loss) < 100%. - type: str -size: - description: - - The size in bytes of the ICMP payload on the pings sent as specified - by the I(size) option. - - Total size of the IP packet is I(size) + the 20 byte IP header + the 8 - byte ICMP header. Therefore, I(size) of 1472 generates an IP packet of - size 1500. - returned: when ping successfully executed and the I(size) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -source: - description: - - The source IP/host of the pings sent as specified by the I(source) - option. - - Key I(source_ip) is also returned for backwards compatibility. - returned: when ping successfully executed and the I(source) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -timeout: - description: - - The number of seconds to wait for a response from the ping RPC. - returned: when ping successfully executed, even if the - I(acceptable_percent_loss) was exceeded. - type: str -ttl: - description: - - The time-to-live set on the pings sent as specified by the - I(ttl) option. - returned: when ping successfully executed and the I(ttl) option was - specified, even if the I(acceptable_percent_loss) was exceeded. - type: str -warnings: - description: - - A list of warning strings, if any, produced from the ping. - returned: when warnings are present - type: list -''' - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - # The argument spec for the module. - argument_spec = dict( - dest=dict(type='str', - required=True, - aliases=['dest_ip', 'dest_host', 'destination', - 'destination_ip', 'destination_host'], - default=None), - acceptable_percent_loss=dict(type='int', - required=False, - aliases=['acceptable_packet_loss'], - default=0), - ) - - # The portion of the argument spec that's specifically a parameter - # to the ping RPC. - ping_argument_spec = dict( - count=dict(type='int', - required=False, - default=5), - rapid=dict(type='bool', - required=False, - default=True), - ttl=dict(type='int', - required=False, - default=None), - size=dict(type='int', - required=False, - default=None), - do_not_fragment=dict(type='bool', - required=False, - default=False), - source=dict(type='str', - required=False, - aliases=['source_ip', 'source_host', 'src', - 'src_ip', 'src_host'], - default=None), - interface=dict(type='str', - required=False, - default=None), - routing_instance=dict(type='str', - required=False, - default=None), - ) - - # Add the ping RPC parameter argument spec fo the full argument_spec. - argument_spec.update(ping_argument_spec) - - argument_spec_keys = list(argument_spec.keys()) - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=argument_spec, - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. - supports_check_mode=True - ) - - # We're going to be using params a lot - params = junos_module.params - - # acceptable packet loss is a percentage. Check to make sure it's between - # 0 and 100 inclusive - if (params['acceptable_percent_loss'] > 100 or - params['acceptable_percent_loss'] < 0): - junos_module.fail_json(msg='The value of the acceptable_percent_loss' - 'option (%d) is a percentage and must have ' - 'a value between 0 and 100.' % - (params['acceptable_percent_loss'])) - - # All of the params keys which are also keys in ping_argument_spec are the - # ping_params. Omit None and False values because they don't need to be - # passed to the RPC. - ping_params = {'host': params.get('dest')} - for key in ping_argument_spec: - value = params.get(key) - # Convert int (but not bool) to str - if not isinstance(value, bool) and isinstance(value, int): - params[key] = str(params[key]) - value = params.get(key) - # None and False values are the default for the RPC and shouldn't be - # passed to the device. - if value is not None and value is not False: - ping_params.update({key: value}) - - # Set initial results values. Assume failure until we know it's success. - results = {'msg': '', 'changed': False, 'failed': True} - # Results should include all the ping params in argument_spec_keys. - for key in argument_spec_keys: - results[key] = params.get(key) - # Overwrite to be a string in the results - results['acceptable_percent_loss'] = str( - params.get('acceptable_percent_loss')) - # Add timeout to the response even though it's a connect parameter. - results['timeout'] = str(params.get('timeout')) - # Add aliases for backwards compatibility - results.update({'host': params.get('dest'), - 'dest_ip': params.get('dest'), - 'source_ip': params.get('source')}) - - # Execute the ping. - results = junos_module.ping( - ping_params, - acceptable_percent_loss=params['acceptable_percent_loss'], - results=results) - - # Return results. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_pmtud.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_pmtud.py deleted file mode 100644 index 6add19a1..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_pmtud.py +++ /dev/null @@ -1,408 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2017, Martin Komon -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_pmtud -author: - - Martin Komon (@mkomon) - - Juniper Networks - Stacy Smith (@stacywsmith) -short_description: Perform path MTU discovery from a Junos device to a - destination -description: - - Determine the maximum IP MTU supported along a path from a Junos device to - a user-specified destination by performing path MTU discovery (PMTUD) using - the ping command. The reported MTU will be between min_test_size and - I(max_size) where I(min_test_size) = (I(max_size) - I(max_range) + 1). - If the actual path MTU is greater than I(max_size), then I(max_size) will - be reported. If the actual path MTU is less than I(min_test_size), then a - failure will be reported. -options: - dest: - description: - - The IPv4 address, or hostname if DNS is configured on the Junos device, - used as the destination of the PMTUD. - required: true - default: none - type: str - aliases: - - dest_ip - - dest_host - - destination - - destination_ip - - destination_host - interface: - description: - - The source interface from which the the PMTUD is performed. If not - specified, the default Junos algorithm for determining the source - interface is used. - required: false - default: none - type: str - max_range: - description: - - The maximum range of MTU values, in bytes, which will be searched - when performing path MTU discovery. This value must be C(0) or - a power of 2 (2^n) between C(2) and C(65536). The minimum IPv4 MTU - value attempted when performing path MTU discovery is - I(min_test_size) = (I(max_size) - I(max_range) + 1) - required: false - default: 512 - type: int - max_size: - description: - - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU - discovery. - - The value returned for I(inet_mtu) will be no more - than this value even if the path actually supports a higher MTU. - - This value must be between 68 and 65496. - required: false - default: 1500 - type: int - routing_instance: - description: - - Name of the source routing instance from which the ping is - originated. - - If not specified, the default routing instance is used. - required: false - default: none - type: str - source: - description: - - The IPv4 address, or hostname if DNS is configured on the Junos device, - used as the source address of the PMTUD. If not specified, the Junos - default algorithm for determining the source address is used. - required: false - default: none - type: str - aliases: - - source_ip - - source_host - - src - - src_ip - - src_host -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_mtud - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Perform PMTUD to 192.68.1.1 with default parameters. - juniper_junos_pmtud: - dest: "192.68.1.1" - - - name: Perform PMTUD to 192.68.1.1. Register response. - juniper_junos_pmtud: - dest: "192.68.1.1" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. - juniper_junos_pmtud: - dest: "192.68.1.1" - max_size: 65496 - max_range: 65536 - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. - juniper_junos_pmtud: - dest: "192.68.1.1" - interface: "ge-0/0/0.0" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. - juniper_junos_pmtud: - dest: "192.68.1.1" - source: "192.168.1.2" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu - - - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. - juniper_junos_pmtud: - dest: "192.68.1.1" - routing_instance: "red" - register: response - - name: Print the discovered MTU. - debug: - var: response.inet_mtu -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's state has changed. Since this module - doesn't change the operational or configuration state of the - device, the value is always set to C(false). - returned: when PMTUD successfully executed. - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -host: - description: - - The destination IP/host of the PMTUD as specified by the I(dest) - option. - - Keys I(dest) and I(dest_ip) are also returned for backwards - compatibility. - returned: when PMTUD successfully executed. - type: str -inet_mtu: - description: - - The IPv4 path MTU size in bytes to the I(dest). This is the lesser of - I(max_size) and the actual path MTU to I(dest). If the actual path - MTU is less than I(min_test_size), then a failure is reported. Where - I(min_test_size) = (I(max_size) - I(max_range) + 1) - returned: when PMTUD successfully executed. - type: str -interface: - description: - - The source interface of the PMTUD as specified by the I(interface) - option. - returned: when the I(interface) option was specified. - type: str -routing_instance: - description: - - The routing-instance from which the PMTUD was performed as specified by - the I(routing_instance) option. - returned: when the I(routing_instance) option was specified. - type: str -source: - description: - - The source IP/host of the PMTUD as specified by the I(source) - option. - - Key I(source_ip) is also returned for backwards compatibility. - returned: when the I(source) option was specified. - type: str -warnings: - description: - - A list of warning strings, if any, produced from the ping. - returned: when warnings are present - type: list -''' - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - # Constants for MTU size - INET_MIN_MTU_SIZE = 68 # As prescribed by RFC 791, Section 3.2 - - # Fragmentation and Reassembly. - INET_MAX_MTU_SIZE = 65496 # Size of inet header's total length field is - # 16 bits. Therefore max inet packet size is 2^16 - # or 65536, but Junos only supports max IP size - # of 65496 for the ping command in order to - # accomodate a (potentially) maximum sized IP - # header. - - # Constants for the size of headers - INET_HEADER_SIZE = 20 - ICMP_HEADER_SIZE = 8 - INET_AND_ICMP_HEADER_SIZE = INET_HEADER_SIZE + ICMP_HEADER_SIZE - - # Choices for max_size - MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2 ** x, range(1, 17))) - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - dest=dict(type='str', - required=True, - aliases=['dest_ip', 'dest_host', 'destination', - 'destination_ip', 'destination_host'], - default=None), - max_size=dict(type='int', - required=False, - default=1500), - max_range=dict(type='int', - required=False, - choices=MAX_SIZE_CHOICES, - default=512), - source=dict(type='str', - required=False, - aliases=['source_ip', 'source_host', 'src', - 'src_ip', 'src_host'], - default=None), - interface=dict(type='str', - required=False, - default=None), - routing_instance=dict(type='str', - required=False, - default=None), - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. - supports_check_mode=True - ) - - # We're going to be using params a lot - params = junos_module.params - - # max_size must be between INET_MIN_MTU_SIZE and INET_MAX_MTU_SIZE - if (params['max_size'] < INET_MIN_MTU_SIZE or - params['max_size'] > INET_MAX_MTU_SIZE): - junos_module.fail_json(msg='The value of the max_size option(%d) ' - 'must be between %d and %d.' % - (params['max_size'], INET_MIN_MTU_SIZE, - INET_MAX_MTU_SIZE)) - - # Initialize ping parameters. - ping_params = {'host': params.get('dest'), - 'count': '3', - 'rapid': True, - 'inet': True, - 'do_not_fragment': True} - - # Add optional ping parameters - o_ping_params = {} - if params['source'] is not None: - o_ping_params['source'] = params['source'] - if params['interface'] is not None: - o_ping_params['interface'] = params['interface'] - if params['routing_instance'] is not None: - o_ping_params['routing_instance'] = params['routing_instance'] - ping_params.update(o_ping_params) - - # Set initial results values. Assume failure until we know it's success. - results = {'changed': False, - 'failed': True, - 'inet_mtu': 0, - 'host': params.get('dest')} - # Results should include all the o_ping_params. - for key in o_ping_params: - results[key] = ping_params.get(key) - # Add aliases for backwards compatibility - results.update({'dest': ping_params.get('host'), - 'dest_ip': ping_params.get('host'), - 'source_ip': ping_params.get('source')}) - - # Execute a minimally-sized ping just to verify basic connectivity. - junos_module.logger.debug("Verifying basic connectivity.") - ping_params['size'] = str(INET_MIN_MTU_SIZE - - INET_AND_ICMP_HEADER_SIZE) - results_for_minimal = dict(results) - results_for_minimal = junos_module.ping(ping_params, - acceptable_percent_loss=100, - results=results_for_minimal) - if int(results_for_minimal.get('packet_loss', 100)) == 100: - results['msg'] = "Basic connectivity to %s failed." % (results['host']) - junos_module.exit_json(**results) - - # Initialize test_size and step - test_size = params['max_size'] - step = params['max_range'] - min_test_size = test_size - (params['max_range'] - 1) - if min_test_size < INET_MIN_MTU_SIZE: - min_test_size = INET_MIN_MTU_SIZE - - while True: - if test_size < INET_MIN_MTU_SIZE: - test_size = INET_MIN_MTU_SIZE - if test_size > params['max_size']: - test_size = params['max_size'] - junos_module.logger.debug("Probing with size: %d", test_size) - step = step // 2 if step >= 2 else 0 - ping_params['size'] = str(test_size - INET_AND_ICMP_HEADER_SIZE) - current_results = dict(results) - current_results = junos_module.ping(ping_params, - acceptable_percent_loss=100, - results=current_results) - loss = int(current_results.get('packet_loss', 100)) - if loss < 100 and test_size == params['max_size']: - # ping success with max test_size, save and break - results['failed'] = False - results['inet_mtu'] = test_size - break - elif loss < 100: - # ping success, increase test_size - results['failed'] = False - results['inet_mtu'] = test_size - test_size += step - else: - # ping fail, lower size - test_size -= step - if step < 1: - break - - if results.get('inet_mtu', 0) == 0: - junos_module.fail_json(msg='The MTU of the path to %s is less than ' - 'the minimum tested size(%d). Try ' - 'decreasing max_size(%d) or increasing ' - 'max_range(%d).' % (results['host'], - min_test_size, - params['max_size'], - params['max_range']), - **results) - - # Return results. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_rpc.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_rpc.py deleted file mode 100644 index dcbcc18c..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_rpc.py +++ /dev/null @@ -1,624 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Nitin Kumar -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function -from six import iteritems - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_rpc -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Execute one or more NETCONF RPCs on a Junos device -description: - - Execute one or more NETCONF RPCs on a Junos device. - - Use the C(| display xml rpc) modifier to determine the equivalent RPC - name for a Junos CLI command. For example, - C(show version | display xml rpc) reveals the equivalent RPC name is - C(get-software-information). -options: - attrs: - description: - - The attributes and values to the RPCs specified by the - I(rpcs) option. The value of this option can either be a single - dictionary of keywords and values, or a list of dictionaries - containing keywords and values. - - There is a one-to-one correspondence between the elements in the - I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the - two lists must always contain the same number of elements. - required: false - default: none - type: dict or list of dict - aliases: - - attr - dest: - description: - - The path to a file, on the Ansible control machine, where the output of - the RPC will be saved. - - The file must be writeable. If the file already exists, it is - overwritten. - - When tasks are executed against more than one target host, - one process is forked for each target host. (Up to the maximum - specified by the forks configuration. See - U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) - for details.) This means that the value of this option must be unique - per target host. This is usually accomplished by including - C({{ inventory_hostname }}) in the I(dest) value. It is the user's - responsibility to ensure this value is unique per target host. - - For this reason, this option is deprecated. It is maintained for - backwards compatibility. Use the I(dest_dir) option in new playbooks. - The I(dest) and I(dest_dir) options are mutually exclusive. - required: false - default: None - type: path - aliases: - - destination - dest_dir: - description: - - The path to a directory, on the Ansible control machine, where - the output of the RPC will be saved. The output will be logged - to a file named C({{ inventory_hostname }}_)I(rpc).I(format) - in the I(dest_dir) directory. - - The destination file must be writeable. If the file already exists, - it is overwritten. It is the users responsibility to ensure a unique - I(dest_dir) value is provided for each execution of this module - within a playbook. - - The I(dest_dir) and I(dest) options are mutually exclusive. The - I(dest_dir) option is recommended for all new playbooks. - required: false - default: None - type: path - aliases: - - destination_dir - - destdir - filter: - description: - - This argument only applies if the I(rpcs) option contains a single - RPC with the value C(get-config). When used, this value specifies an - XML filter used to restrict the portions of the configuration which are - retrieved. See the PyEZ - U(get_config method|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) - for details on the value of this option. - required: false - default: none - type: str - aliases: - - filter_xml - formats: - description: - - The format of the reply for the RPCs specified by the - I(rpcs) option. - - The specified format(s) must be supported by the - target Junos device. - - The value of this option can either be a single - format, or a list of formats. If a single format is specified, it - applies to all RPCs specified by the I(rpcs) option. If a - list of formats are specified, there must be one value in the list for - each RPC specified by the I(rpcs) option. - required: false - default: xml - type: str or list of str - choices: - - text - - xml - - json - aliases: - - format - - display - - output - kwargs: - description: - - The keyword arguments and values to the RPCs specified by the - I(rpcs) option. The value of this option can either be a single - dictionary of keywords and values, or a list of dictionaries - containing keywords and values. - - There must be a one-to-one correspondence between the elements in the - I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the - two lists must always contain the same number of elements. For RPC - arguments which do not require a value, specify the value of True as - shown in the :ref:`juniper_junos_rpc-examples-label`. - required: false - default: none - type: dict or list of dict - aliases: - - kwarg - - args - - arg - return_output: - description: - - Indicates if the output of the RPC should be returned in the - module's response. You might want to set this option to C(false), - and set the I(dest_dir) option, if the RPC output is very large - and you only need to save the output rather than using it's content in - subsequent tasks/plays of your playbook. - required: false - default: true - type: bool - rpcs: - description: - - A list of one or more NETCONF RPCs to execute on the Junos device. - required: true - default: none - type: list - aliases: - - rpc -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_rpc - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Execute single get-software-information RPC. - juniper_junos_rpc: - rpcs: "get-software-information" - register: response - - name: Print the RPC's output as a single multi-line string. - debug: - var: response.stdout - -###### OLD EXAMPLES ########## -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - dest=get_interface_information.conf - register=junos - -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - kwargs="interface_name=em0" - format=xml/text/json - dest=get_interface_information.conf - register=junos - -# Example to fetch device configuration -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - dest=get_config.conf - -# Fetch configuration over console server connection using PyEZ >= 2.0 -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - port=7005 - mode='telnet' - rpc=get-config - dest=get_config.conf - -# Example to fetch device configuration -- name: Get Device Configuration for interface - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - filter_xml="" - dest=get_config.conf - register: junos - -# Example to fetch configuration in json for >=14.2 -# and use it with rpc_reply -- name: Get Device Configuration - hosts: all - collections: - - juniper.device - connection: local - gather_facts: no - tasks: - - name: Get interface information - junos_rpc: - host: "{{ inventory_hostname }}" - rpc: get-interface-information - kwargs: - interface_name: em0 - media: True - format: json - dest: get_interface_information.conf - register: junos - - - name: Print configuration - debug: msg="{{ junos.rpc_reply }}" -###### OLD EXAMPLES ########## -''' - -RETURN = ''' -attrs: - description: - - The RPC attributes and values from the list of dictionaries in the - I(attrs) option. This will be none if no attributes are applied to the - RPC. - returned: always - type: dict -changed: - description: - - Indicates if the device's state has changed. Since this module doesn't - change the operational or configuration state of the device, the value - is always set to C(false). - - You could use this module to execute an RPC which - changes the operational state of the the device. For example, - C(clear-ospf-neighbor-information). Beware, this module is unable to - detect this situation, and will still return a I(changed) value of - C(false) in this case. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. See the I(results) key for additional - details. - returned: always - type: bool -format: - description: - - The format of the RPC response from the list of formats in the I(formats) - option. - returned: always - type: str - choices: - - text - - xml - - json -kwargs: - description: - - The keyword arguments from the list of dictionaries in the I(kwargs) - option. This will be C(none) if no kwargs are applied to the RPC. - returned: always - type: dict -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -parsed_output: - description: - - The RPC reply from the Junos device parsed into a JSON datastructure. - For XML replies, the response is parsed into JSON using the - U(jxmlease|https://github.com/Juniper/jxmlease) - library. For JSON the response is parsed using the Python - U(json|https://docs.python.org/2/library/json.html) library. - - When Ansible converts the jxmlease or native Python data structure - into JSON, it does not guarantee that the order of dictionary/object keys - are maintained. - returned: when RPC executed successfully, I(return_output) is C(true), - and the RPC format is C(xml) or C(json). - type: dict -results: - description: - - The other keys are returned when a single RPC is specified for the - I(rpcs) option. When the value of the I(rpcs) option is a list - of RPCs, this key is returned instead. The value of this key is a - list of dictionaries. Each element in the list corresponds to the - RPCs in the I(rpcs) option. The keys for each element in the list - include all of the other keys listed. The I(failed) key indicates if the - individual RPC failed. In this case, there is also a top-level - I(failed) key. The top-level I(failed) key will have a value of C(false) - if ANY of the RPCs ran successfully. In this case, check the value - of the I(failed) key for each element in the I(results) list for the - results of individual RPCs. - returned: when the I(rpcs) option is a list value. - type: list of dict -rpc: - description: - - The RPC which was executed from the list of RPCs in the I(rpcs) option. - returned: always - type: str -stdout: - description: - - The RPC reply from the Junos device as a single multi-line string. - returned: when RPC executed successfully and I(return_output) is C(true). - type: str -stdout_lines: - description: - - The RPC reply from the Junos device as a list of single-line strings. - returned: when RPC executed successfully and I(return_output) is C(true). - type: list of str -''' - -import os.path - - -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - rpcs=dict(required=True, - type='list', - aliases=['rpc'], - default=None), - formats=dict(required=False, - type='list', - aliases=['format', 'display', 'output'], - default=None), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='str', - default=None), - attrs=dict(required=False, - type='str', - aliases=['attr'], - default=None), - filter=dict(required=False, - type='str', - aliases=['filter_xml'], - default=None), - dest=dict(required=False, - type='path', - aliases=['destination'], - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir'], - default=None), - return_output=dict(required=False, - type='bool', - default=True) - ), - # Since this module doesn't change the device's configuration, there is - # no additional work required to support check mode. It's inherently - # supported. Well, that's not completely true. It does depend on the - # RPC executed. See the I(changed) key in the RETURN documentation - # for more details. - supports_check_mode=True, - min_jxmlease_version=juniper_junos_common.MIN_JXMLEASE_VERSION, - ) - - # Check over rpcs - rpcs = junos_module.params.get('rpcs') - # Ansible allows users to specify a rpcs argument with no value. - if rpcs is None: - junos_module.fail_json(msg="The rpcs option must have a value.") - - # Check over formats - formats = junos_module.params.get('formats') - if formats is None: - # Default to xml format - formats = ['xml'] - valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES - # Check format values - for format in formats: - # Is it a valid format? - if format not in valid_formats: - junos_module.fail_json(msg="The value %s in formats is invalid. " - "Must be one of: %s" % - (format, ', '.join(map(str, - valid_formats)))) - # Correct number of format values? - if len(formats) != 1 and len(formats) != len(rpcs): - junos_module.fail_json(msg="The formats option must have a single " - "value, or one value per rpc. There " - "are %d rpcs and %d formats." % - (len(rpcs), len(formats))) - # Same format for all rpcs - elif len(formats) == 1 and len(rpcs) > 1: - formats = formats * len(rpcs) - - # Check over kwargs - kwstring = junos_module.params.get('kwargs') - kwargs = junos_module.parse_arg_to_list_of_dicts('kwargs', - kwstring, - allow_bool_values=True) - if kwargs is not None: - if len(kwargs) != len(rpcs): - junos_module.fail_json(msg="The kwargs option must have one value " - "per rpc. There are %d rpcs and %d " - "kwargs." % - (len(rpcs), len(kwargs))) - else: - kwargs = [None] * len(rpcs) - - # Check over attrs - attrstring = junos_module.params.get('attrs') - attrs = junos_module.parse_arg_to_list_of_dicts('attrs', - attrstring) - if attrs is not None: - if len(attrs) != len(rpcs): - junos_module.fail_json(msg="The attrs option must have one value" - "per rpc. There are %d rpcs and %d " - "attrs." % - (len(rpcs), len(attrs))) - else: - attrs = [None] * len(rpcs) - - # Check filter - if junos_module.params.get('filter') is not None: - if (len(rpcs) != 1 or (rpcs[0] != 'get-config' and - rpcs[0] != 'get_config')): - junos_module.fail_json(msg="The filter option is only valid " - "when the rpcs option value is a " - "single 'get-config' RPC.") - - results = list() - for (rpc_string, format, kwarg, attr) in zip(rpcs, formats, kwargs, attrs): - # Replace underscores with dashes in RPC name. - rpc_string = rpc_string.replace('_', '-') - # Set initial result values. Assume failure until we know it's success. - result = {'msg': '', - 'rpc': rpc_string, - 'format': format, - 'kwargs': kwarg, - 'attrs': attr, - 'changed': False, - 'failed': True} - - # Execute the RPC - try: - #for get-config in case of exception handling it will not display - #filters and arguments. To be added in future. - rpc = junos_module.etree.Element(rpc_string, format=format) - if rpc_string == 'get-config': - filter = junos_module.params.get('filter') - if attr is None: - attr = {} - if kwarg is None: - kwarg = {} - if format is not None: - attr['format'] = format - junos_module.logger.debug('Executing "get-config" RPC. ' - 'filter_xml=%s, options=%s, ' - 'kwargs=%s', - filter, str(attr), str(kwarg)) - resp = junos_module.dev.rpc.get_config(filter_xml=filter, - options=attr, **kwarg) - result['msg'] = 'The "get-config" RPC executed successfully.' - junos_module.logger.debug('The "get-config" RPC executed ' - 'successfully.') - else: - if kwarg is not None: - # Add kwarg - for (key, value) in iteritems(kwarg): - # Replace underscores with dashes in key name. - key = key.replace('_', '-') - sub_element = junos_module.etree.SubElement(rpc, key) - if not isinstance(value, bool): - sub_element.text = value - if attr is not None: - # Add attr - for (key, value) in iteritems(attr): - # Replace underscores with dashes in key name. - key = key.replace('_', '-') - rpc.set(key, value) - junos_module.logger.debug('Executing RPC "%s".', - junos_module.etree.tostring( - rpc, - pretty_print=True)) - resp = junos_module.dev.rpc(rpc, - normalize=bool(format == 'xml')) - result['msg'] = 'The RPC executed successfully.' - junos_module.logger.debug('RPC "%s" executed successfully.', - junos_module.etree.tostring( - rpc, - pretty_print=True)) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Unable to execute RPC "%s". Error: %s', - junos_module.etree.tostring( - rpc, - pretty_print=True), str(ex)) - result['msg'] = 'Unable to execute the RPC: %s. Error: %s' % \ - (junos_module.etree.tostring(rpc, - pretty_print=True), - str(ex)) - results.append(result) - continue - - text_output = None - parsed_output = None - if resp is True: - text_output = '' - elif (resp, junos_module.etree._Element): - # Handle the output based on format - if format == 'text': - text_output = resp.text - junos_module.logger.debug('Text output set.') - elif format == 'xml': - text_output = junos_module.etree.tostring(resp, - pretty_print=True) - parsed_output = junos_module.jxmlease.parse_etree(resp) - junos_module.logger.debug('XML output set.') - elif format == 'json': - text_output = str(resp) - parsed_output = resp - junos_module.logger.debug('JSON output set.') - else: - result['msg'] = 'Unexpected format %s.' % (format) - results.append(result) - junos_module.logger.debug('Unexpected format %s.', format) - continue - else: - result['msg'] = 'Unexpected response type %s.' % (type(resp)) - results.append(result) - junos_module.logger.debug('Unexpected response type %s.', - type(resp)) - continue - - # Set the output keys - if junos_module.params['return_output'] is True: - if text_output is not None: - result['stdout'] = text_output - result['stdout_lines'] = text_output.splitlines() - if parsed_output is not None: - result['parsed_output'] = parsed_output - # Save the output - junos_module.save_text_output(rpc_string, format, text_output) - # This command succeeded. - result['failed'] = False - # Append to the list of results - results.append(result) - - # Return response. - if len(results) == 1: - junos_module.exit_json(**results[0]) - else: - # Calculate the overall failed. Only failed if all commands failed. - failed = True - for result in results: - if result.get('failed') is False: - failed = False - break - junos_module.exit_json(results=results, - changed=False, - failed=failed) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_software.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_software.py deleted file mode 100644 index e8bff25a..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_software.py +++ /dev/null @@ -1,775 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_software -author: - - Jeremy Schulman - - "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Install software on a Junos device -description: - - > - Install a Junos OS image, or other software package, on a Junos device. - This action is generally equivalent to the C(request system software add) - operational-mode CLI command. It performs the following - steps in order: - - - #. Compare the currently installed Junos version to the desired version - specified by the I(version) option. - - * If the current and desired versions are the same, stop and return - I(changed) with a value of C(false). - * If running in check mode, and the current and desired versions differ, - stop and return I(changed) with a value of C(true). - * Otherwise, proceed. - #. If the I(local_package) option is specified, compute the MD5 checksum - of the I(local_package) file on the local Ansible control machine. - #. Check if the file exists at the I(remote_package) location on the target - Junos device. If so, compute the MD5 checksum of the file on the target - Junos device. - #. If the I(cleanfs) option is C(true), the default, then perform the - equivalent of the C(request system storage cleanup) CLI command. - #. If the checksums computed in steps 2 and 3 differ, or if the - I(remote_package) file does not exist on the target Junos device, then - copy the package from I(local_package) on the local Ansible control - machine to I(remote_package) on the target Junos device. - #. Install the software pacakge from the I(remote_package) location on the - target Junos device using the options specified. - #. If the I(reboot) option is C(true), the default, initiate a reboot of - the target Junos device. -options: - all_re: - description: - - Whether or not to install the software on all Routing Engines of the - target Junos device. If C(true), and the device has multiple Routing - Engines, the software is installed on all Routing Engines. If C(false), - the software is only installed on the current Routing Engine. - required: false - default: true - type: bool - checksum: - description: - - The pre-calculated checksum, using the I(checksum_algorithm) of the - file specified by the I(local_package) option. Specifying this option - is simply an optimization to avoid repeatedly computing the checksum of - the I(local_package) file once for each target Junos host. - required: false - default: none - type: str - checksum_algorithm: - description: - - The algorithm to use when calculating the checksum of the local and - remote software packages. - required: false - default: md5 - type: str - checksum_timeout: - description: - - The number of seconds to wait for the calculation of the checksum to - complete on the target Junos device. - required: false - default: 300 (5 minutes) - type: int - cleanfs: - description: - - Whether or not to perform a C(request system storage cleanup) prior to - copying or installing the software. - required: false - default: true (unless I(no_copy) is C(true), then C(false)) - type: bool - cleanfs_timeout: - description: - - The number of seconds to wait for the - C(request system storage cleanup) to complete on the target Junos - device. - required: false - default: 300 (5 minutes) - type: int - force_host: - description: - - Forces the upgrade of the Host Software package on QFX-series devices. - required: false - default: false - type: bool - install_timeout: - description: - - The number of seconds to wait for the software installation to - complete on the target Junos device. - required: false - default: 1800 (30 minutes) - type: int - issu: - description: - - Indicates if a unified in-service software upgrade (ISSU) should be - attempted. ISSU enables the upgrade between two different - Junos OS releases with no control plane disruption and minimal data - plane traffic disruption. - - In order for an ISSU to succeed, ISSU must be supported. This includes - support for the current to desired Junos versions, the hardware - of the target Junos device, and the current software configuration of - the target Junos device. - - The I(issu) and I(nssu) options are mutually exclusive. - required: false - default: false - type: bool - kwargs: - description: - - Additional keyword arguments and values which are passed to the - C() RPC used to install the software package. The - value of this option is a dictionary of keywords and values. - required: false - default: none - type: dict - aliases: - - kwarg - - args - - arg - local_package: - description: - - The path, on the local Ansible control machine, of a Junos software - package. This Junos software package will be installed on the target - Junos device. - - If this option is specified, and a file with the same MD5 checksum - doesn't already exist at the I(remote_package) location on the target - Junos device, then the file is copied from the local Ansible control - machine to the target Junos device. - - If this option is not specified, it is assumed that the - software package already exists on the target Junos device. In this - case, the I(remote_package) option must be specified. - required: false - default: none - type: path - aliases: - - package - no_copy: - description: - - Indicates if the file containing the software package should be copied - from the I(local_package) location on the local Ansible control - machine to the I(remote_package) location on the target Junos device. - - If the value is C(true), or if the I(local_package) option is not - specified, then the copy is skipped and the file must already exist - at the I(remote_package) location on the target Junos device. - required: false - default: false - type: bool - nssu: - description: - - Indicates if a non-stop software upgrade (NSSU) should be - attempted. NSSU enables the upgrade between two different - Junos OS releases with minimal data plane traffic disruption. - - NSSU is specific to EX-series Virtual Chassis systems or EX-series - stand-alone systems with redundant Routing Engines. - - In order for an NSSU to succeed, NSSU must be supported. This includes - support for the current to desired Junos versions, the hardware - of the target Junos device, and the current software configuration of - the target Junos device. - - The I(nssu) and I(issu) options are mutually exclusive. - required: false - default: false - type: bool - reboot: - description: - - Indicates if the target Junos device should be rebooted after - performing the software install. - required: false - default: true - type: bool - reboot_pause: - description: - - The amount of time, in seconds, to wait after the reboot is issued - before the module returns. This gives time for the reboot to begin. The - default value of 10 seconds is designed to ensure the device is no - longer reachable (because the reboot has begun) when the next task - begins. The value must be an integer greater than or equal to 0. - required: false - default: 10 - type: int - remote_package: - description: - - This option may take one of two formats. - - The first format is a URL, from the perspective of the target Junos - device, from which the device retrieves the software package to be - installed. The acceptable formats for the URL value may be found - U(here|https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-software-formats-filenames-urls.html). - - When using the URL format, the I(local_package) and I(no_copy) options - must not be specified. - - The second format is a file path, on the taget Junos device, to the - software package. - - If the I(local_package) option is also specified, and the - I(no_copy) option is C(false), the software package will be copied - from I(local_package) to I(remote_package), if necessary. - - If the I(no_copy) option is C(true) or the I(local_package) option - is not specified, then the file specified by this option must already - exist on the target Junos device. - - If this option is not specified, it is assumed that the software - package will be copied into the C(/var/tmp) directory on the target - Junos device using the filename portion of the I(local_package) option. - In this case, the I(local_package) option must be specified. - - Specifying the I(remote_package) option and not specifying the - I(local_package) option is equivalent to specifying the - I(local_package) option and the I(no_copy) option. In this case, - you no longer have to explicitly specify the I(no_copy) option. - - If the I(remote_package) value is a directory (ends with /), then - the filename portion of I(local_package) will be appended to the - I(remote_package) value. - - If the I(remote_package) value is a file (does not end with /), - then the filename portion of I(remote_package) must be the same as - the filename portion of I(local_package). - required: false - default: C(/var/tmp/) + filename portion of I(local_package) - type: path - pkg_set: - description: - - install software on the members in a mixed Virtual Chassis. Currently - we are not doing target package check this option is provided. - required: false - default: false - type: list - validate: - description: - - Whether or not to have the target Junos device should validate the - current configuration against the new software package. - required: false - default: false - type: bool - version: - description: - - The version of software contained in the file specified by the - I(local_package) and/or I(remote_package) options. This value should - match the Junos version which will be reported by the device once the - new software is installed. If the device is already running a version - of software which matches the I(version) option value, the software - install is not necessary. In this case the module returns a I(changed) - value of C(false) and an I(failed) value of C(false) and does not - attempt to perform the software install. - required: false - default: Attempt to extract the version from the file name specified by - the I(local_package) or I(remote_package) option values IF the - package appears to be a Junos software package. Otherwise, C(none). - type: str - aliases: - - target_version - - new_version - - desired_version - vmhost: - description: - - Whether or not this is a vmhost software installation. - required: false - default: false - type: bool -notes: - - This module does support connecting to the console of a Junos device, but - does not support copying the software package from the local Ansible - control machine to the target Junos device while connected via the console. - In this situation, the I(remote_package) option must be specified, and the - specified software package must already exist on the target Junos device. - - This module returns after installing the software and, optionally, - initiating a reboot of the target Junos device. It does not wait for - the reboot to complete, and it does not verify that the desired version of - software specified by the I(version) option is actually activated on the - target Junos device. It is the user's responsibility to confirm the - software installation using additional follow on tasks in their playbook. -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_software - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Execute a basic Junos software upgrade. - juniper_junos_software: - local_package: "./images/" - register: response - - name: Print the complete response. - debug: - var: response - -###### OLD EXAMPLES ########## - - junos_install_os: - host={{ inventory_hostname }} - version=12.1X46-D10.2 - package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz - logfile=/usr/local/junos/log/software.log -###### OLD EXAMPLES ########## -''' - - -RETURN = ''' -changed: - description: - - Indicates if the device's state has changed, or if the state would have - changed when executing in check mode. This value is set to C(true) when - the version of software currently running on the target Junos device does - not match the desired version of software specified by the I(version) - option. If the current and desired software versions match, the value - of this key is set to C(false). - returned: success - type: bool -check_mode: - description: - - Indicates whether or not the module ran in check mode. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -msg: - description: - - A human-readable message indicating the result of the software - installation. - returned: always - type: str -''' - -# Standard Library imports -import os.path -import re -import time -try: - # Python 3.x - from urllib.parse import urlparse -except ImportError: - # Python 2.x - from urlparse import urlparse - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def parse_version_from_filename(filename): - """Attempts to parse a version string from the filename of a Junos package. - - There is wide variety in the naming schemes used by Junos software - packages. This function attempts to parse the version string from the - filename, but may not be able to accurately do so. It's also not - guaranteed that the filename of a package accurately reflects the version - of software in the file. (A user may have renamed it.) - If the filename does not appear to be a Junos package (maybe some other - type of package which can be installed on Junos devices), then return None. - - Args: - filename - The filename from which to parse the version string. - - Returns: - The version string, or None if unable to parse. - """ - # Known prefixes for filenames which contain Junos software packages. - JUNOS_PACKAGE_PREFIXES = ['jbundle', 'jinstall', 'junos-install', - 'junos-srx', 'junos-vmhost-install', 'junos-vrr', - 'vmx-bundle'] - for prefix in JUNOS_PACKAGE_PREFIXES: - if filename.startswith(prefix): - # Assumes the version string will be prefixed by -. - # Assume major version will begin with two digits followed by dot. - # Assume the version string ends with the last digit in filename. - match = re.search('-(\d{2}\..*\d).*', filename) - if match is not None: - return match.group(1) - # Not a known Junos package name. - else: - return None - - -def define_progress_callback(junos_module): - """Create callback which can be passed to SW.install(progress=progress) - """ - def myprogress(_, report): - """A progress function which logs report at level INFO. - - Args: - _: The PyEZ device object. Unused because the logger already knows. - report: The string to be logged. - """ - junos_module.logger.info(report) - return myprogress - - -def main(): - CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] - - #Define the argument spec. - software_argument_spec=dict( - local_package=dict(required=False, - aliases=['package'], - type='path', - default=None), - remote_package=dict(required=False, - type='path', - # Default is '/var/tmp/' + filename from the - # local_package option, if set. - default=None), - pkg_set=dict(required=False, - type='list', - default=None), - version=dict(required=False, - aliases=['target_version', 'new_version', - 'desired_version'], - type='str', - # Default is determined from filename portion of - # remote_package option. - default=None), - no_copy=dict(required=False, - type='bool', - default=False), - reboot=dict(required=False, - type='bool', - default=True), - reboot_pause=dict(required=False, - type='int', - default=10), - issu=dict(required=False, - type='bool', - default=False), - nssu=dict(required=False, - type='bool', - default=False), - force_host=dict(required=False, - type='bool', - default=False), - validate=dict(required=False, - type='bool', - default=False), - cleanfs=dict(required=False, - type='bool', - default=True), - all_re=dict(required=False, - type='bool', - default=True), - vmhost=dict(required=False, - type='bool', - default=False), - checksum=dict(required=False, - type='str', - default=None), - checksum_algorithm=dict(required=False, - choices=CHECKSUM_ALGORITHM_CHOICES, - type='str', - default='md5'), - checksum_timeout=dict(required=False, - type='int', - default=300), - cleanfs_timeout=dict(required=False, - type='int', - default=300), - install_timeout=dict(required=False, - type='int', - default=1800), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='dict', - default=None), - ) - # Save keys for later. Must do because software_argument_spec gets - # modified. - option_keys = list(software_argument_spec.keys()) - - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec = software_argument_spec, - # Mutually exclusive options. - mutually_exclusive=[['issu', 'nssu']], - # One of local_package and remote_package is required. - required_one_of=[['local_package', 'remote_package', 'pkg_set']], - supports_check_mode=True - ) - - # Straight from params - local_package = junos_module.params.pop('local_package') - remote_package = junos_module.params.pop('remote_package') - pkg_set = junos_module.params.pop('pkg_set') - target_version = junos_module.params.pop('version') - no_copy = junos_module.params.pop('no_copy') - reboot = junos_module.params.pop('reboot') - reboot_pause = junos_module.params.pop('reboot_pause') - install_timeout = junos_module.params.pop('install_timeout') - cleanfs = junos_module.params.pop('cleanfs') - all_re = junos_module.params.pop('all_re') - kwargs = junos_module.params.pop('kwargs') - - url = None - remote_dir = None - if remote_package is not None: - # Is the remote package a URL? - parsed_url = urlparse(remote_package) - if parsed_url.scheme == '': - # A file on the remote host. - (remote_dir, remote_filename) = os.path.split(remote_package) - else: - url = remote_package - (_, remote_filename) = os.path.split(parsed_url.path) - else: - # Default remote_dir value - remote_dir = '/var/tmp' - remote_filename = '' - - if url is not None and local_package is not None: - junos_module.fail_json(msg='There remote_package (%s) is a URL. ' - 'The local_package option is not allowed.' % - remote_package) - - if url is not None and no_copy is True: - junos_module.fail_json(msg='There remote_package (%s) is a URL. ' - 'The no_copy option is not allowed.' % - remote_package) - - if url is None: - local_filename = None - if local_package is not None: - # Expand out the path. - local_package = os.path.abspath(local_package) - (local_dir, local_filename) = os.path.split(local_package) - if local_filename == '': - junos_module.fail_json(msg='There is no filename component to ' - 'the local_package (%s).' % - local_package) - elif remote_package is not None: - # remote package was, so we must assume no_copy. - no_copy = True - - if no_copy is False: - if local_package is not None and not os.path.isfile(local_package): - junos_module.fail_json(msg='The local_package (%s) is not a ' - 'valid file on the local Ansible ' - 'control machine.' % local_package) - elif pkg_set is not None: - pkg_set = [os.path.abspath(item) for item in pkg_set] - for pkg_set_item in pkg_set: - if not os.path.isfile(pkg_set_item): - junos_module.fail_json( - msg='The pkg (%s) is not a valid file on the local' - ' Ansible control machine.' % pkg_set_item) - - if remote_filename == '': - # Use the same name as local_filename - remote_filename = local_filename - - if local_filename is not None and remote_filename != local_filename: - junos_module.fail_json(msg='The filename of the remote_package ' - '(%s) must be the same as the filename ' - 'of the local_package (%s).' % - (remote_filename, local_filename)) - - # If no_copy is True, then we need to turn off cleanfs to keep from - # deleting the software package which is already present on the device. - if no_copy is True: - cleanfs = False - - if target_version is None and pkg_set is None: - target_version = parse_version_from_filename(remote_filename) - junos_module.logger.debug("New target version is: %s.", target_version) - - # Initialize the results. Assume not changed and failure until we know. - results = {'msg': '', - 'changed': False, - 'check_mode': junos_module.check_mode, - 'failed': True} - - # Check version info to see if we need to do the install. - if target_version is not None: - if all_re is True: - junos_info = junos_module.dev.facts['junos_info'] - for current_re in junos_info: - current_version = junos_info[current_re]['text'] - if target_version != current_version: - junos_module.logger.debug("Current version on %s: %s. " - "Target version: %s.", - current_version, current_re, - target_version) - results['changed'] = True - else: - results['msg'] += "Current version on %s: %s same as Targeted " \ - "version: %s.\n" % (current_version, current_re, - target_version) - else: - current_version = junos_module.dev.facts['version'] - re_name = junos_module.dev.re_name - if target_version != current_version: - junos_module.logger.debug("Current version on %s: %s. " - "Target version: %s.", - current_version, re_name, - target_version) - results['changed'] = True - else: - results['msg'] += "Current version on %s: %s same as Targeted " \ - "version: %s.\n" % (current_version, re_name, - target_version) - else: - # A non-Junos install. Always attempt to install. - results['changed'] = True - - # Do the install if necessary - if results['changed'] is True and not junos_module.check_mode: - junos_module.logger.debug("Beginning installation of %s.", - remote_filename) - # Calculate the install parameters - install_params = {} - if url is not None: - install_params['package'] = url - elif local_package is not None: - install_params['package'] = local_package - elif pkg_set is not None: - install_params['pkg_set'] = pkg_set - else: - install_params['package'] = remote_filename - if remote_dir is not None: - install_params['remote_path'] = remote_dir - install_params['progress'] = define_progress_callback(junos_module) - install_params['cleanfs'] = cleanfs - install_params['no_copy'] = no_copy - install_params['timeout'] = install_timeout - install_params['all_re'] = all_re - for key in option_keys: - value = junos_module.params.get(key) - if value is not None: - install_params[key] = value - if kwargs is not None: - install_params.update(kwargs) - try: - junos_module.logger.debug("Install parameters are: %s", - str(install_params)) - junos_module.add_sw() - ok, msg_ret = junos_module.sw.install(**install_params) - if ok is not True: - results['msg'] = 'Unable to install the software %s', msg_ret - junos_module.fail_json(**results) - msg = 'Package %s successfully installed. Response from device is: %s' % ( - install_params.get('package') or - install_params.get('pkg_set'), - msg_ret) - results['msg'] = msg - junos_module.logger.debug(msg) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - results['msg'] = 'Installation failed. Error: %s' % str(ex) - junos_module.fail_json(**results) - if reboot is True: - try: - # Try to deal with the fact that we might not get the closing - # and therefore might get an RpcTimeout. - # (This is a known Junos bug.) Set the timeout low so this - # happens relatively quickly. - restore_timeout = junos_module.dev.timeout - if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") - junos_module.dev.timeout = 5 - junos_module.logger.debug('Initiating reboot.') - try: - - got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) - junos_module.dev.timeout = restore_timeout - except Exception: # pylint: disable=broad-except - junos_module.dev.timeout = restore_timeout - raise - junos_module.logger.debug("Reboot RPC executed.") - - if got is not None: - results['msg'] += ' Reboot successfully initiated. ' \ - 'Reboot message: %s' % got - else: - # This is the else clause of the for loop. - # It only gets executed if the loop finished without - # hitting the break. - results['msg'] += ' Did not find expected response ' \ - 'from reboot RPC. ' - junos_module.fail_json(**results) - except (junos_module.pyez_exception.RpcTimeoutError) as ex: - # This might be OK. It might just indicate the device didn't - # send the closing (known Junos bug). - # Try to close the device. If it closes cleanly, then it was - # still reachable, which probably indicates a problem. - try: - junos_module.close(raise_exceptions=True) - # This means the device wasn't already disconnected. - results['msg'] += ' Reboot failed. It may not have been ' \ - 'initiated.' - junos_module.fail_json(**results) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.RpcTimeoutError, - junos_module.pyez_exception.ConnectError): - # This is expected. The device has already disconnected. - results['msg'] += ' Reboot succeeded.' - except (junos_module.ncclient_exception.TimeoutExpiredError): - # This is not really expected. Still consider reboot success as - # Looks like rpc was consumed but no response as its rebooting. - results['msg'] += ' Reboot succeeded. Ignoring close error.' - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) - junos_module.fail_json(**results) - else: - try: - junos_module.close() - except (junos_module.ncclient_exception.TimeoutExpiredError): - junos_module.logger.debug("Ignoring TimeoutError for close call") - - junos_module.logger.debug("Reboot RPC successfully initiated.") - if reboot_pause > 0: - junos_module.logger.debug("Sleeping for %d seconds", - reboot_pause) - time.sleep(reboot_pause) - - # If we made it this far, it's success. - results['failed'] = False - - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_srx_cluster.py deleted file mode 100644 index fd505f5c..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_srx_cluster.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Patrik Bok -# 2015, Rick Sherman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_srx_cluster -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Add or remove SRX chassis cluster configuration -description: - - Add an SRX chassis cluster configuration and reboot the device. Assuming - the device is capable of forming an SRX cluster and has the correct - cables connected, this will form an SRX cluster. - - If an SRX chassis cluster is already present, setting I(cluster_enable) to - C(false) will remove the SRX chassis cluster configuration and reboot - the device causing the SRX cluster to be broken and the device to return - to stand-alone mode. -options: - enable: - description: - - Enable or disable cluster mode. When C(true) cluster mode is enabled - and I(cluster_id) and I(node_id) must also be specified. When C(false) - cluster mode is disabled and the device returns to stand-alone mode. - required: true - default: none - type: bool - aliases: - - cluster_enable - cluster_id: - description: - - The cluster ID to configure. - - Required when I(enable) is C(true). - required: false - default: none - type: int - aliases: - - cluster - node_id: - description: - - The node ID to configure. (C(0) or C(1)) - - Required when I(enable) is C(true). - required: false - default: none - type: int - aliases: - - node -''' - -EXAMPLES = ''' ---- -- name: Manipulate the SRX cluster configuration of Junos SRX devices - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - tasks: - - name: Enable an SRX cluster - juniper_junos_srx_cluster: - enable: true - cluster_id: 4 - node_id: 0 - register: response - - name: Print the response. - debug: - var: response.config_lines - - - name: Disable an SRX cluster - juniper_junos_srx_cluster: - enable: false - register: response - - name: Print the response. - debug: - var: response.config_lines -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's configuration has changed, or would have - changed when in check mode. - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -reboot: - description: - - Indicates if a reboot of the device has been initiated. - returned: success - type: bool -''' - -# Standard library imports - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - enable=dict(type='bool', - required=True, - aliases=['cluster_enable'], - default=None), - cluster_id=dict(type='int', - required=False, - aliases=['cluster'], - default=None), - node_id=dict(type='int', - required=False, - aliases=['node'], - default=None) - ), - # Required if options - # If enable is True, then cluster_id and node_id must be set. - required_if=[['enable', True, ['cluster_id', 'node_id']]], - # Check mode is implemented. - supports_check_mode=True - ) - # Do additional argument verification. - - # Straight from params - enable = junos_module.params.get('enable') - cluster_id = junos_module.params.get('cluster_id') - node_id = junos_module.params.get('node_id') - - # cluster_id must be between 0 and 255 - if cluster_id is not None: - if cluster_id < 0 or cluster_id > 255: - junos_module.fail_json(msg="The cluster_id option (%s) must have " - "an integer value between 0 and 255." % - (cluster_id)) - - # node_id must be between 0 and 1 - if node_id is not None: - if node_id < 0 or node_id > 1: - junos_module.fail_json(msg="The node_id option (%s) must have a " - "value of 0 or 1." % (node_id)) - - # Initialize the results. Assume failure until we know it's success. - results = {'msg': '', - 'changed': False, - 'reboot': False, - 'failed': True} - - junos_module.logger.debug("Check current SRX cluster operational state.") - current_cluster_state = junos_module.dev.facts['srx_cluster'] - current_cluster_id = junos_module.dev.facts['srx_cluster_id'] - if current_cluster_id is not None: - current_cluster_id = int(current_cluster_id) - current_node_name = junos_module.dev.re_name - current_node_id = None - if current_node_name is not None: - (_, _, current_node_id) = current_node_name.partition('node') - if current_node_id: - current_node_id = int(current_node_id) - junos_module.logger.debug( - "Current SRX cluster operational state: %s, cluster_id: %s, " - "node_id: %s", - 'enabled' if current_cluster_state else 'disabled', - str(current_cluster_id), - str(current_node_id)) - - # Is a state change needed? - if current_cluster_state != enable: - junos_module.logger.debug( - "SRX cluster configuration change needed. Current state: %s. " - "Desired state: %s", - 'enabled' if current_cluster_state else 'disabled', - 'enabled' if enable else 'disabled') - results['changed'] = True - - # Is a cluster ID change needed? - if (enable is True and current_cluster_id is not None and - current_cluster_id != cluster_id): - junos_module.logger.debug( - "SRX cluster ID change needed. Current cluster ID: %d. " - "Desired cluster ID: %d", - current_cluster_id, cluster_id) - results['changed'] = True - - # Is a node ID change needed? - if (enable is True and current_node_id is not None and - current_node_id != node_id): - junos_module.logger.debug( - "SRX node ID change needed. Current node ID: %d. " - "Desired cluster ID: %d", - current_node_id, node_id) - results['changed'] = True - - results['msg'] = 'Current state: %s, cluster_id: %s, node_id: %s' % \ - ('enabled' if current_cluster_state else 'disabled', - str(current_cluster_id), - str(current_node_id)) - - if results['changed'] is True: - results['msg'] += ' Desired state: %s, cluster_id: %s, ' \ - 'node_id: %s' % \ - ('enabled' if enable else 'disabled', - str(cluster_id), - str(node_id)) - - if not junos_module.check_mode: - results['msg'] += ' Initiating change.' - try: - output = None - if enable is True: - resp = junos_module.dev.rpc.set_chassis_cluster_enable( - cluster_id=str(cluster_id), node=str(node_id), - reboot=True, normalize=True - ) - else: - resp = junos_module.dev.rpc.set_chassis_cluster_disable( - reboot=True, normalize=True - ) - if resp is not None: - output = resp.getparent().findtext('.//output') - if output is None: - output = resp.getparent().findtext('.//message') - results['msg'] += ' Reboot initiated. Response: %s' % (output) - results['reboot'] = True - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Error: %s', str(ex)) - results['msg'] += ' Error: %s' % (str(ex)) - junos_module.fail_json(**results) - - # If we made it this far, everything was successful. - results['failed'] = False - - # Return response. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_system.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_system.py deleted file mode 100644 index 3dff9907..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_system.py +++ /dev/null @@ -1,398 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_system -author: "Juniper Networks - Stacy Smith (@stacywsmith)" -short_description: Initiate operational actions on the Junos system -description: - - Initiate an operational action (shutdown, reboot, halt or zeroize) on a - Junos system. The particular action to execute is defined by the mandatory - I(action) option. -options: - action: - description: - - The action performed by the module. - - > - The following actions are supported: - - B(shutdown) - Power off the Junos devices. The values C(off), - C(power-off), and C(power_off) are aliases for this value. - This is the equivalent of the C(request system power-off) CLI - command. - - B(halt) - Stop the Junos OS running on the RE, but do not power off - the system. Once the system is halted, it will reboot if a - keystroke is entered on the console. This is the equivalent - of the C(request system halt) CLI command. - - B(reboot) - Reboot the system. This is the equivalent of the - C(request system reboot) CLI command. - - B(zeroize) - Restore the system (configuration, log files, etc.) to a - factory default state. This is the equivalent of the - C(request system zeroize) CLI command. - required: true - default: none - type: str - choices: - - shutdown - - halt - - reboot - - zeroize - - 'off' - - power-off - - power_off - at: - description: - - The time at which to shutdown, halt, or reboot the system. - - > - The value may be specified in one of the following ways: - - B(now) - The action takes effect immediately. - - B(+minutes) — The action takes effect in C(minutes) minutes from now. - - B(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute - time, specified as year, month, day, hour, and minute. - - B(hh:mm) — The action takes effect at C(hh:mm) absolute time on the - current day, specified in 24-hour time. - - The I(at) option can not be used when the I(action) option has a - value of C(zeroize). The I(at) option is mutually exclusive with the - I(in_min) option. - required: false - default: none - type: str - in_min: - description: - - Specify a delay, in minutes, before the shutdown, halt, or reboot. - - The I(in_min) option can not be used when the I(action) option has a - value of C(zeroize). The I(in_min) option is mutually exclusive with - the I(at) option. - required: false - default: none - type: int - all_re: - description: - - If the system has multiple Routing Engines and this option is C(true), - then the action is performed on all REs in the system. If the system - does not have multiple Routing Engines, then this option has no effect. - - This option applies to all I(action) values. - - The I(all_re) option is mutually exclusive with the I(other_re) option. - required: false - default: true - type: bool - other_re: - description: - - If the system has dual Routing Engines and this option is C(true), - then the action is performed on the other REs in the system. If the - system does not have dual Routing Engines, then this option has no - effect. - - The I(other_re) option can not be used when the I(action) option has a - value of C(zeroize). - - The I(other_re) option is mutually exclusive with the I(all_re) option. - required: false - default: false - type: bool - media: - description: - - Overwrite media when performing the zeroize operation. This option is - only valid when the I(action) option has a value of C(zeroize). - required: false - default: false - type: bool - vmhost: - description: - - Whether or not this is a vmhost reboot. - required: false - default: false - type: bool -notes: - - This module only B(INITIATES) the action. It does B(NOT) wait for the - action to complete. - - Some Junos devices are effected by a Junos defect which causes this Ansible - module to hang indefinitely when connected to the Junos device via - the console. This problem is not seen when connecting to the Junos device - using the normal NETCONF over SSH transport connection. Therefore, it is - recommended to use this module only with a NETCONF over SSH transport - connection. However, this module does still permit connecting to Junos - devices via the console port and this functionality may still be used for - Junos devices running Junos versions less than 15.1. -''' - -EXAMPLES = ''' ---- -- name: Examples of juniper_junos_system - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Reboot all REs of the device - juniper_junos_system: - action: "reboot" - - - name: Power off the other RE of the device. - juniper_junos_system: - action: "shutdown" - othe_re: True - - - name: Reboot this RE at 8pm today. - juniper_junos_system: - action: "reboot" - all_re: False - at: "20:00" - - - name: Halt the system on 25 January 2018 at 4pm. - juniper_junos_system: - action: "halt" - at: "1801251600" - - - name: Reboot the system in 30 minutes. - juniper_junos_system: - action: "reboot" - in_min: 30 - - - name: Reboot the system in 30 minutes. - juniper_junos_system: - action: "reboot" - at: "+30m" - - - name: Zeroize the local RE only. - juniper_junos_system: - action: "zeroize" - all_re: False - - - name: Zeroize all REs and overwrite medea. - juniper_junos_system: - action: "zeroize" - media: True -''' - -RETURN = ''' -action: - description: - - The value of the I(action) option. - returned: always - type: str -all_re: - description: - - The value of the I(all_re) option. - returned: always - type: str -changed: - description: - - Indicates if the device's state has changed. If the action is performed - (or if it would have been performed when in check mode) then the value - will be C(true). If there was an error before the action, then the value - will be C(false). - returned: always - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -media: - description: - - The value of the I(media) option. - returned: always - type: str -msg: - description: - - A human-readable message indicating the result. - returned: always - type: str -other_re: - description: - - The value of the I(other_re) option. - returned: always - type: str -''' - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - action=dict(type='str', - required=True, - choices=['shutdown', 'off', 'power-off', 'power_off', - 'halt', 'reboot', 'zeroize'], - default=None), - at=dict(type='str', - required=False, - default=None), - in_min=dict(type='int', - required=False, - aliases=['in'], - default=None), - all_re=dict(type='bool', - required=False, - default=True), - other_re=dict(type='bool', - required=False, - default=False), - vmhost=dict(required=False, - type='bool', - default=False), - media=dict(type='bool', - required=False, - default=False), - ), - mutually_exclusive=[['at', 'in_min'], ['all_re', 'other_re']], - supports_check_mode=True - ) - - # We're going to be using params a lot - params = junos_module.params - - action = params['action'] - at = params.get('at') - in_min = params.get('in_min') - all_re = params.get('all_re') - other_re = params.get('other_re') - media = params.get('media') - vmhost = params.get('vmhost') - - # Synonymn for shutdown - if action == 'off' or action == 'power_off' or action == 'power-off': - action = 'shutdown' - - if action == 'reboot' and vmhost is True: - junos_module.fail_json(msg='The vmhost option can only be used when ' - 'the action option has the value "reboot".') - - #Four actions are expected - reboot, shutdown, halt and zeroize - if action not in ['reboot', 'shutdown', 'halt']: - # at, in_min and other_re option only applies to reboot, shutdown, or halt action. - for arg_type,arg_val in {"at":at,"in_min":in_min,"other_re":other_re}: - if arg_val is not None: - junos_module.fail_json(msg='The %s option can only be used when ' - 'the action option has the value "reboot", ' - '"shutdown", or "halt".' % arg_type) - - elif media is True: # media option only applies to zeroize action. - junos_module.fail_json(msg='The media option can only be used when ' - 'the action option has the value "zeroize".') - - # Set initial results values. Assume failure until we know it's success. - results = {'changed': True, - 'msg': '', - 'reboot': bool(action == 'reboot'), - 'action': action, - 'all_re': all_re, - 'other_re': other_re, - 'media': media, - 'vmhost': vmhost, - 'failed': True} - - if not junos_module.check_mode: - if action != 'zeroize': - # If we're going to do a shutdown, reboot, or halt right away then - # try to deal with the fact that we might not get the closing - # and therefore might get an RpcTimeout. - # (This is a known Junos bug.) Set the timeout low so this happens - # relatively quickly. - if (at == 'now' or in_min == 0 or - (at is None and in_min is None)): - if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") - junos_module.dev.timeout = 5 - - # Execute the RPC. - try: - junos_module.logger.debug("Executing RPC") - - if action == 'reboot': - got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) - elif action == 'shutdown': - got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) - elif action == 'halt': - got = junos_module.sw.halt(in_min, at, all_re, other_re) - elif action == 'zeroize': - got = junos_module.sw.zeroize(all_re, media) - else: - junos_module.fail_json(msg='Relevant action not found') - - junos_module.logger.debug("RPC executed") - if got is None: - results['msg'] = 'Did not find expected RPC response.' - results['changed'] = False - else: - results['msg'] = '%s successfully initiated. Response got %s' % (action, got) - results['failed'] = False - except (junos_module.pyez_exception.RpcTimeoutError) as ex: - # This might be OK. It might just indicate the device didn't - # send the closing (known Junos bug). - # Try to close the device. If it closes cleanly, then it was - # still reachable, which probably indicates there was a problem. - try: - junos_module.close(raise_exceptions=True) - # This means the device wasn't already disconnected. - results['changed'] = False - results['msg'] = '%s failed. %s may not have been ' \ - 'initiated.' % (action, action) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError): - # This is expected. The device has already disconnected. - results['msg'] = '%s succeeded.' % (action) - results['failed'] = False - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['changed'] = False - results['msg'] = '%s failed. Error: %s' % (action, str(ex)) - - # Return results. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/juniper_junos_table.py b/ansible_collections/juniper/device/plugins/modules/juniper_junos_table.py deleted file mode 100644 index b0c5d7f9..00000000 --- a/ansible_collections/juniper/device/plugins/modules/juniper_junos_table.py +++ /dev/null @@ -1,475 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright 2016 Jason Edelman -# Network to Code, LLC -# -# Copyright (c) 2017-2018, Juniper Networks Inc. -# -# All rights reserved. -# -# License: Apache 2.0 -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Juniper Networks nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -from __future__ import absolute_import, division, print_function - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} - -DOCUMENTATION = ''' ---- -extends_documentation_fragment: - - juniper_junos_common.connection_documentation - - juniper_junos_common.logging_documentation -module: juniper_junos_table -author: - - Jason Edelman (@jedelman8) - - Updated by Juniper Networks - Stacy Smith (@stacywsmith) -short_description: Retrieve data from a Junos device using a PyEZ table/view -description: - - Retrieve data from a Junos device using PyEZ's operational table/views. - This module may be used with the tables/views which are included in the - PyEZ distribution or it may be used with user-defined tables/views. -options: - file: - description: - - Name of the YAML file, relative to the I(path) option, that contains - the table/view definition. The file name must end with the C(.yml) or - C(.yaml) extension. - required: true - default: none - type: path - kwargs: - description: - - Optional keyword arguments and values to the table's get() method. The - value of this option is a dictionary of keywords and values which are - used to refine the data return from performing a get() on the table. - The exact keywords and values which are supported are specific to the - table's definition and the underlying RPC which the table invokes. - required: false - default: none - type: dict - aliases: - - kwarg - - args - - arg - path: - description: - - The directory containing the YAML table/view definition file as - specified by the I(file) option. The default value is the C(op) - directory in C(jnpr.junos.op). This is the directory containing the - table/view definitions which are included in the PyEZ distribution. - required: false - default: C(op) directory in C(jnpr.junos.op) - type: path - aliases: - - directory - - dir - response_type: - description: - - Defines the format of data returned by the module. See RETURN. - The value of the I(resource) key in the module's response is either - a list of dictionaries C(list_of_dicts) or PyEZ's native return - format C(juniper_items). Because Ansible module's may only return JSON - data, PyEZ's native return format C(juniper_items) is translated into - a list of lists. - required: false - default: list_of_dicts - choices: - - list_of_dicts - - juniper_items - type: str - table: - description: - - Name of the PyEZ table used to retrieve data. If not specified, - defaults to the name of the table defined in the I(file) option. Any - table names in I(file) which begin with C(_) are ignored. If more than - one table is defined in I(file), the module fails with an error - message. In this case, you must manually specify the name of the table - by setting this option. - required: false - default: The name of the table defined in the I(file) option. - type: str -notes: - - This module only works with operational tables/views; it does not work with - configuration tables/views. -''' - -EXAMPLES = ''' ---- -- name: Retrieve data from a Junos device using a PyEZ table/view. - hosts: junos-all - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table - juniper_junos_table: - file: "lldp.yml" - register: response - - name: Print response - debug: - var: response - - - name: Retrieve routes within 192.68.1/8 - juniper_junos_table: - file: "routes.yml" - table: "RouteTable" - kwargs: - destination: "192.68.1.0/8" - response_type: "juniper_items" - register: response - - name: Print response - debug: - var: response - - - name: Retrieve from custom table in playbook directory - juniper_junos_table: - file: "fpc.yaml" - path: "." - register: response - - name: Print response - debug: - var: response -''' - -RETURN = ''' -changed: - description: - - Indicates if the device's configuration has changed. Since this - module does not change the operational or configuration state of the - device, the value is always set to C(false). - returned: success - type: bool -failed: - description: - - Indicates if the task failed. - returned: always - type: bool -msg: - description: - - A human-readable message indicating a summary of the result. - returned: always - type: str -resource: - description: - - The items retrieved by the table/view. - returned: success - type: list of dicts if I(response_type) is C(list_of_dicts) or list of - lists if I(respsonse_type) is C(juniper_items). - sample: | - # when response_type == 'list_of_dicts' - [ - { - "local_int": "ge-0/0/3", - "local_parent": "-", - "remote_chassis_id": "00:05:86:08:d4:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/0", - "remote_sysname": "r5", - "remote_type": "Mac address" - }, - { - "local_int": "ge-0/0/0", - "local_parent": "-", - "remote_chassis_id": "00:05:86:18:f3:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/2", - "remote_sysname": "r4", - "remote_type": "Mac address" - } - ] - # when response_type == 'juniper_items' - [ - [ - "ge-0/0/3", - [ - [ - "local_parent", - "-" - ], - [ - "remote_port_id", - "ge-0/0/0" - ], - [ - "remote_chassis_id", - "00:05:86:08:d4:c0" - ], - [ - "remote_port_desc", - null - ], - [ - "remote_type", - "Mac address" - ], - [ - "local_int", - "ge-0/0/3" - ], - [ - "remote_sysname", - "r5" - ] - ] - ], - [ - "ge-0/0/0", - [ - [ - "local_parent", - "-" - ], - [ - "remote_port_id", - "ge-0/0/2" - ], - [ - "remote_chassis_id", - "00:05:86:18:f3:c0" - ], - [ - "remote_port_desc", - null - ], - [ - "remote_type", - "Mac address" - ], - [ - "local_int", - "ge-0/0/0" - ], - [ - "remote_sysname", - "r4" - ] - ] - ] - ] -''' - -# Standard library imports -import os.path - -# Constants -RESPONSE_CHOICES = ['list_of_dicts', 'juniper_items'] - - -"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules -But custom module_utils directory is supported from Ansible 2.3 -Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ - -# Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common - -def expand_items(module, data): - """Recursively expand any table items - """ - resources = [] - # data.items() is a list of tuples - for table_key, table_fields in data.items(): - # sample: - # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), - # ('neighbor', 'vmx2')] - # table_key - element 0 is the key from the Table - not using at all - # table_fields - element 1 is also a list of tuples - temp = [] - for key, value in table_fields: - # calling it normalized value because YOU/WE created the keys - if value and isinstance(value, module.pyez_factory_table.Table): - value = expand_items(module, value) - temp.append((key, value)) - resources.append((table_key, temp)) - return resources - - -def juniper_items_to_list_of_dicts(module, data): - """Recursively convert Juniper PyEZ Table/View items to list of dicts. - """ - resources = [] - # data.items() is a list of tuples - for table_key, table_fields in data.items(): - # sample: - # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), - # ('neighbor', 'vmx2')] - # table_key - element 0 is the key from the Table - not using at all - # table_fields - element 1 is also a list of tuples - temp = {} - for key, value in table_fields: - if isinstance(value, module.pyez_factory_table.Table): - value = juniper_items_to_list_of_dicts(module, value) - temp[key] = value - resources.append(temp) - return resources - - -def main(): - # Create the module instance. - junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec=dict( - file=dict(type='path', - required=True, - default=None), - table=dict(type='str', - required=False, - default=None), - path=dict(type='path', - required=False, - aliases=['directory', 'dir'], - default=None), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='dict', - default=None), - response_type=dict(choices=RESPONSE_CHOICES, - type='str', - required=False, - default='list_of_dicts'), - ), - # Check mode is implemented. - supports_check_mode=True, - min_yaml_version=juniper_junos_common.MIN_YAML_VERSION, - ) - - # Straight from params - file = junos_module.params.get('file') - table = junos_module.params.get('table') - path = junos_module.params.get('path') - kwargs = junos_module.params.get('kwargs') - response_type = junos_module.params.get('response_type') - - if not file.endswith('.yml') and not file.endswith('.yaml'): - junos_module.fail_json(msg='The value of the file option must end ' - 'with the .yml or .yaml extension') - - # If needed, get the default path - if path is None: - path = os.path.dirname( - os.path.abspath(junos_module.pyez_op_table.__file__)) - - # file_name is path + file - file_name = os.path.join(path, file) - - junos_module.logger.debug("Attempting to open: %s.", file_name) - try: - with open(file_name, 'r') as fp: - try: - junos_module.logger.debug("Attempting to parse YAML from : " - "%s.", file_name) - table_view = junos_module.yaml.load(fp) - junos_module.logger.debug("YAML from %s successfully parsed.", - file_name) - except junos_module.yaml.YAMLError as ex: - junos_module.fail_json(msg='Failed parsing YAML file %s. ' - 'Error: %s' % (file_name, str(ex))) - except IOError: - junos_module.fail_json(msg='The file name %s could not be opened for' - 'reading.' % (file_name)) - junos_module.logger.debug("%s successfully read.", file_name) - - # Initialize the results. Assume failure until we know it's success. - results = {'msg': '', - 'changed': False, - 'failed': True} - - # Default to the table defined in file_name. - # Ignore table names which begin with an underscore. - if table is None: - for key in table_view: - if not key.startswith('_') and 'Table' in key: - if table is not None: - junos_module.fail_json( - msg='The file name %s contains multiple table ' - 'definitions. Specify the desired table with the ' - 'table option.' % (file_name)) - table = key - - if table is None: - junos_module.fail_json( - msg='No table definition was found in the %s file. Specify a ' - 'value for the file option which contains a valid table/view ' - 'definition.' % (file_name)) - junos_module.logger.debug("Table: %s", table) - - try: - loader = \ - junos_module.pyez_factory_loader.FactoryLoader().load(table_view) - junos_module.logger.debug("Loader created successfully.") - except Exception as ex: - junos_module.fail_json(msg='Unable to create a table loader from the ' - '%s file. Error: %s' % (file_name, str(ex))) - try: - data = loader[table](junos_module.dev) - junos_module.logger.debug("Table %s created successfully.", table) - if kwargs is None: - data.get() - else: - data.get(**kwargs) - junos_module.logger.debug("Data retrieved from %s successfully.", - table) - except KeyError: - junos_module.fail_json(msg='Unable to find table %s in the ' - '%s file.' % (table, file_name)) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.fail_json(msg='Unable to retrieve data from table %s. ' - 'Error: %s' % (table, str(ex))) - - if data is not None: - try: - len_data = len(data) - except Exception as ex: - junos_module.fail_json(msg='Unable to parse table %s data into ' - 'items. Error: %s' % (table, str(ex))) - junos_module.logger.debug('Successfully retrieved %d items from %s.', - len_data, table) - results['msg'] = 'Successfully retrieved %d items from %s.' % \ - (len_data, table) - - if response_type == 'list_of_dicts': - junos_module.logger.debug('Converting data to list of dicts.') - resource = juniper_items_to_list_of_dicts(junos_module, data) - else: - resource = expand_items(junos_module, data) - - # If we made it this far, everything was successful. - results['failed'] = False - results['resource'] = resource - - # Return response. - junos_module.exit_json(**results) - - -if __name__ == '__main__': - main() diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py deleted file mode 120000 index 3ca2aa75..00000000 --- a/ansible_collections/juniper/device/plugins/modules/ping.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_ping.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py new file mode 100644 index 00000000..872b6826 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/ping.py @@ -0,0 +1,502 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2016, Damien Garros +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_ping +author: Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Execute ping from a Junos device +description: + - Execute the ping command from a Junos device to a specified destination in + order to test network reachability from the Junos device . +options: + acceptable_percent_loss: + description: + - Maximum percentage of packets that may be lost and still consider the + task not to have failed. + required: false + default: 0 + type: int + aliases: + - acceptable_packet_loss + count: + description: + - Number of packets to send. + required: false + default: 5 + type: int + dest: + description: + - The IP address, or hostname if DNS is configured on the Junos device, + used as the destination of the ping. + required: true + default: none + type: str + aliases: + - dest_ip + - dest_host + - destination + - destination_ip + - destination_host + do_not_fragment: + description: + - Set Do Not Fragment bit on ping packets. + required: false + default: false + type: bool + interface: + description: + - The source interface from which the the ping is sent. If not + specified, the default Junos algorithm for determining the source + interface is used. + required: false + default: none + type: str + rapid: + description: + - Send ping requests rapidly + required: false + default: true + type: bool + routing_instance: + description: + - Name of the source routing instance from which the ping is + originated. If not specified, the default routing instance is used. + required: false + default: none + type: str + size: + description: + - The size of the ICMP payload of the ping. + - Total size of the IP packet is I(size) + the 20 byte IP header + + the 8 byte ICMP header. Therefore, I(size) of C(1472) generates an IP + packet of size 1500. + required: false + default: none (default size for device) + type: int + source: + description: + - The IP address, or hostname if DNS is configured on the Junos device, + used as the source address of the ping. If not specified, the Junos + default algorithm for determining the source address is used. + required: false + default: none + type: str + aliases: + - source_ip + - source_host + - src + - src_ip + - src_host + ttl: + description: + - Maximum number of IP routers (hops) allowed between source and + destination. + required: false + default: none (default ttl for device) + type: int +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_ping + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. + juniper_junos_ping: + dest: "192.68.1.1" + + - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + acceptable_percent_loss: 50 + register: response + - name: Print all keys in the response. + debug: + var: response + + - name: Ping 192.68.1.1. Send 20 packets. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + count: 20 + register: response + - name: Print packet sent from the response. + debug: + var: response.packets_sent + + - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + count: 10 + rapid: false + register: response + - name: Print the average round-trip-time from the response. + debug: + var: response.rtt_average + + - name: Ping www.juniper.net with ttl 15. Register response. + juniper_junos_ping: + dest: "www.juniper.net" + ttl: 15 + register: response + - name: Print the packet_loss percentage from the response. + debug: + var: response.packet_loss + + - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + size: 1472 + register: response + - name: Print the packets_received from the response. + debug: + var: response.packets_received + + - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + do_not_fragment: true + register: response + - name: Print the maximum round-trip-time from the response. + debug: + var: response.rtt_maximum + + - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. + juniper_junos_ping: + dest: "192.68.1.1" + source: "192.68.1.2" + register: response + - name: Print the source from the response. + debug: + var: response.source + + - name: Ping 192.168.1.1 from the red routing-instance. + juniper_junos_ping: + dest: "192.168.1.1" + routing_instance: "red" + + - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface + juniper_junos_ping: + dest: "224.0.0.1" + interface: "ge-0/0/0.0" +''' + +RETURN = ''' +acceptable_percent_loss: + description: + - The acceptable packet loss (as a percentage) for this task as specified + by the I(acceptable_percent_loss) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +changed: + description: + - Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to C(false). + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +count: + description: + - The number of pings sent, as specified by the I(count) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +do_not_fragment: + description: + - Whether or not the do not fragment bit was set on the pings sent, as + specified by the I(do_not_fragment) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +host: + description: + - The destination IP/host of the pings sent as specified by the I(dest) + option. + - Keys I(dest) and I(dest_ip) are also returned for backwards + compatibility. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +interface: + description: + - The source interface of the pings sent as specified by the + I(interface) option. + returned: when ping successfully executed and the I(interface) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +packet_loss: + description: + - The percentage of packets lost. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +packets_sent: + description: + - The number of packets sent. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +packets_received: + description: + - The number of packets received. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +rapid: + description: + - Whether or not the pings were sent rapidly, as specified by the + I(rapid) option. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: bool +routing_instance: + description: + - The routing-instance from which the pings were sent as specified by + the I(routing_instance) option. + returned: when ping successfully executed and the I(routing_instance) + option was specified, even if the I(acceptable_percent_loss) was + exceeded. + type: str +rtt_average: + description: + - The average round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_maximum: + description: + - The maximum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_minimum: + description: + - The minimum round-trip-time, in microseconds, of all ping responses + received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +rtt_stddev: + description: + - The standard deviation of round-trip-time, in microseconds, of all ping + responses received. + returned: when ping successfully executed, and I(packet_loss) < 100%. + type: str +size: + description: + - The size in bytes of the ICMP payload on the pings sent as specified + by the I(size) option. + - Total size of the IP packet is I(size) + the 20 byte IP header + the 8 + byte ICMP header. Therefore, I(size) of 1472 generates an IP packet of + size 1500. + returned: when ping successfully executed and the I(size) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +source: + description: + - The source IP/host of the pings sent as specified by the I(source) + option. + - Key I(source_ip) is also returned for backwards compatibility. + returned: when ping successfully executed and the I(source) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +timeout: + description: + - The number of seconds to wait for a response from the ping RPC. + returned: when ping successfully executed, even if the + I(acceptable_percent_loss) was exceeded. + type: str +ttl: + description: + - The time-to-live set on the pings sent as specified by the + I(ttl) option. + returned: when ping successfully executed and the I(ttl) option was + specified, even if the I(acceptable_percent_loss) was exceeded. + type: str +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list +''' + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + +def main(): + # The argument spec for the module. + argument_spec = dict( + dest=dict(type='str', + required=True, + aliases=['dest_ip', 'dest_host', 'destination', + 'destination_ip', 'destination_host'], + default=None), + acceptable_percent_loss=dict(type='int', + required=False, + aliases=['acceptable_packet_loss'], + default=0), + ) + + # The portion of the argument spec that's specifically a parameter + # to the ping RPC. + ping_argument_spec = dict( + count=dict(type='int', + required=False, + default=5), + rapid=dict(type='bool', + required=False, + default=True), + ttl=dict(type='int', + required=False, + default=None), + size=dict(type='int', + required=False, + default=None), + do_not_fragment=dict(type='bool', + required=False, + default=False), + source=dict(type='str', + required=False, + aliases=['source_ip', 'source_host', 'src', + 'src_ip', 'src_host'], + default=None), + interface=dict(type='str', + required=False, + default=None), + routing_instance=dict(type='str', + required=False, + default=None), + ) + + # Add the ping RPC parameter argument spec fo the full argument_spec. + argument_spec.update(ping_argument_spec) + + argument_spec_keys = list(argument_spec.keys()) + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=argument_spec, + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + # acceptable packet loss is a percentage. Check to make sure it's between + # 0 and 100 inclusive + if (params['acceptable_percent_loss'] > 100 or + params['acceptable_percent_loss'] < 0): + junos_module.fail_json(msg='The value of the acceptable_percent_loss' + 'option (%d) is a percentage and must have ' + 'a value between 0 and 100.' % + (params['acceptable_percent_loss'])) + + # All of the params keys which are also keys in ping_argument_spec are the + # ping_params. Omit None and False values because they don't need to be + # passed to the RPC. + ping_params = {'host': params.get('dest')} + for key in ping_argument_spec: + value = params.get(key) + # Convert int (but not bool) to str + if not isinstance(value, bool) and isinstance(value, int): + params[key] = str(params[key]) + value = params.get(key) + # None and False values are the default for the RPC and shouldn't be + # passed to the device. + if value is not None and value is not False: + ping_params.update({key: value}) + + # Set initial results values. Assume failure until we know it's success. + results = {'msg': '', 'changed': False, 'failed': True} + # Results should include all the ping params in argument_spec_keys. + for key in argument_spec_keys: + results[key] = params.get(key) + # Overwrite to be a string in the results + results['acceptable_percent_loss'] = str( + params.get('acceptable_percent_loss')) + # Add timeout to the response even though it's a connect parameter. + results['timeout'] = str(params.get('timeout')) + # Add aliases for backwards compatibility + results.update({'host': params.get('dest'), + 'dest_ip': params.get('dest'), + 'source_ip': params.get('source')}) + + # Execute the ping. + results = junos_module.ping( + ping_params, + acceptable_percent_loss=params['acceptable_percent_loss'], + results=results) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py deleted file mode 120000 index b7bdfe9b..00000000 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_pmtud.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py new file mode 100644 index 00000000..6add19a1 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -0,0 +1,408 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2017, Martin Komon +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_pmtud +author: + - Martin Komon (@mkomon) + - Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Perform path MTU discovery from a Junos device to a + destination +description: + - Determine the maximum IP MTU supported along a path from a Junos device to + a user-specified destination by performing path MTU discovery (PMTUD) using + the ping command. The reported MTU will be between min_test_size and + I(max_size) where I(min_test_size) = (I(max_size) - I(max_range) + 1). + If the actual path MTU is greater than I(max_size), then I(max_size) will + be reported. If the actual path MTU is less than I(min_test_size), then a + failure will be reported. +options: + dest: + description: + - The IPv4 address, or hostname if DNS is configured on the Junos device, + used as the destination of the PMTUD. + required: true + default: none + type: str + aliases: + - dest_ip + - dest_host + - destination + - destination_ip + - destination_host + interface: + description: + - The source interface from which the the PMTUD is performed. If not + specified, the default Junos algorithm for determining the source + interface is used. + required: false + default: none + type: str + max_range: + description: + - The maximum range of MTU values, in bytes, which will be searched + when performing path MTU discovery. This value must be C(0) or + a power of 2 (2^n) between C(2) and C(65536). The minimum IPv4 MTU + value attempted when performing path MTU discovery is + I(min_test_size) = (I(max_size) - I(max_range) + 1) + required: false + default: 512 + type: int + max_size: + description: + - The maximum IPv4 MTU, in bytes, to attempt when performing path MTU + discovery. + - The value returned for I(inet_mtu) will be no more + than this value even if the path actually supports a higher MTU. + - This value must be between 68 and 65496. + required: false + default: 1500 + type: int + routing_instance: + description: + - Name of the source routing instance from which the ping is + originated. + - If not specified, the default routing instance is used. + required: false + default: none + type: str + source: + description: + - The IPv4 address, or hostname if DNS is configured on the Junos device, + used as the source address of the PMTUD. If not specified, the Junos + default algorithm for determining the source address is used. + required: false + default: none + type: str + aliases: + - source_ip + - source_host + - src + - src_ip + - src_host +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_mtud + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Perform PMTUD to 192.68.1.1 with default parameters. + juniper_junos_pmtud: + dest: "192.68.1.1" + + - name: Perform PMTUD to 192.68.1.1. Register response. + juniper_junos_pmtud: + dest: "192.68.1.1" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. + juniper_junos_pmtud: + dest: "192.68.1.1" + max_size: 65496 + max_range: 65536 + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. + juniper_junos_pmtud: + dest: "192.68.1.1" + interface: "ge-0/0/0.0" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. + juniper_junos_pmtud: + dest: "192.68.1.1" + source: "192.168.1.2" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. + juniper_junos_pmtud: + dest: "192.68.1.1" + routing_instance: "red" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's state has changed. Since this module + doesn't change the operational or configuration state of the + device, the value is always set to C(false). + returned: when PMTUD successfully executed. + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +host: + description: + - The destination IP/host of the PMTUD as specified by the I(dest) + option. + - Keys I(dest) and I(dest_ip) are also returned for backwards + compatibility. + returned: when PMTUD successfully executed. + type: str +inet_mtu: + description: + - The IPv4 path MTU size in bytes to the I(dest). This is the lesser of + I(max_size) and the actual path MTU to I(dest). If the actual path + MTU is less than I(min_test_size), then a failure is reported. Where + I(min_test_size) = (I(max_size) - I(max_range) + 1) + returned: when PMTUD successfully executed. + type: str +interface: + description: + - The source interface of the PMTUD as specified by the I(interface) + option. + returned: when the I(interface) option was specified. + type: str +routing_instance: + description: + - The routing-instance from which the PMTUD was performed as specified by + the I(routing_instance) option. + returned: when the I(routing_instance) option was specified. + type: str +source: + description: + - The source IP/host of the PMTUD as specified by the I(source) + option. + - Key I(source_ip) is also returned for backwards compatibility. + returned: when the I(source) option was specified. + type: str +warnings: + description: + - A list of warning strings, if any, produced from the ping. + returned: when warnings are present + type: list +''' + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + +def main(): + # Constants for MTU size + INET_MIN_MTU_SIZE = 68 # As prescribed by RFC 791, Section 3.2 - + # Fragmentation and Reassembly. + INET_MAX_MTU_SIZE = 65496 # Size of inet header's total length field is + # 16 bits. Therefore max inet packet size is 2^16 + # or 65536, but Junos only supports max IP size + # of 65496 for the ping command in order to + # accomodate a (potentially) maximum sized IP + # header. + + # Constants for the size of headers + INET_HEADER_SIZE = 20 + ICMP_HEADER_SIZE = 8 + INET_AND_ICMP_HEADER_SIZE = INET_HEADER_SIZE + ICMP_HEADER_SIZE + + # Choices for max_size + MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2 ** x, range(1, 17))) + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + dest=dict(type='str', + required=True, + aliases=['dest_ip', 'dest_host', 'destination', + 'destination_ip', 'destination_host'], + default=None), + max_size=dict(type='int', + required=False, + default=1500), + max_range=dict(type='int', + required=False, + choices=MAX_SIZE_CHOICES, + default=512), + source=dict(type='str', + required=False, + aliases=['source_ip', 'source_host', 'src', + 'src_ip', 'src_host'], + default=None), + interface=dict(type='str', + required=False, + default=None), + routing_instance=dict(type='str', + required=False, + default=None), + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + # max_size must be between INET_MIN_MTU_SIZE and INET_MAX_MTU_SIZE + if (params['max_size'] < INET_MIN_MTU_SIZE or + params['max_size'] > INET_MAX_MTU_SIZE): + junos_module.fail_json(msg='The value of the max_size option(%d) ' + 'must be between %d and %d.' % + (params['max_size'], INET_MIN_MTU_SIZE, + INET_MAX_MTU_SIZE)) + + # Initialize ping parameters. + ping_params = {'host': params.get('dest'), + 'count': '3', + 'rapid': True, + 'inet': True, + 'do_not_fragment': True} + + # Add optional ping parameters + o_ping_params = {} + if params['source'] is not None: + o_ping_params['source'] = params['source'] + if params['interface'] is not None: + o_ping_params['interface'] = params['interface'] + if params['routing_instance'] is not None: + o_ping_params['routing_instance'] = params['routing_instance'] + ping_params.update(o_ping_params) + + # Set initial results values. Assume failure until we know it's success. + results = {'changed': False, + 'failed': True, + 'inet_mtu': 0, + 'host': params.get('dest')} + # Results should include all the o_ping_params. + for key in o_ping_params: + results[key] = ping_params.get(key) + # Add aliases for backwards compatibility + results.update({'dest': ping_params.get('host'), + 'dest_ip': ping_params.get('host'), + 'source_ip': ping_params.get('source')}) + + # Execute a minimally-sized ping just to verify basic connectivity. + junos_module.logger.debug("Verifying basic connectivity.") + ping_params['size'] = str(INET_MIN_MTU_SIZE - + INET_AND_ICMP_HEADER_SIZE) + results_for_minimal = dict(results) + results_for_minimal = junos_module.ping(ping_params, + acceptable_percent_loss=100, + results=results_for_minimal) + if int(results_for_minimal.get('packet_loss', 100)) == 100: + results['msg'] = "Basic connectivity to %s failed." % (results['host']) + junos_module.exit_json(**results) + + # Initialize test_size and step + test_size = params['max_size'] + step = params['max_range'] + min_test_size = test_size - (params['max_range'] - 1) + if min_test_size < INET_MIN_MTU_SIZE: + min_test_size = INET_MIN_MTU_SIZE + + while True: + if test_size < INET_MIN_MTU_SIZE: + test_size = INET_MIN_MTU_SIZE + if test_size > params['max_size']: + test_size = params['max_size'] + junos_module.logger.debug("Probing with size: %d", test_size) + step = step // 2 if step >= 2 else 0 + ping_params['size'] = str(test_size - INET_AND_ICMP_HEADER_SIZE) + current_results = dict(results) + current_results = junos_module.ping(ping_params, + acceptable_percent_loss=100, + results=current_results) + loss = int(current_results.get('packet_loss', 100)) + if loss < 100 and test_size == params['max_size']: + # ping success with max test_size, save and break + results['failed'] = False + results['inet_mtu'] = test_size + break + elif loss < 100: + # ping success, increase test_size + results['failed'] = False + results['inet_mtu'] = test_size + test_size += step + else: + # ping fail, lower size + test_size -= step + if step < 1: + break + + if results.get('inet_mtu', 0) == 0: + junos_module.fail_json(msg='The MTU of the path to %s is less than ' + 'the minimum tested size(%d). Try ' + 'decreasing max_size(%d) or increasing ' + 'max_range(%d).' % (results['host'], + min_test_size, + params['max_size'], + params['max_range']), + **results) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py deleted file mode 120000 index 9e5b8b3f..00000000 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_rpc.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py new file mode 100644 index 00000000..855dc55a --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -0,0 +1,651 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2016, Nitin Kumar +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function +from six import iteritems + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_rpc +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Execute one or more NETCONF RPCs on a Junos device +description: + - Execute one or more NETCONF RPCs on a Junos device. + - Use the C(| display xml rpc) modifier to determine the equivalent RPC + name for a Junos CLI command. For example, + C(show version | display xml rpc) reveals the equivalent RPC name is + C(get-software-information). +options: + attrs: + description: + - The attributes and values to the RPCs specified by the + I(rpcs) option. The value of this option can either be a single + dictionary of keywords and values, or a list of dictionaries + containing keywords and values. + - There is a one-to-one correspondence between the elements in the + I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the + two lists must always contain the same number of elements. + required: false + default: none + type: dict or list of dict + aliases: + - attr + dest: + description: + - The path to a file, on the Ansible control machine, where the output of + the RPC will be saved. + - The file must be writeable. If the file already exists, it is + overwritten. + - When tasks are executed against more than one target host, + one process is forked for each target host. (Up to the maximum + specified by the forks configuration. See + U(forks|http://docs.ansible.com/ansible/latest/intro_configuration.html#forks) + for details.) This means that the value of this option must be unique + per target host. This is usually accomplished by including + C({{ inventory_hostname }}) in the I(dest) value. It is the user's + responsibility to ensure this value is unique per target host. + - For this reason, this option is deprecated. It is maintained for + backwards compatibility. Use the I(dest_dir) option in new playbooks. + The I(dest) and I(dest_dir) options are mutually exclusive. + required: false + default: None + type: path + aliases: + - destination + dest_dir: + description: + - The path to a directory, on the Ansible control machine, where + the output of the RPC will be saved. The output will be logged + to a file named C({{ inventory_hostname }}_)I(rpc).I(format) + in the I(dest_dir) directory. + - The destination file must be writeable. If the file already exists, + it is overwritten. It is the users responsibility to ensure a unique + I(dest_dir) value is provided for each execution of this module + within a playbook. + - The I(dest_dir) and I(dest) options are mutually exclusive. The + I(dest_dir) option is recommended for all new playbooks. + required: false + default: None + type: path + aliases: + - destination_dir + - destdir + filter: + description: + - This argument only applies if the I(rpcs) option contains a single + RPC with the value C(get-config). When used, this value specifies an + XML filter used to restrict the portions of the configuration which are + retrieved. See the PyEZ + U(get_config method|http://junos-pyez.readthedocs.io/en/stable/jnpr.junos.html#jnpr.junos.rpcmeta._RpcMetaExec.get_config) + for details on the value of this option. + required: false + default: none + type: str + aliases: + - filter_xml + formats: + description: + - The format of the reply for the RPCs specified by the + I(rpcs) option. + - The specified format(s) must be supported by the + target Junos device. + - The value of this option can either be a single + format, or a list of formats. If a single format is specified, it + applies to all RPCs specified by the I(rpcs) option. If a + list of formats are specified, there must be one value in the list for + each RPC specified by the I(rpcs) option. + required: false + default: xml + type: str or list of str + choices: + - text + - xml + - json + aliases: + - format + - display + - output + ignore_warning: + description: + - A boolean, string or list of strings. If the value is C(true), + ignore all warnings regardless of the warning message. If the value + is a string, it will ignore warning(s) if the message of each warning + matches the string. If the value is a list of strings, ignore + warning(s) if the message of each warning matches at least one of the + strings in the list. The value of the I(ignore_warning) option is + applied to the load and commit operations performed by this module. + required: false + default: none + type: bool, str, or list of str + kwargs: + description: + - The keyword arguments and values to the RPCs specified by the + I(rpcs) option. The value of this option can either be a single + dictionary of keywords and values, or a list of dictionaries + containing keywords and values. + - There must be a one-to-one correspondence between the elements in the + I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the + two lists must always contain the same number of elements. For RPC + arguments which do not require a value, specify the value of True as + shown in the :ref:`juniper_junos_rpc-examples-label`. + required: false + default: none + type: dict or list of dict + aliases: + - kwarg + - args + - arg + return_output: + description: + - Indicates if the output of the RPC should be returned in the + module's response. You might want to set this option to C(false), + and set the I(dest_dir) option, if the RPC output is very large + and you only need to save the output rather than using it's content in + subsequent tasks/plays of your playbook. + required: false + default: true + type: bool + rpcs: + description: + - A list of one or more NETCONF RPCs to execute on the Junos device. + required: true + default: none + type: list + aliases: + - rpc +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_rpc + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Execute single get-software-information RPC. + juniper_junos_rpc: + rpcs: "get-software-information" + register: response + - name: Print the RPC's output as a single multi-line string. + debug: + var: response.stdout + +###### OLD EXAMPLES ########## +- junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + dest=get_interface_information.conf + register=junos + +- junos_rpc: + host={{ inventory_hostname }} + rpc=get-interface-information + kwargs="interface_name=em0" + format=xml/text/json + dest=get_interface_information.conf + register=junos + +# Example to fetch device configuration +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + dest=get_config.conf + +# Fetch configuration over console server connection using PyEZ >= 2.0 +- name: Get Device Configuration + junos_rpc: + host={{ inventory_hostname }} + port=7005 + mode='telnet' + rpc=get-config + dest=get_config.conf + +# Example to fetch device configuration +- name: Get Device Configuration for interface + junos_rpc: + host={{ inventory_hostname }} + rpc=get-config + filter_xml="" + dest=get_config.conf + register: junos + +# Example to fetch configuration in json for >=14.2 +# and use it with rpc_reply +- name: Get Device Configuration + hosts: all + collections: + - juniper.device + connection: local + gather_facts: no + tasks: + - name: Get interface information + junos_rpc: + host: "{{ inventory_hostname }}" + rpc: get-interface-information + kwargs: + interface_name: em0 + media: True + format: json + dest: get_interface_information.conf + register: junos + + - name: Print configuration + debug: msg="{{ junos.rpc_reply }}" +###### OLD EXAMPLES ########## +''' + +RETURN = ''' +attrs: + description: + - The RPC attributes and values from the list of dictionaries in the + I(attrs) option. This will be none if no attributes are applied to the + RPC. + returned: always + type: dict +changed: + description: + - Indicates if the device's state has changed. Since this module doesn't + change the operational or configuration state of the device, the value + is always set to C(false). + - You could use this module to execute an RPC which + changes the operational state of the the device. For example, + C(clear-ospf-neighbor-information). Beware, this module is unable to + detect this situation, and will still return a I(changed) value of + C(false) in this case. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. See the I(results) key for additional + details. + returned: always + type: bool +format: + description: + - The format of the RPC response from the list of formats in the I(formats) + option. + returned: always + type: str + choices: + - text + - xml + - json +kwargs: + description: + - The keyword arguments from the list of dictionaries in the I(kwargs) + option. This will be C(none) if no kwargs are applied to the RPC. + returned: always + type: dict +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +parsed_output: + description: + - The RPC reply from the Junos device parsed into a JSON datastructure. + For XML replies, the response is parsed into JSON using the + U(jxmlease|https://github.com/Juniper/jxmlease) + library. For JSON the response is parsed using the Python + U(json|https://docs.python.org/2/library/json.html) library. + - When Ansible converts the jxmlease or native Python data structure + into JSON, it does not guarantee that the order of dictionary/object keys + are maintained. + returned: when RPC executed successfully, I(return_output) is C(true), + and the RPC format is C(xml) or C(json). + type: dict +results: + description: + - The other keys are returned when a single RPC is specified for the + I(rpcs) option. When the value of the I(rpcs) option is a list + of RPCs, this key is returned instead. The value of this key is a + list of dictionaries. Each element in the list corresponds to the + RPCs in the I(rpcs) option. The keys for each element in the list + include all of the other keys listed. The I(failed) key indicates if the + individual RPC failed. In this case, there is also a top-level + I(failed) key. The top-level I(failed) key will have a value of C(false) + if ANY of the RPCs ran successfully. In this case, check the value + of the I(failed) key for each element in the I(results) list for the + results of individual RPCs. + returned: when the I(rpcs) option is a list value. + type: list of dict +rpc: + description: + - The RPC which was executed from the list of RPCs in the I(rpcs) option. + returned: always + type: str +stdout: + description: + - The RPC reply from the Junos device as a single multi-line string. + returned: when RPC executed successfully and I(return_output) is C(true). + type: str +stdout_lines: + description: + - The RPC reply from the Junos device as a list of single-line strings. + returned: when RPC executed successfully and I(return_output) is C(true). + type: list of str +''' + +import os.path + + +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + rpcs=dict(required=True, + type='list', + aliases=['rpc'], + default=None), + formats=dict(required=False, + type='list', + aliases=['format', 'display', 'output'], + default=None), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='str', + default=None), + attrs=dict(required=False, + type='str', + aliases=['attr'], + default=None), + filter=dict(required=False, + type='str', + aliases=['filter_xml'], + default=None), + dest=dict(required=False, + type='path', + aliases=['destination'], + default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir'], + default=None), + ignore_warning=dict(required=False, + type='list', + default=None), + return_output=dict(required=False, + type='bool', + default=True) + ), + # Since this module doesn't change the device's configuration, there is + # no additional work required to support check mode. It's inherently + # supported. Well, that's not completely true. It does depend on the + # RPC executed. See the I(changed) key in the RETURN documentation + # for more details. + supports_check_mode=True, + min_jxmlease_version=cfg.MIN_JXMLEASE_VERSION, + ) + + # Check over rpcs + rpcs = junos_module.params.get('rpcs') + # Ansible allows users to specify a rpcs argument with no value. + if rpcs is None: + junos_module.fail_json(msg="The rpcs option must have a value.") + + # Parse ignore_warning value + ignore_warning = junos_module.parse_ignore_warning_option() + # Check over formats + formats = junos_module.params.get('formats') + if formats is None: + # Default to xml format + formats = ['xml'] + valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES + # Check format values + for format in formats: + # Is it a valid format? + if format not in valid_formats: + junos_module.fail_json(msg="The value %s in formats is invalid. " + "Must be one of: %s" % + (format, ', '.join(map(str, + valid_formats)))) + # Correct number of format values? + if len(formats) != 1 and len(formats) != len(rpcs): + junos_module.fail_json(msg="The formats option must have a single " + "value, or one value per rpc. There " + "are %d rpcs and %d formats." % + (len(rpcs), len(formats))) + # Same format for all rpcs + elif len(formats) == 1 and len(rpcs) > 1: + formats = formats * len(rpcs) + + # Check over kwargs + kwstring = junos_module.params.get('kwargs') + kwargs = junos_module.parse_arg_to_list_of_dicts('kwargs', + kwstring, + allow_bool_values=True) + if kwargs is not None: + if len(kwargs) != len(rpcs): + junos_module.fail_json(msg="The kwargs option must have one value " + "per rpc. There are %d rpcs and %d " + "kwargs." % + (len(rpcs), len(kwargs))) + else: + kwargs = [None] * len(rpcs) + + # Check over attrs + attrstring = junos_module.params.get('attrs') + attrs = junos_module.parse_arg_to_list_of_dicts('attrs', + attrstring) + if attrs is not None: + if len(attrs) != len(rpcs): + junos_module.fail_json(msg="The attrs option must have one value" + "per rpc. There are %d rpcs and %d " + "attrs." % + (len(rpcs), len(attrs))) + else: + attrs = [None] * len(rpcs) + + # Check filter + if junos_module.params.get('filter') is not None: + if (len(rpcs) != 1 or (rpcs[0] != 'get-config' and + rpcs[0] != 'get_config')): + junos_module.fail_json(msg="The filter option is only valid " + "when the rpcs option value is a " + "single 'get-config' RPC.") + + results = list() + for (rpc_string, format, kwarg, attr) in zip(rpcs, formats, kwargs, attrs): + # Replace underscores with dashes in RPC name. + rpc_string = rpc_string.replace('_', '-') + # Set initial result values. Assume failure until we know it's success. + result = {'msg': '', + 'rpc': rpc_string, + 'format': format, + 'kwargs': kwarg, + 'attrs': attr, + 'changed': False, + 'failed': True} + + # Execute the RPC + try: + #for get-config in case of exception handling it will not display + #filters and arguments. To be added in future. + rpc = junos_module.etree.Element(rpc_string, format=format) + if rpc_string == 'get-config': + filter = junos_module.params.get('filter') + if attr is None: + attr = {} + if kwarg is None: + kwarg = {} + if format is not None: + attr['format'] = format + junos_module.logger.debug('Executing "get-config" RPC. ' + 'filter_xml=%s, options=%s, ' + 'kwargs=%s', + filter, str(attr), str(kwarg)) + # not adding ignore_warning as we don't expect to get rpc-error + # with severity warning during get_config + if junos_module.conn_type == "local": + resp = junos_module.dev.rpc.get_config(filter_xml=filter, + options=attr, **kwarg) + else: + resp = junos_module.get_config(filter_xml=filter, + options=attr, **kwarg) + result['msg'] = 'The "get-config" RPC executed successfully.' + junos_module.logger.debug('The "get-config" RPC executed ' + 'successfully.') + else: + if kwarg is not None: + # Add kwarg + for (key, value) in iteritems(kwarg): + # Replace underscores with dashes in key name. + key = key.replace('_', '-') + sub_element = junos_module.etree.SubElement(rpc, key) + if not isinstance(value, bool): + sub_element.text = value + if attr is not None: + # Add attr + for (key, value) in iteritems(attr): + # Replace underscores with dashes in key name. + key = key.replace('_', '-') + rpc.set(key, value) + junos_module.logger.debug('Executing RPC "%s".', + junos_module.etree.tostring( + rpc, + pretty_print=True)) + if junos_module.conn_type == "local": + resp = junos_module.dev.rpc(rpc, + normalize=bool(format == 'xml')) + else: + resp = junos_module.get_rpc(rpc, + ignore_warning=ignore_warning) + result['msg'] = 'The RPC executed successfully.' + junos_module.logger.debug('RPC "%s" executed successfully.', + junos_module.etree.tostring( + rpc, + pretty_print=True)) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Unable to execute RPC "%s". Error: %s', + junos_module.etree.tostring( + rpc, + pretty_print=True), str(ex)) + result['msg'] = 'Unable to execute the RPC: %s. Error: %s' % \ + (junos_module.etree.tostring(rpc, + pretty_print=True), + str(ex)) + results.append(result) + continue + + text_output = None + parsed_output = None + if resp is True: + text_output = '' + elif (resp, junos_module.etree._Element): + # Handle the output based on format + if format == 'text': + text_output = resp.text + junos_module.logger.debug('Text output set.') + elif format == 'xml': + text_output = junos_module.etree.tostring(resp, + pretty_print=True) + parsed_output = junos_module.jxmlease.parse_etree(resp) + junos_module.logger.debug('XML output set.') + elif format == 'json': + text_output = str(resp) + parsed_output = resp + junos_module.logger.debug('JSON output set.') + else: + result['msg'] = 'Unexpected format %s.' % (format) + results.append(result) + junos_module.logger.debug('Unexpected format %s.', format) + continue + else: + result['msg'] = 'Unexpected response type %s.' % (type(resp)) + results.append(result) + junos_module.logger.debug('Unexpected response type %s.', + type(resp)) + continue + + # Set the output keys + if junos_module.params['return_output'] is True: + if text_output is not None: + result['stdout'] = text_output + result['stdout_lines'] = text_output.splitlines() + if parsed_output is not None: + result['parsed_output'] = parsed_output + # Save the output + junos_module.save_text_output(rpc_string, format, text_output) + # This command succeeded. + result['failed'] = False + # Append to the list of results + results.append(result) + + # Return response. + if len(results) == 1: + junos_module.exit_json(**results[0]) + else: + # Calculate the overall failed. Only failed if all commands failed. + failed = True + for result in results: + if result.get('failed') is False: + failed = False + break + junos_module.exit_json(results=results, + changed=False, + failed=failed) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py deleted file mode 120000 index 0a63f359..00000000 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_software.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py new file mode 100644 index 00000000..9eaf046f --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -0,0 +1,785 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_software +author: + - Jeremy Schulman + - "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Install software on a Junos device +description: + - > + Install a Junos OS image, or other software package, on a Junos device. + This action is generally equivalent to the C(request system software add) + operational-mode CLI command. It performs the following + steps in order: + + + #. Compare the currently installed Junos version to the desired version + specified by the I(version) option. + + * If the current and desired versions are the same, stop and return + I(changed) with a value of C(false). + * If running in check mode, and the current and desired versions differ, + stop and return I(changed) with a value of C(true). + * Otherwise, proceed. + #. If the I(local_package) option is specified, compute the MD5 checksum + of the I(local_package) file on the local Ansible control machine. + #. Check if the file exists at the I(remote_package) location on the target + Junos device. If so, compute the MD5 checksum of the file on the target + Junos device. + #. If the I(cleanfs) option is C(true), the default, then perform the + equivalent of the C(request system storage cleanup) CLI command. + #. If the checksums computed in steps 2 and 3 differ, or if the + I(remote_package) file does not exist on the target Junos device, then + copy the package from I(local_package) on the local Ansible control + machine to I(remote_package) on the target Junos device. + #. Install the software pacakge from the I(remote_package) location on the + target Junos device using the options specified. + #. If the I(reboot) option is C(true), the default, initiate a reboot of + the target Junos device. +options: + all_re: + description: + - Whether or not to install the software on all Routing Engines of the + target Junos device. If C(true), and the device has multiple Routing + Engines, the software is installed on all Routing Engines. If C(false), + the software is only installed on the current Routing Engine. + required: false + default: true + type: bool + checksum: + description: + - The pre-calculated checksum, using the I(checksum_algorithm) of the + file specified by the I(local_package) option. Specifying this option + is simply an optimization to avoid repeatedly computing the checksum of + the I(local_package) file once for each target Junos host. + required: false + default: none + type: str + checksum_algorithm: + description: + - The algorithm to use when calculating the checksum of the local and + remote software packages. + required: false + default: md5 + type: str + checksum_timeout: + description: + - The number of seconds to wait for the calculation of the checksum to + complete on the target Junos device. + required: false + default: 300 (5 minutes) + type: int + cleanfs: + description: + - Whether or not to perform a C(request system storage cleanup) prior to + copying or installing the software. + required: false + default: true (unless I(no_copy) is C(true), then C(false)) + type: bool + cleanfs_timeout: + description: + - The number of seconds to wait for the + C(request system storage cleanup) to complete on the target Junos + device. + required: false + default: 300 (5 minutes) + type: int + force_host: + description: + - Forces the upgrade of the Host Software package on QFX-series devices. + required: false + default: false + type: bool + install_timeout: + description: + - The number of seconds to wait for the software installation to + complete on the target Junos device. + required: false + default: 1800 (30 minutes) + type: int + issu: + description: + - Indicates if a unified in-service software upgrade (ISSU) should be + attempted. ISSU enables the upgrade between two different + Junos OS releases with no control plane disruption and minimal data + plane traffic disruption. + - In order for an ISSU to succeed, ISSU must be supported. This includes + support for the current to desired Junos versions, the hardware + of the target Junos device, and the current software configuration of + the target Junos device. + - The I(issu) and I(nssu) options are mutually exclusive. + required: false + default: false + type: bool + kwargs: + description: + - Additional keyword arguments and values which are passed to the + C() RPC used to install the software package. The + value of this option is a dictionary of keywords and values. + required: false + default: none + type: dict + aliases: + - kwarg + - args + - arg + local_package: + description: + - The path, on the local Ansible control machine, of a Junos software + package. This Junos software package will be installed on the target + Junos device. + - If this option is specified, and a file with the same MD5 checksum + doesn't already exist at the I(remote_package) location on the target + Junos device, then the file is copied from the local Ansible control + machine to the target Junos device. + - If this option is not specified, it is assumed that the + software package already exists on the target Junos device. In this + case, the I(remote_package) option must be specified. + required: false + default: none + type: path + aliases: + - package + no_copy: + description: + - Indicates if the file containing the software package should be copied + from the I(local_package) location on the local Ansible control + machine to the I(remote_package) location on the target Junos device. + - If the value is C(true), or if the I(local_package) option is not + specified, then the copy is skipped and the file must already exist + at the I(remote_package) location on the target Junos device. + required: false + default: false + type: bool + nssu: + description: + - Indicates if a non-stop software upgrade (NSSU) should be + attempted. NSSU enables the upgrade between two different + Junos OS releases with minimal data plane traffic disruption. + - NSSU is specific to EX-series Virtual Chassis systems or EX-series + stand-alone systems with redundant Routing Engines. + - In order for an NSSU to succeed, NSSU must be supported. This includes + support for the current to desired Junos versions, the hardware + of the target Junos device, and the current software configuration of + the target Junos device. + - The I(nssu) and I(issu) options are mutually exclusive. + required: false + default: false + type: bool + reboot: + description: + - Indicates if the target Junos device should be rebooted after + performing the software install. + required: false + default: true + type: bool + reboot_pause: + description: + - The amount of time, in seconds, to wait after the reboot is issued + before the module returns. This gives time for the reboot to begin. The + default value of 10 seconds is designed to ensure the device is no + longer reachable (because the reboot has begun) when the next task + begins. The value must be an integer greater than or equal to 0. + required: false + default: 10 + type: int + remote_package: + description: + - This option may take one of two formats. + - The first format is a URL, from the perspective of the target Junos + device, from which the device retrieves the software package to be + installed. The acceptable formats for the URL value may be found + U(here|https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-software-formats-filenames-urls.html). + - When using the URL format, the I(local_package) and I(no_copy) options + must not be specified. + - The second format is a file path, on the taget Junos device, to the + software package. + - If the I(local_package) option is also specified, and the + I(no_copy) option is C(false), the software package will be copied + from I(local_package) to I(remote_package), if necessary. + - If the I(no_copy) option is C(true) or the I(local_package) option + is not specified, then the file specified by this option must already + exist on the target Junos device. + - If this option is not specified, it is assumed that the software + package will be copied into the C(/var/tmp) directory on the target + Junos device using the filename portion of the I(local_package) option. + In this case, the I(local_package) option must be specified. + - Specifying the I(remote_package) option and not specifying the + I(local_package) option is equivalent to specifying the + I(local_package) option and the I(no_copy) option. In this case, + you no longer have to explicitly specify the I(no_copy) option. + - If the I(remote_package) value is a directory (ends with /), then + the filename portion of I(local_package) will be appended to the + I(remote_package) value. + - If the I(remote_package) value is a file (does not end with /), + then the filename portion of I(remote_package) must be the same as + the filename portion of I(local_package). + required: false + default: C(/var/tmp/) + filename portion of I(local_package) + type: path + pkg_set: + description: + - install software on the members in a mixed Virtual Chassis. Currently + we are not doing target package check this option is provided. + required: false + default: false + type: list + validate: + description: + - Whether or not to have the target Junos device should validate the + current configuration against the new software package. + required: false + default: false + type: bool + version: + description: + - The version of software contained in the file specified by the + I(local_package) and/or I(remote_package) options. This value should + match the Junos version which will be reported by the device once the + new software is installed. If the device is already running a version + of software which matches the I(version) option value, the software + install is not necessary. In this case the module returns a I(changed) + value of C(false) and an I(failed) value of C(false) and does not + attempt to perform the software install. + required: false + default: Attempt to extract the version from the file name specified by + the I(local_package) or I(remote_package) option values IF the + package appears to be a Junos software package. Otherwise, C(none). + type: str + aliases: + - target_version + - new_version + - desired_version + vmhost: + description: + - Whether or not this is a vmhost software installation. + required: false + default: false + type: bool +notes: + - This module does support connecting to the console of a Junos device, but + does not support copying the software package from the local Ansible + control machine to the target Junos device while connected via the console. + In this situation, the I(remote_package) option must be specified, and the + specified software package must already exist on the target Junos device. + - This module returns after installing the software and, optionally, + initiating a reboot of the target Junos device. It does not wait for + the reboot to complete, and it does not verify that the desired version of + software specified by the I(version) option is actually activated on the + target Junos device. It is the user's responsibility to confirm the + software installation using additional follow on tasks in their playbook. +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_software + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Execute a basic Junos software upgrade. + juniper_junos_software: + local_package: "./images/" + register: response + - name: Print the complete response. + debug: + var: response + +###### OLD EXAMPLES ########## + - junos_install_os: + host={{ inventory_hostname }} + version=12.1X46-D10.2 + package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz + logfile=/usr/local/junos/log/software.log +###### OLD EXAMPLES ########## +''' + + +RETURN = ''' +changed: + description: + - Indicates if the device's state has changed, or if the state would have + changed when executing in check mode. This value is set to C(true) when + the version of software currently running on the target Junos device does + not match the desired version of software specified by the I(version) + option. If the current and desired software versions match, the value + of this key is set to C(false). + returned: success + type: bool +check_mode: + description: + - Indicates whether or not the module ran in check mode. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +msg: + description: + - A human-readable message indicating the result of the software + installation. + returned: always + type: str +''' + +# Standard Library imports +import os.path +import re +import time +try: + # Python 3.x + from urllib.parse import urlparse +except ImportError: + # Python 2.x + from urlparse import urlparse + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + +def parse_version_from_filename(filename): + """Attempts to parse a version string from the filename of a Junos package. + + There is wide variety in the naming schemes used by Junos software + packages. This function attempts to parse the version string from the + filename, but may not be able to accurately do so. It's also not + guaranteed that the filename of a package accurately reflects the version + of software in the file. (A user may have renamed it.) + If the filename does not appear to be a Junos package (maybe some other + type of package which can be installed on Junos devices), then return None. + + Args: + filename - The filename from which to parse the version string. + + Returns: + The version string, or None if unable to parse. + """ + # Known prefixes for filenames which contain Junos software packages. + JUNOS_PACKAGE_PREFIXES = ['jbundle', 'jinstall', 'junos-install', + 'junos-srx', 'junos-vmhost-install', 'junos-vrr', + 'vmx-bundle'] + for prefix in JUNOS_PACKAGE_PREFIXES: + if filename.startswith(prefix): + # Assumes the version string will be prefixed by -. + # Assume major version will begin with two digits followed by dot. + # Assume the version string ends with the last digit in filename. + match = re.search('-(\d{2}\..*\d).*', filename) + if match is not None: + return match.group(1) + # Not a known Junos package name. + else: + return None + + +def define_progress_callback(junos_module): + """Create callback which can be passed to SW.install(progress=progress) + """ + def myprogress(_, report): + """A progress function which logs report at level INFO. + + Args: + _: The PyEZ device object. Unused because the logger already knows. + report: The string to be logged. + """ + junos_module.logger.info(report) + return myprogress + + +def main(): + CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] + + #Define the argument spec. + software_argument_spec=dict( + local_package=dict(required=False, + aliases=['package'], + type='path', + default=None), + remote_package=dict(required=False, + type='path', + # Default is '/var/tmp/' + filename from the + # local_package option, if set. + default=None), + pkg_set=dict(required=False, + type='list', + default=None), + version=dict(required=False, + aliases=['target_version', 'new_version', + 'desired_version'], + type='str', + # Default is determined from filename portion of + # remote_package option. + default=None), + no_copy=dict(required=False, + type='bool', + default=False), + reboot=dict(required=False, + type='bool', + default=True), + reboot_pause=dict(required=False, + type='int', + default=10), + issu=dict(required=False, + type='bool', + default=False), + nssu=dict(required=False, + type='bool', + default=False), + force_host=dict(required=False, + type='bool', + default=False), + validate=dict(required=False, + type='bool', + default=False), + cleanfs=dict(required=False, + type='bool', + default=True), + all_re=dict(required=False, + type='bool', + default=True), + vmhost=dict(required=False, + type='bool', + default=False), + checksum=dict(required=False, + type='str', + default=None), + checksum_algorithm=dict(required=False, + choices=CHECKSUM_ALGORITHM_CHOICES, + type='str', + default='md5'), + checksum_timeout=dict(required=False, + type='int', + default=300), + cleanfs_timeout=dict(required=False, + type='int', + default=300), + install_timeout=dict(required=False, + type='int', + default=1800), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='dict', + default=None), + ) + # Save keys for later. Must do because software_argument_spec gets + # modified. + option_keys = list(software_argument_spec.keys()) + + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec = software_argument_spec, + # Mutually exclusive options. + mutually_exclusive=[['issu', 'nssu']], + # One of local_package and remote_package is required. + required_one_of=[['local_package', 'remote_package', 'pkg_set']], + supports_check_mode=True + ) + + # Straight from params + local_package = junos_module.params.pop('local_package') + remote_package = junos_module.params.pop('remote_package') + pkg_set = junos_module.params.pop('pkg_set') + target_version = junos_module.params.pop('version') + no_copy = junos_module.params.pop('no_copy') + reboot = junos_module.params.pop('reboot') + reboot_pause = junos_module.params.pop('reboot_pause') + install_timeout = junos_module.params.pop('install_timeout') + cleanfs = junos_module.params.pop('cleanfs') + all_re = junos_module.params.pop('all_re') + kwargs = junos_module.params.pop('kwargs') + + url = None + remote_dir = None + if remote_package is not None: + # Is the remote package a URL? + parsed_url = urlparse(remote_package) + if parsed_url.scheme == '': + # A file on the remote host. + (remote_dir, remote_filename) = os.path.split(remote_package) + else: + url = remote_package + (_, remote_filename) = os.path.split(parsed_url.path) + else: + # Default remote_dir value + remote_dir = '/var/tmp' + remote_filename = '' + + if url is not None and local_package is not None: + junos_module.fail_json(msg='There remote_package (%s) is a URL. ' + 'The local_package option is not allowed.' % + remote_package) + + if url is not None and no_copy is True: + junos_module.fail_json(msg='There remote_package (%s) is a URL. ' + 'The no_copy option is not allowed.' % + remote_package) + + if url is None: + local_filename = None + if local_package is not None: + # Expand out the path. + local_package = os.path.abspath(local_package) + (local_dir, local_filename) = os.path.split(local_package) + if local_filename == '': + junos_module.fail_json(msg='There is no filename component to ' + 'the local_package (%s).' % + local_package) + elif remote_package is not None: + # remote package was, so we must assume no_copy. + no_copy = True + + if no_copy is False: + if local_package is not None and not os.path.isfile(local_package): + junos_module.fail_json(msg='The local_package (%s) is not a ' + 'valid file on the local Ansible ' + 'control machine.' % local_package) + elif pkg_set is not None: + pkg_set = [os.path.abspath(item) for item in pkg_set] + for pkg_set_item in pkg_set: + if not os.path.isfile(pkg_set_item): + junos_module.fail_json( + msg='The pkg (%s) is not a valid file on the local' + ' Ansible control machine.' % pkg_set_item) + + if remote_filename == '': + # Use the same name as local_filename + remote_filename = local_filename + + if local_filename is not None and remote_filename != local_filename: + junos_module.fail_json(msg='The filename of the remote_package ' + '(%s) must be the same as the filename ' + 'of the local_package (%s).' % + (remote_filename, local_filename)) + + # If no_copy is True, then we need to turn off cleanfs to keep from + # deleting the software package which is already present on the device. + if no_copy is True: + cleanfs = False + + if target_version is None and pkg_set is None: + target_version = parse_version_from_filename(remote_filename) + junos_module.logger.debug("New target version is: %s.", target_version) + + # Initialize the results. Assume not changed and failure until we know. + results = {'msg': '', + 'changed': False, + 'check_mode': junos_module.check_mode, + 'failed': True} + + if junos_module.conn_type == "local" : + facts = dict(junos_module.dev.facts) + else: + facts = junos_module.get_facts() + # facts checking has been done as part of persitent connection itself. + # for these scenario, persistent connection is not expected. + # there is also limitation of JSON for persistent connection. + # closing the persistent connection and creating a normal connection. + junos_module._pyez_conn.close() + junos_module.open() + # Check version info to see if we need to do the install. + if target_version is not None: + if all_re is True: + junos_info = facts['junos_info'] + for current_re in junos_info: + current_version = junos_info[current_re]['text'] + if target_version != current_version: + junos_module.logger.debug("Current version on %s: %s. " + "Target version: %s.", + current_version, current_re, + target_version) + results['changed'] = True + else: + results['msg'] += "Current version on %s: %s same as Targeted " \ + "version: %s.\n" % (current_version, current_re, + target_version) + else: + current_version = facts['version'] + re_name = junos_module.dev.re_name + if target_version != current_version: + junos_module.logger.debug("Current version on %s: %s. " + "Target version: %s.", + current_version, re_name, + target_version) + results['changed'] = True + else: + results['msg'] += "Current version on %s: %s same as Targeted " \ + "version: %s.\n" % (current_version, re_name, + target_version) + else: + # A non-Junos install. Always attempt to install. + results['changed'] = True + + # Do the install if necessary + if results['changed'] is True and not junos_module.check_mode: + junos_module.logger.debug("Beginning installation of %s.", + remote_filename) + # Calculate the install parameters + install_params = {} + if url is not None: + install_params['package'] = url + elif local_package is not None: + install_params['package'] = local_package + elif pkg_set is not None: + install_params['pkg_set'] = pkg_set + else: + install_params['package'] = remote_filename + if remote_dir is not None: + install_params['remote_path'] = remote_dir + install_params['progress'] = define_progress_callback(junos_module) + install_params['cleanfs'] = cleanfs + install_params['no_copy'] = no_copy + install_params['timeout'] = install_timeout + install_params['all_re'] = all_re + for key in option_keys: + value = junos_module.params.get(key) + if value is not None: + install_params[key] = value + if kwargs is not None: + install_params.update(kwargs) + try: + junos_module.logger.debug("Install parameters are: %s", + str(install_params)) + junos_module.add_sw() + ok, msg_ret = junos_module.sw.install(**install_params) + if ok is not True: + results['msg'] = 'Unable to install the software %s', msg_ret + junos_module.fail_json(**results) + msg = 'Package %s successfully installed. Response from device is: %s' % ( + install_params.get('package') or + install_params.get('pkg_set'), + msg_ret) + results['msg'] = msg + junos_module.logger.debug(msg) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + results['msg'] = 'Installation failed. Error: %s' % str(ex) + junos_module.fail_json(**results) + if reboot is True: + try: + # Try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this + # happens relatively quickly. + restore_timeout = junos_module.dev.timeout + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + junos_module.logger.debug('Initiating reboot.') + try: + + got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) + junos_module.dev.timeout = restore_timeout + except Exception: # pylint: disable=broad-except + junos_module.dev.timeout = restore_timeout + raise + junos_module.logger.debug("Reboot RPC executed.") + + if got is not None: + results['msg'] += ' Reboot successfully initiated. ' \ + 'Reboot message: %s' % got + else: + # This is the else clause of the for loop. + # It only gets executed if the loop finished without + # hitting the break. + results['msg'] += ' Did not find expected response ' \ + 'from reboot RPC. ' + junos_module.fail_json(**results) + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['msg'] += ' Reboot failed. It may not have been ' \ + 'initiated.' + junos_module.fail_json(**results) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.RpcTimeoutError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] += ' Reboot succeeded.' + except (junos_module.ncclient_exception.TimeoutExpiredError): + # This is not really expected. Still consider reboot success as + # Looks like rpc was consumed but no response as its rebooting. + results['msg'] += ' Reboot succeeded. Ignoring close error.' + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) + junos_module.fail_json(**results) + else: + try: + junos_module.close() + except (junos_module.ncclient_exception.TimeoutExpiredError): + junos_module.logger.debug("Ignoring TimeoutError for close call") + + junos_module.logger.debug("Reboot RPC successfully initiated.") + if reboot_pause > 0: + junos_module.logger.debug("Sleeping for %d seconds", + reboot_pause) + time.sleep(reboot_pause) + + # If we made it this far, it's success. + results['failed'] = False + + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py deleted file mode 120000 index 511a3de3..00000000 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_srx_cluster.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py new file mode 100644 index 00000000..fd505f5c --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -0,0 +1,293 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Patrik Bok +# 2015, Rick Sherman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_srx_cluster +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Add or remove SRX chassis cluster configuration +description: + - Add an SRX chassis cluster configuration and reboot the device. Assuming + the device is capable of forming an SRX cluster and has the correct + cables connected, this will form an SRX cluster. + - If an SRX chassis cluster is already present, setting I(cluster_enable) to + C(false) will remove the SRX chassis cluster configuration and reboot + the device causing the SRX cluster to be broken and the device to return + to stand-alone mode. +options: + enable: + description: + - Enable or disable cluster mode. When C(true) cluster mode is enabled + and I(cluster_id) and I(node_id) must also be specified. When C(false) + cluster mode is disabled and the device returns to stand-alone mode. + required: true + default: none + type: bool + aliases: + - cluster_enable + cluster_id: + description: + - The cluster ID to configure. + - Required when I(enable) is C(true). + required: false + default: none + type: int + aliases: + - cluster + node_id: + description: + - The node ID to configure. (C(0) or C(1)) + - Required when I(enable) is C(true). + required: false + default: none + type: int + aliases: + - node +''' + +EXAMPLES = ''' +--- +- name: Manipulate the SRX cluster configuration of Junos SRX devices + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + tasks: + - name: Enable an SRX cluster + juniper_junos_srx_cluster: + enable: true + cluster_id: 4 + node_id: 0 + register: response + - name: Print the response. + debug: + var: response.config_lines + + - name: Disable an SRX cluster + juniper_junos_srx_cluster: + enable: false + register: response + - name: Print the response. + debug: + var: response.config_lines +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed, or would have + changed when in check mode. + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +reboot: + description: + - Indicates if a reboot of the device has been initiated. + returned: success + type: bool +''' + +# Standard library imports + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + enable=dict(type='bool', + required=True, + aliases=['cluster_enable'], + default=None), + cluster_id=dict(type='int', + required=False, + aliases=['cluster'], + default=None), + node_id=dict(type='int', + required=False, + aliases=['node'], + default=None) + ), + # Required if options + # If enable is True, then cluster_id and node_id must be set. + required_if=[['enable', True, ['cluster_id', 'node_id']]], + # Check mode is implemented. + supports_check_mode=True + ) + # Do additional argument verification. + + # Straight from params + enable = junos_module.params.get('enable') + cluster_id = junos_module.params.get('cluster_id') + node_id = junos_module.params.get('node_id') + + # cluster_id must be between 0 and 255 + if cluster_id is not None: + if cluster_id < 0 or cluster_id > 255: + junos_module.fail_json(msg="The cluster_id option (%s) must have " + "an integer value between 0 and 255." % + (cluster_id)) + + # node_id must be between 0 and 1 + if node_id is not None: + if node_id < 0 or node_id > 1: + junos_module.fail_json(msg="The node_id option (%s) must have a " + "value of 0 or 1." % (node_id)) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': '', + 'changed': False, + 'reboot': False, + 'failed': True} + + junos_module.logger.debug("Check current SRX cluster operational state.") + current_cluster_state = junos_module.dev.facts['srx_cluster'] + current_cluster_id = junos_module.dev.facts['srx_cluster_id'] + if current_cluster_id is not None: + current_cluster_id = int(current_cluster_id) + current_node_name = junos_module.dev.re_name + current_node_id = None + if current_node_name is not None: + (_, _, current_node_id) = current_node_name.partition('node') + if current_node_id: + current_node_id = int(current_node_id) + junos_module.logger.debug( + "Current SRX cluster operational state: %s, cluster_id: %s, " + "node_id: %s", + 'enabled' if current_cluster_state else 'disabled', + str(current_cluster_id), + str(current_node_id)) + + # Is a state change needed? + if current_cluster_state != enable: + junos_module.logger.debug( + "SRX cluster configuration change needed. Current state: %s. " + "Desired state: %s", + 'enabled' if current_cluster_state else 'disabled', + 'enabled' if enable else 'disabled') + results['changed'] = True + + # Is a cluster ID change needed? + if (enable is True and current_cluster_id is not None and + current_cluster_id != cluster_id): + junos_module.logger.debug( + "SRX cluster ID change needed. Current cluster ID: %d. " + "Desired cluster ID: %d", + current_cluster_id, cluster_id) + results['changed'] = True + + # Is a node ID change needed? + if (enable is True and current_node_id is not None and + current_node_id != node_id): + junos_module.logger.debug( + "SRX node ID change needed. Current node ID: %d. " + "Desired cluster ID: %d", + current_node_id, node_id) + results['changed'] = True + + results['msg'] = 'Current state: %s, cluster_id: %s, node_id: %s' % \ + ('enabled' if current_cluster_state else 'disabled', + str(current_cluster_id), + str(current_node_id)) + + if results['changed'] is True: + results['msg'] += ' Desired state: %s, cluster_id: %s, ' \ + 'node_id: %s' % \ + ('enabled' if enable else 'disabled', + str(cluster_id), + str(node_id)) + + if not junos_module.check_mode: + results['msg'] += ' Initiating change.' + try: + output = None + if enable is True: + resp = junos_module.dev.rpc.set_chassis_cluster_enable( + cluster_id=str(cluster_id), node=str(node_id), + reboot=True, normalize=True + ) + else: + resp = junos_module.dev.rpc.set_chassis_cluster_disable( + reboot=True, normalize=True + ) + if resp is not None: + output = resp.getparent().findtext('.//output') + if output is None: + output = resp.getparent().findtext('.//message') + results['msg'] += ' Reboot initiated. Response: %s' % (output) + results['reboot'] = True + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.logger.debug('Error: %s', str(ex)) + results['msg'] += ' Error: %s' % (str(ex)) + junos_module.fail_json(**results) + + # If we made it this far, everything was successful. + results['failed'] = False + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py deleted file mode 120000 index 95f32974..00000000 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_system.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py new file mode 100644 index 00000000..880a0ed2 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -0,0 +1,403 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 1999-2018, Juniper Networks Inc. +# 2014, Jeremy Schulman +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_system +author: "Juniper Networks - Stacy Smith (@stacywsmith)" +short_description: Initiate operational actions on the Junos system +description: + - Initiate an operational action (shutdown, reboot, halt or zeroize) on a + Junos system. The particular action to execute is defined by the mandatory + I(action) option. +options: + action: + description: + - The action performed by the module. + - > + The following actions are supported: + - B(shutdown) - Power off the Junos devices. The values C(off), + C(power-off), and C(power_off) are aliases for this value. + This is the equivalent of the C(request system power-off) CLI + command. + - B(halt) - Stop the Junos OS running on the RE, but do not power off + the system. Once the system is halted, it will reboot if a + keystroke is entered on the console. This is the equivalent + of the C(request system halt) CLI command. + - B(reboot) - Reboot the system. This is the equivalent of the + C(request system reboot) CLI command. + - B(zeroize) - Restore the system (configuration, log files, etc.) to a + factory default state. This is the equivalent of the + C(request system zeroize) CLI command. + required: true + default: none + type: str + choices: + - shutdown + - halt + - reboot + - zeroize + - 'off' + - power-off + - power_off + at: + description: + - The time at which to shutdown, halt, or reboot the system. + - > + The value may be specified in one of the following ways: + - B(now) - The action takes effect immediately. + - B(+minutes) — The action takes effect in C(minutes) minutes from now. + - B(yymmddhhmm) — The action takes effect at C(yymmddhhmm) absolute + time, specified as year, month, day, hour, and minute. + - B(hh:mm) — The action takes effect at C(hh:mm) absolute time on the + current day, specified in 24-hour time. + - The I(at) option can not be used when the I(action) option has a + value of C(zeroize). The I(at) option is mutually exclusive with the + I(in_min) option. + required: false + default: none + type: str + in_min: + description: + - Specify a delay, in minutes, before the shutdown, halt, or reboot. + - The I(in_min) option can not be used when the I(action) option has a + value of C(zeroize). The I(in_min) option is mutually exclusive with + the I(at) option. + required: false + default: 0 + type: int + all_re: + description: + - If the system has multiple Routing Engines and this option is C(true), + then the action is performed on all REs in the system. If the system + does not have multiple Routing Engines, then this option has no effect. + - This option applies to all I(action) values. + - The I(all_re) option is mutually exclusive with the I(other_re) option. + required: false + default: true + type: bool + other_re: + description: + - If the system has dual Routing Engines and this option is C(true), + then the action is performed on the other REs in the system. If the + system does not have dual Routing Engines, then this option has no + effect. + - The I(other_re) option can not be used when the I(action) option has a + value of C(zeroize). + - The I(other_re) option is mutually exclusive with the I(all_re) option. + required: false + default: false + type: bool + media: + description: + - Overwrite media when performing the zeroize operation. This option is + only valid when the I(action) option has a value of C(zeroize). + required: false + default: false + type: bool + vmhost: + description: + - Whether or not this is a vmhost reboot. + required: false + default: false + type: bool +notes: + - This module only B(INITIATES) the action. It does B(NOT) wait for the + action to complete. + - Some Junos devices are effected by a Junos defect which causes this Ansible + module to hang indefinitely when connected to the Junos device via + the console. This problem is not seen when connecting to the Junos device + using the normal NETCONF over SSH transport connection. Therefore, it is + recommended to use this module only with a NETCONF over SSH transport + connection. However, this module does still permit connecting to Junos + devices via the console port and this functionality may still be used for + Junos devices running Junos versions less than 15.1. +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_junos_system + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Reboot all REs of the device + juniper_junos_system: + action: "reboot" + + - name: Power off the other RE of the device. + juniper_junos_system: + action: "shutdown" + othe_re: True + + - name: Reboot this RE at 8pm today. + juniper_junos_system: + action: "reboot" + all_re: False + at: "20:00" + + - name: Halt the system on 25 January 2018 at 4pm. + juniper_junos_system: + action: "halt" + at: "1801251600" + + - name: Reboot the system in 30 minutes. + juniper_junos_system: + action: "reboot" + in_min: 30 + + - name: Reboot the system in 30 minutes. + juniper_junos_system: + action: "reboot" + at: "+30m" + + - name: Zeroize the local RE only. + juniper_junos_system: + action: "zeroize" + all_re: False + + - name: Zeroize all REs and overwrite medea. + juniper_junos_system: + action: "zeroize" + media: True +''' + +RETURN = ''' +action: + description: + - The value of the I(action) option. + returned: always + type: str +all_re: + description: + - The value of the I(all_re) option. + returned: always + type: str +changed: + description: + - Indicates if the device's state has changed. If the action is performed + (or if it would have been performed when in check mode) then the value + will be C(true). If there was an error before the action, then the value + will be C(false). + returned: always + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +media: + description: + - The value of the I(media) option. + returned: always + type: str +msg: + description: + - A human-readable message indicating the result. + returned: always + type: str +other_re: + description: + - The value of the I(other_re) option. + returned: always + type: str +''' + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + action=dict(type='str', + required=True, + choices=['shutdown', 'off', 'power-off', 'power_off', + 'halt', 'reboot', 'zeroize'], + default=None), + at=dict(type='str', + required=False, + default=None), + in_min=dict(type='int', + required=False, + aliases=['in'], + default=0), + all_re=dict(type='bool', + required=False, + default=True), + other_re=dict(type='bool', + required=False, + default=False), + vmhost=dict(required=False, + type='bool', + default=False), + media=dict(type='bool', + required=False, + default=False), + ), + mutually_exclusive=[['at', 'in_min'], ['all_re', 'other_re']], + supports_check_mode=True + ) + + # We're going to be using params a lot + params = junos_module.params + + action = params['action'] + at = params.get('at') + in_min = params.get('in_min') + all_re = params.get('all_re') + other_re = params.get('other_re') + media = params.get('media') + vmhost = params.get('vmhost') + + # Synonymn for shutdown + if action == 'off' or action == 'power_off' or action == 'power-off': + action = 'shutdown' + + if action != 'reboot' and vmhost is True: + junos_module.fail_json(msg='The vmhost option can only be used when ' + 'the action option has the value "reboot".') + + # Four actions are expected - reboot, shutdown, halt and zeroize + + # if action is zeroize, at, in_min, other_re shouldn't be set + if action == "zeroize": + # at, in_min and other_re option only applies to reboot, shutdown, or halt action. + if (at != None) or (in_min != 0) or (other_re == True): + junos_module.fail_json(msg='The options at, in_min and other_re can only be used when ' + 'the action option has the value "zeroize"') + elif media is True: # media option only applies to zeroize action. + junos_module.fail_json(msg='The media option can only be used when ' + 'the action option has the value "zeroize".') + + # Set initial results values. Assume failure until we know it's success. + results = {'changed': True, + 'msg': '', + 'reboot': bool(action == 'reboot'), + 'action': action, + 'all_re': all_re, + 'other_re': other_re, + 'media': media, + 'vmhost': vmhost, + 'failed': True} + + # for these scenario, persistent connection is not expected. + # there is also limitation of JSON for persistent connection. + # closing the persistent connection and creating a normal connection. + if junos_module.conn_type != "local": + junos_module._pyez_conn.close() + junos_module.open() + + if not junos_module.check_mode: + if action != 'zeroize': + # If we're going to do a shutdown, reboot, or halt right away then + # try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this happens + # relatively quickly. + if (at == 'now' or (in_min == 0 and at is None)): + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + + # Execute the RPC. + try: + junos_module.logger.debug("Executing RPC") + junos_module.add_sw() + if action == 'reboot': + got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) + elif action == 'shutdown': + got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) + elif action == 'halt': + got = junos_module.sw.halt(in_min, at, all_re, other_re) + elif action == 'zeroize': + got = junos_module.sw.zeroize(all_re, media) + else: + junos_module.fail_json(msg='Relevant action not found') + + junos_module.logger.debug("RPC executed") + if got is None: + results['msg'] = 'Did not find expected RPC response.' + results['changed'] = False + else: + results['msg'] = '%s successfully initiated. Response got %s' % (action, got) + results['failed'] = False + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates there was a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['changed'] = False + results['msg'] = '%s failed. %s may not have been ' \ + 'initiated.' % (action, action) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] = '%s succeeded.' % (action) + results['failed'] = False + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + results['changed'] = False + results['msg'] = '%s failed. Error: %s' % (action, str(ex)) + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py deleted file mode 120000 index 32df1a46..00000000 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ /dev/null @@ -1 +0,0 @@ -juniper_junos_table.py \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py new file mode 100644 index 00000000..529d21df --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -0,0 +1,482 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright 2016 Jason Edelman +# Network to Code, LLC +# +# Copyright (c) 2017-2018, Juniper Networks Inc. +# +# All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: juniper_junos_table +author: + - Jason Edelman (@jedelman8) + - Updated by Juniper Networks - Stacy Smith (@stacywsmith) +short_description: Retrieve data from a Junos device using a PyEZ table/view +description: + - Retrieve data from a Junos device using PyEZ's operational table/views. + This module may be used with the tables/views which are included in the + PyEZ distribution or it may be used with user-defined tables/views. +options: + file: + description: + - Name of the YAML file, relative to the I(path) option, that contains + the table/view definition. The file name must end with the C(.yml) or + C(.yaml) extension. + required: true + default: none + type: path + kwargs: + description: + - Optional keyword arguments and values to the table's get() method. The + value of this option is a dictionary of keywords and values which are + used to refine the data return from performing a get() on the table. + The exact keywords and values which are supported are specific to the + table's definition and the underlying RPC which the table invokes. + required: false + default: none + type: dict + aliases: + - kwarg + - args + - arg + path: + description: + - The directory containing the YAML table/view definition file as + specified by the I(file) option. The default value is the C(op) + directory in C(jnpr.junos.op). This is the directory containing the + table/view definitions which are included in the PyEZ distribution. + required: false + default: C(op) directory in C(jnpr.junos.op) + type: path + aliases: + - directory + - dir + response_type: + description: + - Defines the format of data returned by the module. See RETURN. + The value of the I(resource) key in the module's response is either + a list of dictionaries C(list_of_dicts) or PyEZ's native return + format C(juniper_items). Because Ansible module's may only return JSON + data, PyEZ's native return format C(juniper_items) is translated into + a list of lists. + required: false + default: list_of_dicts + choices: + - list_of_dicts + - juniper_items + type: str + table: + description: + - Name of the PyEZ table used to retrieve data. If not specified, + defaults to the name of the table defined in the I(file) option. Any + table names in I(file) which begin with C(_) are ignored. If more than + one table is defined in I(file), the module fails with an error + message. In this case, you must manually specify the name of the table + by setting this option. + required: false + default: The name of the table defined in the I(file) option. + type: str +notes: + - This module only works with operational tables/views; it does not work with + configuration tables/views. +''' + +EXAMPLES = ''' +--- +- name: Retrieve data from a Junos device using a PyEZ table/view. + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table + juniper_junos_table: + file: "lldp.yml" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve routes within 192.68.1/8 + juniper_junos_table: + file: "routes.yml" + table: "RouteTable" + kwargs: + destination: "192.68.1.0/8" + response_type: "juniper_items" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve from custom table in playbook directory + juniper_junos_table: + file: "fpc.yaml" + path: "." + register: response + - name: Print response + debug: + var: response +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's configuration has changed. Since this + module does not change the operational or configuration state of the + device, the value is always set to C(false). + returned: success + type: bool +failed: + description: + - Indicates if the task failed. + returned: always + type: bool +msg: + description: + - A human-readable message indicating a summary of the result. + returned: always + type: str +resource: + description: + - The items retrieved by the table/view. + returned: success + type: list of dicts if I(response_type) is C(list_of_dicts) or list of + lists if I(respsonse_type) is C(juniper_items). + sample: | + # when response_type == 'list_of_dicts' + [ + { + "local_int": "ge-0/0/3", + "local_parent": "-", + "remote_chassis_id": "00:05:86:08:d4:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/0", + "remote_sysname": "r5", + "remote_type": "Mac address" + }, + { + "local_int": "ge-0/0/0", + "local_parent": "-", + "remote_chassis_id": "00:05:86:18:f3:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/2", + "remote_sysname": "r4", + "remote_type": "Mac address" + } + ] + # when response_type == 'juniper_items' + [ + [ + "ge-0/0/3", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/0" + ], + [ + "remote_chassis_id", + "00:05:86:08:d4:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/3" + ], + [ + "remote_sysname", + "r5" + ] + ] + ], + [ + "ge-0/0/0", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/2" + ], + [ + "remote_chassis_id", + "00:05:86:18:f3:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/0" + ], + [ + "remote_sysname", + "r4" + ] + ] + ] + ] +''' + +# Standard library imports +import os.path + +# Constants +RESPONSE_CHOICES = ['list_of_dicts', 'juniper_items'] + + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + +def expand_items(module, data): + """Recursively expand any table items + """ + resources = [] + # data.items() is a list of tuples + for table_key, table_fields in data.items(): + # sample: + # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), + # ('neighbor', 'vmx2')] + # table_key - element 0 is the key from the Table - not using at all + # table_fields - element 1 is also a list of tuples + temp = [] + for key, value in table_fields: + # calling it normalized value because YOU/WE created the keys + if value and isinstance(value, module.pyez_factory_table.Table): + value = expand_items(module, value) + temp.append((key, value)) + resources.append((table_key, temp)) + return resources + + +def juniper_items_to_list_of_dicts(module, data): + """Recursively convert Juniper PyEZ Table/View items to list of dicts. + """ + resources = [] + # data.items() is a list of tuples + for table_key, table_fields in data.items(): + # sample: + # ('fxp0', [('neighbor_interface', '1'), ('local_interface', 'fxp0'), + # ('neighbor', 'vmx2')] + # table_key - element 0 is the key from the Table - not using at all + # table_fields - element 1 is also a list of tuples + temp = {} + for key, value in table_fields: + if isinstance(value, module.pyez_factory_table.Table): + value = juniper_items_to_list_of_dicts(module, value) + temp[key] = value + resources.append(temp) + return resources + + +def main(): + # Create the module instance. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec=dict( + file=dict(type='path', + required=True, + default=None), + table=dict(type='str', + required=False, + default=None), + path=dict(type='path', + required=False, + aliases=['directory', 'dir'], + default=None), + kwargs=dict(required=False, + aliases=['kwarg', 'args', 'arg'], + type='dict', + default=None), + response_type=dict(choices=RESPONSE_CHOICES, + type='str', + required=False, + default='list_of_dicts'), + ), + # Check mode is implemented. + supports_check_mode=True, + min_yaml_version=juniper_junos_common.MIN_YAML_VERSION, + ) + + # Straight from params + file = junos_module.params.get('file') + table = junos_module.params.get('table') + path = junos_module.params.get('path') + kwargs = junos_module.params.get('kwargs') + response_type = junos_module.params.get('response_type') + + if not file.endswith('.yml') and not file.endswith('.yaml'): + junos_module.fail_json(msg='The value of the file option must end ' + 'with the .yml or .yaml extension') + + # If needed, get the default path + if path is None: + path = os.path.dirname( + os.path.abspath(junos_module.pyez_op_table.__file__)) + + # file_name is path + file + file_name = os.path.join(path, file) + + junos_module.logger.debug("Attempting to open: %s.", file_name) + try: + with open(file_name, 'r') as fp: + try: + junos_module.logger.debug("Attempting to parse YAML from : " + "%s.", file_name) + table_view = junos_module.yaml.load(fp) + junos_module.logger.debug("YAML from %s successfully parsed.", + file_name) + except junos_module.yaml.YAMLError as ex: + junos_module.fail_json(msg='Failed parsing YAML file %s. ' + 'Error: %s' % (file_name, str(ex))) + except IOError: + junos_module.fail_json(msg='The file name %s could not be opened for' + 'reading.' % (file_name)) + junos_module.logger.debug("%s successfully read.", file_name) + + # Initialize the results. Assume failure until we know it's success. + results = {'msg': '', + 'changed': False, + 'failed': True} + + # Default to the table defined in file_name. + # Ignore table names which begin with an underscore. + if table is None: + for key in table_view: + if not key.startswith('_') and 'Table' in key: + if table is not None: + junos_module.fail_json( + msg='The file name %s contains multiple table ' + 'definitions. Specify the desired table with the ' + 'table option.' % (file_name)) + table = key + + if table is None: + junos_module.fail_json( + msg='No table definition was found in the %s file. Specify a ' + 'value for the file option which contains a valid table/view ' + 'definition.' % (file_name)) + junos_module.logger.debug("Table: %s", table) + + try: + loader = \ + junos_module.pyez_factory_loader.FactoryLoader().load(table_view) + junos_module.logger.debug("Loader created successfully.") + except Exception as ex: + junos_module.fail_json(msg='Unable to create a table loader from the ' + '%s file. Error: %s' % (file_name, str(ex))) + try: + # there is a limitation of JSON for persistent connection. + # the connection passes information as JSON and can't use device object here. + # closing the persistent connection and creating a normal connection. + if junos_module.conn_type != "local": + junos_module._pyez_conn.close() + junos_module.open() + + data = loader[table](junos_module.dev) + junos_module.logger.debug("Table %s created successfully.", table) + if kwargs is None: + data.get() + else: + data.get(**kwargs) + junos_module.logger.debug("Data retrieved from %s successfully.", + table) + except KeyError: + junos_module.fail_json(msg='Unable to find table %s in the ' + '%s file.' % (table, file_name)) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + junos_module.fail_json(msg='Unable to retrieve data from table %s. ' + 'Error: %s' % (table, str(ex))) + + if data is not None: + try: + len_data = len(data) + except Exception as ex: + junos_module.fail_json(msg='Unable to parse table %s data into ' + 'items. Error: %s' % (table, str(ex))) + junos_module.logger.debug('Successfully retrieved %d items from %s.', + len_data, table) + results['msg'] = 'Successfully retrieved %d items from %s.' % \ + (len_data, table) + + if response_type == 'list_of_dicts': + junos_module.logger.debug('Converting data to list of dicts.') + resource = juniper_items_to_list_of_dicts(junos_module, data) + else: + resource = expand_items(junos_module, data) + + # If we made it this far, everything was successful. + results['failed'] = False + results['resource'] = resource + + # Return response. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt index 3cefaede..d93988c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ ansible >= 2.9 junos-eznc >= 2.5.2 jsnapy>=1.3.4 jxmlease +xmltodict \ No newline at end of file From 9b8f627fd7090d169bd29c51f03e9762576e463a Mon Sep 17 00:00:00 2001 From: Stephen Steiner Date: Fri, 6 Nov 2020 08:13:00 -0500 Subject: [PATCH 329/426] pinned Ansible role to 2.4.3 Signed-off-by: Stephen Steiner --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bc968bcc..3c43c50f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ COPY entrypoint.sh . RUN chmod +x entrypoint.sh # Also install the roles, until collections is ready for prime-time -RUN ansible-galaxy role install Juniper.junos +RUN ansible-galaxy role install Juniper.junos==2.4.3 WORKDIR /project From 620eb72db0fa40aa32748eb0b4c52d27291ce092 Mon Sep 17 00:00:00 2001 From: rahkumar651991 <58289625+rahkumar651991@users.noreply.github.com> Date: Fri, 6 Nov 2020 18:48:45 +0530 Subject: [PATCH 330/426] Update README.md --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 14835161..d69a4bf1 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ retrieving information, and resetting, rebooting, or shutting down managed devic ## juniper.junos roles by Juniper Networks -Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have Juniper.junos_collection support. Juniper.junos roles have been moved to roles branch. Roles will be supported for now. For more information for roles, check: [https://github.com/Juniper/ansible-junos-stdlib/tree/roles](https://github.com/Juniper/ansible-junos-stdlib/tree/roles) Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have juniper.device collection support. Juniper.junos roles have been moved to roles branch. For more information for roles, check: @@ -54,8 +53,7 @@ This juniper.device collection includes the following modules: ### PyEZ Version Requirement -For ansible collection junos_collection we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. -For ansible collection junos we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. +For ansible collection juniper.device we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. ### Overview of Plugins @@ -111,8 +109,6 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge You can use the ansible-galaxy install command to install the latest version of the juniper.device collection. - sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git,,Juniper.junos_collection - sudo ansible-galaxy collection install juniper.device ### Git clone From dc894935b7649c53d3491e7af8e685d765aa56d8 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 9 Nov 2020 12:48:44 +0530 Subject: [PATCH 331/426] roles is installed for specific version with a comma --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3c43c50f..9073b285 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ COPY entrypoint.sh . RUN chmod +x entrypoint.sh # Also install the roles, until collections is ready for prime-time -RUN ansible-galaxy role install Juniper.junos==2.4.3 +RUN ansible-galaxy role install Juniper.junos,2.4.3 WORKDIR /project From 62447abc2ff395b8b5739d53889e2289bf956254 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 30 Nov 2020 00:13:19 +0530 Subject: [PATCH 332/426] Changes to support persistent connection for config and system modules --- .../juniper/device/plugins/connection/pyez.py | 235 +++++++++++++++++- .../module_utils/juniper_junos_common.py | 44 +++- .../juniper/device/plugins/modules/facts.py | 2 +- .../juniper/device/plugins/modules/jsnapy.py | 51 ++-- .../device/plugins/modules/software.py | 165 ++++++------ .../device/plugins/modules/srx_cluster.py | 28 ++- .../juniper/device/plugins/modules/system.py | 115 +++++---- 7 files changed, 447 insertions(+), 193 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 6998e7d8..2b432eed 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -260,6 +260,7 @@ class Connection(NetworkConnectionBase): def __init__(self, play_context, new_stdin, *args, **kwargs): super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) self.dev = None + self.config = None @property @ensure_connect @@ -296,6 +297,7 @@ def open(self): # Move all of the connection arguments into connect_args connect_args = {} connect_args['host'] = self.get_option('host') + connect_args['port'] = self.get_option('port') connect_args['user'] = self.get_option('remote_user') connect_args['passwd'] = self.get_option('password') connect_args['ssh_private_key_file'] = self.get_option('private_key_file') @@ -318,7 +320,7 @@ def open(self): # ConnectError, so this should catch all connection-related exceptions # raised from PyEZ. except pyez_exception.ConnectError as ex: - raise AnsibleError(msg="Unable to make a PyEZ connection: %s" % (str(ex))) + raise AnsibleError("Unable to make a PyEZ connection: %s" % (str(ex))) def close(self): """Close the self.dev PyEZ Device instance. @@ -350,16 +352,16 @@ def get_config(self, filter_xml=None, options=None, model=None, namespace=None, remove_ns=True, **kwarg): resp = self.dev.rpc.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) return etree.tostring(resp) - + def get_rpc_resp(self,rpc, ignore_warning=None): - # data comes in JSON format, needs to be converted - rpc_val = xmltodict.unparse(rpc) + # data comes in JSON format, needs to be converted + rpc_val = xmltodict.unparse(rpc) rpc_val = rpc_val.encode('utf-8') parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8') rpc_etree = etree.fromstring(rpc_val, parser=parser) resp = self.dev.rpc(rpc_etree, normalize=bool(format == 'xml'), ignore_warning=ignore_warning) return etree.tostring(resp) - + def get_facts(self): return dict(self.dev.facts) @@ -372,6 +374,18 @@ def get_chassis_inventory(self): resp = self.dev.rpc.get_chassis_inventory() return etree.tostring(resp) + def get_re_name(self): + return self.dev.re_name + + def set_chassis_cluster_enable(self, cluster_id, node_id): + return self.dev.rpc.set_chassis_cluster_enable( + cluster_id=cluster_id, node=node_id, + reboot=True, normalize=True) + + def set_chassis_cluster_disable(self): + return self.dev.rpc.set_chassis_cluster_disable( + reboot=True, normalize=True) + def invoke_jsnapy(self, data, action): try: self.queue_message("vvvv", "Creating jnpr.jsnapy.SnapAdmin instance.") @@ -415,3 +429,214 @@ def invoke_jsnapy(self, data, action): else: results = responses return json.dumps(results) + + def open_configuration(self, mode, ignore_warn=None): + if self.config is None: + if mode not in CONFIG_MODE_CHOICES: + raise AnsibleError("Invalid configuration mode: %s" % mode) + if self.dev is None: + self.open() + config = jnpr.junos.utils.config.Config(self.dev, mode=mode) + try: + if config.mode == 'exclusive': + config.lock() + elif config.mode == 'private': + self.dev.rpc.open_configuration( + private=True, + ignore_warning=ignore_warn) + except (pyez_exception.ConnectError, + pyez_exception.RpcError) as ex: + raise AnsibleError('Unable to open the configuration in %s ' + 'mode: %s' % (config.mode, str(ex))) + self.config = config + self.queue_message("log", "Configuration opened in %s mode."% config.mode) + + def close_configuration(self): + if self.config is not None: + config = self.config + self.config = None + try: + if config.mode == 'exclusive': + config.unlock() + elif config.mode == 'private': + self.dev.rpc.close_configuration() + self.queue_message("log", "Configuration closed.") + except (pyez_exception.ConnectError, + pyez_exception.RpcError) as ex: + raise AnsibleError('Unable to close the configuration: %s' % + (str(ex))) + + def rollback_configuration(self, id): + if self.dev is None or self.config is None: + raise AnsibleError('The device or configuration is not open.') + + if id == 'rescue': + self.queue_message("log", "Rolling back to the rescue configuration.") + try: + self.config.rescue(action='reload') + self.queue_message("log", "Rescue configuration loaded.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError('Unable to load the rescue configuraton: ' + '%s' % (str(ex))) + elif id >= 0 and id <= 49: + self.queue_message("log", "Loading rollback %d configuration.", id) + try: + self.config.rollback(rb_id=id) + self.queue_message("log", "Rollback %d configuration loaded.", id) + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError('Unable to load the rollback %d ' + 'configuraton: %s' % (id, str(ex))) + else: + raise AnsibleError('Unrecognized rollback configuraton value: %s' + % (id)) + + def check_configuration(self): + try: + self.config.commit_check() + self.queue_message("log", "Configuration checked.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError('Failure checking the configuraton: %s' % + (str(ex))) + + def diff_configuration(self, ignore_warning=False): + try: + diff = self.config.diff(rb_id=0, ignore_warning=ignore_warning) + self.queue_message("log", "Configuration diff completed.") + return diff + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError('Failure diffing the configuraton: %s' % + (str(ex))) + + def load_configuration(self, config, load_args): + try: + if config is not None: + self.config.load(config, **load_args) + else: + self.queue_message("log", "Load args %s.", str(load_args)) + self.config.load(**load_args) + self.queue_message("log", "Configuration loaded.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError('Failure loading the configuraton: %s' % + (str(ex))) + + def commit_configuration(self, ignore_warning=None, comment=None, + confirmed=None): + try: + self.config.commit(ignore_warning=ignore_warning, + comment=comment, + confirm=confirmed) + self.queue_message("log", "Configuration committed.") + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError('Failure committing the configuraton: %s' % + (str(ex))) + + def system_api(self, action, in_min, at, all_re, vmhost, other_re, media): + msg = None + if action != 'zeroize': + if (at == 'now' or (in_min == 0 and at is None)): + if self.dev.timeout > 5: + self.queue_message("log", "Decreasing device RPC timeout to 5 seconds.") + self.dev.timeout = 5 + + try: + self.sw = jnpr.junos.utils.sw.SW(self.dev) + if action == 'reboot': + got = self.sw.reboot(in_min, at, all_re, None, vmhost, other_re) + elif action == 'shutdown': + got = self.sw.poweroff(in_min, at, None, all_re, other_re) + elif action == 'halt': + got = self.sw.halt(in_min, at, all_re, other_re) + elif action == 'zeroize': + got = self.sw.zeroize(all_re, media) + else: + raise AnsibleError('Relevant action not found') + + self.queue_message("log", "RPC executed") + if got is None: + msg = 'Did not find expected RPC response.' + else: + msg = '%s successfully initiated. Response got %s' % (action, got) + except (self.pyez_exception.RpcTimeoutError) as ex: + try: + self.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + raise AnsibleError('%s failed. %s may not have been ' \ + 'initiated.' % (action, action)) + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + msg = '%s succeeded.' % (action) + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError('%s failed. Error: %s' % (action, str(ex))) + return msg + + def software_api(self, install_params): + try: + self.sw = jnpr.junos.utils.sw.SW(self.dev) + ok, msg_ret = self.sw.install(**install_params) + if ok is not True: + raise AnsibleError('Unable to install the software %s' % msg_ret) + msg = 'Package %s successfully installed. Response from device is: %s' % ( + install_params.get('package') or + install_params.get('pkg_set'), + msg_ret) + self.queue_message("log", "%s" % msg) + return msg + except (self.pyez_exception.ConnectError, + self.pyez_exception.RpcError) as ex: + raise AnsibleError('Installation failed. Error: %s' % str(ex)) + + def reboot_api(self, all_re, vmhost): + msg = None + try: + restore_timeout = self.dev.timeout + if self.dev.timeout > 5: + self.dev.timeout = 5 + try: + got = self.sw.reboot(0, None, all_re, None, vmhost) + self.dev.timeout = restore_timeout + except Exception: # pylint: disable=broad-except + self.dev.timeout = restore_timeout + raise + self.queue_message("log", "Reboot RPC executed.") + + if got is not None: + msg += ' Reboot successfully initiated. ' \ + 'Reboot message: %s' % got + else: + raise AnsibleError(' Did not find expected response ' \ + 'from reboot RPC. ') + except (self.pyez_exception.RpcTimeoutError) as ex: + try: + self.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + raise AnsibleError(' Reboot failed. It may not have been ' \ + 'initiated.') + except (self.pyez_exception.RpcError, + self.pyez_exception.RpcTimeoutError, + self.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + msg += ' Reboot succeeded.' + except (self.ncclient_exception.TimeoutExpiredError): + # This is not really expected. Still consider reboot success as + # Looks like rpc was consumed but no response as its rebooting. + msg += ' Reboot succeeded. Ignoring close error.' + except (self.pyez_exception.RpcError, + self.pyez_exception.ConnectError) as ex: + raise AnsibleError(' Reboot failed. Error: %s' % (str(ex))) + else: + try: + self.close() + except (self.ncclient_exception.TimeoutExpiredError): + self.queue_message("log", "Ignoring TimeoutError for close call") + + self.queue_message("log", "Reboot RPC successfully initiated.") + + return msg \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index a1773017..c25a6e05 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1133,15 +1133,11 @@ def open_configuration(self, mode, ignore_warning=None): elif ignore_warning != None and isinstance(ignore_warning, list): ignore_warn = ignore_warn + ignore_warning - # Already have an open configuration? - if self.config is None: - # configuration check and loading should not be done as peristent connection - # with other modules like rpc and command. The mode of connection - # will be different along with other additional parameter. - # if connection mode is pyez, close the connection. - if self.conn_type != "local": - self._pyez_conn.close() + if self.conn_type != "local": + self._pyez_conn.open_configuration(mode,ignore_warn) + return + if self.config is None: if mode not in CONFIG_MODE_CHOICES: self.fail_json(msg='Invalid configuration mode: %s' % (mode)) if self.dev is None: @@ -1168,6 +1164,10 @@ def close_configuration(self): - ConnectError: When there's a problem with the PyEZ connection. - RpcError: When there's a RPC problem closing the config. """ + if self.conn_type != "local": + self._pyez_conn.close_configuration() + return + if self.config is not None: # Because self.fail_json() calls self.close_configuration(), we # must set self.config = None BEFORE closing the config in order to @@ -1305,6 +1305,10 @@ def rollback_configuration(self, id): - Unable to rollback the configuration due to an RpcError or ConnectError. """ + if self.conn_type != "local": + self._pyez_conn.rollback_configuration(id) + return + if self.dev is None or self.config is None: self.fail_json(msg='The device or configuration is not open.') @@ -1340,10 +1344,14 @@ def check_configuration(self): Failures: - An error returned from checking the configuration. """ + self.logger.debug("Checking the configuration.") + if self.conn_type != "local": + self._pyez_conn.check_configuration() + return + if self.dev is None or self.config is None: self.fail_json(msg='The device or configuration is not open.') - self.logger.debug("Checking the configuration.") try: self.config.commit_check() self.logger.debug("Configuration checked.") @@ -1363,10 +1371,13 @@ def diff_configuration(self, ignore_warning=False): Failures: - An error returned from diffing the configuration. """ + self.logger.debug("Diffing candidate and committed configurations.") + if self.conn_type != "local": + diff = self._pyez_conn.diff_configuration(ignore_warning) + return diff if self.dev is None or self.config is None: self.fail_json(msg='The device or configuration is not open.') - self.logger.debug("Diffing candidate and committed configurations.") try: diff = self.config.diff(rb_id=0, ignore_warning=ignore_warning) self.logger.debug("Configuration diff completed.") @@ -1406,8 +1417,9 @@ def load_configuration(self, Failures: - An error returned from loading the configuration. """ - if self.dev is None or self.config is None: - self.fail_json(msg='The device or configuration is not open.') + if self.conn_type == "local": + if self.dev is None or self.config is None: + self.fail_json(msg='The device or configuration is not open.') load_args = {} config = None @@ -1440,6 +1452,10 @@ def load_configuration(self, load_args['url'] = url self.logger.debug("Loading the configuration from %s.", url) + if self.conn_type != "local": + self._pyez_conn.load_configuration(config, load_args) + return + try: if config is not None: self.config.load(config, **load_args) @@ -1466,6 +1482,10 @@ def commit_configuration(self, ignore_warning=None, comment=None, Failures: - An error returned from committing the configuration. """ + if self.conn_type != "local": + self._pyez_conn.commit_configuration(ignore_warning, comment, confirmed) + return + if self.dev is None or self.config is None: self.fail_json(msg='The device or configuration is not open.') diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py index db8866ed..6f2d6f88 100644 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -196,7 +196,7 @@ def get_facts_dict(junos_module): Returns: A dict containing the device facts. """ - if junos_module.conn_type == "local" : + if junos_module.conn_type == "local": dev = junos_module.dev # Retrieve all PyEZ-supported facts and copy to a standard dict. facts = dict(dev.facts) diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index bdf9bff6..dd24fe96 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -209,6 +209,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg def main(): JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] @@ -235,7 +236,7 @@ def main(): # One of test_files or config_file is required. required_one_of=[['test_files', 'config_file']], supports_check_mode=True, - min_jsnapy_version=juniper_junos_common.MIN_JSNAPY_VERSION, + min_jsnapy_version=cfg.MIN_JSNAPY_VERSION, ) # Straight from params @@ -283,33 +284,29 @@ def main(): junos_module.fail_json(msg="No config_file or test_files specified.") try: - if junos_module.conn_type == "local": - junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') - jsa = junos_module.jsnapy.SnapAdmin() - junos_module.logger.debug('Executing %s action.', action) - if action == 'check': - responses = jsa.check(data=data, - dev=junos_module.dev, - pre_file='PRE', - post_file='POST') - elif action == 'snapcheck': - responses = jsa.snapcheck(data=data, - dev=junos_module.dev) - elif action == 'snap_pre': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='PRE') - elif action == 'snap_post': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='POST') - else: - junos_module.fail_json(msg="Unexpected action: %s." % (action)) - junos_module.logger.debug('The %s action executed successfully.', - action) + junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') + jsa = junos_module.jsnapy.SnapAdmin() + junos_module.logger.debug('Executing %s action.', action) + if action == 'check': + responses = jsa.check(data=data, + dev=junos_module.dev, + pre_file='PRE', + post_file='POST') + elif action == 'snapcheck': + responses = jsa.snapcheck(data=data, + dev=junos_module.dev) + elif action == 'snap_pre': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='PRE') + elif action == 'snap_post': + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='POST') else: - conn = junos_module.get_connection() - responses = json.loads(conn.invoke_jsnapy(data, action)) + junos_module.fail_json(msg="Unexpected action: %s." % (action)) + junos_module.logger.debug('The %s action executed successfully.', + action) except (junos_module.pyez_exception.RpcError, junos_module.pyez_exception.ConnectError) as ex: junos_module.fail_json(msg="Error communicating with the device: %s" % diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 9eaf046f..724a78ba 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -625,11 +625,7 @@ def main(): else: facts = junos_module.get_facts() # facts checking has been done as part of persitent connection itself. - # for these scenario, persistent connection is not expected. - # there is also limitation of JSON for persistent connection. - # closing the persistent connection and creating a normal connection. - junos_module._pyez_conn.close() - junos_module.open() + # Check version info to see if we need to do the install. if target_version is not None: if all_re is True: @@ -648,7 +644,10 @@ def main(): target_version) else: current_version = facts['version'] - re_name = junos_module.dev.re_name + if junos_module.conn_type == "local": + re_name = junos_module.dev.re_name + else: + re_name = junos_module._pyez_conn.get_re_name() if target_version != current_version: junos_module.logger.debug("Current version on %s: %s. " "Target version: %s.", @@ -690,86 +689,92 @@ def main(): install_params[key] = value if kwargs is not None: install_params.update(kwargs) - try: - junos_module.logger.debug("Install parameters are: %s", - str(install_params)) - junos_module.add_sw() - ok, msg_ret = junos_module.sw.install(**install_params) - if ok is not True: - results['msg'] = 'Unable to install the software %s', msg_ret - junos_module.fail_json(**results) - msg = 'Package %s successfully installed. Response from device is: %s' % ( - install_params.get('package') or - install_params.get('pkg_set'), - msg_ret) - results['msg'] = msg - junos_module.logger.debug(msg) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - results['msg'] = 'Installation failed. Error: %s' % str(ex) - junos_module.fail_json(**results) - if reboot is True: - try: - # Try to deal with the fact that we might not get the closing - # and therefore might get an RpcTimeout. - # (This is a known Junos bug.) Set the timeout low so this - # happens relatively quickly. - restore_timeout = junos_module.dev.timeout - if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") - junos_module.dev.timeout = 5 - junos_module.logger.debug('Initiating reboot.') - try: - - got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) - junos_module.dev.timeout = restore_timeout - except Exception: # pylint: disable=broad-except - junos_module.dev.timeout = restore_timeout - raise - junos_module.logger.debug("Reboot RPC executed.") - if got is not None: - results['msg'] += ' Reboot successfully initiated. ' \ - 'Reboot message: %s' % got - else: - # This is the else clause of the for loop. - # It only gets executed if the loop finished without - # hitting the break. - results['msg'] += ' Did not find expected response ' \ - 'from reboot RPC. ' - junos_module.fail_json(**results) - except (junos_module.pyez_exception.RpcTimeoutError) as ex: - # This might be OK. It might just indicate the device didn't - # send the closing (known Junos bug). - # Try to close the device. If it closes cleanly, then it was - # still reachable, which probably indicates a problem. - try: - junos_module.close(raise_exceptions=True) - # This means the device wasn't already disconnected. - results['msg'] += ' Reboot failed. It may not have been ' \ - 'initiated.' + junos_module.logger.debug("Install parameters are: %s", + str(install_params)) + if junos_module.conn_type != "local": + results['msg'] = junos_module._pyez_conn.software_api(install_params) + else: + try: + junos_module.add_sw() + ok, msg_ret = junos_module.sw.install(**install_params) + if ok is not True: + results['msg'] = 'Unable to install the software %s', msg_ret junos_module.fail_json(**results) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.RpcTimeoutError, - junos_module.pyez_exception.ConnectError): - # This is expected. The device has already disconnected. - results['msg'] += ' Reboot succeeded.' - except (junos_module.ncclient_exception.TimeoutExpiredError): - # This is not really expected. Still consider reboot success as - # Looks like rpc was consumed but no response as its rebooting. - results['msg'] += ' Reboot succeeded. Ignoring close error.' - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) + msg = 'Package %s successfully installed. Response from device is: %s' % ( + install_params.get('package') or + install_params.get('pkg_set'), + msg_ret) + results['msg'] = msg + junos_module.logger.debug(msg) + except (junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError) as ex: + results['msg'] = 'Installation failed. Error: %s' % str(ex) junos_module.fail_json(**results) + if reboot is True: + junos_module.logger.debug('Initiating reboot.') + if junos_module.conn_type != "local": + results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost')) else: try: - junos_module.close() - except (junos_module.ncclient_exception.TimeoutExpiredError): - junos_module.logger.debug("Ignoring TimeoutError for close call") + # Try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this + # happens relatively quickly. + restore_timeout = junos_module.dev.timeout + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + try: + got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) + junos_module.dev.timeout = restore_timeout + except Exception: # pylint: disable=broad-except + junos_module.dev.timeout = restore_timeout + raise + junos_module.logger.debug("Reboot RPC executed.") + + if got is not None: + results['msg'] += ' Reboot successfully initiated. ' \ + 'Reboot message: %s' % got + else: + # This is the else clause of the for loop. + # It only gets executed if the loop finished without + # hitting the break. + results['msg'] += ' Did not find expected response ' \ + 'from reboot RPC. ' + junos_module.fail_json(**results) + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['msg'] += ' Reboot failed. It may not have been ' \ + 'initiated.' + junos_module.fail_json(**results) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.RpcTimeoutError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] += ' Reboot succeeded.' + except (junos_module.ncclient_exception.TimeoutExpiredError): + # This is not really expected. Still consider reboot success as + # Looks like rpc was consumed but no response as its rebooting. + results['msg'] += ' Reboot succeeded. Ignoring close error.' + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError) as ex: + results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) + junos_module.fail_json(**results) + else: + try: + junos_module.close() + except (junos_module.ncclient_exception.TimeoutExpiredError): + junos_module.logger.debug("Ignoring TimeoutError for close call") - junos_module.logger.debug("Reboot RPC successfully initiated.") + junos_module.logger.debug("Reboot RPC successfully initiated.") if reboot_pause > 0: junos_module.logger.debug("Sleeping for %d seconds", reboot_pause) diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index fd505f5c..72c9fae9 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -205,7 +205,10 @@ def main(): current_cluster_id = junos_module.dev.facts['srx_cluster_id'] if current_cluster_id is not None: current_cluster_id = int(current_cluster_id) - current_node_name = junos_module.dev.re_name + if junos_module.conn_type == "local": + current_node_name = junos_module.dev.re_name + else: + current_node_name = junos_module._pyez_conn.get_re_name() current_node_id = None if current_node_name is not None: (_, _, current_node_id) = current_node_name.partition('node') @@ -262,14 +265,22 @@ def main(): try: output = None if enable is True: - resp = junos_module.dev.rpc.set_chassis_cluster_enable( - cluster_id=str(cluster_id), node=str(node_id), - reboot=True, normalize=True - ) + if junos_module.conn_type == "local": + resp = junos_module.dev.rpc.set_chassis_cluster_enable( + cluster_id=str(cluster_id), node=str(node_id), + reboot=True, normalize=True + ) + else: + resp = junos_module._pyez_conn.set_chassis_cluster_enable( + cluster_id=str(cluster_id), node=str(node_id)) else: - resp = junos_module.dev.rpc.set_chassis_cluster_disable( - reboot=True, normalize=True - ) + if junos_module.conn_type == "local": + resp = junos_module.dev.rpc.set_chassis_cluster_disable( + reboot=True, normalize=True + ) + else: + resp = junos_module._pyez_conn.set_chassis_cluster_disable() + if resp is not None: output = resp.getparent().findtext('.//output') if output is None: @@ -288,6 +299,5 @@ def main(): # Return response. junos_module.exit_json(**results) - if __name__ == '__main__': main() diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index 880a0ed2..99f7ddcb 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -332,68 +332,65 @@ def main(): 'vmhost': vmhost, 'failed': True} - # for these scenario, persistent connection is not expected. - # there is also limitation of JSON for persistent connection. - # closing the persistent connection and creating a normal connection. - if junos_module.conn_type != "local": - junos_module._pyez_conn.close() - junos_module.open() - if not junos_module.check_mode: - if action != 'zeroize': - # If we're going to do a shutdown, reboot, or halt right away then - # try to deal with the fact that we might not get the closing - # and therefore might get an RpcTimeout. - # (This is a known Junos bug.) Set the timeout low so this happens - # relatively quickly. - if (at == 'now' or (in_min == 0 and at is None)): - if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") - junos_module.dev.timeout = 5 - - # Execute the RPC. - try: - junos_module.logger.debug("Executing RPC") - junos_module.add_sw() - if action == 'reboot': - got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) - elif action == 'shutdown': - got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) - elif action == 'halt': - got = junos_module.sw.halt(in_min, at, all_re, other_re) - elif action == 'zeroize': - got = junos_module.sw.zeroize(all_re, media) - else: - junos_module.fail_json(msg='Relevant action not found') - - junos_module.logger.debug("RPC executed") - if got is None: - results['msg'] = 'Did not find expected RPC response.' - results['changed'] = False - else: - results['msg'] = '%s successfully initiated. Response got %s' % (action, got) - results['failed'] = False - except (junos_module.pyez_exception.RpcTimeoutError) as ex: - # This might be OK. It might just indicate the device didn't - # send the closing (known Junos bug). - # Try to close the device. If it closes cleanly, then it was - # still reachable, which probably indicates there was a problem. + if junos_module.conn_type != "local": + results['msg'] = junos_module._pyez_conn.system_api(action, in_min, at, all_re, vmhost, other_re, media) + results['failed'] = False + else: + if action != 'zeroize': + # If we're going to do a shutdown, reboot, or halt right away then + # try to deal with the fact that we might not get the closing + # and therefore might get an RpcTimeout. + # (This is a known Junos bug.) Set the timeout low so this happens + # relatively quickly. + if (at == 'now' or (in_min == 0 and at is None)): + if junos_module.dev.timeout > 5: + junos_module.logger.debug("Decreasing device RPC timeout " + "to 5 seconds.") + junos_module.dev.timeout = 5 + + # Execute the RPC. try: - junos_module.close(raise_exceptions=True) - # This means the device wasn't already disconnected. - results['changed'] = False - results['msg'] = '%s failed. %s may not have been ' \ - 'initiated.' % (action, action) + junos_module.logger.debug("Executing RPC") + junos_module.add_sw() + if action == 'reboot': + got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) + elif action == 'shutdown': + got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) + elif action == 'halt': + got = junos_module.sw.halt(in_min, at, all_re, other_re) + elif action == 'zeroize': + got = junos_module.sw.zeroize(all_re, media) + else: + junos_module.fail_json(msg='Relevant action not found') + + junos_module.logger.debug("RPC executed") + if got is None: + results['msg'] = 'Did not find expected RPC response.' + results['changed'] = False + else: + results['msg'] = '%s successfully initiated. Response got %s' % (action, got) + results['failed'] = False + except (junos_module.pyez_exception.RpcTimeoutError) as ex: + # This might be OK. It might just indicate the device didn't + # send the closing (known Junos bug). + # Try to close the device. If it closes cleanly, then it was + # still reachable, which probably indicates there was a problem. + try: + junos_module.close(raise_exceptions=True) + # This means the device wasn't already disconnected. + results['changed'] = False + results['msg'] = '%s failed. %s may not have been ' \ + 'initiated.' % (action, action) + except (junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError): + # This is expected. The device has already disconnected. + results['msg'] = '%s succeeded.' % (action) + results['failed'] = False except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError): - # This is expected. The device has already disconnected. - results['msg'] = '%s succeeded.' % (action) - results['failed'] = False - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['changed'] = False - results['msg'] = '%s failed. Error: %s' % (action, str(ex)) + junos_module.pyez_exception.ConnectError) as ex: + results['changed'] = False + results['msg'] = '%s failed. Error: %s' % (action, str(ex)) # Return results. junos_module.exit_json(**results) From 8e0bb3d71840ce2abbfa9884e9ef9b022d619805 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 30 Nov 2020 12:31:12 +0530 Subject: [PATCH 333/426] Sample files added for collection --- README.md | 6 + Samples/persistent_conn.yml | 59 ++++++ Samples/sample_command.yaml | 61 ++++++ Samples/sample_config.yaml | 175 ++++++++++++++++++ Samples/sample_facts.yaml | 26 +++ Samples/sample_rpc.yaml | 61 ++++++ Samples/sample_software.yaml | 28 +++ Samples/sample_system.yaml | 47 +++++ Samples/test_jsnapy.yaml | 12 ++ .../module_utils/juniper_junos_common.py | 14 +- .../juniper/device/plugins/modules/command.py | 64 +++---- .../juniper/device/plugins/modules/config.py | 59 ++++-- .../juniper/device/plugins/modules/facts.py | 27 ++- .../juniper/device/plugins/modules/jsnapy.py | 16 +- .../juniper/device/plugins/modules/ping.py | 24 +-- .../juniper/device/plugins/modules/pmtud.py | 14 +- .../juniper/device/plugins/modules/rpc.py | 121 +++++------- .../device/plugins/modules/software.py | 26 +-- .../device/plugins/modules/srx_cluster.py | 6 +- .../juniper/device/plugins/modules/system.py | 22 +-- .../juniper/device/plugins/modules/table.py | 8 +- 21 files changed, 685 insertions(+), 191 deletions(-) create mode 100644 Samples/persistent_conn.yml create mode 100644 Samples/sample_command.yaml create mode 100644 Samples/sample_config.yaml create mode 100644 Samples/sample_facts.yaml create mode 100644 Samples/sample_rpc.yaml create mode 100644 Samples/sample_software.yaml create mode 100644 Samples/sample_system.yaml create mode 100644 Samples/test_jsnapy.yaml diff --git a/README.md b/README.md index 1cddb69c..20e82cf4 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,12 @@ version of the juniper.device collection. sudo ansible-galaxy collection install juniper.device ``` +You can also use the ansible-galaxy install command to install the latest development version of the junos role directly from GitHub. + +```bash +sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git#/ansible_collections/juniper/device +``` + ### Git clone For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: diff --git a/Samples/persistent_conn.yml b/Samples/persistent_conn.yml new file mode 100644 index 00000000..ac9f74a6 --- /dev/null +++ b/Samples/persistent_conn.yml @@ -0,0 +1,59 @@ +--- +- name: 'Explicit host argument' + hosts: junos +# connection: local + connection: juniper.device.pyez + + gather_facts: no + collections: + - juniper.device + + tasks: + # Command module executed + - name: show version with command + command: + commands: + - "show version" + register: junos_result + + # Jsnapy module executed + - name: 'Performa a snap_check of storage' + jsnapy: + action: "check" + test_files: "test_jsnapy.yaml" + register: response + + - name: "Print the response" + debug: + var: response + + # Facts module executed + - name: "Get facts" + facts: + register: response + + # Rpc module executed + - name: "Test RPC" + rpc: + rpcs: + - "get-config" + - "get-software-information" + + # Config module executed + - name: SET JUNOS CONFIG + config: + load: "set" + format: "set" + lines: 'set system login message "This is login message"' + comment: "comment" + ignore_warning: "True" + config_mode: private + register: junos_response + + # System module executed + - name: Reboot switch after upgrade + system: + all_re: false + action: "reboot" + register: response + diff --git a/Samples/sample_command.yaml b/Samples/sample_command.yaml new file mode 100644 index 00000000..47d777bf --- /dev/null +++ b/Samples/sample_command.yaml @@ -0,0 +1,61 @@ +- name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: "Execute single command in text format" + command: + commands: "show configuration system services netconf traceoptions" + format: text + + - name: "Execute command with login credentials" + command: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + commands: + - "show system storage" + register: junos_result + + - name: Execute three commands. + command: + commands: + - "show version" + - "show system uptime" + - "show interface terse" + register: response + + - name: Print the command output of each. + debug: + var: item.stdout + with_items: "{{ response.results }}" + + - name: show route with XML output - show version with JSON output + command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + + - name: Multiple commands, save outputs, but don't return them + command: + commands: + - "show route" + - "show version" + formats: + - "xml" + dest_dir: "../Output" + return_output: false + + - name: save output to dest + command: + command: + - "show route" + - "show lldp neighbors" + dest: "/tmp/{{ inventory_hostname }}.commands.output" diff --git a/Samples/sample_config.yaml b/Samples/sample_config.yaml new file mode 100644 index 00000000..b8ef45ce --- /dev/null +++ b/Samples/sample_config.yaml @@ -0,0 +1,175 @@ +- name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Retrieve the committed configuration + config: + retrieve: 'committed' + diff: false + check: false + commit: false + register: response + + - name: Print the lines in the config. + debug: + var: response.config_lines + + - name: Append .foo to the hostname using private config mode. + config: + config_mode: 'private' + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + register: response + + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the previous config. + config: + config_mode: 'private' + rollback: 1 + register: response + + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the rescue config. + config: + rollback: 'rescue' + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load override from a file. + config: + load: 'override' + src: "{{ inventory_hostname }}.conf" + register: response + + - name: Print the complete response. + debug: + var: response + + - name: Load from a Jinja2 template. + config: + load: 'merge' + format: 'xml' + template: "{{ inventory_hostname }}.j2" + vars: + host: "{{ inventory_hostname }}" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device. + config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device, skip the commit check + config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + check: false + register: response + - name: Print the msg. + debug: + var: response.msg + + - name: Print diff between current and rollback 10. No check. No commit. + config: + rollback: 11 + diff: true + check: false + commit: false + register: response + + - name: Print the msg. + debug: + var: response + + - name: Retrieve [edit system services] of current committed config. + config: + retrieve: 'committed' + filter: 'system/services' + diff: true + check: false + commit: false + register: response + + - name: Print the resulting config lines. + debug: + var: response.config_lines + + - name: Enable NETCONF SSH and traceoptions, save config, and diffs. + config: + load: 'merge' + lines: + - 'set system services netconf ssh' + - 'set system services netconf traceoptions flag all' + - 'set system services netconf traceoptions file netconf.log' + format: 'set' + retrieve: 'candidate' + filter: 'system/services' + comment: 'Enable NETCONF with traceoptions' + dest_dir: './output' + register: response + + - name: Print the complete response + debug: + var: response + + - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit + config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + confirm: 5 + check_commit_wait: 3 + register: response + + - name: Print the complete response + debug: + var: response + + - name: Confirm the previous commit with a commit check (but no commit) + config: + check: true + diff: false + commit: false + register: response + + - name: Print the complete response + debug: + var: response + + - name: fetch config from the device with filter and login credentials + config: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + retrieve: 'committed' + format: xml + commit: no + check: no + diff: no + dest_dir: "/tmp/" + filter: re0 + return_output: True + register: config_output + + + diff --git a/Samples/sample_facts.yaml b/Samples/sample_facts.yaml new file mode 100644 index 00000000..a8aaa4ba --- /dev/null +++ b/Samples/sample_facts.yaml @@ -0,0 +1,26 @@ +- name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: "Get facts" + facts: + register: response + + - name: Facts with login credentials + facts: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + + - name: Facts in telnet mode + facts: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "23" + mode: "telnet" \ No newline at end of file diff --git a/Samples/sample_rpc.yaml b/Samples/sample_rpc.yaml new file mode 100644 index 00000000..a5eaff9b --- /dev/null +++ b/Samples/sample_rpc.yaml @@ -0,0 +1,61 @@ +- name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: "Execute RPC with filters" + rpc: + rpcs: + - "get-config" + format: xml + filter: re0 + attr: name=re0 + register: test1 + ignore_errors: True + + - name: Check TEST 1 + debug: + var: test1 + + - name: "Execute RPC with host data and store logging" + rpc: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + rpcs: + - "get-software-information" + logfile: "/var/tmp/rpc.log" + ignore_warning: true + register: test1 + ignore_errors: True + + - name: "Print results - summary" + debug: + var: test1.stdout_lines + + - name: "Execute multiple RPC" + rpc: + rpcs: + - "get-config" + - "get-software-information" + + - name: Get Device Configuration for vlan - 1 + rpc: + rpc: "get-config" + filter_xml: "" + dest: "get_config_vlan.conf" + register: junos + + - name: Get interface information with kwargs + rpc: + rpc: get-interface-information + kwargs: + interface_name: em1 + media: True + format: json + dest: get_interface_information.conf + register: junos \ No newline at end of file diff --git a/Samples/sample_software.yaml b/Samples/sample_software.yaml new file mode 100644 index 00000000..16fde826 --- /dev/null +++ b/Samples/sample_software.yaml @@ -0,0 +1,28 @@ +- name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Execute a basic Junos software upgrade. + software: + local_package: "./images/" + register: response + + - name: Print the complete response. + debug: + var: response + + - name: Upgrade Junos OS from package copied at device + software: + host: "10.x.x.x" + user: "user" + passwd: "user123" + remote_package: "/var/tmp/junos-install-mx-x86-64-20.1R1.5.tgz" + no_copy: false + cleanfs: false + validate: true + register: response + diff --git a/Samples/sample_system.yaml b/Samples/sample_system.yaml new file mode 100644 index 00000000..abc2c399 --- /dev/null +++ b/Samples/sample_system.yaml @@ -0,0 +1,47 @@ +- name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Reboot all REs of the device + system: + action: "reboot" + + - name: Power off the other RE of the device. + system: + action: "shutdown" + othe_re: True + + - name: Reboot this RE at 8pm today. + system: + action: "reboot" + all_re: False + at: "20:00" + + - name: Halt the system on 25 January 2018 at 4pm. + system: + action: "halt" + at: "1801251600" + + - name: Reboot the system in 30 minutes. + system: + action: "reboot" + in_min: 30 + + - name: Reboot the system in 30 minutes. + system: + action: "reboot" + at: "+30m" + + - name: Zeroize the local RE only. + system: + action: "zeroize" + all_re: False + + - name: Zeroize all REs and overwrite medea. + system: + action: "zeroize" + media: True \ No newline at end of file diff --git a/Samples/test_jsnapy.yaml b/Samples/test_jsnapy.yaml new file mode 100644 index 00000000..26347c75 --- /dev/null +++ b/Samples/test_jsnapy.yaml @@ -0,0 +1,12 @@ +tests_include: + - test_rpc_version + +test_rpc_version: + - rpc: get-software-information + - iterate: + xpath: package-information + id: './name' + tests: + - no-diff: comment + info: "Test Succeeded!!, comment is <{{post['comment']}}>" + err: "Test Failed!!!, comment is <{{post['comment']}}>" \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index c25a6e05..abf734d1 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -50,6 +50,12 @@ import logging import os +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -1280,9 +1286,9 @@ def get_configuration(self, database='committed', format='text', if model is None and config.tag != 'configuration': self.fail_json(msg='Unexpected XML tag returned. ' 'Configuration is: %s' % - (etree.tostring(config, pretty_print=True))) - return_val = (etree.tostring(config, pretty_print=True), - jxmlease.parse_etree(config)) + (self.etree.tostring(config, pretty_print=True))) + return_val = (self.etree.tostring(config, pretty_print=True), + self.jxmlease.parse_etree(config)) elif format == 'json': return_val = (json.dumps(config), config) else: @@ -1721,4 +1727,4 @@ def get_facts(self): def get_chassis_inventory(self): chassis = self._pyez_conn.get_chassis_inventory() - return chassis \ No newline at end of file + return chassis diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index 0fdb9937..9180bed2 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_command +module: command author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Execute one or more CLI commands on a Junos device description: @@ -147,26 +147,30 @@ ''' EXAMPLES = ''' ---- -- name: Examples of juniper_junos_command - hosts: junos-all +- name: 'Explicit host argument' + hosts: junos connection: local gather_facts: no collections: - juniper.device tasks: - - name: Execute single "show version" command. - juniper_junos_command: - commands: "show version" - register: response + - name: "Execute single command in text format" + command: + commands: "show configuration system services netconf traceoptions" + format: text - - name: Print the command output - debug: - var: response.stdout + - name: "Execute command with login credentials" + command: + host: "10.x.x.x." + user: "user" + passwd: "user123" + commands: + - "show system storage" + register: junos_result - name: Execute three commands. - juniper_junos_command: + command: commands: - "show version" - "show system uptime" @@ -178,15 +182,8 @@ var: item.stdout with_items: "{{ response.results }}" - - name: Two commands with XML output. - juniper_junos_command: - commands: - - "show route" - - "show lldp neighbors" - format: xml - - name: show route with XML output - show version with JSON output - juniper_junos_command: + command: commands: - "show route" - "show version" @@ -194,35 +191,22 @@ - "xml" - "json" - - name: save outputs in dest_dir - juniper_junos_command: + - name: Multiple commands, save outputs, but don't return them + command: commands: - "show route" - "show version" - dest_dir: "./output" - - - name: save output to dest - juniper_junos_command: - command: "show system uptime" - dest: "/tmp/{{ inventory_hostname }}.uptime.output" + formats: + - "xml" + dest_dir: "../Output" + return_output: false - name: save output to dest - juniper_junos_command: + command: command: - "show route" - "show lldp neighbors" dest: "/tmp/{{ inventory_hostname }}.commands.output" - - - name: Multiple commands, save outputs, but don't return them - juniper_junos_command: - commands: - - "show route" - - "show version" - formats: - - "xml" - - "json" - dest_dir: "/tmp/outputs/" - return_output: false ''' RETURN = ''' diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index 180056f5..297bbfc1 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -46,7 +46,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_config +module: config author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Manipulate the configuration of a Junos device description: @@ -515,46 +515,50 @@ EXAMPLES = ''' --- -- name: Manipulate the configuration of Junos devices - hosts: junos-all +- name: 'Explicit host argument' + hosts: junos connection: local gather_facts: no collections: - juniper.device + tasks: - name: Retrieve the committed configuration - juniper_junos_config: + config: retrieve: 'committed' diff: false check: false commit: false register: response + - name: Print the lines in the config. debug: var: response.config_lines - name: Append .foo to the hostname using private config mode. - juniper_junos_config: + config: config_mode: 'private' load: 'merge' lines: - "set system host-name {{ inventory_hostname }}.foo" register: response + - name: Print the config changes. debug: var: response.diff_lines - name: Rollback to the previous config. - juniper_junos_config: + config: config_mode: 'private' rollback: 1 register: response + - name: Print the config changes. debug: var: response.diff_lines - name: Rollback to the rescue config. - juniper_junos_config: + config: rollback: 'rescue' register: response - name: Print the complete response. @@ -562,16 +566,17 @@ var: response - name: Load override from a file. - juniper_junos_config: + config: load: 'override' src: "{{ inventory_hostname }}.conf" register: response + - name: Print the complete response. debug: var: response - name: Load from a Jinja2 template. - juniper_junos_config: + config: load: 'merge' format: 'xml' template: "{{ inventory_hostname }}.j2" @@ -583,7 +588,7 @@ var: response - name: Load from a file on the Junos device. - juniper_junos_config: + config: load: 'merge' url: "{{ inventory_hostname }}.conf" register: response @@ -592,7 +597,7 @@ var: response - name: Load from a file on the Junos device, skip the commit check - juniper_junos_config: + config: load: 'merge' url: "{{ inventory_hostname }}.conf" check: false @@ -602,30 +607,32 @@ var: response.msg - name: Print diff between current and rollback 10. No check. No commit. - juniper_junos_config: + config: rollback: 11 diff: true check: false commit: false register: response + - name: Print the msg. debug: var: response - name: Retrieve [edit system services] of current committed config. - juniper_junos_config: + config: retrieve: 'committed' filter: 'system/services' diff: true check: false commit: false register: response + - name: Print the resulting config lines. debug: var: response.config_lines - name: Enable NETCONF SSH and traceoptions, save config, and diffs. - juniper_junos_config: + config: load: 'merge' lines: - 'set system services netconf ssh' @@ -637,29 +644,49 @@ comment: 'Enable NETCONF with traceoptions' dest_dir: './output' register: response + - name: Print the complete response debug: var: response - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit - juniper_junos_config: + config: load: 'merge' url: "{{ inventory_hostname }}.conf" confirm: 5 check_commit_wait: 3 register: response + - name: Print the complete response debug: var: response + - name: Confirm the previous commit with a commit check (but no commit) - juniper_junos_config: + config: check: true diff: false commit: false register: response + - name: Print the complete response debug: var: response + + - name: fetch config from the device with filter and login credentials + config: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + retrieve: 'committed' + format: xml + commit: no + check: no + diff: no + dest_dir: "/tmp/" + filter: re0 + return_output: True + register: config_output ''' RETURN = ''' diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py index 6f2d6f88..d8816344 100644 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_facts +module: facts author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Retrieve facts from a Junos device description: @@ -85,15 +85,32 @@ EXAMPLES = ''' --- -- name: Gather facts from Junos devices - hosts: junos-all +- name: 'Explicit host argument' + hosts: junos connection: local gather_facts: no collections: - juniper.device + tasks: - - name: Gather Junos facts with no configuration - juniper_junos_facts: + - name: "Get facts" + facts: + register: response + + - name: Facts with login credentials + facts: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + + - name: Facts in telnet mode + facts: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "23" + mode: "telnet" # Print a fact diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index dd24fe96..6355bbe2 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_jsnapy +module: jsnapy author: - Juniper Networks - Roslan Zaki @@ -61,7 +61,7 @@ fails to execute the JSNAPy tests. If does NOT report C(failed) if one or more of the JSNAPy tests fail. To check the test results, register the module's response and use the assert module to verify the expected result - in the response. (See :ref:`juniper_junos_jsnapy-examples-label`.) + in the response. (See :ref:`jsnapy-examples-label`.) - A callback plugin which formats and prints JSNAPy test results for human consumption is also available. This callback plugin is enabled by adding C(callback_whitelist = jsnapy) to the Ansible configuration file. @@ -110,7 +110,7 @@ EXAMPLES = ''' --- -- name: Examples of juniper_junos_jsnapy +- name: Examples of jsnapy hosts: junos-all connection: local gather_facts: no @@ -119,7 +119,7 @@ tasks: - name: JUNOS Post Checklist - juniper_junos_jsnapy: + jsnapy: action: "snap_post" config_file: "first_test.yml" logfile: "migration_post.log" @@ -133,7 +133,7 @@ var: test1 - name: Test based on a test_file directly - juniper_junos_jsnapy: + jsnapy: action: "snapcheck" test_files: "tests/test_junos_interface.yaml" register: test2 @@ -146,17 +146,17 @@ var: test2 - name: "Collect Pre Snapshot" - juniper_junos_jsnapy: + jsnapy: action: "snap_pre" test_files: "tests/test_loopback.yml" - name: "Collect Post Snapshot" - juniper_junos_jsnapy: + jsnapy: action: "snap_post" test_files: "tests/test_loopback.yml" - name: "Check after Pre and Post Snapshots" - juniper_junos_jsnapy: + jsnapy: action: "check" test_files: "tests/test_loopback.yml" register: test3 diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py index 872b6826..1d9f2d05 100644 --- a/ansible_collections/juniper/device/plugins/modules/ping.py +++ b/ansible_collections/juniper/device/plugins/modules/ping.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_ping +module: ping author: Juniper Networks - Stacy Smith (@stacywsmith) short_description: Execute ping from a Junos device description: @@ -141,7 +141,7 @@ EXAMPLES = ''' --- -- name: Examples of juniper_junos_ping +- name: Examples of ping hosts: junos-all connection: local gather_facts: no @@ -150,11 +150,11 @@ tasks: - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. - juniper_junos_ping: + ping: dest: "192.68.1.1" - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. - juniper_junos_ping: + ping: dest: "192.68.1.1" acceptable_percent_loss: 50 register: response @@ -163,7 +163,7 @@ var: response - name: Ping 192.68.1.1. Send 20 packets. Register response. - juniper_junos_ping: + ping: dest: "192.68.1.1" count: 20 register: response @@ -172,7 +172,7 @@ var: response.packets_sent - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. - juniper_junos_ping: + ping: dest: "192.68.1.1" count: 10 rapid: false @@ -182,7 +182,7 @@ var: response.rtt_average - name: Ping www.juniper.net with ttl 15. Register response. - juniper_junos_ping: + ping: dest: "www.juniper.net" ttl: 15 register: response @@ -191,7 +191,7 @@ var: response.packet_loss - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. - juniper_junos_ping: + ping: dest: "192.68.1.1" size: 1472 register: response @@ -200,7 +200,7 @@ var: response.packets_received - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. - juniper_junos_ping: + ping: dest: "192.68.1.1" do_not_fragment: true register: response @@ -209,7 +209,7 @@ var: response.rtt_maximum - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. - juniper_junos_ping: + ping: dest: "192.68.1.1" source: "192.68.1.2" register: response @@ -218,12 +218,12 @@ var: response.source - name: Ping 192.168.1.1 from the red routing-instance. - juniper_junos_ping: + ping: dest: "192.168.1.1" routing_instance: "red" - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface - juniper_junos_ping: + ping: dest: "224.0.0.1" interface: "ge-0/0/0.0" ''' diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py index 6add19a1..f8b1d205 100644 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_pmtud +module: pmtud author: - Martin Komon (@mkomon) - Juniper Networks - Stacy Smith (@stacywsmith) @@ -136,11 +136,11 @@ tasks: - name: Perform PMTUD to 192.68.1.1 with default parameters. - juniper_junos_pmtud: + pmtud: dest: "192.68.1.1" - name: Perform PMTUD to 192.68.1.1. Register response. - juniper_junos_pmtud: + pmtud: dest: "192.68.1.1" register: response - name: Print the discovered MTU. @@ -148,7 +148,7 @@ var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. - juniper_junos_pmtud: + pmtud: dest: "192.68.1.1" max_size: 65496 max_range: 65536 @@ -158,7 +158,7 @@ var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. - juniper_junos_pmtud: + pmtud: dest: "192.68.1.1" interface: "ge-0/0/0.0" register: response @@ -167,7 +167,7 @@ var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. - juniper_junos_pmtud: + pmtud: dest: "192.68.1.1" source: "192.168.1.2" register: response @@ -176,7 +176,7 @@ var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. - juniper_junos_pmtud: + pmtud: dest: "192.68.1.1" routing_instance: "red" register: response diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index 855dc55a..328bbfe8 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_rpc +module: rpc author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Execute one or more NETCONF RPCs on a Junos device description: @@ -166,7 +166,7 @@ I(kwargs) list and the RPCs in the I(rpcs) list. In other words, the two lists must always contain the same number of elements. For RPC arguments which do not require a value, specify the value of True as - shown in the :ref:`juniper_junos_rpc-examples-label`. + shown in the :ref:`rpc-examples-label`. required: false default: none type: dict or list of dict @@ -196,86 +196,67 @@ EXAMPLES = ''' --- -- name: Examples of juniper_junos_rpc - hosts: junos-all +- name: 'Explicit host argument' + hosts: junos connection: local gather_facts: no collections: - juniper.device tasks: - - name: Execute single get-software-information RPC. - juniper_junos_rpc: - rpcs: "get-software-information" - register: response - - name: Print the RPC's output as a single multi-line string. + - name: "Execute RPC with filters" + rpc: + rpcs: + - "get-config" + format: xml + filter: re0 + attr: name=re0 + register: test1 + ignore_errors: True + + - name: Check TEST 1 debug: - var: response.stdout - -###### OLD EXAMPLES ########## -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - dest=get_interface_information.conf - register=junos - -- junos_rpc: - host={{ inventory_hostname }} - rpc=get-interface-information - kwargs="interface_name=em0" - format=xml/text/json - dest=get_interface_information.conf - register=junos - -# Example to fetch device configuration -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - dest=get_config.conf - -# Fetch configuration over console server connection using PyEZ >= 2.0 -- name: Get Device Configuration - junos_rpc: - host={{ inventory_hostname }} - port=7005 - mode='telnet' - rpc=get-config - dest=get_config.conf - -# Example to fetch device configuration -- name: Get Device Configuration for interface - junos_rpc: - host={{ inventory_hostname }} - rpc=get-config - filter_xml="" - dest=get_config.conf - register: junos - -# Example to fetch configuration in json for >=14.2 -# and use it with rpc_reply -- name: Get Device Configuration - hosts: all - collections: - - juniper.device - connection: local - gather_facts: no - tasks: - - name: Get interface information - junos_rpc: - host: "{{ inventory_hostname }}" + var: test1 + + - name: "Execute RPC with host data and store logging" + rpc: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + rpcs: + - "get-software-information" + logfile: "/var/tmp/rpc.log" + ignore_warning: true + register: test1 + ignore_errors: True + + - name: "Print results - summary" + debug: + var: test1.stdout_lines + + - name: "Execute multiple RPC" + rpc: + rpcs: + - "get-config" + - "get-software-information" + + - name: Get Device Configuration for vlan - 1 + rpc: + rpc: "get-config" + filter_xml: "" + dest: "get_config_vlan.conf" + register: junos + + - name: Get interface information with kwargs + rpc: rpc: get-interface-information kwargs: - interface_name: em0 + interface_name: em1 media: True format: json dest: get_interface_information.conf - register: junos - - - name: Print configuration - debug: msg="{{ junos.rpc_reply }}" -###### OLD EXAMPLES ########## -''' + register: junos''' RETURN = ''' attrs: diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 724a78ba..d7e02b46 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_software +module: software author: - Jeremy Schulman - "Juniper Networks - Stacy Smith (@stacywsmith)" @@ -317,8 +317,8 @@ EXAMPLES = ''' --- -- name: Examples of juniper_junos_software - hosts: junos-all +- name: 'Explicit host argument' + hosts: junos connection: local gather_facts: no collections: @@ -326,20 +326,24 @@ tasks: - name: Execute a basic Junos software upgrade. - juniper_junos_software: + software: local_package: "./images/" register: response + - name: Print the complete response. debug: var: response -###### OLD EXAMPLES ########## - - junos_install_os: - host={{ inventory_hostname }} - version=12.1X46-D10.2 - package=/usr/local/junos/images/junos-vsrx-12.1X46-D10.2-domestic.tgz - logfile=/usr/local/junos/log/software.log -###### OLD EXAMPLES ########## + - name: Upgrade Junos OS from package copied at device + software: + host: "10.x.x.x" + user: "user" + passwd: "user123" + remote_package: "/var/tmp/junos-install-mx-x86-64-20.1R1.5.tgz" + no_copy: false + cleanfs: false + validate: true + register: response ''' diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index 72c9fae9..24d8c54e 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -46,7 +46,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_srx_cluster +module: srx_cluster author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Add or remove SRX chassis cluster configuration description: @@ -98,7 +98,7 @@ - juniper.device tasks: - name: Enable an SRX cluster - juniper_junos_srx_cluster: + srx_cluster: enable: true cluster_id: 4 node_id: 0 @@ -108,7 +108,7 @@ var: response.config_lines - name: Disable an SRX cluster - juniper_junos_srx_cluster: + srx_cluster: enable: false register: response - name: Print the response. diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index 99f7ddcb..34424759 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -45,7 +45,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_system +module: system author: "Juniper Networks - Stacy Smith (@stacywsmith)" short_description: Initiate operational actions on the Junos system description: @@ -158,8 +158,8 @@ EXAMPLES = ''' --- -- name: Examples of juniper_junos_system - hosts: junos-all +- name: 'Explicit host argument' + hosts: junos connection: local gather_facts: no collections: @@ -167,42 +167,42 @@ tasks: - name: Reboot all REs of the device - juniper_junos_system: + system: action: "reboot" - name: Power off the other RE of the device. - juniper_junos_system: + system: action: "shutdown" othe_re: True - name: Reboot this RE at 8pm today. - juniper_junos_system: + system: action: "reboot" all_re: False at: "20:00" - name: Halt the system on 25 January 2018 at 4pm. - juniper_junos_system: + system: action: "halt" at: "1801251600" - name: Reboot the system in 30 minutes. - juniper_junos_system: + system: action: "reboot" in_min: 30 - name: Reboot the system in 30 minutes. - juniper_junos_system: + system: action: "reboot" at: "+30m" - name: Zeroize the local RE only. - juniper_junos_system: + system: action: "zeroize" all_re: False - name: Zeroize all REs and overwrite medea. - juniper_junos_system: + system: action: "zeroize" media: True ''' diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py index 529d21df..8c6bbf2e 100644 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -47,7 +47,7 @@ extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: juniper_junos_table +module: table author: - Jason Edelman (@jedelman8) - Updated by Juniper Networks - Stacy Smith (@stacywsmith) @@ -132,7 +132,7 @@ tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table - juniper_junos_table: + table: file: "lldp.yml" register: response - name: Print response @@ -140,7 +140,7 @@ var: response - name: Retrieve routes within 192.68.1/8 - juniper_junos_table: + table: file: "routes.yml" table: "RouteTable" kwargs: @@ -152,7 +152,7 @@ var: response - name: Retrieve from custom table in playbook directory - juniper_junos_table: + table: file: "fpc.yaml" path: "." register: response From 80ca3db3302dd4e2fd7c80023660dba85e6b526b Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 11 Dec 2020 00:57:30 +0530 Subject: [PATCH 334/426] Fixed Documentation, facts parameter and licence details --- README.md | 28 +-- Samples/inventory | 5 + Samples/persistent_conn.yml | 4 + .../action/juniper_junos_common_action.py | 4 +- .../juniper/device/plugins/callback/jsnapy.py | 32 ++++ .../juniper/device/plugins/connection/pyez.py | 181 ++++-------------- .../plugins/module_utils/configuration.py | 33 +++- .../module_utils/juniper_junos_common.py | 18 +- .../juniper/device/plugins/modules/command.py | 5 +- .../juniper/device/plugins/modules/config.py | 6 +- .../juniper/device/plugins/modules/facts.py | 7 +- .../juniper/device/plugins/modules/jsnapy.py | 5 +- .../juniper/device/plugins/modules/ping.py | 5 +- .../juniper/device/plugins/modules/pmtud.py | 7 +- .../juniper/device/plugins/modules/rpc.py | 5 +- .../device/plugins/modules/software.py | 5 +- .../device/plugins/modules/srx_cluster.py | 6 +- .../juniper/device/plugins/modules/system.py | 5 +- .../juniper/device/plugins/modules/table.py | 7 +- requirements.txt | 2 +- 20 files changed, 149 insertions(+), 221 deletions(-) create mode 100644 Samples/inventory diff --git a/README.md b/README.md index 20e82cf4..d2aa7fc4 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ https://github.com/Juniper/ansible-junos-stdlib/tree/roles Since Ansible version >= 2.1, Ansible also natively includes [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.device -collection have names which begin with the prefix `juniper_junos_`. These two sets of Junos modules can coexist on the same +collection have names startinng with module types. These two sets of Junos modules can coexist on the same Ansible control machine, and an Ansible play may invoke a module from either (or both) sets. Juniper Networks recommends using the modules in this collection when writing new playbooks that manage Junos devices. @@ -39,24 +39,24 @@ using the modules in this collection when writing new playbooks that manage Juno This juniper.device collection includes the following modules: -- **juniper_junos_command** — Execute one or more CLI commands on a Junos device. -- **juniper_junos_config** — Manipulate the configuration of a Junos device. -- **juniper_junos_facts** — Retrieve facts from a Junos device. -- **juniper_junos_jsnapy** — Execute JSNAPy tests on a Junos device. -- **juniper_junos_ping** — Execute ping from a Junos device. -- **juniper_junos_pmtud** — Perform path MTU discovery from a Junos device to a destination. -- **juniper_junos_rpc** — Execute one or more NETCONF RPCs on a Junos device. -- **juniper_junos_software** — Install software on a Junos device. -- **juniper_junos_srx_cluster** — Add or remove SRX chassis cluster configuration. -- **juniper_junos_system** — Initiate operational actions on the Junos system. -- **juniper_junos_table** — Retrieve data from a Junos device using a PyEZ table/view. +- **command** — Execute one or more CLI commands on a Junos device. +- **config** — Manipulate the configuration of a Junos device. +- **facts** — Retrieve facts from a Junos device. +- **jsnapy** — Execute JSNAPy tests on a Junos device. +- **ping** — Execute ping from a Junos device. +- **pmtud** — Perform path MTU discovery from a Junos device to a destination. +- **rpc** — Execute one or more NETCONF RPCs on a Junos device. +- **software** — Install software on a Junos device. +- **srx_cluster** — Add or remove SRX chassis cluster configuration. +- **system** — Initiate operational actions on the Junos system. +- **table** — Retrieve data from a Junos device using a PyEZ table/view. ### PyEZ Version Requirement For ansible collection junos we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. ### Overview of Plugins -In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `juniper_junos_jsnapy`. +In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `jsnapy`. The callback_plugin `jsnapy` helps to print on the screen additional information regarding jsnapy failed tests. For each failed test, a log will be printed after the RECAP of the playbook as shown in this example: @@ -252,7 +252,7 @@ This example outlines how to use Ansible to install or upgrade the software imag - name: Checking NETCONF connectivity wait_for: host={{ inventory_hostname }} port=830 timeout=5 - name: Install Junos OS package - juniper_junos_software: + software: reboot: yes version: "{{ OS_version }}" package: "{{ pkg_dir }}/{{ OS_package }}" diff --git a/Samples/inventory b/Samples/inventory new file mode 100644 index 00000000..91232e2e --- /dev/null +++ b/Samples/inventory @@ -0,0 +1,5 @@ +[junos] +vm ansible_host=10.x.x.x ansible_user=user ansible_password=user123 ansible_port=22 + +[all:vars] +ansible_python_interpreter=/usr/local/bin/python3.8 diff --git a/Samples/persistent_conn.yml b/Samples/persistent_conn.yml index ac9f74a6..f352d027 100644 --- a/Samples/persistent_conn.yml +++ b/Samples/persistent_conn.yml @@ -8,6 +8,10 @@ collections: - juniper.device +# The example here uses inventory file for user-authentication. +# the command to be used will look something like below +# ansible-playbook persistent_conn.yml -i inventory + tasks: # Command module executed - name: show version with command diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py index 4f918b2a..c118b84a 100755 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py +++ b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py @@ -37,9 +37,9 @@ # The Ansible core engine will call ActionModule.run() class ActionModule(ExtractData, ActionNormal): - """A subclass of ansible.plugins.action.network.ActionModule used by all juniper_junos_* modules. + """A subclass of ansible.plugins.action.network.ActionModule used by all modules. - All juniper_junos_* modules share common behavior which is implemented in + All modules share common behavior which is implemented in this class. This includes specific option fallback/default behavior and passing the "hidden" _module_utils_path option to the module. diff --git a/ansible_collections/juniper/device/plugins/callback/jsnapy.py b/ansible_collections/juniper/device/plugins/callback/jsnapy.py index 7b07a9d2..983f17af 100644 --- a/ansible_collections/juniper/device/plugins/callback/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/callback/jsnapy.py @@ -1,3 +1,35 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# from __future__ import (absolute_import, division, print_function) __metaclass__ = type diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 2b432eed..d257c118 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -1,154 +1,43 @@ -# (c) 2016 Red Hat Inc. -# (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# from __future__ import absolute_import, division, print_function import xmltodict __metaclass__ = type -DOCUMENTATION = """author: Ansible Networking Team -connection: pyez -short_description: Use pyez to run command on JUNOS appliances -description: -- This connection plugin provides a connection to remote devices over the junos-pyez library. -options: - host: - description: - - Specifies the remote device FQDN or IP address to establish the SSH connection - to. - default: inventory_hostname - vars: - - name: ansible_host - port: - type: int - description: - - Specifies the port on the remote device that listens for connections when establishing - the SSH connection. - ini: - - section: defaults - key: remote_port - env: - - name: ANSIBLE_REMOTE_PORT - vars: - - name: ansible_port - remote_user: - description: - - The username used to authenticate to the remote device when the SSH connection - is first established. If the remote_user is not specified, the connection will - use the username of the logged in user. - - Can be configured from the CLI via the C(--user) or C(-u) options. - ini: - - section: defaults - key: remote_user - env: - - name: ANSIBLE_REMOTE_USER - vars: - - name: ansible_user - password: - description: - - Configures the user password used to authenticate to the remote device when - first establishing the SSH connection. - vars: - - name: ansible_password - - name: ansible_ssh_pass - - name: ansible_ssh_password - pyez_console: - description: - - console option. - ini: - - section: pyez_connection - key: console - env: - - name: ANSIBLE_PYEZ_CONSOLE - vars: - - name: ansible_pyez_console - private_key_file: - description: - - The private SSH key or certificate file used to authenticate to the remote device - when first establishing the SSH connection. - ini: - - section: defaults - key: private_key_file - env: - - name: ANSIBLE_PRIVATE_KEY_FILE - vars: - - name: ansible_private_key_file - host_key_auto_add: - type: boolean - description: - - By default, Ansible will prompt the user before adding SSH keys to the known - hosts file. Since persistent connections such as network_cli run in background - processes, the user will never be prompted. By enabling this option, unknown - host keys will automatically be added to the known hosts file. - - Be sure to fully understand the security implications of enabling this option - on production systems as it could create a security vulnerability. - default: false - ini: - - section: pyez_connection - key: host_key_auto_add - env: - - name: ANSIBLE_HOST_KEY_AUTO_ADD - persistent_connect_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait when trying to initially - establish a persistent connection. If this value expires before the connection - to the remote device is completed, the connection will fail. - default: 30 - ini: - - section: persistent_connection - key: connect_timeout - env: - - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT - vars: - - name: ansible_connect_timeout - persistent_command_timeout: - type: int - description: - - Configures, in seconds, the amount of time to wait for a command to return from - the remote device. If this timer is exceeded before the command returns, the - connection plugin will raise an exception and close. - default: 30 - ini: - - section: persistent_connection - key: command_timeout - env: - - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT - vars: - - name: ansible_command_timeout - persistent_log_messages: - type: boolean - description: - - This flag will enable logging the command executed and response received from - target device in the ansible log file. For this option to work 'log_path' ansible - configuration option is required to be set to a file path with write access. - - Be sure to fully understand the security implications of enabling this option - as it could create a security vulnerability by logging sensitive information - in log file. - default: false - ini: - - section: persistent_connection - key: log_messages - env: - - name: ANSIBLE_PERSISTENT_LOG_MESSAGES - vars: - - name: ansible_persistent_log_messages - pyez_ssh_config: - description: - - This variable is used to enable bastion/jump host with netconf connection. If - set to True the bastion/jump host ssh settings should be present in ~/.ssh/config - file, alternatively it can be set to custom ssh configuration file path to read - the bastion/jump host settings. - ini: - - section: pyez_connection - key: ssh_config - env: - - name: ANSIBLE_PYEZ_SSH_CONFIG - vars: - - name: ansible_pyez_ssh_config +DOCUMENTATION = """ """ -import pickle from ansible.errors import AnsibleConnectionFailure, AnsibleError from ansible.module_utils._text import to_bytes, to_native, to_text @@ -160,7 +49,6 @@ # Non-standard library imports and checks try: from jnpr.junos.version import VERSION - HAS_PYEZ_VERSION = VERSION except ImportError: HAS_PYEZ_VERSION = None @@ -204,7 +92,6 @@ try: from jnpr.jsnapy import SnapAdmin, __version__ - HAS_JSNAPY_VERSION = __version__ except ImportError: HAS_JSNAPY_VERSION = None @@ -254,7 +141,7 @@ class Connection(NetworkConnectionBase): """NetConf connections""" - transport = "junipernetworks.pyez.pyez" + transport = "juniper.device.pyez" has_pipelining = False def __init__(self, play_context, new_stdin, *args, **kwargs): diff --git a/ansible_collections/juniper/device/plugins/module_utils/configuration.py b/ansible_collections/juniper/device/plugins/module_utils/configuration.py index 105ab48a..8497d1d2 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/configuration.py +++ b/ansible_collections/juniper/device/plugins/module_utils/configuration.py @@ -1,5 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + from distutils.version import LooseVersion -import logging import os # Non-standard library imports and checks diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index abf734d1..8253d241 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -60,16 +60,16 @@ class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. - All juniper_junos_* modules share a common set of connection parameters + All modules share a common set of connection parameters which are documented in this class. Attributes: CONNECTION_DOCUMENTATION: The documentation string defining the connection-related parameters for the - juniper_junos_* modules. + modules. LOGGING_DOCUMENTATION: The documentation string defining the logging-related parameters for the - juniper_junos_* modules. + modules. """ # The connection-specific options. Defined here so it can be re-used as @@ -491,9 +491,9 @@ class ModuleDocFragment(object): CONFIG_MODEL_CHOICES = ['openconfig', 'custom', 'ietf', 'True'] class JuniperJunosModule(AnsibleModule): - """A subclass of AnsibleModule used by all juniper_junos_* modules. + """A subclass of AnsibleModule used by all modules. - All juniper_junos_* modules share common behavior which is implemented in + All modules share common behavior which is implemented in this class. Attributes: @@ -539,7 +539,7 @@ def __init__(self, """Initialize a new JuniperJunosModule instance. Combines module-specific parameters with the common parameters shared - by all juniper_junos_* modules. Performs additional checks on options. + by all modules. Performs additional checks on options. Checks the minimum PyEZ version. Creates and opens the PyEZ Device instance. Args: @@ -607,10 +607,12 @@ def __init__(self, self.pyez_exception = pyez_exception self.ncclient_exception = cfg.ncclient_exception self.etree = cfg.etree - self.jsnapy = jnpr.jsnapy self.jxmlease = cfg.jxmlease self.yaml = cfg.yaml + if min_jsnapy_version is not None: + self.jsnapy = jnpr.jsnapy + # Setup logging. self.logger = self._setup_logging() @@ -1727,4 +1729,4 @@ def get_facts(self): def get_chassis_inventory(self): chassis = self._pyez_conn.get_chassis_inventory() - return chassis + return self.etree.fromstring(chassis) diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index 9180bed2..014e686d 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -1,10 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index 297bbfc1..38793f8c 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -1,11 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# 2015, Rick Sherman -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py index d8816344..0f69ac03 100644 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -1,10 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # @@ -340,7 +337,7 @@ def main(): if junos_module.conn_type == "local": inventory = junos_module.dev.rpc.get_chassis_inventory() else: - junos_module.get_chassis_inventory() + inventory = junos_module.get_chassis_inventory() junos_module.logger.debug("Inventory gathered.") save_inventory(junos_module, junos_module.etree.tostring(inventory, diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 6355bbe2..9bf799c1 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -1,10 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Roslan Zaki -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py index 1d9f2d05..821a440b 100644 --- a/ansible_collections/juniper/device/plugins/modules/ping.py +++ b/ansible_collections/juniper/device/plugins/modules/ping.py @@ -1,10 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Damien Garros -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py index f8b1d205..0f53f7bf 100644 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -1,10 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2017, Martin Komon -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # @@ -127,7 +124,7 @@ EXAMPLES = ''' --- -- name: Examples of juniper_junos_mtud +- name: Examples of pmtud hosts: junos-all connection: local gather_facts: no diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index 328bbfe8..662cd937 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2016, Nitin Kumar -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index d7e02b46..85856b5c 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -1,10 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index 24d8c54e..2fd0318b 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -1,11 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Patrik Bok -# 2015, Rick Sherman -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index 34424759..485e47ba 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -1,10 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 1999-2018, Juniper Networks Inc. -# 2014, Jeremy Schulman -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py index 8c6bbf2e..aba4a1fa 100644 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -4,9 +4,7 @@ # Copyright 2016 Jason Edelman # Network to Code, LLC # -# Copyright (c) 2017-2018, Juniper Networks Inc. -# -# All rights reserved. +# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # @@ -292,6 +290,7 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg def expand_items(module, data): """Recursively expand any table items @@ -359,7 +358,7 @@ def main(): ), # Check mode is implemented. supports_check_mode=True, - min_yaml_version=juniper_junos_common.MIN_YAML_VERSION, + min_yaml_version=cfg.MIN_YAML_VERSION, ) # Straight from params diff --git a/requirements.txt b/requirements.txt index d93988c7..1fc8f9cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ansible >= 2.9 -junos-eznc >= 2.5.2 +junos-eznc >= 2.5.4 jsnapy>=1.3.4 jxmlease xmltodict \ No newline at end of file From a85843ca1b202901c1cfaa266ffb0dce693ef7fb Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 11 Dec 2020 02:01:07 +0530 Subject: [PATCH 335/426] Junos sw imported --- .../juniper/device/plugins/connection/pyez.py | 144 +++++++++++++++++- .../module_utils/juniper_junos_common.py | 5 +- 2 files changed, 145 insertions(+), 4 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index d257c118..ffffd64b 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -36,8 +36,148 @@ __metaclass__ = type -DOCUMENTATION = """ +DOCUMENTATION = """author: Ansible Networking Team +connection: pyez +short_description: Use pyez to run command on JUNOS appliances +description: +- This connection plugin provides a connection to remote devices over the junos-pyez library. +options: + host: + description: + - Specifies the remote device FQDN or IP address to establish the SSH connection + to. + default: inventory_hostname + vars: + - name: ansible_host + port: + type: int + description: + - Specifies the port on the remote device that listens for connections when establishing + the SSH connection. + ini: + - section: defaults + key: remote_port + env: + - name: ANSIBLE_REMOTE_PORT + vars: + - name: ansible_port + remote_user: + description: + - The username used to authenticate to the remote device when the SSH connection + is first established. If the remote_user is not specified, the connection will + use the username of the logged in user. + - Can be configured from the CLI via the C(--user) or C(-u) options. + ini: + - section: defaults + key: remote_user + env: + - name: ANSIBLE_REMOTE_USER + vars: + - name: ansible_user + password: + description: + - Configures the user password used to authenticate to the remote device when + first establishing the SSH connection. + vars: + - name: ansible_password + - name: ansible_ssh_pass + - name: ansible_ssh_password + pyez_console: + description: + - console option. + ini: + - section: pyez_connection + key: console + env: + - name: ANSIBLE_PYEZ_CONSOLE + vars: + - name: ansible_pyez_console + private_key_file: + description: + - The private SSH key or certificate file used to authenticate to the remote device + when first establishing the SSH connection. + ini: + - section: defaults + key: private_key_file + env: + - name: ANSIBLE_PRIVATE_KEY_FILE + vars: + - name: ansible_private_key_file + host_key_auto_add: + type: boolean + description: + - By default, Ansible will prompt the user before adding SSH keys to the known + hosts file. Since persistent connections such as network_cli run in background + processes, the user will never be prompted. By enabling this option, unknown + host keys will automatically be added to the known hosts file. + - Be sure to fully understand the security implications of enabling this option + on production systems as it could create a security vulnerability. + default: false + ini: + - section: pyez_connection + key: host_key_auto_add + env: + - name: ANSIBLE_HOST_KEY_AUTO_ADD + persistent_connect_timeout: + type: int + description: + - Configures, in seconds, the amount of time to wait when trying to initially + establish a persistent connection. If this value expires before the connection + to the remote device is completed, the connection will fail. + default: 30 + ini: + - section: persistent_connection + key: connect_timeout + env: + - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT + vars: + - name: ansible_connect_timeout + persistent_command_timeout: + type: int + description: + - Configures, in seconds, the amount of time to wait for a command to return from + the remote device. If this timer is exceeded before the command returns, the + connection plugin will raise an exception and close. + default: 30 + ini: + - section: persistent_connection + key: command_timeout + env: + - name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT + vars: + - name: ansible_command_timeout + persistent_log_messages: + type: boolean + description: + - This flag will enable logging the command executed and response received from + target device in the ansible log file. For this option to work 'log_path' ansible + configuration option is required to be set to a file path with write access. + - Be sure to fully understand the security implications of enabling this option + as it could create a security vulnerability by logging sensitive information + in log file. + default: false + ini: + - section: persistent_connection + key: log_messages + env: + - name: ANSIBLE_PERSISTENT_LOG_MESSAGES + vars: + - name: ansible_persistent_log_messages + pyez_ssh_config: + description: + - This variable is used to enable bastion/jump host with netconf connection. If + set to True the bastion/jump host ssh settings should be present in ~/.ssh/config + file, alternatively it can be set to custom ssh configuration file path to read + the bastion/jump host settings. + ini: + - section: pyez_connection + key: ssh_config + env: + - name: ANSIBLE_PYEZ_SSH_CONFIG + vars: + - name: ansible_pyez_ssh_config """ +import pickle from ansible.errors import AnsibleConnectionFailure, AnsibleError from ansible.module_utils._text import to_bytes, to_native, to_text @@ -526,4 +666,4 @@ def reboot_api(self, all_re, vmhost): self.queue_message("log", "Reboot RPC successfully initiated.") - return msg \ No newline at end of file + return msg diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 8253d241..e5d4e7a8 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -40,6 +40,7 @@ from ansible.module_utils._text import to_bytes, to_text from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg import jnpr +from jnpr.junos.utils import sw as Sw from jnpr.junos import exception as pyez_exception # Standard library imports @@ -1117,9 +1118,9 @@ def close(self, raise_exceptions=False): pass def add_sw(self): - """Add an instance of jnp.junos.utils.sw.SW() to self. + """Add an instance of jnpr.junos.utils.sw.SW() to self. """ - self.sw = jnpr.junos.utils.sw.SW(self.dev) + self.sw = Sw.SW(self.dev) def open_configuration(self, mode, ignore_warning=None): """Open candidate configuration database in exclusive or private mode. From 358afd8c74e16f97009b5910f89d18ab95871c44 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 11 Dec 2020 11:28:13 +0530 Subject: [PATCH 336/426] Sample files modified --- README.md | 2 +- Samples/sample_config.yaml | 3 --- Samples/sample_facts.yaml | 2 +- Samples/sample_rpc.yaml | 2 +- Samples/sample_software.yaml | 1 - Samples/sample_system.yaml | 2 +- Samples/test_jsnapy.yaml | 2 +- 7 files changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d2aa7fc4..7401115a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ https://github.com/Juniper/ansible-junos-stdlib/tree/roles Since Ansible version >= 2.1, Ansible also natively includes [core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.device -collection have names startinng with module types. These two sets of Junos modules can coexist on the same +collection have names starting with module types. These two sets of Junos modules can coexist on the same Ansible control machine, and an Ansible play may invoke a module from either (or both) sets. Juniper Networks recommends using the modules in this collection when writing new playbooks that manage Junos devices. diff --git a/Samples/sample_config.yaml b/Samples/sample_config.yaml index b8ef45ce..e6a4f817 100644 --- a/Samples/sample_config.yaml +++ b/Samples/sample_config.yaml @@ -170,6 +170,3 @@ filter: re0 return_output: True register: config_output - - - diff --git a/Samples/sample_facts.yaml b/Samples/sample_facts.yaml index a8aaa4ba..2fd366d4 100644 --- a/Samples/sample_facts.yaml +++ b/Samples/sample_facts.yaml @@ -23,4 +23,4 @@ user: "user" passwd: "user123" port: "23" - mode: "telnet" \ No newline at end of file + mode: "telnet" diff --git a/Samples/sample_rpc.yaml b/Samples/sample_rpc.yaml index a5eaff9b..0d93f914 100644 --- a/Samples/sample_rpc.yaml +++ b/Samples/sample_rpc.yaml @@ -58,4 +58,4 @@ media: True format: json dest: get_interface_information.conf - register: junos \ No newline at end of file + register: junos diff --git a/Samples/sample_software.yaml b/Samples/sample_software.yaml index 16fde826..8ab56067 100644 --- a/Samples/sample_software.yaml +++ b/Samples/sample_software.yaml @@ -25,4 +25,3 @@ cleanfs: false validate: true register: response - diff --git a/Samples/sample_system.yaml b/Samples/sample_system.yaml index abc2c399..960c23f1 100644 --- a/Samples/sample_system.yaml +++ b/Samples/sample_system.yaml @@ -44,4 +44,4 @@ - name: Zeroize all REs and overwrite medea. system: action: "zeroize" - media: True \ No newline at end of file + media: True diff --git a/Samples/test_jsnapy.yaml b/Samples/test_jsnapy.yaml index 26347c75..0edd2c98 100644 --- a/Samples/test_jsnapy.yaml +++ b/Samples/test_jsnapy.yaml @@ -9,4 +9,4 @@ test_rpc_version: tests: - no-diff: comment info: "Test Succeeded!!, comment is <{{post['comment']}}>" - err: "Test Failed!!!, comment is <{{post['comment']}}>" \ No newline at end of file + err: "Test Failed!!!, comment is <{{post['comment']}}>" From 7e7b78576b022745fe034cc72ba6c5821ce04df4 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 11 Dec 2020 16:12:39 +0530 Subject: [PATCH 337/426] Resolving the review comments --- README.md | 3 +++ Samples/persistent_conn.yml | 1 + Samples/sample_command.yaml | 4 ++++ Samples/sample_config.yaml | 4 ++++ Samples/sample_rpc.yaml | 5 ++++- Samples/sample_software.yaml | 4 ++++ .../juniper/device/plugins/action/extract_data.py | 2 +- .../device/plugins/action/juniper_junos_common_action.py | 2 +- .../juniper/device/plugins/callback/jsnapy.py | 2 +- .../juniper/device/plugins/connection/pyez.py | 4 ++-- .../juniper/device/plugins/module_utils/configuration.py | 2 +- .../device/plugins/module_utils/juniper_junos_common.py | 6 +++--- .../juniper/device/plugins/modules/command.py | 2 +- .../juniper/device/plugins/modules/config.py | 2 +- ansible_collections/juniper/device/plugins/modules/facts.py | 2 +- .../juniper/device/plugins/modules/jsnapy.py | 2 +- ansible_collections/juniper/device/plugins/modules/ping.py | 2 +- ansible_collections/juniper/device/plugins/modules/pmtud.py | 2 +- ansible_collections/juniper/device/plugins/modules/rpc.py | 2 +- .../juniper/device/plugins/modules/software.py | 2 +- .../juniper/device/plugins/modules/srx_cluster.py | 2 +- .../juniper/device/plugins/modules/system.py | 2 +- ansible_collections/juniper/device/plugins/modules/table.py | 2 +- 23 files changed, 40 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 7401115a..fc7d845c 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,9 @@ You can also use the ansible-galaxy install command to install the latest develo sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git#/ansible_collections/juniper/device ``` +For more information visit - https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#specifying-the-location-to-search-for-collections + + ### Git clone For testing you can `git clone` this repo and run the `env-setup` script in the repo directory: diff --git a/Samples/persistent_conn.yml b/Samples/persistent_conn.yml index f352d027..23c7c935 100644 --- a/Samples/persistent_conn.yml +++ b/Samples/persistent_conn.yml @@ -61,3 +61,4 @@ action: "reboot" register: response +# close connection diff --git a/Samples/sample_command.yaml b/Samples/sample_command.yaml index 47d777bf..9fba970f 100644 --- a/Samples/sample_command.yaml +++ b/Samples/sample_command.yaml @@ -29,6 +29,10 @@ - "show interface terse" register: response + - name: "Print the response" + debug: + var: response + - name: Print the command output of each. debug: var: item.stdout diff --git a/Samples/sample_config.yaml b/Samples/sample_config.yaml index e6a4f817..47ec89d3 100644 --- a/Samples/sample_config.yaml +++ b/Samples/sample_config.yaml @@ -170,3 +170,7 @@ filter: re0 return_output: True register: config_output + + - name: "Print the response" + debug: + var: response diff --git a/Samples/sample_rpc.yaml b/Samples/sample_rpc.yaml index 0d93f914..c6e02665 100644 --- a/Samples/sample_rpc.yaml +++ b/Samples/sample_rpc.yaml @@ -50,6 +50,10 @@ dest: "get_config_vlan.conf" register: junos + - name: "Print the response" + debug: + var: junos + - name: Get interface information with kwargs rpc: rpc: get-interface-information @@ -58,4 +62,3 @@ media: True format: json dest: get_interface_information.conf - register: junos diff --git a/Samples/sample_software.yaml b/Samples/sample_software.yaml index 8ab56067..ac5190df 100644 --- a/Samples/sample_software.yaml +++ b/Samples/sample_software.yaml @@ -25,3 +25,7 @@ cleanfs: false validate: true register: response + + - name: "Print the response" + debug: + var: response \ No newline at end of file diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py index e1b4cecc..086304d3 100644 --- a/ansible_collections/juniper/device/plugins/action/extract_data.py +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py index c118b84a..464fbda2 100755 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py +++ b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/callback/jsnapy.py b/ansible_collections/juniper/device/plugins/callback/jsnapy.py index 983f17af..668ca69f 100644 --- a/ansible_collections/juniper/device/plugins/callback/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/callback/jsnapy.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index ffffd64b..2caa3aca 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # @@ -36,7 +36,7 @@ __metaclass__ = type -DOCUMENTATION = """author: Ansible Networking Team +DOCUMENTATION = """author: Juniper Automation Team connection: pyez short_description: Use pyez to run command on JUNOS appliances description: diff --git a/ansible_collections/juniper/device/plugins/module_utils/configuration.py b/ansible_collections/juniper/device/plugins/module_utils/configuration.py index 8497d1d2..d37a820a 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/configuration.py +++ b/ansible_collections/juniper/device/plugins/module_utils/configuration.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index e5d4e7a8..efb72bc0 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # @@ -40,7 +40,7 @@ from ansible.module_utils._text import to_bytes, to_text from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg import jnpr -from jnpr.junos.utils import sw as Sw +from jnpr.junos.utils.sw import SW from jnpr.junos import exception as pyez_exception # Standard library imports @@ -1120,7 +1120,7 @@ def close(self, raise_exceptions=False): def add_sw(self): """Add an instance of jnpr.junos.utils.sw.SW() to self. """ - self.sw = Sw.SW(self.dev) + self.sw = SW(self.dev) def open_configuration(self, mode, ignore_warning=None): """Open candidate configuration database in exclusive or private mode. diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index 014e686d..88f7d8cb 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index 38793f8c..67fd555c 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py index 0f69ac03..e6082ccc 100644 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 9bf799c1..f3c8be17 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py index 821a440b..e1010310 100644 --- a/ansible_collections/juniper/device/plugins/modules/ping.py +++ b/ansible_collections/juniper/device/plugins/modules/ping.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py index 0f53f7bf..a6bfb450 100644 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index 662cd937..d910eb03 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 85856b5c..a6c244af 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index 2fd0318b..f62ee961 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index 485e47ba..b8aa3c86 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py index aba4a1fa..953c2da1 100644 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -4,7 +4,7 @@ # Copyright 2016 Jason Edelman # Network to Code, LLC # -# Copyright (c) 2017-2018, Juniper Networks Inc. All rights reserved. +# Copyright (c) 2017-2020, Juniper Networks Inc. All rights reserved. # # License: Apache 2.0 # From a1607e5dda2cb20d24fccbab89bc6c8002c4f02f Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 4 Jan 2021 13:15:35 +0530 Subject: [PATCH 338/426] Pass user credentials in vars --- Samples/persistent_conn_with_vars.yml | 38 +++++++++++++++ Samples/sample_command.yaml | 10 ++++ Samples/sample_config.yaml | 10 ++++ Samples/sample_rpc.yaml | 10 ++++ .../device/plugins/action/extract_data.py | 20 +++++--- .../juniper/device/plugins/connection/pyez.py | 46 ++++++++++++++++++- 6 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 Samples/persistent_conn_with_vars.yml diff --git a/Samples/persistent_conn_with_vars.yml b/Samples/persistent_conn_with_vars.yml new file mode 100644 index 00000000..bb13d240 --- /dev/null +++ b/Samples/persistent_conn_with_vars.yml @@ -0,0 +1,38 @@ +--- +- name: 'Explicit host argument' + hosts: junos +# connection: local + connection: juniper.device.pyez + + gather_facts: no + collections: + - juniper.device + + vars: + host: 10.x.x.x + user: user + passwd: user123 + timeout: 300 + +# The example here uses vars for user-authentication. + + tasks: + # Command module executed + - name: show version with command + command: + commands: + - "show version" + register: response + + - name: "Print the response" + debug: + var: response + + # Rpc module executed + - name: "Test RPC" + rpc: + rpcs: + - "get-config" + - "get-software-information" + +# close connection diff --git a/Samples/sample_command.yaml b/Samples/sample_command.yaml index 9fba970f..9a251bf1 100644 --- a/Samples/sample_command.yaml +++ b/Samples/sample_command.yaml @@ -5,6 +5,16 @@ collections: - juniper.device +# the user-credentials and other task specific parameter can be specified with vars +# which will be applicable to all the tasks. +# Uncomment the below lines to use them. + +# vars: +# host: 10.x.x.x +# user: user +# passwd: user123 +# timeout: 300 + tasks: - name: "Execute single command in text format" command: diff --git a/Samples/sample_config.yaml b/Samples/sample_config.yaml index 47ec89d3..479ecbb8 100644 --- a/Samples/sample_config.yaml +++ b/Samples/sample_config.yaml @@ -5,6 +5,16 @@ collections: - juniper.device +# the user-credentials and other task specific parameter can be specified with vars +# which will be applicable to all the tasks. +# Uncomment the below lines to use them. + +# vars: +# host: 10.x.x.x +# user: user +# passwd: user123 +# timeout: 300 + tasks: - name: Retrieve the committed configuration config: diff --git a/Samples/sample_rpc.yaml b/Samples/sample_rpc.yaml index c6e02665..f3656586 100644 --- a/Samples/sample_rpc.yaml +++ b/Samples/sample_rpc.yaml @@ -5,6 +5,16 @@ collections: - juniper.device +# the user-credentials and other task specific parameter can be specified with vars +# which will be applicable to all the tasks. +# Uncomment the below lines to use them. + +# vars: +# host: 10.x.x.x +# user: user +# passwd: user123 +# timeout: 300 + tasks: - name: "Execute RPC with filters" rpc: diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py index 086304d3..7868862a 100644 --- a/ansible_collections/juniper/device/plugins/action/extract_data.py +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -35,12 +35,20 @@ import os connection_spec_fallbacks = { - 'host': ['ansible_host', 'inventory_hostname'], - 'user': ['ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['ansible_ssh_pass', 'ansible_pass'], - 'port': ['ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ansible_ssh_private_key_file', - 'ansible_private_key_file'] + 'host': ['host', 'ansible_host', 'inventory_hostname'], + 'user': ['user', 'ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'passwd': ['passwd', 'ansible_ssh_pass', 'ansible_pass'], + 'port': ['port', 'ansible_ssh_port', 'ansible_port'], + 'ssh_private_key_file': ['ssh_private_key_file', 'ansible_ssh_private_key_file', + 'ansible_private_key_file'], + 'ssh_config': ['ssh_config'], + 'cs_user': ['cs_user'], + 'cs_passwd': ['cs_passwd'], + 'attempts': ['attempts'], + 'baud': ['baud'], + 'console': ['console'], + 'mode': ['mode'], + 'timeout': ['timeout', 'ansible_timeout'] } class ExtractData: diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 2caa3aca..8cb7465c 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -49,8 +49,8 @@ default: inventory_hostname vars: - name: ansible_host + - name: host port: - type: int description: - Specifies the port on the remote device that listens for connections when establishing the SSH connection. @@ -61,6 +61,22 @@ - name: ANSIBLE_REMOTE_PORT vars: - name: ansible_port + - name: port + mode: + description: + - Specifies the mode for the remote device connections. + vars: + - name: mode + baud: + description: + - The serial baud rate. + vars: + - name: baud + attempts: + description: + - The number of times to try connecting and logging in to the Junos device. + vars: + - name: attempts remote_user: description: - The username used to authenticate to the remote device when the SSH connection @@ -74,6 +90,7 @@ - name: ANSIBLE_REMOTE_USER vars: - name: ansible_user + - name: user password: description: - Configures the user password used to authenticate to the remote device when @@ -82,6 +99,7 @@ - name: ansible_password - name: ansible_ssh_pass - name: ansible_ssh_password + - name: passwd pyez_console: description: - console option. @@ -103,6 +121,7 @@ - name: ANSIBLE_PRIVATE_KEY_FILE vars: - name: ansible_private_key_file + - name: ssh_private_key_file host_key_auto_add: type: boolean description: @@ -132,6 +151,7 @@ - name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT vars: - name: ansible_connect_timeout + - name: timeout persistent_command_timeout: type: int description: @@ -176,6 +196,7 @@ - name: ANSIBLE_PYEZ_SSH_CONFIG vars: - name: ansible_pyez_ssh_config + """ import pickle @@ -323,8 +344,29 @@ def open(self): """ # Move all of the connection arguments into connect_args connect_args = {} + + # check for mode + if self.get_option('port') is None: + if self.get_option('mode') == 'telnet': + connect_args['port'] = 23 + elif self.get_option('mode') == 'serial': + connect_args['port'] = '/dev/ttyUSB0' + else: + connect_args['port'] = 830 + else: + connect_args['port'] = self.get_option('port') + + if (self.get_option('mode') == 'telnet' or + self.get_option('mode') == 'serial'): + if self.get_option('baud') is None: + # Default baud if serial or telnet mode + connect_args['baud'] = 9600 + if self.get_option('attempts') is None: + # Default attempts if serial or telnet mode + connect_args['attempts'] = 10 + connect_args['host'] = self.get_option('host') - connect_args['port'] = self.get_option('port') + # connect_args['port'] = self.get_option('port') connect_args['user'] = self.get_option('remote_user') connect_args['passwd'] = self.get_option('password') connect_args['ssh_private_key_file'] = self.get_option('private_key_file') From 58f0a0c4a66613286175af50bb9589a198026ce6 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 18 Jan 2021 10:47:49 +0530 Subject: [PATCH 339/426] Jsnapy related fixes for collections. --- .../juniper/device/plugins/connection/pyez.py | 50 +++++++++++++------ .../module_utils/juniper_junos_common.py | 6 ++- .../juniper/device/plugins/modules/command.py | 2 +- .../juniper/device/plugins/modules/jsnapy.py | 3 +- .../juniper/device/plugins/modules/rpc.py | 2 +- requirements.txt | 2 +- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 8cb7465c..d2dee50c 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -422,13 +422,15 @@ def get_config(self, filter_xml=None, options=None, model=None, resp = self.dev.rpc.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) return etree.tostring(resp) - def get_rpc_resp(self,rpc, ignore_warning=None): + def get_rpc_resp(self,rpc, ignore_warning, format): # data comes in JSON format, needs to be converted rpc_val = xmltodict.unparse(rpc) rpc_val = rpc_val.encode('utf-8') parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8') rpc_etree = etree.fromstring(rpc_val, parser=parser) resp = self.dev.rpc(rpc_etree, normalize=bool(format == 'xml'), ignore_warning=ignore_warning) + if(format == 'json'): + return resp return etree.tostring(resp) def get_facts(self): @@ -467,7 +469,8 @@ def invoke_jsnapy(self, data, action): post_file='POST') elif action == 'snapcheck': responses = jsa.snapcheck(data=data, - dev=self.dev) + dev=self.dev, + file_name='PRE') elif action == 'snap_pre': responses = jsa.snap(data=data, dev=self.dev, @@ -482,22 +485,39 @@ def invoke_jsnapy(self, data, action): except (pyez_exception.RpcError, pyez_exception.ConnectError) as ex: raise AnsibleError("Error communicating with the device: %s" % str(ex)) + results = {} if isinstance(responses, list) and len(responses) == 1: if action in ('snapcheck', 'check'): - results = [] - for response in to_list(responses): - result = {} - result['device'] = response.device - result['result'] = response.result - result['no_passed'] = response.no_passed - result['no_failed'] = response.no_failed - result['test_results'] = response.test_results - results.append(result) - else: - results = [to_text(responses)] + for response in responses: + results['device'] = response.device + results['router'] = response.device + results['final_result'] = response.result + results['total_passed'] = response.no_passed + results['total_failed'] = response.no_failed + results['test_results'] = response.test_results + total_tests = int(response.no_passed) + int(response.no_failed) + results['total_tests'] = total_tests + pass_percentage = 0 + if total_tests > 0: + pass_percentage = ((int(response.no_passed) * 100) // + total_tests) + results['passPercentage'] = pass_percentage + results['pass_percentage'] = pass_percentage + if results['final_result'] == 'Failed': + results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + else: + results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + elif action in ('snap_pre', 'snap_post'): + results['msg'] = "The %s action successfully executed." % (action) else: - results = responses - return json.dumps(results) + raise AnsibleError("Unexpected JSNAPy responses. Type: %s." + "Responses: %s" % + (type(responses), str(responses))) + return results def open_configuration(self, mode, ignore_warn=None): if self.config is None: diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index efb72bc0..0994d038 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1717,11 +1717,13 @@ def get_config(self, filter_xml=None, options=None, model=None, response = self._pyez_conn.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) return self.etree.fromstring(response) - def get_rpc(self, rpc, ignore_warning=None): + def get_rpc(self, rpc, ignore_warning=None, format=None): rpc_1 = self.etree.tostring(rpc) rpc_str = xmltodict.parse(rpc_1) #json.dumps(rpc_str) - response = self._pyez_conn.get_rpc_resp(rpc_str, ignore_warning=ignore_warning) + response = self._pyez_conn.get_rpc_resp(rpc_str, ignore_warning=ignore_warning, format=format) + if format == 'json': + return response return self.etree.fromstring(response) def get_facts(self): diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index 88f7d8cb..67996228 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -404,7 +404,7 @@ def main(): resp = junos_module.dev.rpc(rpc, ignore_warning=ignore_warning, normalize=bool(format == 'xml')) else: resp = junos_module.get_rpc(rpc, - ignore_warning=ignore_warning) + ignore_warning=ignore_warning, format=format) result['msg'] = 'The command executed successfully.' junos_module.logger.debug('Command "%s" executed successfully.', command) diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index f3c8be17..81454523 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -291,7 +291,8 @@ def main(): post_file='POST') elif action == 'snapcheck': responses = jsa.snapcheck(data=data, - dev=junos_module.dev) + dev=junos_module.dev, + file_name='PRE') elif action == 'snap_pre': responses = jsa.snap(data=data, dev=junos_module.dev, diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index d910eb03..2478ceef 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -547,7 +547,7 @@ def main(): normalize=bool(format == 'xml')) else: resp = junos_module.get_rpc(rpc, - ignore_warning=ignore_warning) + ignore_warning=ignore_warning, format=format) result['msg'] = 'The RPC executed successfully.' junos_module.logger.debug('RPC "%s" executed successfully.', junos_module.etree.tostring( diff --git a/requirements.txt b/requirements.txt index 1fc8f9cc..2ec42d6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ansible >= 2.9 +ansible >= 2.10 junos-eznc >= 2.5.4 jsnapy>=1.3.4 jxmlease From 57319d4cd9ef897a6cb977a3e041462175eb04b8 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Tue, 23 Feb 2021 22:42:16 +0530 Subject: [PATCH 340/426] Documentation related changes for the ansible collection --- README.md | 7 +- .../juniper/device/docs/ansible2rst.py | 2 +- .../juniper/device/docs/conf.py | 10 +- .../device/plugins/action/extract_data.py | 2 +- .../juniper/device/plugins/connection/pyez.py | 130 ++++++++++++++++++ ansible_collections/juniper/device/version.py | 2 + 6 files changed, 144 insertions(+), 9 deletions(-) create mode 100755 ansible_collections/juniper/device/version.py diff --git a/README.md b/README.md index b6613486..936bdcce 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ should be added to the Ansible configuration file in order to allow the jsnapy c ## INSTALLATION -You must have the [DEPENDENCIES](#dependencies) installed on your system. +You must have the [DEPENDENCIES](#dependencies) installed on your system. +Check requirements.txt for the dependencies. ### NOTICES @@ -109,7 +110,9 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge You can use the ansible-galaxy install command to install the latest version of the juniper.device collection. - sudo ansible-galaxy collection install juniper.device +```bash +sudo ansible-galaxy collection install juniper.device +``` You can also use the ansible-galaxy install command to install the latest development version of the junos role directly from GitHub. diff --git a/ansible_collections/juniper/device/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py index b6375483..34cfbd6e 100755 --- a/ansible_collections/juniper/device/docs/ansible2rst.py +++ b/ansible_collections/juniper/device/docs/ansible2rst.py @@ -66,7 +66,7 @@ def html_escape(text, quote=True): _UNDERSCORE = re.compile(r"_") DEPRECATED = b" (D)" -MODULE_NAME_STARTS_WITH = "juniper_junos_" +MODULE_NAME_STARTS_WITH = "" MODULEDIR = "../plugins/modules/" OUTPUTDIR = "./" diff --git a/ansible_collections/juniper/device/docs/conf.py b/ansible_collections/juniper/device/docs/conf.py index 3371c88b..f0a1ae38 100644 --- a/ansible_collections/juniper/device/docs/conf.py +++ b/ansible_collections/juniper/device/docs/conf.py @@ -202,7 +202,7 @@ def setup(app): #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'JunosAnsibleModulesdoc' +htmlhelp_basename = 'JunosAnsibleCollectionModulesdoc' # -- Options for LaTeX output --------------------------------------------- @@ -222,7 +222,7 @@ def setup(app): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'JunosAnsibleModules.tex', u'Junos Ansible Modules Documentation', + ('index', 'JunosAnsibleModules.tex', u'Junos Ansible Collection Modules Documentation', u'Juniper Networks, Inc.', 'manual'), ] @@ -252,7 +252,7 @@ def setup(app): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'junosansiblemodules', u'Junos Ansible Modules Documentation', + ('index', 'junosansiblemodules', u'Junos Ansible Collection Modules Documentation', [u'Juniper Networks, Inc.'], 1) ] @@ -266,8 +266,8 @@ def setup(app): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'JunosAnsibleModules', u'Junos Ansible Modules Documentation', - u'Juniper Networks, Inc.', 'JunosAnsibleModules', 'Ansible Modules for ' + ('index', 'JunosAnsibleModules', u'Junos Ansible Collectionn Modules Documentation', + u'Juniper Networks, Inc.', 'JunosAnsibleModules', 'Ansible Collection Modules for ' 'Junos', 'Miscellaneous'), ] diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py index 7868862a..a3a24113 100644 --- a/ansible_collections/juniper/device/plugins/action/extract_data.py +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -40,7 +40,7 @@ 'passwd': ['passwd', 'ansible_ssh_pass', 'ansible_pass'], 'port': ['port', 'ansible_ssh_port', 'ansible_port'], 'ssh_private_key_file': ['ssh_private_key_file', 'ansible_ssh_private_key_file', - 'ansible_private_key_file'], + 'ansible_private_key_file', 'ssh_keyfile'], 'ssh_config': ['ssh_config'], 'cs_user': ['cs_user'], 'cs_passwd': ['cs_passwd'], diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index d2dee50c..c5e87a53 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -122,6 +122,7 @@ vars: - name: ansible_private_key_file - name: ssh_private_key_file + - name: ssh_keyfile host_key_auto_add: type: boolean description: @@ -316,7 +317,10 @@ def manager(self): return self.dev def _connect(self): + """Connect with the device. + Establish the connection with the device. + """ self.queue_message("log", "ssh connection done, starting junos-eznc") self.open() if not self.dev.connected: @@ -415,14 +419,51 @@ def close(self): @ensure_connect def get_capabilities(self): + """Get the capabilities for network api.. + """ return json.dumps({'network_api': 'pyez'}) def get_config(self, filter_xml=None, options=None, model=None, namespace=None, remove_ns=True, **kwarg): + """Get Configuration. + + Args: + filter_xml: A string of XML, or '/'-separated configuration hierarchies, + which specifies a filter used to restrict the portions of the + configuration which are retrieved. + options: Additional options, specified as a dictionary of key/value pairs, used + when retrieving the configuration. + model: the model of the configuration + namespace: namespace to be used. + remove_ns: if namespace is to be removed from the end output. + + Returns: + - The configuration in the requested format as a single + multi-line string. Returned for all formats. + + Fails: + - Invalid database. + - Invalid filter. + - Format not understood by device. + """ resp = self.dev.rpc.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) return etree.tostring(resp) def get_rpc_resp(self,rpc, ignore_warning, format): + """Execute rpc on the device and get response. + + Args: + rpc: the rpc to be executed on the device + ignore_warning: flag to check if warning received by device are to be ignored or not. + format: the format of the response received. + + Returns: + - Response in the requested format as a single multi-line string. + - if format is json then return in json format + + Fails: + - If the RPC produces an exception. + """ # data comes in JSON format, needs to be converted rpc_val = xmltodict.unparse(rpc) rpc_val = rpc_val.encode('utf-8') @@ -434,30 +475,55 @@ def get_rpc_resp(self,rpc, ignore_warning, format): return etree.tostring(resp) def get_facts(self): + """Get device facts. + """ return dict(self.dev.facts) def ping_device(self, normalize=True, **params): + """Ping the device. + + Args: + params: dict of parameters passed directly to the ping RPC. + normalize: flag to check if to normalize the results. + + Returns: + Response in the requested format as a single multi-line string. + + Fails: + - If the ping RPC produces an exception. + """ resp = self.dev.rpc.ping(normalize, **params) rpc_str = etree.tostring(resp) return rpc_str def get_chassis_inventory(self): + """Get chassis inventory details from the device. + """ + resp = self.dev.rpc.get_chassis_inventory() return etree.tostring(resp) def get_re_name(self): + """Get re name from the device. + """ return self.dev.re_name def set_chassis_cluster_enable(self, cluster_id, node_id): + """send set chassis cluster enable rpc to the device. + """ return self.dev.rpc.set_chassis_cluster_enable( cluster_id=cluster_id, node=node_id, reboot=True, normalize=True) def set_chassis_cluster_disable(self): + """send set chassis cluster disable rpc to the device. + """ return self.dev.rpc.set_chassis_cluster_disable( reboot=True, normalize=True) def invoke_jsnapy(self, data, action): + """invoke jsnapy for persistent connection. + """ try: self.queue_message("vvvv", "Creating jnpr.jsnapy.SnapAdmin instance.") jsa = jnpr.jsnapy.SnapAdmin() @@ -520,6 +586,13 @@ def invoke_jsnapy(self, data, action): return results def open_configuration(self, mode, ignore_warn=None): + """Open candidate configuration database in exclusive or private mode. + + Failures: + - When there's a problem with the PyEZ connection. + - When there's a RPC problem including an already locked + config or an already opened private config. + """ if self.config is None: if mode not in CONFIG_MODE_CHOICES: raise AnsibleError("Invalid configuration mode: %s" % mode) @@ -541,6 +614,12 @@ def open_configuration(self, mode, ignore_warn=None): self.queue_message("log", "Configuration opened in %s mode."% config.mode) def close_configuration(self): + """Close candidate configuration database. + + Failures: + - When there's a problem with the PyEZ connection. + - When there's a RPC problem closing the config. + """ if self.config is not None: config = self.config self.config = None @@ -556,6 +635,19 @@ def close_configuration(self): (str(ex))) def rollback_configuration(self, id): + """Rollback the device configuration to the specified id. + + Rolls back the configuration to the specified id. Assumes the + configuration is already opened. Does NOT commit the configuration. + + Args: + id: The id to which the configuration should be rolled back. Either + an integer rollback value or the string 'rescue' to roll back + to the previously saved rescue configuration. + + Failures: + - Unable to rollback the configuration due to an RpcError or ConnectError + """ if self.dev is None or self.config is None: raise AnsibleError('The device or configuration is not open.') @@ -582,6 +674,13 @@ def rollback_configuration(self, id): % (id)) def check_configuration(self): + """Check the candidate configuration. Assumes the configuration is already opened. + Performs the equivalent of a "commit check", but does NOT commit the + configuration. + + Failures: + - An error returned from checking the configuration. + """ try: self.config.commit_check() self.queue_message("log", "Configuration checked.") @@ -591,6 +690,14 @@ def check_configuration(self): (str(ex))) def diff_configuration(self, ignore_warning=False): + """Diff the candidate and committed configurations. + + Returns: + A string with the configuration differences in text "diff" format. + + Failures: + - An error returned from diffing the configuration. + """ try: diff = self.config.diff(rb_id=0, ignore_warning=ignore_warning) self.queue_message("log", "Configuration diff completed.") @@ -601,6 +708,12 @@ def diff_configuration(self, ignore_warning=False): (str(ex))) def load_configuration(self, config, load_args): + """Load the candidate configuration from the specified src file using the + specified action. + + Failures: + - An error returned from loading the configuration. + """ try: if config is not None: self.config.load(config, **load_args) @@ -615,6 +728,17 @@ def load_configuration(self, config, load_args): def commit_configuration(self, ignore_warning=None, comment=None, confirmed=None): + """Commit the candidate configuration. + Assumes the configuration is already opened. + + Args: + ignore_warning - Which warnings to ignore. + comment - The commit comment + confirmed - Number of minutes for commit confirmed. + + Failures: + - An error returned from committing the configuration. + """ try: self.config.commit(ignore_warning=ignore_warning, comment=comment, @@ -626,6 +750,8 @@ def commit_configuration(self, ignore_warning=None, comment=None, (str(ex))) def system_api(self, action, in_min, at, all_re, vmhost, other_re, media): + """Triggers the system calls like reboot, shutdown, halt and zeroize to device. + """ msg = None if action != 'zeroize': if (at == 'now' or (in_min == 0 and at is None)): @@ -667,6 +793,8 @@ def system_api(self, action, in_min, at, all_re, vmhost, other_re, media): return msg def software_api(self, install_params): + """Installs package to device. + """ try: self.sw = jnpr.junos.utils.sw.SW(self.dev) ok, msg_ret = self.sw.install(**install_params) @@ -683,6 +811,8 @@ def software_api(self, install_params): raise AnsibleError('Installation failed. Error: %s' % str(ex)) def reboot_api(self, all_re, vmhost): + """reboots the device. + """ msg = None try: restore_timeout = self.dev.timeout diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py new file mode 100755 index 00000000..bdb3ef47 --- /dev/null +++ b/ansible_collections/juniper/device/version.py @@ -0,0 +1,2 @@ +VERSION = "0.1.0" +DATE = "2020-Apr-30" From cb6887c17efd12a2db4905634a2dae79c9eb9caa Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Sat, 27 Feb 2021 11:17:21 +0530 Subject: [PATCH 341/426] version modified to newer version --- .../device/plugins/module_utils/juniper_junos_common.py | 2 +- ansible_collections/juniper/device/version.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 0994d038..f61e1add 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1252,7 +1252,7 @@ def get_configuration(self, database='committed', format='text', remove_ns=remove_ns, namespace=namespace) else: - self.get_config(options=options, + config = self.get_config(options=options, filter_xml=filter, model=model, remove_ns=remove_ns, diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index bdb3ef47..398cc1f1 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "0.1.0" -DATE = "2020-Apr-30" +VERSION = "0.1.1" +DATE = "2021-Feb-25" From 1b3039293652399ee8d9e9b89b3d7982b7f75943 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Sat, 27 Feb 2021 11:27:33 +0530 Subject: [PATCH 342/426] Modifying typing mistake in conf.py --- ansible_collections/juniper/device/docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/docs/conf.py b/ansible_collections/juniper/device/docs/conf.py index f0a1ae38..5594d279 100644 --- a/ansible_collections/juniper/device/docs/conf.py +++ b/ansible_collections/juniper/device/docs/conf.py @@ -266,7 +266,7 @@ def setup(app): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'JunosAnsibleModules', u'Junos Ansible Collectionn Modules Documentation', + ('index', 'JunosAnsibleModules', u'Junos Ansible Collection Modules Documentation', u'Juniper Networks, Inc.', 'JunosAnsibleModules', 'Ansible Collection Modules for ' 'Junos', 'Miscellaneous'), From a0f456a505148c2392b9ef7c4527eef8e916174b Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Sat, 27 Feb 2021 11:37:18 +0530 Subject: [PATCH 343/426] snapcheck to pass file name PRE --- ansible_collections/juniper/device/plugins/modules/jsnapy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 81454523..43f87754 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -292,7 +292,7 @@ def main(): elif action == 'snapcheck': responses = jsa.snapcheck(data=data, dev=junos_module.dev, - file_name='PRE') + pre_file='PRE') elif action == 'snap_pre': responses = jsa.snap(data=data, dev=junos_module.dev, From 0f8fe241d791b6cb1285f1fe0c8f51169e3287cc Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 3 Mar 2021 21:13:31 +0530 Subject: [PATCH 344/426] Modifiying the package details in ansible collection for setup file --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 419afdbc..6c151295 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ license="Apache 2.0", keywords="Ansible Junos NETCONF networking automation", url="http://www.github.com/Juniper/ansible-junos-stdlib", - packages=['library'], + packages=['ansible_collections/juniper/device/plugins/modules'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', From e97e8d58f6df953e570947aa553084bc77583e07 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 3 Mar 2021 21:21:25 +0530 Subject: [PATCH 345/426] Modifiying the requirements in ansible collection for setup file --- ansible_collections/juniper/device/docs/docreq.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ansible_collections/juniper/device/docs/docreq.txt b/ansible_collections/juniper/device/docs/docreq.txt index bc96b3b0..d26d53ff 100644 --- a/ansible_collections/juniper/device/docs/docreq.txt +++ b/ansible_collections/juniper/device/docs/docreq.txt @@ -1,2 +1,5 @@ git+https://github.com/ryan-roemer/sphinx-bootstrap-theme.git#egg=sphinx-bootstrap-theme ansible +six +jxmlease +xmltodict \ No newline at end of file From da2a32d21220c0f8e192565c4d9689cdbf29f520 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 3 Mar 2021 21:34:18 +0530 Subject: [PATCH 346/426] Modifiying the package details in ansible collection for setup file --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c151295..090618fa 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ license="Apache 2.0", keywords="Ansible Junos NETCONF networking automation", url="http://www.github.com/Juniper/ansible-junos-stdlib", - packages=['ansible_collections/juniper/device/plugins/modules'], + packages=['ansible_collections/juniper/device/plugins/modules', 'ansible_collections/juniper/device/plugins/module_utils' ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', From 773f071717f41ed6cb7a47ac610d35ba82bd727d Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 3 Mar 2021 21:39:29 +0530 Subject: [PATCH 347/426] Modifiying the requirements in ansible collection for setup file --- ansible_collections/juniper/device/docs/docreq.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/docs/docreq.txt b/ansible_collections/juniper/device/docs/docreq.txt index d26d53ff..cbd83c58 100644 --- a/ansible_collections/juniper/device/docs/docreq.txt +++ b/ansible_collections/juniper/device/docs/docreq.txt @@ -2,4 +2,6 @@ git+https://github.com/ryan-roemer/sphinx-bootstrap-theme.git#egg=sphinx-bootstr ansible six jxmlease -xmltodict \ No newline at end of file +xmltodict +junos-eznc >= 2.5.4 +jsnapy>=1.3.4 \ No newline at end of file From 29dc56d2320987c28b685707116bcf52ff0a9ad8 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 8 Mar 2021 23:08:42 +0530 Subject: [PATCH 348/426] Modifying collection documentation parameters --- .../juniper/device/docs/ansible2rst.py | 2 +- ansible_collections/juniper/device/docs/conf.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ansible_collections/juniper/device/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py index b6375483..34cfbd6e 100755 --- a/ansible_collections/juniper/device/docs/ansible2rst.py +++ b/ansible_collections/juniper/device/docs/ansible2rst.py @@ -66,7 +66,7 @@ def html_escape(text, quote=True): _UNDERSCORE = re.compile(r"_") DEPRECATED = b" (D)" -MODULE_NAME_STARTS_WITH = "juniper_junos_" +MODULE_NAME_STARTS_WITH = "" MODULEDIR = "../plugins/modules/" OUTPUTDIR = "./" diff --git a/ansible_collections/juniper/device/docs/conf.py b/ansible_collections/juniper/device/docs/conf.py index 3371c88b..0d460fe5 100644 --- a/ansible_collections/juniper/device/docs/conf.py +++ b/ansible_collections/juniper/device/docs/conf.py @@ -55,7 +55,7 @@ def setup(app): master_doc = 'index' # General information about the project. -project = u'Junos Ansible Modules' +project = u'Junos Ansible Collection Modules' copyright = u'2014-2017, Juniper Networks, Inc' # The version info for the project you're documenting, acts as replacement for @@ -202,7 +202,7 @@ def setup(app): #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'JunosAnsibleModulesdoc' +htmlhelp_basename = 'JunosAnsibleCollectionModulesdoc' # -- Options for LaTeX output --------------------------------------------- @@ -222,7 +222,7 @@ def setup(app): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'JunosAnsibleModules.tex', u'Junos Ansible Modules Documentation', + ('index', 'JunosAnsibleCollectionModules.tex', u'Junos Ansible Collection Modules Documentation', u'Juniper Networks, Inc.', 'manual'), ] @@ -252,7 +252,7 @@ def setup(app): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'junosansiblemodules', u'Junos Ansible Modules Documentation', + ('index', 'junosansiblecollectionmodules', u'Junos Ansible Collection Modules Documentation', [u'Juniper Networks, Inc.'], 1) ] @@ -266,8 +266,8 @@ def setup(app): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'JunosAnsibleModules', u'Junos Ansible Modules Documentation', - u'Juniper Networks, Inc.', 'JunosAnsibleModules', 'Ansible Modules for ' + ('index', 'JunosAnsibleCollectionModules', u'Junos Ansible Collection Modules Documentation', + u'Juniper Networks, Inc.', 'JunosAnsibleCollectionModules', 'Ansible Modules for ' 'Junos', 'Miscellaneous'), ] From c4f5cf8a055efe21d211aa1674932ffa9e0f1c68 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 24 Mar 2021 13:17:29 +0530 Subject: [PATCH 349/426] Removing provider from the readthedocs for collection --- .../plugins/module_utils/juniper_junos_common.py | 13 ------------- requirements.txt | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index f61e1add..77262d28 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -344,19 +344,6 @@ class ModuleDocFragment(object): # Build actual DOCUMENTATION string by putting the pieces together. CONNECTION_DOCUMENTATION = ''' connection_options:''' + _CONNECT_DOCUMENTATION + ''' - provider: - description: - - An alternative syntax for specifying the connection options. Rather - than specifying each connection-related top-level option, the - connection-related options may be specified as a dictionary of - suboptions to the I(provider) option. All connection-related options - must either be specified as top-level options or as suboptions of - the I(provider) option. You can not combine the two methods of - specifying connection-related options. - required: false - default: none - type: dict - suboptions:''' + _SUB_CONNECT_DOCUMENTATION + ''' requirements: - U(junos-eznc|https://github.com/Juniper/py-junos-eznc) >= ''' + cfg.MIN_PYEZ_VERSION + ''' - Python >= 3.5 diff --git a/requirements.txt b/requirements.txt index 2ec42d6b..737434c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ansible >= 2.10 junos-eznc >= 2.5.4 -jsnapy>=1.3.4 +jsnapy>=1.3.6 jxmlease -xmltodict \ No newline at end of file +xmltodict From 5f43fea9e9e4e3e873bfe87d42e6e873792352fd Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 31 Mar 2021 18:04:11 +0530 Subject: [PATCH 350/426] Adding support for variables to be passed in vars --- .../juniper/device/plugins/action/extract_data.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py index a3a24113..62f10847 100644 --- a/ansible_collections/juniper/device/plugins/action/extract_data.py +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -35,15 +35,15 @@ import os connection_spec_fallbacks = { - 'host': ['host', 'ansible_host', 'inventory_hostname'], - 'user': ['user', 'ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], + 'host': ['host', 'hostname', 'ip', 'ansible_host', 'inventory_hostname'], + 'user': ['user', 'username', 'ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], 'passwd': ['passwd', 'ansible_ssh_pass', 'ansible_pass'], 'port': ['port', 'ansible_ssh_port', 'ansible_port'], 'ssh_private_key_file': ['ssh_private_key_file', 'ansible_ssh_private_key_file', 'ansible_private_key_file', 'ssh_keyfile'], 'ssh_config': ['ssh_config'], - 'cs_user': ['cs_user'], - 'cs_passwd': ['cs_passwd'], + 'cs_user': ['cs_user', 'console_username'], + 'cs_passwd': ['cs_passwd', 'console_password'], 'attempts': ['attempts'], 'baud': ['baud'], 'console': ['console'], @@ -85,6 +85,12 @@ def extract(self, tmp=None, task_vars=None): for task_var_key in connection_spec_fallbacks[key]: if task_var_key in task_vars: new_connection_args[key] = task_vars[task_var_key] + # check if the value is in form of a variable {{var}} + # In case of variables, resolve it to the value + index = str(new_connection_args[key]).find('{{') + if index == 0: + tempKey = new_connection_args[key][2:-2].strip() + new_connection_args[key] = task_vars[tempKey] break # Backwards compatible behavior to fallback to USER env. variable. From b8cad085fd440a9929ec763ebb796a847de41206 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 12 Apr 2021 21:24:24 +0530 Subject: [PATCH 351/426] Adding alias for the parameters --- .../juniper/device/plugins/action/extract_data.py | 2 +- .../juniper/device/plugins/connection/pyez.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py index 62f10847..6b4df350 100644 --- a/ansible_collections/juniper/device/plugins/action/extract_data.py +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -37,7 +37,7 @@ connection_spec_fallbacks = { 'host': ['host', 'hostname', 'ip', 'ansible_host', 'inventory_hostname'], 'user': ['user', 'username', 'ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['passwd', 'ansible_ssh_pass', 'ansible_pass'], + 'passwd': ['passwd', 'password', 'ansible_ssh_pass', 'ansible_pass'], 'port': ['port', 'ansible_ssh_port', 'ansible_port'], 'ssh_private_key_file': ['ssh_private_key_file', 'ansible_ssh_private_key_file', 'ansible_private_key_file', 'ssh_keyfile'], diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index c5e87a53..11f93923 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -50,6 +50,8 @@ vars: - name: ansible_host - name: host + - name: hostname + - name: ip port: description: - Specifies the port on the remote device that listens for connections when establishing @@ -91,6 +93,7 @@ vars: - name: ansible_user - name: user + - name: username password: description: - Configures the user password used to authenticate to the remote device when @@ -100,6 +103,7 @@ - name: ansible_ssh_pass - name: ansible_ssh_password - name: passwd + - name: password pyez_console: description: - console option. @@ -197,6 +201,7 @@ - name: ANSIBLE_PYEZ_SSH_CONFIG vars: - name: ansible_pyez_ssh_config + - name: ssh_config """ import pickle From ad4f6f30e45543f7a5d95667252b7a93488a8bff Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 16 Apr 2021 14:10:17 +0530 Subject: [PATCH 352/426] Readthedocs related changes to generate inndex file properly --- .../juniper/device/docs/ansible2rst.py | 8 +- .../juniper/device/docs/command.rst | 568 +++++++++ .../juniper/device/docs/config.rst | 1020 +++++++++++++++++ .../juniper/device/docs/facts.rst | 511 +++++++++ .../juniper/device/docs/jsnapy.rst | 499 ++++++++ .../juniper/device/docs/ping.rst | 773 +++++++++++++ .../juniper/device/docs/pmtud.rst | 561 +++++++++ .../juniper/device/docs/rpc.rst | 639 +++++++++++ .../juniper/device/docs/software.rst | 686 +++++++++++ .../juniper/device/docs/srx_cluster.rst | 454 ++++++++ .../juniper/device/docs/system.rst | 565 +++++++++ .../juniper/device/docs/table.rst | 576 ++++++++++ 12 files changed, 6856 insertions(+), 4 deletions(-) create mode 100644 ansible_collections/juniper/device/docs/command.rst create mode 100644 ansible_collections/juniper/device/docs/config.rst create mode 100644 ansible_collections/juniper/device/docs/facts.rst create mode 100644 ansible_collections/juniper/device/docs/jsnapy.rst create mode 100644 ansible_collections/juniper/device/docs/ping.rst create mode 100644 ansible_collections/juniper/device/docs/pmtud.rst create mode 100644 ansible_collections/juniper/device/docs/rpc.rst create mode 100644 ansible_collections/juniper/device/docs/software.rst create mode 100644 ansible_collections/juniper/device/docs/srx_cluster.rst create mode 100644 ansible_collections/juniper/device/docs/system.rst create mode 100644 ansible_collections/juniper/device/docs/table.rst diff --git a/ansible_collections/juniper/device/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py index 34cfbd6e..fd3ab65f 100755 --- a/ansible_collections/juniper/device/docs/ansible2rst.py +++ b/ansible_collections/juniper/device/docs/ansible2rst.py @@ -238,8 +238,8 @@ def get_docstring(filename, verbose=False): data = read_docstring(filename, verbose=verbose) # add fragments to documentation - if data.get('doc', False): - add_fragments(data['doc'], filename) + # if data.get('doc', False): + # add_fragments(data['doc'], filename) return data['doc'], data['plainexamples'], data['returndocs'], data['metadata'] @@ -392,7 +392,7 @@ def process_module(fname, template, outputname, aliases=None): # here is where we build the table of contents... text = template.render(doc) - write_data(text, outputname, module_name, OUTPUTDIR) + # write_data(text, outputname, module_name, OUTPUTDIR) ##################################################################################### @@ -410,7 +410,7 @@ def main(): index_file_path = os.path.join(OUTPUTDIR, "index.rst") index_file = open(index_file_path, "w") index_file.write('juniper.device Ansible Modules\n') - index_file.write('=================================================\n') + index_file.write('==============================\n') index_file.write('\n') index_file.write('Contents:\n') index_file.write('\n') diff --git a/ansible_collections/juniper/device/docs/command.rst b/ansible_collections/juniper/device/docs/command.rst new file mode 100644 index 00000000..aa1ba5f4 --- /dev/null +++ b/ansible_collections/juniper/device/docs/command.rst @@ -0,0 +1,568 @@ +.. _command: + +command ++++++++ +Execute one or more CLI commands on a Junos device + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Execute one or more CLI commands on a Junos device. +* Alias command +* This module does NOT use the Junos CLI to execute the CLI command. Instead, it uses the ```` RPC over a NETCONF channel. The ```` RPC takes a CLI command as it's input and is very similar to executing the command on the CLI, but you can NOT include any pipe modifies (i.e. ``| match``, ``| count``, etc.) with the CLI commands executed by this module. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
commands
listyesnone +
A list of one or more CLI commands to execute on the Junos device.
+
aliases: cli, command, cmd, cmds
+
dest
pathnoNone +
The path to a file, on the Ansible control machine, where the output of the cli command will be saved.
+
The file must be writeable. If the file already exists, it is overwritten.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the value of the dest option. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the dest_dir option in new playbooks. The dest and dest_dir options are mutually exclusive.
+
aliases: destination
+
dest_dir
pathnoNone +
The path to a directory, on the Ansible control machine, where the output of the cli command will be saved. The output will be logged to a file named {{ inventory_hostname }}_command.format in the directory specified by the value of the dest_dir option.
+
The destination file must be writeable. If the file already exists, it is overwritten. It is the users responsibility to ensure a unique dest_dir value is provided for each execution of this module within a playbook.
+
The dest_dir and dest options are mutually exclusive. The dest_dir option is recommended for all new playbooks.
+
aliases: destination_dir, destdir
+
formats
str or list of strnotext
  • text
  • xml
  • json
+
The format of the reply for the CLI command(s) specified by the commands option. The specified format(s) must be supported by the target Junos device. The value of this option can either be a single format, or a list of formats. If a single format is specified, it applies to all command(s) specified by the commands option. If a list of formats are specified, there must be one value in the list for each command specified by the commands option. Specifying the value xml for the formats option is similar to appending | display xml to a CLI command, and specifying the value json for the formats option is similar to appending | display json to a CLI command.
+
aliases: format, display, output
+
return_output
boolnoTrue
  • yes
  • no
+
Indicates if the output of the command should be returned in the module's response. You might want to set this option to false, and set the dest_dir option, if the command output is very large and you only need to save the output rather than using it's content in subsequent tasks/plays of your playbook.
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _command-examples-label: + +Examples +-------- + +:: + + + - name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: "Execute single command in text format" + command: + commands: "show configuration system services netconf traceoptions" + format: text + + - name: "Execute command with login credentials" + command: + host: "10.x.x.x." + user: "user" + passwd: "user123" + commands: + - "show system storage" + register: junos_result + + - name: Execute three commands. + command: + commands: + - "show version" + - "show system uptime" + - "show interface terse" + register: response + + - name: Print the command output of each. + debug: + var: item.stdout + with_items: "{{ response.results }}" + + - name: show route with XML output - show version with JSON output + command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + + - name: Multiple commands, save outputs, but don't return them + command: + commands: + - "show route" + - "show version" + formats: + - "xml" + dest_dir: "../Output" + return_output: false + + - name: save output to dest + command: + command: + - "show route" + - "show lldp neighbors" + dest: "/tmp/{{ inventory_hostname }}.commands.output" + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
changed +
Indicates if the device's state has changed. Since this module does not change the operational or configuration state of the device, the value is always set to false.
+
You could use this module to execute a command which changes the operational state of the the device. For example, clear ospf neighbors. Beware, this module is unable to detect this situation, and will still return the value false for changed in this case.
+
successboolFalse
command +
The CLI command which was executed.
+
alwaysstr
failed +
Indicates if the task failed. See the results key for additional details.
+
alwaysbool
format +
The format of the command response.
+
alwaysstr
msg +
A human-readable message indicating the result.
+
alwaysstr
parsed_output +
The command reply from the Junos device parsed into a JSON data structure. For XML replies, the response is parsed into JSON using the jxmlease library. For JSON the response is parsed using the Python json library.
+
When Ansible converts the jxmlease or native Python data structure into JSON, it does not guarantee that the order of dictionary/object keys are maintained.
+
when command executed successfully, return_output is true, and the value of the formats option is xml or json.dict
results +
The other keys are returned when a single command is specified for the commands option. When the value of the commands option is a list of commands, this key is returned instead. The value of this key is a list of dictionaries. Each element in the list corresponds to the commands in the commands option. The keys for each element in the list include all of the other keys listed. The failed key indicates if the individual command failed. In this case, there is also a top-level failed key. The top-level failed key will have a value of false if ANY of the commands ran successfully. In this case, check the value of the failed key for each element in the results list for the results of individual commands.
+
when the commands option is a list value.list of dict
stdout +
The command reply from the Junos device as a single multi-line string.
+
when command executed successfully and return_output is true.str
stdout_lines +
The command reply from the Junos device as a list of single-line strings.
+
when command executed successfully and return_output is true.list of str
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/config.rst b/ansible_collections/juniper/device/docs/config.rst new file mode 100644 index 00000000..c31cb80d --- /dev/null +++ b/ansible_collections/juniper/device/docs/config.rst @@ -0,0 +1,1020 @@ +.. _config: + +config +++++++ +Manipulate the configuration of a Junos device + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Manipulate the configuration of a Junos device. This module allows a combination of loading or rolling back, checking, diffing, retrieving, and committing the configuration of a Junos device. It performs the following steps in order: +#. Open a candidate configuration database. + + * If the *config_mode* option has a value of ``exclusive``, the default, + take a lock on the candidate configuration database. If the lock fails + the module fails and reports an error. + * If the *config_mode* option has a value of ``private``, open a private + candidate configuration database. If opening the private configuration + database fails the module fails and reports an error. +#. Load configuration data into the candidate configuration database. + + * Configuration data may be loaded using the *load* or *rollback* + options. If either of these options are specified, new configuration + data is loaded. If neither option is specified, this step is skipped. + * If the *rollback* option is specified, replace the candidate + configuration with the previous configuration specified by the value + of the *rollback* option. + * If the *load* option is specified, load new configuration data. + * The value of the *load* option defines the type of load which is + performed. + * The source of the new configuration data is one of the following: + + * *src* - A file path on the local Ansible control machine. + * *lines* - A list of strings containing the configuration data. + * *template* - A file path to a Jinja2 template on the local + Ansible control machine. This template is rendered with the variables + specified by the *vars* option. If the *template* option is + specified, the *vars* option must also be specified. + * *url* - A URL reachable from the target Junos device. + * If the *format* option is specified, the configuration file being + loaded is in the specified format, rather than the format determined + from the file name. +#. Check the validity of the candidate configuration database. + + * If the *check* option is ``true``, the default, check the validity + of the configuration by performing a "commit check" operation. + * This option may be specified with *diff* ``false`` and *commit* + ``false`` to confirm a previous "commit confirmed " operation + without actually performing an additional commit. + * If the configuration check fails, further processing stops, the module + fails, and an error is reported. +#. Determine differences between the candidate and committed configuration + databases. + + * If step 2 was not skipped, and the *diff* option is ``true``, + the default, perform a diff between the candidate and committed + configuration databases. + * If the *diffs_file* or *dest_dir* option is specified, save the + generated configuration differences. + * If the *return_output* option is ``true``, the default, include the + generated configuration difference in the *diff* and *diff_lines* + keys of the module's response. +#. Retrieve the configuration database from the Junos device. + + * If the *retrieve* option is specified, retrieve the configuration + database specified by the *retrieve* value from the target Junos + device to the local Ansible control machine. + * The format in which the configuration is retrieved is specified by the + value of the *format* option. + * The optional *filter* controls which portions of the configuration + are retrieved. + * If *options* are specified, they control the content of the + configuration retrieved. + * If the *dest* or *dest_dir* option is specified, save the + retrieved configuration to a file on the local Ansible control + machine. + * If the *return_output* option is ``true``, the default, include the + retrieved configuration in the *config*, *config_lines*, and + *config_parsed* keys of the module's response. +#. Commit the configuration changes. + + * If the *commit* option is ``true``, the default, commit the + configuration changes. + * This option may be specified with *diff* ``false`` and *check* + ``false`` to confirm a previous "commit confirmed " operation. + * If the *comment* option is specified, add the comment to the commit. + * If the *confirmed* option is specified, perform a + ``commit confirmed`` *min* operation where *min* is the value of the + *confirmed* option. + * If the *check* option is ``true`` and the *check_commit_wait* + option is specified, wait *check_commit_wait* seconds before + performing the commit. +#. Close the candidate configuration database. + + * Close and discard the candidate configuration database. + * If the *config_mode* option has a value of ``exclusive``, the default, + unlock the candidate configuration database. + + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
check
boolnotrue (false if retrieve is set and load and rollback are not set)
  • yes
  • no
+
Perform a commit check operation.
+
aliases: check_commit, commit_check
+
check_commit_wait
intnonone +
The number of seconds to wait between check and commit operations.
+
This option is only valid if check is true and commit is true.
+
This option should not normally be needed. It works around an issue in some versions of Junos.
+
comment
strnonone +
Provide a comment to be used with the commit operation.
+
This option is only valid if the commit option is true.
+
commit
boolnotrue (false if retrieve is set and load and rollback are not set)
  • yes
  • no
+
Perform a commit operation.
+
commit_empty_changes
boolnoFalse
  • yes
  • no
+
Perform a commit operation, even if there are no changes between the candidate configuration and the committed configuration.
+
config_mode
strnoexclusive
  • exclusive
  • private
+
The mode used to access the candidate configuration database.
+
aliases: config_access, edit_mode, edit_access
+
confirmed
intnonone +
Provide a confirmed timeout, in minutes, to be used with the commit operation.
+
This option is only valid if the commit option is true.
+
The value of this option is the number of minutes to wait for another commit operation before automatically rolling back the configuration change performed by this task. In other words, this option causes the module to perform a commit confirmed min where min is the value of the confirmed option. This option DOES NOT confirm a previous commit confirmed min operation. To confirm a previous commit operation, invoke this module with the check or commit option set to true.
+
aliases: confirm
+
dest
pathnonone +
The path to a file, on the local Ansible control machine, where the configuration will be saved if the retrieve option is specified.
+
The file must be writeable. If the file already exists, it is overwritten.
+
This option is only valid if the retrieve option is not none.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the dest value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the dest_dir option in new playbooks. The dest and dest_dir options are mutually exclusive.
+
aliases: destination
+
dest_dir
pathnonone +
The path to a directory, on the Ansible control machine. This is the directory where the configuration will be saved if the retrieve option is specified. It is also the directory where the configuration diff will be specified if the diff option is true.
+
This option is only valid if the retrieve option is not none or the diff option is true.
+
The retrieved configuration will be saved to a file named {{ inventory_hostname }}.format_extension in the dest_dir directory. Where format_extension is conf for text format, xml for XML format, json for JSON format, and set for set format.
+
If the diff option is true, the configuration diff will be saved to a file named {{ inventory_hostname }}.diff in the dest_dir directory.
+
The destination file must be writeable. If the file already exists, it is overwritten. It is the users responsibility to ensure a unique dest_dir value is provided for each execution of this module within a playbook.
+
The dest_dir and dest options are mutually exclusive. The dest_dir option is recommended for all new playbooks.
+
The dest_dir and diff_file options are mutually exclusive. The dest_dir option is recommended for all new playbooks.
+
aliases: destination_dir, destdir, savedir, save_dir
+
diff
boolnotrue (false if retrieve is set and load and rollback are not set)
  • yes
  • no
+
Perform a configuration compare (aka diff) operation.
+
aliases: compare, diffs
+
diffs_file
pathnoNone +
The path to a file, on the Ansible control machine, where the configuration differences will be saved if the diff option is specified.
+
The file must be writeable. If the file already exists, it is overwritten.
+
This option is only valid if the diff option is true.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the diffs_file value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the dest_dir option in new playbooks.
+
The diffs_file and dest_dir options are mutually exclusive.
+
filter
strnonone +
A string of XML, or '/'-separated configuration hierarchies, which specifies a filter used to restrict the portions of the configuration which are retrieved. See PyEZ's get_config method documentation for details on the value of this option.
+
aliases: filter_xml
+
format
strnonone (auto-detect on load, text on retrieve)
  • xml
  • set
  • text
  • json
+
Specifies the format of the configuration retrieved, if retrieve is not none.
+
Specifies the format of the configuration to be loaded, if load is not none.
+
The specified format must be supported by the target Junos device.
+
ignore_warning
bool, str, or list of strnonone +
A boolean, string or list of strings. If the value is true, ignore all warnings regardless of the warning message. If the value is a string, it will ignore warning(s) if the message of each warning matches the string. If the value is a list of strings, ignore warning(s) if the message of each warning matches at least one of the strings in the list. The value of the ignore_warning option is applied to the load and commit operations performed by this module.
+
lines
listnonone +
Used with the load option. Specifies a list of list of configuration strings containing the configuration to be loaded.
+
The src, lines, template, and url options are mutually exclusive.
+
By default, the format of the configuration data is auto-dectected by the content of the first line in the lines list.
+
If the format option is specified, the format value overrides the format auto-detection.
+
load
strnonone
  • none
  • set
  • merge
  • update
  • replace
  • override
  • overwrite
+
Specifies the type of load operation to be performed.
+
The load and rollback options are mutually exclusive.
+
The choices have the following meanings: +
+
none - Do not perform a load operation.
+
merge - Combine the new configuration with the existing configuration. If statements in the new configuration conflict with statements in the existing configuration, the statements in the new configuration replace those in the existing configuration.
+
replace - This option is a superset of the merge option. It combines the new configuration with the existing configuration. If the new configuration is in text format and a hierarchy level in the new configuartion is prefixed with the string replace:, then the hierarchy level in the new configuration replaces the entire corresponding hierarchy level in the existing configuration, regardles of the existence or content of that hierarchy level in the existing configuration. If the configuration is in XML format, the XML attribute replace = "replace" is equivalent to the text format's replace: prefix. If a configuration hierarchy in the new configuration is not prefixed with replace:, then the merge behavior is used. Specifically, for any statements in the new configuration which conflict with statements in the existing configuration, the statements in the new configuration replace those in the existing configuration.
+
override - Discard the entire existing configuration and replace it with the new configuration. When the configuration is later committed, all system processes are notified and the entire new configuration is marked as 'changed' even if some statements previously existed in the configuration. The value overwrite is a synonym for override.
+
update - This option is similar to the override option. The new configuration completely replaces the existing configuration. The difference comes when the configuration is later committed. This option performs a 'diff' between the new candidate configuration and the existing committed configuration. It then only notifies system processes repsonsible for the changed portions of the configuration, and only marks the actual configuration changes as 'changed'.
+
set - This option is used when the new configuration data is in set format (a series of configuration mode commands). The new configuration data is loaded line by line and may contain any configuration mode commands, such as set, delete, edit, or deactivate. This value must be specified if the new configuration is in set format.
+
options
dictnoNone +
Additional options, specified as a dictionary of key/value pairs, used when retrieving the configuration. See the <get-configuration> RPC documentation for information on available options.
+
retrieve
strnonone
  • none
  • candidate
  • committed
+
The configuration database to be retrieved.
+
return_output
boolnoTrue
  • yes
  • no
+
Indicates if the output of the diff and retreive options should be returned in the module's response. You might want to set this option to false, and set the dest_dir option, if the configuration or diff output is very large and you only need to save the output rather than using it's content in subsequent tasks/plays of your playbook.
+
rollback
int or strnonone
  • 0-49
  • rescue
+
Populate the candidate configuration from a previously committed configuration. This value can be a configuration number between 0 and 49, or the keyword rescue to load the previously saved rescue configuration.
+
By default, some Junos platforms store fewer than 50 previous configurations. Specifying a value greater than the number of previous configurations available, or specifying rescue when no rescue configuration has been saved, will result in an error when the module attempts to perform the rollback.
+
The rollback and load options are mutually exclusive.
+
src
pathnonone +
Used with the load option. Specifies the path to a file, on the local Ansible control machine, containing the configuration to be loaded.
+
The src, lines, template, and url options are mutually exclusive.
+
By default, the format of the configuration data is determined by the file extension of this path name. If the file has a .conf extension, the content is treated as text format. If the file has a .xml extension, the content is treated as XML format. If the file has a .set extension, the content is treated as Junos set commands.
+
If the format option is specified, the format value overrides the file-extension based format detection.
+
aliases: source, file
+
template
pathnonone +
The path to a Jinja2 template file, on the local Ansible control machine. This template file, along with the vars option, is used to generate the configuration to be loaded on the target Junos device.
+
The src, lines, template, and url options are mutually exclusive.
+
The template and vars options are required together. If one is specified, the other must be specified.
+
aliases: template_path
+
url
strnonone +
A URL which specifies the configuration data to load on the target Junos device.
+
The Junos device uses this URL to load the configuration, therefore this URL must be reachable by the target Junos device.
+
The possible formats of this value are documented in the 'url' section of the <load-configuration> RPC documentation.
+
The src, lines, template, and url options are mutually exclusive.
+
vars
dictnonone +
A dictionary of keys and values used to render the Jinja2 template specified by the template option.
+
The template and vars options are required together. If one is specified, the other must be specified.
+
aliases: template_vars
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _config-examples-label: + +Examples +-------- + +:: + + + --- + - name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Retrieve the committed configuration + config: + retrieve: 'committed' + diff: false + check: false + commit: false + register: response + + - name: Print the lines in the config. + debug: + var: response.config_lines + + - name: Append .foo to the hostname using private config mode. + config: + config_mode: 'private' + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + register: response + + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the previous config. + config: + config_mode: 'private' + rollback: 1 + register: response + + - name: Print the config changes. + debug: + var: response.diff_lines + + - name: Rollback to the rescue config. + config: + rollback: 'rescue' + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load override from a file. + config: + load: 'override' + src: "{{ inventory_hostname }}.conf" + register: response + + - name: Print the complete response. + debug: + var: response + + - name: Load from a Jinja2 template. + config: + load: 'merge' + format: 'xml' + template: "{{ inventory_hostname }}.j2" + vars: + host: "{{ inventory_hostname }}" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device. + config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + register: response + - name: Print the complete response. + debug: + var: response + + - name: Load from a file on the Junos device, skip the commit check + config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + check: false + register: response + - name: Print the msg. + debug: + var: response.msg + + - name: Print diff between current and rollback 10. No check. No commit. + config: + rollback: 11 + diff: true + check: false + commit: false + register: response + + - name: Print the msg. + debug: + var: response + + - name: Retrieve [edit system services] of current committed config. + config: + retrieve: 'committed' + filter: 'system/services' + diff: true + check: false + commit: false + register: response + + - name: Print the resulting config lines. + debug: + var: response.config_lines + + - name: Enable NETCONF SSH and traceoptions, save config, and diffs. + config: + load: 'merge' + lines: + - 'set system services netconf ssh' + - 'set system services netconf traceoptions flag all' + - 'set system services netconf traceoptions file netconf.log' + format: 'set' + retrieve: 'candidate' + filter: 'system/services' + comment: 'Enable NETCONF with traceoptions' + dest_dir: './output' + register: response + + - name: Print the complete response + debug: + var: response + + - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit + config: + load: 'merge' + url: "{{ inventory_hostname }}.conf" + confirm: 5 + check_commit_wait: 3 + register: response + + - name: Print the complete response + debug: + var: response + + - name: Confirm the previous commit with a commit check (but no commit) + config: + check: true + diff: false + commit: false + register: response + + - name: Print the complete response + debug: + var: response + + - name: fetch config from the device with filter and login credentials + config: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + retrieve: 'committed' + format: xml + commit: no + check: no + diff: no + dest_dir: "/tmp/" + filter: re0 + return_output: True + register: config_output + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
changed +
Indicates if the device's configuration has changed, or would have changed when in check mode.
+
successbool
config +
The retrieved configuration. The value is a single multi-line string in the format specified by the format option.
+
when retrieved is not none and return_output is true.str
config_lines +
The retrieved configuration. The value is a list of single-line strings in the format specified by the format option.
+
when retrieved is not none and return_output is true.list
config_parsed +
The retrieved configuration parsed into a JSON datastructure. For XML replies, the response is parsed into JSON using the jxmlease library. For JSON the response is parsed using the Python json library.
+
When Ansible converts the jxmlease or native Python data structure into JSON, it does not guarantee that the order of dictionary/object keys are maintained.
+
when retrieved is not none, the format option is xml or json and return_output is true.dict
diff +
The configuration differences between the previous and new configurations. The value is a dict that contains a single key named "prepared". Value associated with that key is a single multi-line string in "diff" format.
+
when load or rollback is specified, diff is true, and return_output is true.dict
diff_lines +
The configuration differences between the previous and new configurations. The value is a list of single-line strings in "diff" format.
+
when load or rollback is specified, diff is true, and return_output is true.list
failed +
Indicates if the task failed.
+
alwaysbool
file +
The value of the src option.
+
when load is not none and src is not nonestr
msg +
A human-readable message indicating the result.
+
alwaysstr
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/facts.rst b/ansible_collections/juniper/device/docs/facts.rst new file mode 100644 index 00000000..e02ab78d --- /dev/null +++ b/ansible_collections/juniper/device/docs/facts.rst @@ -0,0 +1,511 @@ +.. _facts: + +facts ++++++ +Retrieve facts from a Junos device + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Retrieve facts from a Junos device using the `PyEZ fact gathering system `_. +* Also returns the committed configuration of the Junos device if the *config_format* option has a value other than ``none``. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
config_format
nonone
  • none
  • xml
  • set
  • text
  • json
+
The format of the configuration returned. The specified format must be supported by the target Junos device.
+
savedir
pathnonone +
A path to a directory, on the Ansible control machine, where facts will be stored in a JSON file.
+
The resulting JSON file is saved in savedir/hostname-facts.json.
+
The savedir directory is the value of the savedir option.
+
The hostname-facts.json filename begins with the value of the hostname fact returned from the Junos device, which might be different than the value of the host option passed to the module.
+
If the value of the savedir option is none, the default, then facts are NOT saved to a file.
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _facts-examples-label: + +Examples +-------- + +:: + + + --- + - name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: "Get facts" + facts: + register: response + + - name: Facts with login credentials + facts: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + + - name: Facts in telnet mode + facts: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "23" + mode: "telnet" + + # Print a fact + + # Using config_format option + + # Print the config + + # Using savedir option + + # Print the saved JSON file + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
ansible_facts.junos +
Facts collected from the Junos device. This dictionary contains the keys listed in the contains section of this documentation PLUS all of the keys returned from PyEZ's fact gathering system. See PyEZ facts for a complete list of these keys and their meaning.
+
successcomplex
contains: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
config +
The device's committed configuration, in the format specified by config_format, as a single multi-line string.
+
when config_format is not none.str
has_2RE +
Indicates if the device has more than one Routing Engine installed. Because Ansible does not allow keys to begin with a number, this fact is returned in place of PyEZ's 2RE fact.
+
successbool
re_name +
The name of the current Routing Engine to which Ansible is connected.
+
successstr
master_state +
The mastership state of the Routing Engine to which Ansible is connected. true if the RE is the master Routing Engine. false if the RE is not the master Routing Engine.
+
successbool
+
changed +
Indicates if the device's state has changed. Since this module does not change the operational or configuration state of the device, the value is always set to false.
+
successboolFalse
facts +
Returned for backwards compatibility. Returns the same keys and values which are returned under ansible_facts.junos.
+
successdict
failed +
Indicates if the task failed.
+
alwaysboolFalse
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/jsnapy.rst b/ansible_collections/juniper/device/docs/jsnapy.rst new file mode 100644 index 00000000..a2c506ec --- /dev/null +++ b/ansible_collections/juniper/device/docs/jsnapy.rst @@ -0,0 +1,499 @@ +.. _jsnapy: + +jsnapy +++++++ +Execute JSNAPy tests on a Junos device + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. JSNAPy is documented on `Github `_ and this `Day One Book `_ +* This module only reports ``failed`` if the module encounters an error and fails to execute the JSNAPy tests. If does NOT report ``failed`` if one or more of the JSNAPy tests fail. To check the test results, register the module's response and use the assert module to verify the expected result in the response. (See :ref:`jsnapy-examples-label`.) +* A callback plugin which formats and prints JSNAPy test results for human consumption is also available. This callback plugin is enabled by adding ``callback_whitelist = jsnapy`` to the Ansible configuration file. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
action
stryesnone
  • check
  • snapcheck
  • snap_pre
  • snap_post
+
The JSNAPy action to perform.
+
config_file
pathnonone +
The filename of a JSNAPy configuration file (in YAML format). The test_files option and the config_file option are mutually exclusive. Either the test_files option or the config_file option is required.
+
dir
pathno/etc/jsnapy/testfiles +
The path to the directory containing the JSNAPy test file(s) specified by the test_files option or the JSNAPy configuration file specified by the config_file option.
+
aliases: directory
+
test_files
list of pathnonone +
The filename of file(s) in the dir directory. Each file contains JSNAPy test case definitions. The test_files option and the config_file option are mutually exclusive. Either the test_files option or the config_file option is required.
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _jsnapy-examples-label: + +Examples +-------- + +:: + + + --- + - name: Examples of jsnapy + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: JUNOS Post Checklist + jsnapy: + action: "snap_post" + config_file: "first_test.yml" + logfile: "migration_post.log" + register: test1 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test1.passPercentage == 100" + - name: Print the full test response + debug: + var: test1 + + - name: Test based on a test_file directly + jsnapy: + action: "snapcheck" + test_files: "tests/test_junos_interface.yaml" + register: test2 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test2.passPercentage == 100" + - name: Print the full test response + debug: + var: test2 + + - name: "Collect Pre Snapshot" + jsnapy: + action: "snap_pre" + test_files: "tests/test_loopback.yml" + + - name: "Collect Post Snapshot" + jsnapy: + action: "snap_post" + test_files: "tests/test_loopback.yml" + + - name: "Check after Pre and Post Snapshots" + jsnapy: + action: "check" + test_files: "tests/test_loopback.yml" + register: test3 + - name: Verify all JSNAPy tests passed + assert: + that: + - "test3.|succeeded" + - "test3.passPercentage == 100" + - name: Print the full test response + debug: + var: test3 + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
action +
The JSNAPy action performed as specified by the action option.
+
successstr
changed +
Indicates if the device's state has changed. Since this module doesn't change the operational or configuration state of the device, the value is always set to false.
+
successbool
failed +
Indicates if the task failed.
+
alwaysbool
msg +
A human-readable message indicating the result of the JSNAPy tests.
+
alwaysstr
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks +* Roslan Zaki +* Damien Garros +* Stacy Smith (@stacywsmith)" + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/ping.rst b/ansible_collections/juniper/device/docs/ping.rst new file mode 100644 index 00000000..72516d37 --- /dev/null +++ b/ansible_collections/juniper/device/docs/ping.rst @@ -0,0 +1,773 @@ +.. _ping: + +ping +++++ +Execute ping from a Junos device + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Execute the ping command from a Junos device to a specified destination in order to test network reachability from the Junos device . + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
acceptable_percent_loss
intno0 +
Maximum percentage of packets that may be lost and still consider the task not to have failed.
+
aliases: acceptable_packet_loss
+
count
intno5 +
Number of packets to send.
+
dest
stryesnone +
The IP address, or hostname if DNS is configured on the Junos device, used as the destination of the ping.
+
aliases: dest_ip, dest_host, destination, destination_ip, destination_host
+
do_not_fragment
boolnoFalse
  • yes
  • no
+
Set Do Not Fragment bit on ping packets.
+
interface
strnonone +
The source interface from which the the ping is sent. If not specified, the default Junos algorithm for determining the source interface is used.
+
rapid
boolnoTrue
  • yes
  • no
+
Send ping requests rapidly
+
routing_instance
strnonone +
Name of the source routing instance from which the ping is originated. If not specified, the default routing instance is used.
+
size
intnonone (default size for device) +
The size of the ICMP payload of the ping.
+
Total size of the IP packet is size + the 20 byte IP header + the 8 byte ICMP header. Therefore, size of 1472 generates an IP packet of size 1500.
+
source
strnonone +
The IP address, or hostname if DNS is configured on the Junos device, used as the source address of the ping. If not specified, the Junos default algorithm for determining the source address is used.
+
aliases: source_ip, source_host, src, src_ip, src_host
+
ttl
intnonone (default ttl for device) +
Maximum number of IP routers (hops) allowed between source and destination.
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _ping-examples-label: + +Examples +-------- + +:: + + + --- + - name: Examples of ping + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. + ping: + dest: "192.68.1.1" + + - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. + ping: + dest: "192.68.1.1" + acceptable_percent_loss: 50 + register: response + - name: Print all keys in the response. + debug: + var: response + + - name: Ping 192.68.1.1. Send 20 packets. Register response. + ping: + dest: "192.68.1.1" + count: 20 + register: response + - name: Print packet sent from the response. + debug: + var: response.packets_sent + + - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. + ping: + dest: "192.68.1.1" + count: 10 + rapid: false + register: response + - name: Print the average round-trip-time from the response. + debug: + var: response.rtt_average + + - name: Ping www.juniper.net with ttl 15. Register response. + ping: + dest: "www.juniper.net" + ttl: 15 + register: response + - name: Print the packet_loss percentage from the response. + debug: + var: response.packet_loss + + - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. + ping: + dest: "192.68.1.1" + size: 1472 + register: response + - name: Print the packets_received from the response. + debug: + var: response.packets_received + + - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. + ping: + dest: "192.68.1.1" + do_not_fragment: true + register: response + - name: Print the maximum round-trip-time from the response. + debug: + var: response.rtt_maximum + + - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. + ping: + dest: "192.68.1.1" + source: "192.68.1.2" + register: response + - name: Print the source from the response. + debug: + var: response.source + + - name: Ping 192.168.1.1 from the red routing-instance. + ping: + dest: "192.168.1.1" + routing_instance: "red" + + - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface + ping: + dest: "224.0.0.1" + interface: "ge-0/0/0.0" + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
acceptable_percent_loss +
The acceptable packet loss (as a percentage) for this task as specified by the acceptable_percent_loss option.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.str
changed +
Indicates if the device's state has changed. Since this module doesn't change the operational or configuration state of the device, the value is always set to false.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.bool
count +
The number of pings sent, as specified by the count option.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.str
do_not_fragment +
Whether or not the do not fragment bit was set on the pings sent, as specified by the do_not_fragment option.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.bool
failed +
Indicates if the task failed.
+
alwaysbool
host +
The destination IP/host of the pings sent as specified by the dest option.
+
Keys dest and dest_ip are also returned for backwards compatibility.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.str
interface +
The source interface of the pings sent as specified by the interface option.
+
when ping successfully executed and the interface option was specified, even if the acceptable_percent_loss was exceeded.str
msg +
A human-readable message indicating the result.
+
alwaysstr
packet_loss +
The percentage of packets lost.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.str
packets_received +
The number of packets received.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.str
packets_sent +
The number of packets sent.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.str
rapid +
Whether or not the pings were sent rapidly, as specified by the rapid option.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.bool
routing_instance +
The routing-instance from which the pings were sent as specified by the routing_instance option.
+
when ping successfully executed and the routing_instance option was specified, even if the acceptable_percent_loss was exceeded.str
rtt_average +
The average round-trip-time, in microseconds, of all ping responses received.
+
when ping successfully executed, and packet_loss < 100%.str
rtt_maximum +
The maximum round-trip-time, in microseconds, of all ping responses received.
+
when ping successfully executed, and packet_loss < 100%.str
rtt_minimum +
The minimum round-trip-time, in microseconds, of all ping responses received.
+
when ping successfully executed, and packet_loss < 100%.str
rtt_stddev +
The standard deviation of round-trip-time, in microseconds, of all ping responses received.
+
when ping successfully executed, and packet_loss < 100%.str
size +
The size in bytes of the ICMP payload on the pings sent as specified by the size option.
+
Total size of the IP packet is size + the 20 byte IP header + the 8 byte ICMP header. Therefore, size of 1472 generates an IP packet of size 1500.
+
when ping successfully executed and the size option was specified, even if the acceptable_percent_loss was exceeded.str
source +
The source IP/host of the pings sent as specified by the source option.
+
Key source_ip is also returned for backwards compatibility.
+
when ping successfully executed and the source option was specified, even if the acceptable_percent_loss was exceeded.str
timeout +
The number of seconds to wait for a response from the ping RPC.
+
when ping successfully executed, even if the acceptable_percent_loss was exceeded.str
ttl +
The time-to-live set on the pings sent as specified by the ttl option.
+
when ping successfully executed and the ttl option was specified, even if the acceptable_percent_loss was exceeded.str
warnings +
A list of warning strings, if any, produced from the ping.
+
when warnings are presentlist
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/pmtud.rst b/ansible_collections/juniper/device/docs/pmtud.rst new file mode 100644 index 00000000..bedca7b4 --- /dev/null +++ b/ansible_collections/juniper/device/docs/pmtud.rst @@ -0,0 +1,561 @@ +.. _pmtud: + +pmtud ++++++ +Perform path MTU discovery from a Junos device to a destination + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Determine the maximum IP MTU supported along a path from a Junos device to a user-specified destination by performing path MTU discovery (PMTUD) using the ping command. The reported MTU will be between min_test_size and *max_size* where *min_test_size* = (*max_size* - *max_range* + 1). If the actual path MTU is greater than *max_size*, then *max_size* will be reported. If the actual path MTU is less than *min_test_size*, then a failure will be reported. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
dest
stryesnone +
The IPv4 address, or hostname if DNS is configured on the Junos device, used as the destination of the PMTUD.
+
aliases: dest_ip, dest_host, destination, destination_ip, destination_host
+
interface
strnonone +
The source interface from which the the PMTUD is performed. If not specified, the default Junos algorithm for determining the source interface is used.
+
max_range
intno512 +
The maximum range of MTU values, in bytes, which will be searched when performing path MTU discovery. This value must be 0 or a power of 2 (2^n) between 2 and 65536. The minimum IPv4 MTU value attempted when performing path MTU discovery is min_test_size = (max_size - max_range + 1)
+
max_size
intno1500 +
The maximum IPv4 MTU, in bytes, to attempt when performing path MTU discovery.
+
The value returned for inet_mtu will be no more than this value even if the path actually supports a higher MTU.
+
This value must be between 68 and 65496.
+
routing_instance
strnonone +
Name of the source routing instance from which the ping is originated.
+
If not specified, the default routing instance is used.
+
source
strnonone +
The IPv4 address, or hostname if DNS is configured on the Junos device, used as the source address of the PMTUD. If not specified, the Junos default algorithm for determining the source address is used.
+
aliases: source_ip, source_host, src, src_ip, src_host
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _pmtud-examples-label: + +Examples +-------- + +:: + + + --- + - name: Examples of pmtud + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Perform PMTUD to 192.68.1.1 with default parameters. + pmtud: + dest: "192.68.1.1" + + - name: Perform PMTUD to 192.68.1.1. Register response. + pmtud: + dest: "192.68.1.1" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. + pmtud: + dest: "192.68.1.1" + max_size: 65496 + max_range: 65536 + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. + pmtud: + dest: "192.68.1.1" + interface: "ge-0/0/0.0" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. + pmtud: + dest: "192.68.1.1" + source: "192.168.1.2" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. + pmtud: + dest: "192.68.1.1" + routing_instance: "red" + register: response + - name: Print the discovered MTU. + debug: + var: response.inet_mtu + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
changed +
Indicates if the device's state has changed. Since this module doesn't change the operational or configuration state of the device, the value is always set to false.
+
when PMTUD successfully executed.bool
failed +
Indicates if the task failed.
+
alwaysbool
host +
The destination IP/host of the PMTUD as specified by the dest option.
+
Keys dest and dest_ip are also returned for backwards compatibility.
+
when PMTUD successfully executed.str
inet_mtu +
The IPv4 path MTU size in bytes to the dest. This is the lesser of max_size and the actual path MTU to dest. If the actual path MTU is less than min_test_size, then a failure is reported. Where min_test_size = (max_size - max_range + 1)
+
when PMTUD successfully executed.str
interface +
The source interface of the PMTUD as specified by the interface option.
+
when the interface option was specified.str
routing_instance +
The routing-instance from which the PMTUD was performed as specified by the routing_instance option.
+
when the routing_instance option was specified.str
source +
The source IP/host of the PMTUD as specified by the source option.
+
Key source_ip is also returned for backwards compatibility.
+
when the source option was specified.str
warnings +
A list of warning strings, if any, produced from the ping.
+
when warnings are presentlist
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Martin Komon (@mkomon) +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/rpc.rst b/ansible_collections/juniper/device/docs/rpc.rst new file mode 100644 index 00000000..6f85f873 --- /dev/null +++ b/ansible_collections/juniper/device/docs/rpc.rst @@ -0,0 +1,639 @@ +.. _rpc: + +rpc ++++ +Execute one or more NETCONF RPCs on a Junos device + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Execute one or more NETCONF RPCs on a Junos device. +* Use the ``| display xml rpc`` modifier to determine the equivalent RPC name for a Junos CLI command. For example, ``show version | display xml rpc`` reveals the equivalent RPC name is ``get-software-information``. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attrs
dict or list of dictnonone +
The attributes and values to the RPCs specified by the rpcs option. The value of this option can either be a single dictionary of keywords and values, or a list of dictionaries containing keywords and values.
+
There is a one-to-one correspondence between the elements in the kwargs list and the RPCs in the rpcs list. In other words, the two lists must always contain the same number of elements.
+
aliases: attr
+
dest
pathnoNone +
The path to a file, on the Ansible control machine, where the output of the RPC will be saved.
+
The file must be writeable. If the file already exists, it is overwritten.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the dest value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the dest_dir option in new playbooks. The dest and dest_dir options are mutually exclusive.
+
aliases: destination
+
dest_dir
pathnoNone +
The path to a directory, on the Ansible control machine, where the output of the RPC will be saved. The output will be logged to a file named {{ inventory_hostname }}_rpc.format in the dest_dir directory.
+
The destination file must be writeable. If the file already exists, it is overwritten. It is the users responsibility to ensure a unique dest_dir value is provided for each execution of this module within a playbook.
+
The dest_dir and dest options are mutually exclusive. The dest_dir option is recommended for all new playbooks.
+
aliases: destination_dir, destdir
+
filter
strnonone +
This argument only applies if the rpcs option contains a single RPC with the value get-config. When used, this value specifies an XML filter used to restrict the portions of the configuration which are retrieved. See the PyEZ get_config method for details on the value of this option.
+
aliases: filter_xml
+
formats
str or list of strnoxml
  • text
  • xml
  • json
+
The format of the reply for the RPCs specified by the rpcs option.
+
The specified format(s) must be supported by the target Junos device.
+
The value of this option can either be a single format, or a list of formats. If a single format is specified, it applies to all RPCs specified by the rpcs option. If a list of formats are specified, there must be one value in the list for each RPC specified by the rpcs option.
+
aliases: format, display, output
+
ignore_warning
bool, str, or list of strnonone +
A boolean, string or list of strings. If the value is true, ignore all warnings regardless of the warning message. If the value is a string, it will ignore warning(s) if the message of each warning matches the string. If the value is a list of strings, ignore warning(s) if the message of each warning matches at least one of the strings in the list. The value of the ignore_warning option is applied to the load and commit operations performed by this module.
+
kwargs
dict or list of dictnonone +
The keyword arguments and values to the RPCs specified by the rpcs option. The value of this option can either be a single dictionary of keywords and values, or a list of dictionaries containing keywords and values.
+
There must be a one-to-one correspondence between the elements in the kwargs list and the RPCs in the rpcs list. In other words, the two lists must always contain the same number of elements. For RPC arguments which do not require a value, specify the value of True as shown in the :ref:`rpc-examples-label`.
+
aliases: kwarg, args, arg
+
return_output
boolnoTrue
  • yes
  • no
+
Indicates if the output of the RPC should be returned in the module's response. You might want to set this option to false, and set the dest_dir option, if the RPC output is very large and you only need to save the output rather than using it's content in subsequent tasks/plays of your playbook.
+
rpcs
listyesnone +
A list of one or more NETCONF RPCs to execute on the Junos device.
+
aliases: rpc
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _rpc-examples-label: + +Examples +-------- + +:: + + + --- + - name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: "Execute RPC with filters" + rpc: + rpcs: + - "get-config" + format: xml + filter: re0 + attr: name=re0 + register: test1 + ignore_errors: True + + - name: Check TEST 1 + debug: + var: test1 + + - name: "Execute RPC with host data and store logging" + rpc: + host: "10.x.x.x" + user: "user" + passwd: "user123" + port: "22" + rpcs: + - "get-software-information" + logfile: "/var/tmp/rpc.log" + ignore_warning: true + register: test1 + ignore_errors: True + + - name: "Print results - summary" + debug: + var: test1.stdout_lines + + - name: "Execute multiple RPC" + rpc: + rpcs: + - "get-config" + - "get-software-information" + + - name: Get Device Configuration for vlan - 1 + rpc: + rpc: "get-config" + filter_xml: "" + dest: "get_config_vlan.conf" + register: junos + + - name: Get interface information with kwargs + rpc: + rpc: get-interface-information + kwargs: + interface_name: em1 + media: True + format: json + dest: get_interface_information.conf + register: junos + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
attrs +
The RPC attributes and values from the list of dictionaries in the attrs option. This will be none if no attributes are applied to the RPC.
+
alwaysdict
changed +
Indicates if the device's state has changed. Since this module doesn't change the operational or configuration state of the device, the value is always set to false.
+
You could use this module to execute an RPC which changes the operational state of the the device. For example, clear-ospf-neighbor-information. Beware, this module is unable to detect this situation, and will still return a changed value of false in this case.
+
successbool
failed +
Indicates if the task failed. See the results key for additional details.
+
alwaysbool
format +
The format of the RPC response from the list of formats in the formats option.
+
alwaysstr
kwargs +
The keyword arguments from the list of dictionaries in the kwargs option. This will be none if no kwargs are applied to the RPC.
+
alwaysdict
msg +
A human-readable message indicating the result.
+
alwaysstr
parsed_output +
The RPC reply from the Junos device parsed into a JSON datastructure. For XML replies, the response is parsed into JSON using the jxmlease library. For JSON the response is parsed using the Python json library.
+
When Ansible converts the jxmlease or native Python data structure into JSON, it does not guarantee that the order of dictionary/object keys are maintained.
+
when RPC executed successfully, return_output is true, and the RPC format is xml or json.dict
results +
The other keys are returned when a single RPC is specified for the rpcs option. When the value of the rpcs option is a list of RPCs, this key is returned instead. The value of this key is a list of dictionaries. Each element in the list corresponds to the RPCs in the rpcs option. The keys for each element in the list include all of the other keys listed. The failed key indicates if the individual RPC failed. In this case, there is also a top-level failed key. The top-level failed key will have a value of false if ANY of the RPCs ran successfully. In this case, check the value of the failed key for each element in the results list for the results of individual RPCs.
+
when the rpcs option is a list value.list of dict
rpc +
The RPC which was executed from the list of RPCs in the rpcs option.
+
alwaysstr
stdout +
The RPC reply from the Junos device as a single multi-line string.
+
when RPC executed successfully and return_output is true.str
stdout_lines +
The RPC reply from the Junos device as a list of single-line strings.
+
when RPC executed successfully and return_output is true.list of str
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/software.rst b/ansible_collections/juniper/device/docs/software.rst new file mode 100644 index 00000000..69730ccb --- /dev/null +++ b/ansible_collections/juniper/device/docs/software.rst @@ -0,0 +1,686 @@ +.. _software: + +software +++++++++ +Install software on a Junos device + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Install a Junos OS image, or other software package, on a Junos device. This action is generally equivalent to the ``request system software add`` operational-mode CLI command. It performs the following steps in order: + +#. Compare the currently installed Junos version to the desired version + specified by the *version* option. + + * If the current and desired versions are the same, stop and return + *changed* with a value of ``false``. + * If running in check mode, and the current and desired versions differ, + stop and return *changed* with a value of ``true``. + * Otherwise, proceed. +#. If the *local_package* option is specified, compute the MD5 checksum + of the *local_package* file on the local Ansible control machine. +#. Check if the file exists at the *remote_package* location on the target + Junos device. If so, compute the MD5 checksum of the file on the target + Junos device. +#. If the *cleanfs* option is ``true``, the default, then perform the + equivalent of the ``request system storage cleanup`` CLI command. +#. If the checksums computed in steps 2 and 3 differ, or if the + *remote_package* file does not exist on the target Junos device, then + copy the package from *local_package* on the local Ansible control + machine to *remote_package* on the target Junos device. +#. Install the software pacakge from the *remote_package* location on the + target Junos device using the options specified. +#. If the *reboot* option is ``true``, the default, initiate a reboot of + the target Junos device. + + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
all_re
boolnoTrue
  • yes
  • no
+
Whether or not to install the software on all Routing Engines of the target Junos device. If true, and the device has multiple Routing Engines, the software is installed on all Routing Engines. If false, the software is only installed on the current Routing Engine.
+
checksum
strnonone +
The pre-calculated checksum, using the checksum_algorithm of the file specified by the local_package option. Specifying this option is simply an optimization to avoid repeatedly computing the checksum of the local_package file once for each target Junos host.
+
checksum_algorithm
strnomd5 +
The algorithm to use when calculating the checksum of the local and remote software packages.
+
checksum_timeout
intno300 (5 minutes) +
The number of seconds to wait for the calculation of the checksum to complete on the target Junos device.
+
cleanfs
boolnotrue (unless no_copy is true, then false)
  • yes
  • no
+
Whether or not to perform a request system storage cleanup prior to copying or installing the software.
+
cleanfs_timeout
intno300 (5 minutes) +
The number of seconds to wait for the request system storage cleanup to complete on the target Junos device.
+
force_host
boolnoFalse
  • yes
  • no
+
Forces the upgrade of the Host Software package on QFX-series devices.
+
install_timeout
intno1800 (30 minutes) +
The number of seconds to wait for the software installation to complete on the target Junos device.
+
issu
boolnoFalse
  • yes
  • no
+
Indicates if a unified in-service software upgrade (ISSU) should be attempted. ISSU enables the upgrade between two different Junos OS releases with no control plane disruption and minimal data plane traffic disruption.
+
In order for an ISSU to succeed, ISSU must be supported. This includes support for the current to desired Junos versions, the hardware of the target Junos device, and the current software configuration of the target Junos device.
+
The issu and nssu options are mutually exclusive.
+
kwargs
dictnonone +
Additional keyword arguments and values which are passed to the <request-package-add> RPC used to install the software package. The value of this option is a dictionary of keywords and values.
+
aliases: kwarg, args, arg
+
local_package
pathnonone +
The path, on the local Ansible control machine, of a Junos software package. This Junos software package will be installed on the target Junos device.
+
If this option is specified, and a file with the same MD5 checksum doesn't already exist at the remote_package location on the target Junos device, then the file is copied from the local Ansible control machine to the target Junos device.
+
If this option is not specified, it is assumed that the software package already exists on the target Junos device. In this case, the remote_package option must be specified.
+
aliases: package
+
no_copy
boolnoFalse
  • yes
  • no
+
Indicates if the file containing the software package should be copied from the local_package location on the local Ansible control machine to the remote_package location on the target Junos device.
+
If the value is true, or if the local_package option is not specified, then the copy is skipped and the file must already exist at the remote_package location on the target Junos device.
+
nssu
boolnoFalse
  • yes
  • no
+
Indicates if a non-stop software upgrade (NSSU) should be attempted. NSSU enables the upgrade between two different Junos OS releases with minimal data plane traffic disruption.
+
NSSU is specific to EX-series Virtual Chassis systems or EX-series stand-alone systems with redundant Routing Engines.
+
In order for an NSSU to succeed, NSSU must be supported. This includes support for the current to desired Junos versions, the hardware of the target Junos device, and the current software configuration of the target Junos device.
+
The nssu and issu options are mutually exclusive.
+
pkg_set
listnoFalse +
install software on the members in a mixed Virtual Chassis. Currently we are not doing target package check this option is provided.
+
reboot
boolnoTrue
  • yes
  • no
+
Indicates if the target Junos device should be rebooted after performing the software install.
+
reboot_pause
intno10 +
The amount of time, in seconds, to wait after the reboot is issued before the module returns. This gives time for the reboot to begin. The default value of 10 seconds is designed to ensure the device is no longer reachable (because the reboot has begun) when the next task begins. The value must be an integer greater than or equal to 0.
+
remote_package
pathno/var/tmp/ + filename portion of local_package +
This option may take one of two formats.
+
The first format is a URL, from the perspective of the target Junos device, from which the device retrieves the software package to be installed. The acceptable formats for the URL value may be found here.
+
When using the URL format, the local_package and no_copy options must not be specified.
+
The second format is a file path, on the taget Junos device, to the software package.
+
If the local_package option is also specified, and the no_copy option is false, the software package will be copied from local_package to remote_package, if necessary.
+
If the no_copy option is true or the local_package option is not specified, then the file specified by this option must already exist on the target Junos device.
+
If this option is not specified, it is assumed that the software package will be copied into the /var/tmp directory on the target Junos device using the filename portion of the local_package option. In this case, the local_package option must be specified.
+
Specifying the remote_package option and not specifying the local_package option is equivalent to specifying the local_package option and the no_copy option. In this case, you no longer have to explicitly specify the no_copy option.
+
If the remote_package value is a directory (ends with /), then the filename portion of local_package will be appended to the remote_package value.
+
If the remote_package value is a file (does not end with /), then the filename portion of remote_package must be the same as the filename portion of local_package.
+
validate
boolnoFalse
  • yes
  • no
+
Whether or not to have the target Junos device should validate the current configuration against the new software package.
+
version
strnoAttempt to extract the version from the file name specified by the local_package or remote_package option values IF the package appears to be a Junos software package. Otherwise, none. +
The version of software contained in the file specified by the local_package and/or remote_package options. This value should match the Junos version which will be reported by the device once the new software is installed. If the device is already running a version of software which matches the version option value, the software install is not necessary. In this case the module returns a changed value of false and an failed value of false and does not attempt to perform the software install.
+
aliases: target_version, new_version, desired_version
+
vmhost
boolnoFalse
  • yes
  • no
+
Whether or not this is a vmhost software installation.
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _software-examples-label: + +Examples +-------- + +:: + + + --- + - name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Execute a basic Junos software upgrade. + software: + local_package: "./images/" + register: response + + - name: Print the complete response. + debug: + var: response + + - name: Upgrade Junos OS from package copied at device + software: + host: "10.x.x.x" + user: "user" + passwd: "user123" + remote_package: "/var/tmp/junos-install-mx-x86-64-20.1R1.5.tgz" + no_copy: false + cleanfs: false + validate: true + register: response + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
changed +
Indicates if the device's state has changed, or if the state would have changed when executing in check mode. This value is set to true when the version of software currently running on the target Junos device does not match the desired version of software specified by the version option. If the current and desired software versions match, the value of this key is set to false.
+
successbool
check_mode +
Indicates whether or not the module ran in check mode.
+
successbool
failed +
Indicates if the task failed.
+
alwaysbool
msg +
A human-readable message indicating the result of the software installation.
+
alwaysstr
+
+
+ + +Notes +----- + +.. note:: + - This module does support connecting to the console of a Junos device, but does not support copying the software package from the local Ansible control machine to the target Junos device while connected via the console. In this situation, the *remote_package* option must be specified, and the specified software package must already exist on the target Junos device. + - This module returns after installing the software and, optionally, initiating a reboot of the target Junos device. It does not wait for the reboot to complete, and it does not verify that the desired version of software specified by the *version* option is actually activated on the target Junos device. It is the user's responsibility to confirm the software installation using additional follow on tasks in their playbook. + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Jeremy Schulman +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/srx_cluster.rst b/ansible_collections/juniper/device/docs/srx_cluster.rst new file mode 100644 index 00000000..207b1799 --- /dev/null +++ b/ansible_collections/juniper/device/docs/srx_cluster.rst @@ -0,0 +1,454 @@ +.. _srx_cluster: + +srx_cluster ++++++++++++ +Add or remove SRX chassis cluster configuration + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Add an SRX chassis cluster configuration and reboot the device. Assuming the device is capable of forming an SRX cluster and has the correct cables connected, this will form an SRX cluster. +* If an SRX chassis cluster is already present, setting *cluster_enable* to ``false`` will remove the SRX chassis cluster configuration and reboot the device causing the SRX cluster to be broken and the device to return to stand-alone mode. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
cluster_id
intnonone +
The cluster ID to configure.
+
Required when enable is true.
+
aliases: cluster
+
enable
boolyesnone
  • yes
  • no
+
Enable or disable cluster mode. When true cluster mode is enabled and cluster_id and node_id must also be specified. When false cluster mode is disabled and the device returns to stand-alone mode.
+
aliases: cluster_enable
+
node_id
intnonone +
The node ID to configure. (0 or 1)
+
Required when enable is true.
+
aliases: node
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _srx_cluster-examples-label: + +Examples +-------- + +:: + + + --- + - name: Manipulate the SRX cluster configuration of Junos SRX devices + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + tasks: + - name: Enable an SRX cluster + srx_cluster: + enable: true + cluster_id: 4 + node_id: 0 + register: response + - name: Print the response. + debug: + var: response.config_lines + + - name: Disable an SRX cluster + srx_cluster: + enable: false + register: response + - name: Print the response. + debug: + var: response.config_lines + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
changed +
Indicates if the device's configuration has changed, or would have changed when in check mode.
+
successbool
failed +
Indicates if the task failed.
+
alwaysbool
msg +
A human-readable message indicating the result.
+
alwaysstr
reboot +
Indicates if a reboot of the device has been initiated.
+
successbool
+
+
+ + +Notes +----- + +.. note:: + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/system.rst b/ansible_collections/juniper/device/docs/system.rst new file mode 100644 index 00000000..0a1a9d7d --- /dev/null +++ b/ansible_collections/juniper/device/docs/system.rst @@ -0,0 +1,565 @@ +.. _system: + +system +++++++ +Initiate operational actions on the Junos system + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Initiate an operational action (shutdown, reboot, halt or zeroize) on a Junos system. The particular action to execute is defined by the mandatory *action* option. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
action
stryesnone
  • shutdown
  • halt
  • reboot
  • zeroize
  • off
  • power-off
  • power_off
+
The action performed by the module.
+
The following actions are supported: +
+
shutdown - Power off the Junos devices. The values off, power-off, and power_off are aliases for this value. This is the equivalent of the request system power-off CLI command.
+
halt - Stop the Junos OS running on the RE, but do not power off the system. Once the system is halted, it will reboot if a keystroke is entered on the console. This is the equivalent of the request system halt CLI command.
+
reboot - Reboot the system. This is the equivalent of the request system reboot CLI command.
+
zeroize - Restore the system (configuration, log files, etc.) to a factory default state. This is the equivalent of the request system zeroize CLI command.
+
all_re
boolnoTrue
  • yes
  • no
+
If the system has multiple Routing Engines and this option is true, then the action is performed on all REs in the system. If the system does not have multiple Routing Engines, then this option has no effect.
+
This option applies to all action values.
+
The all_re option is mutually exclusive with the other_re option.
+
at
strnonone +
The time at which to shutdown, halt, or reboot the system.
+
The value may be specified in one of the following ways: +
+
now - The action takes effect immediately.
+
+minutes — The action takes effect in minutes minutes from now.
+
yymmddhhmm — The action takes effect at yymmddhhmm absolute time, specified as year, month, day, hour, and minute.
+
hh:mm — The action takes effect at hh:mm absolute time on the current day, specified in 24-hour time.
+
The at option can not be used when the action option has a value of zeroize. The at option is mutually exclusive with the in_min option.
+
in_min
intno0 +
Specify a delay, in minutes, before the shutdown, halt, or reboot.
+
The in_min option can not be used when the action option has a value of zeroize. The in_min option is mutually exclusive with the at option.
+
media
boolnoFalse
  • yes
  • no
+
Overwrite media when performing the zeroize operation. This option is only valid when the action option has a value of zeroize.
+
other_re
boolnoFalse
  • yes
  • no
+
If the system has dual Routing Engines and this option is true, then the action is performed on the other REs in the system. If the system does not have dual Routing Engines, then this option has no effect.
+
The other_re option can not be used when the action option has a value of zeroize.
+
The other_re option is mutually exclusive with the all_re option.
+
vmhost
boolnoFalse
  • yes
  • no
+
Whether or not this is a vmhost reboot.
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _system-examples-label: + +Examples +-------- + +:: + + + --- + - name: 'Explicit host argument' + hosts: junos + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Reboot all REs of the device + system: + action: "reboot" + + - name: Power off the other RE of the device. + system: + action: "shutdown" + othe_re: True + + - name: Reboot this RE at 8pm today. + system: + action: "reboot" + all_re: False + at: "20:00" + + - name: Halt the system on 25 January 2018 at 4pm. + system: + action: "halt" + at: "1801251600" + + - name: Reboot the system in 30 minutes. + system: + action: "reboot" + in_min: 30 + + - name: Reboot the system in 30 minutes. + system: + action: "reboot" + at: "+30m" + + - name: Zeroize the local RE only. + system: + action: "zeroize" + all_re: False + + - name: Zeroize all REs and overwrite medea. + system: + action: "zeroize" + media: True + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
action +
The value of the action option.
+
alwaysstr
all_re +
The value of the all_re option.
+
alwaysstr
changed +
Indicates if the device's state has changed. If the action is performed (or if it would have been performed when in check mode) then the value will be true. If there was an error before the action, then the value will be false.
+
alwaysbool
failed +
Indicates if the task failed.
+
alwaysbool
media +
The value of the media option.
+
alwaysstr
msg +
A human-readable message indicating the result.
+
alwaysstr
other_re +
The value of the other_re option.
+
alwaysstr
+
+
+ + +Notes +----- + +.. note:: + - This module only **INITIATES** the action. It does **NOT** wait for the action to complete. + - Some Junos devices are effected by a Junos defect which causes this Ansible module to hang indefinitely when connected to the Junos device via the console. This problem is not seen when connecting to the Junos device using the normal NETCONF over SSH transport connection. Therefore, it is recommended to use this module only with a NETCONF over SSH transport connection. However, this module does still permit connecting to Junos devices via the console port and this functionality may still be used for Junos devices running Junos versions less than 15.1. + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + diff --git a/ansible_collections/juniper/device/docs/table.rst b/ansible_collections/juniper/device/docs/table.rst new file mode 100644 index 00000000..bfc323ed --- /dev/null +++ b/ansible_collections/juniper/device/docs/table.rst @@ -0,0 +1,576 @@ +.. _table: + +table ++++++ +Retrieve data from a Junos device using a PyEZ table/view + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Retrieve data from a Junos device using PyEZ's operational table/views. This module may be used with the tables/views which are included in the PyEZ distribution or it may be used with user-defined tables/views. + + + +Requirements +------------ +The following software packages must be installed on hosts that execute this module: + +* `junos-eznc `_ >= 2.5.2 +* Python >= 3.5 + + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
file
pathyesnone +
Name of the YAML file, relative to the path option, that contains the table/view definition. The file name must end with the .yml or .yaml extension.
+
kwargs
dictnonone +
Optional keyword arguments and values to the table's get() method. The value of this option is a dictionary of keywords and values which are used to refine the data return from performing a get() on the table. The exact keywords and values which are supported are specific to the table's definition and the underlying RPC which the table invokes.
+
aliases: kwarg, args, arg
+
path
pathnoop directory in jnpr.junos.op +
The directory containing the YAML table/view definition file as specified by the file option. The default value is the op directory in jnpr.junos.op. This is the directory containing the table/view definitions which are included in the PyEZ distribution.
+
aliases: directory, dir
+
response_type
strnolist_of_dicts
  • list_of_dicts
  • juniper_items
+
Defines the format of data returned by the module. See RETURN. The value of the resource key in the module's response is either a list of dictionaries list_of_dicts or PyEZ's native return format juniper_items. Because Ansible module's may only return JSON data, PyEZ's native return format juniper_items is translated into a list of lists.
+
table
strnoThe name of the table defined in the file option. +
Name of the PyEZ table used to retrieve data. If not specified, defaults to the name of the table defined in the file option. Any table names in file which begin with _ are ignored. If more than one table is defined in file, the module fails with an error message. In this case, you must manually specify the name of the table by setting this option.
+
+
+ +Common Connection-related Options +--------------------------------- +In addition to the :ref:`module-specific-options-label`, the following connection-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
attempts
intno10 +
The number of times to try connecting and logging in to the Junos device. This option is only applicable when using mode = 'telnet' or mode = 'serial'. Mutually exclusive with the console option.
+
baud
intno9600 +
The serial baud rate, in bits per second, used to connect to the Junos device. This option is only applicable when using mode = 'serial'. Mutually exclusive with the console option.
+
console
strnonone +
An alternate method of specifying a NETCONF over serial console connection to the Junos device using Telnet to a console server. The value of this option must be a string in the format --telnet <console_hostname>,<console_port_number>. This option is deprecated. It is present only for backwards compatibility. The string value of this option is exactly equivalent to specifying host with a value of <console_hostname>, mode with a value of telnet, and port with a value of <console_port_number>. Mutually exclusive with the mode, port, baud, and attempts options.
+
cs_passwd
strno +
The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_password
+
cs_user
strno +
The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the console option.
+
aliases: console_username
+
host
stryes{{ inventory_hostname }} +
The hostname or IP address of the Junos device to which the connection should be established. This is normally the Junos device itself, but is the hostname or IP address of a console server when connecting to the console of the device by setting the mode option to the value telnet. This option is required, but does not have to be specified explicitly by the user because it defaults to {{ inventory_hostname }}.
+
aliases: hostname, ip
+
mode
strnonone
  • none
  • telnet
  • serial
+
The PyEZ mode used to establish a NETCONF connection to the Junos device. A value of none uses the default NETCONF over SSH mode. Depending on the values of the host and port options, a value of telnet results in either a direct NETCONF over Telnet connection to the Junos device, or a NETCONF over serial console connection to the Junos device using Telnet to a console server. A value of serial results in a NETCONF over serial console connection to the Junos device. Mutually exclusive with the console option.
+
passwd
strnoThe first defined value from the following list 1) The ANSIBLE_NET_PASSWORD environment variable. (used by Ansible Tower) 2) The value specified using the -k or --ask-pass command line arguments to the ansible or ansible-playbook command. 3) none (An empty password/passphrase) +
The password, or ssh key's passphrase, used to authenticate with the Junos device. If this option is not specified, authentication is attempted using an empty password, or ssh key passphrase.
+
aliases: password
+
port
int or strno830 if mode = none, 23 if mode = 'telnet', '/dev/ttyUSB0' if (mode = 'serial') +
The TCP port number or serial device port used to establish the connection. Mutually exclusive with the console option.
+
ssh_config
pathno +
The path to the SSH client configuration file. If this option is not specified, then the PyEZ Device instance by default queries file ~/.ssh/config.
+
ssh_private_key_file
pathnoThe first defined value from the following list 1) The ANSIBLE_NET_SSH_KEYFILE environment variable. (used by Ansible Tower) 2) The value specified using the --private-key or --key-file command line arguments to the ansible or ansible-playbook command. 3) none (the file specified in the user's SSH configuration, or the operating-system-specific default) +
The path to the SSH private key file used to authenticate with the Junos device. If this option is not specified, and no default value is found using the algorithm below, then the SSH private key file specified in the user's SSH configuration, or the operating-system-specific default is used.
+
This must be in the RSA PEM format, and not the newer OPENSSH format. To check if the private key is in the correct format, issue the command `head -n1 ~/.ssh/some_private_key` and ensure that it's RSA and not OPENSSH. To create a key in the RSA PEM format, issue the command `ssh-keygen -m PEM -t rsa -b 4096`. To convert an OPENSSH key to an RSA key, issue the command `ssh-keygen -p -m PEM -f ~/.ssh/some_private_key`
+
aliases: ssh_keyfile
+
timeout
intno30 +
The maximum number of seconds to wait for RPC responses from the Junos device. This option does NOT control the initial connection timeout value.
+
user
stryesThe first defined value from the following list 1) The ANSIBLE_NET_USERNAME environment variable. (used by Ansible Tower) 2) The remote_user as defined by Ansible. Ansible sets this value via several methods including a) -u or --user command line arguments to the ansible or ansible-playbook command. b) ANSIBLE_REMOTE_USER environment variable. c) remote_user configuration setting. See the Ansible documentation for the precedence used to set the remote_user value. 3) The USER environment variable. +
The username used to authenticate with the Junos device. This option is required, but does not have to be specified explicitly by the user due to the algorithm for determining the default value.
+
aliases: username
+
+
+ +Common Logging-related Options +------------------------------ +In addition to the :ref:`module-specific-options-label`, the following logging-related options are also supported by this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
level
strnoWARNING
  • INFO
  • DEBUG
+
The level of information to be logged can be modified using this option
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
logdir
pathnonone +
The path to a directory, on the Ansible control machine, where debugging information for the particular task is logged.
+
If this option is specified, debugging information is logged to a file named {{ inventory_hostname }}.log in the directory specified by the logdir option.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
The logfile and logdir options are mutually exclusive. The logdir option is recommended for all new playbooks.
+
aliases: log_dir
+
logfile
pathnonone +
The path to a file, on the Ansible control machine, where debugging information for the particular task is logged.
+
The log file must be writeable. If the file already exists, it is appended. It is the users responsibility to delete/rotate log files.
+
The level of information logged in this file is controlled by Ansible's verbosity, debug options and level option in task
+
1) By default, messages at level WARNING or higher are logged.
+
2) If the -v or --verbose command-line options to the ansible-playbook command are specified, messages at level INFO or higher are logged.
+
3) If the -vv (or more verbose) command-line option to the ansible-playbook command is specified, or the ANSIBLE_DEBUG environment variable is set, then messages at level DEBUG or higher are logged.
+
4) If level is mentioned then messages at level level or more are logged.
+
When tasks are executed against more than one target host, one process is forked for each target host. (Up to the maximum specified by the forks configuration. See forks for details.) This means that the value of this option must be unique per target host. This is usually accomplished by including {{ inventory_hostname }} in the logfile value. It is the user's responsibility to ensure this value is unique per target host.
+
For this reason, this option is deprecated. It is maintained for backwards compatibility. Use the logdir option in new playbooks. The logfile and logdir options are mutually exclusive.
+
aliases: log_file
+
+
+ +.. _table-examples-label: + +Examples +-------- + +:: + + + --- + - name: Retrieve data from a Junos device using a PyEZ table/view. + hosts: junos-all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table + table: + file: "lldp.yml" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve routes within 192.68.1/8 + table: + file: "routes.yml" + table: "RouteTable" + kwargs: + destination: "192.68.1.0/8" + response_type: "juniper_items" + register: response + - name: Print response + debug: + var: response + + - name: Retrieve from custom table in playbook directory + table: + file: "fpc.yaml" + path: "." + register: response + - name: Print response + debug: + var: response + + + +Return Values +------------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptionreturnedtypesample
changed +
Indicates if the device's configuration has changed. Since this module does not change the operational or configuration state of the device, the value is always set to false.
+
successbool
failed +
Indicates if the task failed.
+
alwaysbool
msg +
A human-readable message indicating a summary of the result.
+
alwaysstr
resource +
The items retrieved by the table/view.
+
successlist of dicts if response_type is list_of_dicts or list of lists if respsonse_type is juniper_items.# when response_type == 'list_of_dicts' + [ + { + "local_int": "ge-0/0/3", + "local_parent": "-", + "remote_chassis_id": "00:05:86:08:d4:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/0", + "remote_sysname": "r5", + "remote_type": "Mac address" + }, + { + "local_int": "ge-0/0/0", + "local_parent": "-", + "remote_chassis_id": "00:05:86:18:f3:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/2", + "remote_sysname": "r4", + "remote_type": "Mac address" + } + ] + # when response_type == 'juniper_items' + [ + [ + "ge-0/0/3", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/0" + ], + [ + "remote_chassis_id", + "00:05:86:08:d4:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/3" + ], + [ + "remote_sysname", + "r5" + ] + ] + ], + [ + "ge-0/0/0", + [ + [ + "local_parent", + "-" + ], + [ + "remote_port_id", + "ge-0/0/2" + ], + [ + "remote_chassis_id", + "00:05:86:18:f3:c0" + ], + [ + "remote_port_desc", + null + ], + [ + "remote_type", + "Mac address" + ], + [ + "local_int", + "ge-0/0/0" + ], + [ + "remote_sysname", + "r4" + ] + ] + ] + ] +
+
+
+ + +Notes +----- + +.. note:: + - This module only works with operational tables/views; it does not work with configuration tables/views. + - The NETCONF system service must be enabled on the target Junos device. + + +Author +~~~~~~ + +* Jason Edelman (@jedelman8) +* Updated by Juniper Networks - Stacy Smith (@stacywsmith) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + + From a6ea123597a54f03daa085ff122ae8be917666c5 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Thu, 22 Apr 2021 18:32:35 +0530 Subject: [PATCH 353/426] PyEZ related version changes --- README.md | 4 ++-- ansible_collections/juniper/device/version.py | 4 ++-- requirements.txt | 2 +- setup.py | 2 +- version.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 936bdcce..5dd98db9 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ This juniper.device collection includes the following modules: ### PyEZ Version Requirement -For ansible collection juniper.device we will need to install junos-eznc(PyEZ) version 2.5.0 or higher. +For ansible collection juniper.device we will need to install junos-eznc(PyEZ) version 2.6.0 or higher. ### Overview of Plugins @@ -258,7 +258,7 @@ This modules requires the following to be installed on the Ansible control machi - Python >= 3.5 - [Ansible](http://www.ansible.com) 2.9 or later -- Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.5.0 or later +- Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.6.0 or later - [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later ## LICENSE diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 398cc1f1..6d1aff12 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "0.1.1" -DATE = "2021-Feb-25" +VERSION = "1.0.0" +DATE = "2021-Apr-22" diff --git a/requirements.txt b/requirements.txt index 737434c0..0052a0d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ ansible >= 2.10 -junos-eznc >= 2.5.4 +junos-eznc >= 2.6.0 jsnapy>=1.3.6 jxmlease xmltodict diff --git a/setup.py b/setup.py index 090618fa..5ad2d0b2 100755 --- a/setup.py +++ b/setup.py @@ -22,10 +22,10 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/version.py b/version.py index bdb3ef47..6d1aff12 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "0.1.0" -DATE = "2020-Apr-30" +VERSION = "1.0.0" +DATE = "2021-Apr-22" From ff7bffbd3bbd5185a680457ae8626400356aee7d Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 23 Apr 2021 15:24:45 +0530 Subject: [PATCH 354/426] Version related changes --- README.md | 2 +- ansible_collections/juniper/device/version.py | 4 ++-- setup.py | 2 -- version.py | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5dd98db9..f819cbaf 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ This example outlines how to use Ansible to install or upgrade the software imag This modules requires the following to be installed on the Ansible control machine: -- Python >= 3.5 +- Python >= 3.7 - [Ansible](http://www.ansible.com) 2.9 or later - Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.6.0 or later - [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 6d1aff12..dd4c3ef2 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "1.0.0" -DATE = "2021-Apr-22" +VERSION = "v1.0.0-collections" +DATE = "2021-Apr-23" diff --git a/setup.py b/setup.py index 5ad2d0b2..13db04b5 100755 --- a/setup.py +++ b/setup.py @@ -22,8 +22,6 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Topic :: Software Development :: Libraries', diff --git a/version.py b/version.py index 6d1aff12..dd4c3ef2 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "1.0.0" -DATE = "2021-Apr-22" +VERSION = "v1.0.0-collections" +DATE = "2021-Apr-23" From 28a721f70dec493e89ee1dc90e874d8c61532342 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Fri, 23 Apr 2021 22:16:05 +0530 Subject: [PATCH 355/426] Modifying to dev version after release --- ansible_collections/juniper/device/galaxy.yml | 8 ++------ ansible_collections/juniper/device/version.py | 2 +- version.py | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index 59168ad5..9bc20c84 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -9,7 +9,7 @@ namespace: juniper name: device # The version of the collection. Must be compatible with semantic versioning -version: 0.1.0 +version: 1.0.0 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md @@ -27,13 +27,9 @@ description: set of Ansible modules that perform specific operational and config license: - Apache-2.0 -# The path to the license file for the collection. This path is relative to the root of the collection. This key is -# mutually exclusive with 'license' -license_file: 'LICENSE' - # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' -tags: ['Juniper', 'junos', 'network'] +tags: ['juniper', 'junos', 'network'] # Collections that this collection requires to be installed for it to be usable. The key of the dict is the # collection label 'namespace.name'. The value is a version range diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index dd4c3ef2..f97a894c 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.0-collections" +VERSION = "v1.0.0-collections_dev0" DATE = "2021-Apr-23" diff --git a/version.py b/version.py index dd4c3ef2..f97a894c 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.0-collections" +VERSION = "v1.0.0-collections_dev0" DATE = "2021-Apr-23" From 83f508cd5256c71679349cad590fff218e519e2c Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 31 May 2021 07:54:57 +0530 Subject: [PATCH 356/426] Collection path modified in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9073b285..e78a4023 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN apk del -r --purge gcc make g++ &&\ rm -rf /var/cache/apk/* &&\ rm -rf /tmp/* -WORKDIR /usr/share/ansible/collections/ +WORKDIR /usr/share/ansible/collections/ansible_collections/ COPY ansible_collections/ . WORKDIR /usr/bin From 82481e25657f86ecedb8fb1af73947ed2d04095b Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 2 Jun 2021 00:13:38 +0530 Subject: [PATCH 357/426] Etree import modification --- .../device/plugins/module_utils/juniper_junos_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 77262d28..e062caf6 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1258,7 +1258,7 @@ def get_configuration(self, database='committed', format='text', if model is None and config.tag != 'configuration-text': self.fail_json(msg='Unexpected XML tag returned. ' 'Configuration is: %s' % - (etree.tostring(config, pretty_print=True))) + (self.etree.tostring(config, pretty_print=True))) return_val = (config.text, None) elif format == 'set': if not isinstance(config, self.etree._Element): @@ -1267,7 +1267,7 @@ def get_configuration(self, database='committed', format='text', if model is None and config.tag != 'configuration-set': self.fail_json(msg='Unexpected XML tag returned. ' 'Configuration is: %s' % - (etree.tostring(config, pretty_print=True))) + (self.etree.tostring(config, pretty_print=True))) return_val = (config.text, config.text.splitlines()) elif format == 'xml': if not isinstance(config, self.etree._Element): From 73b00d036b82ba48e8794c1bd47ee28046852647 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Sun, 29 Aug 2021 01:02:28 +0530 Subject: [PATCH 358/426] Adding allow_bool for rpc and enhancing log error --- .../module_utils/juniper_junos_common.py | 18 +++++++++++++----- .../juniper/device/plugins/modules/rpc.py | 7 +++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 77262d28..5c42d9fd 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -560,6 +560,15 @@ def __init__(self, A JuniperJunosModule instance object. """ + #initialize default values here for error scenario while super is called + + # by default local + self.conn_type = "local" + # Initialize the dev attribute + self.dev = None + # Initialize the config attribute + self.config = None + # Update argument_spec with the internal_spec argument_spec.update(internal_spec) # Update argument_spec with the top_spec @@ -623,11 +632,6 @@ def initialize_params(self): # Parse the console option self._parse_console_options() - # Initialize the dev attribute - self.dev = None - # Initialize the config attribute - self.config = None - # Check that we have a user and host if not self.params.get('host'): self.fail_json(msg="missing required arguments: host") @@ -944,6 +948,10 @@ def parse_arg_to_list_of_dicts(self, "invalid. Unable to translate into a list " "of dicts." % (option_name, string_val)) + # check if allow_bool_values passed in kwargs + if "allow_bool_values" in kwarg: + allow_bool_values = kwarg.pop("allow_bool_values") + # Now we just need to make sure the key is a string and the value # is a string or bool. return_item = {} diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index 2478ceef..2ab3d0b7 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -164,6 +164,13 @@ two lists must always contain the same number of elements. For RPC arguments which do not require a value, specify the value of True as shown in the :ref:`rpc-examples-label`. + - By default "0" and "1" will be converted to boolean values. In case + it doesn't need to be transformed to boolean pass first kwargs as + allow_bool_values : "0" + example - + kwargs: + allow_bool_values: "0" + data: "1" required: false default: none type: dict or list of dict From f89532fe613185b04ed4c4018f2d6119d7df2ab1 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Tue, 31 Aug 2021 14:03:13 +0530 Subject: [PATCH 359/426] Change release version --- ansible_collections/juniper/device/galaxy.yml | 2 +- ansible_collections/juniper/device/version.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index 9bc20c84..d1d6f7d5 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -9,7 +9,7 @@ namespace: juniper name: device # The version of the collection. Must be compatible with semantic versioning -version: 1.0.0 +version: 1.0.1 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index f97a894c..5372e497 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.0-collections_dev0" -DATE = "2021-Apr-23" +VERSION = "v1.0.1-collections" +DATE = "2021-Aug-31" From 44bb5cc97d6125d05b0211ccb71976672dea900d Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Fri, 24 Sep 2021 10:10:48 +0200 Subject: [PATCH 360/426] link to requirements.txt in root folderthis is so that when ansible-builder introspect the collection knows the python dep. --- ansible_collections/juniper/device/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 120000 ansible_collections/juniper/device/requirements.txt diff --git a/ansible_collections/juniper/device/requirements.txt b/ansible_collections/juniper/device/requirements.txt new file mode 120000 index 00000000..5f811608 --- /dev/null +++ b/ansible_collections/juniper/device/requirements.txt @@ -0,0 +1 @@ +../../../requirements.txt \ No newline at end of file From 52f7169e926815f6acb47128d5afd3e8c8227f1b Mon Sep 17 00:00:00 2001 From: rahkumar651991 <58289625+rahkumar651991@users.noreply.github.com> Date: Tue, 5 Oct 2021 13:17:26 +0530 Subject: [PATCH 361/426] Update version.py --- ansible_collections/juniper/device/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 5372e497..05686310 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ VERSION = "v1.0.1-collections" -DATE = "2021-Aug-31" +DATE = "2021-Oct-05" From cc6a691a60264d4bf158d50ac7e08bb299161edf Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Wed, 6 Oct 2021 11:21:52 +0530 Subject: [PATCH 362/426] Adding runtime.yml as required by ansible --- ansible_collections/juniper/device/meta/runtime.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 ansible_collections/juniper/device/meta/runtime.yml diff --git a/ansible_collections/juniper/device/meta/runtime.yml b/ansible_collections/juniper/device/meta/runtime.yml new file mode 100644 index 00000000..d15cfe2b --- /dev/null +++ b/ansible_collections/juniper/device/meta/runtime.yml @@ -0,0 +1 @@ +requires_ansible: ">=2.10" From a8196afd2d1ec5a62f4dc73fd44d88ca305d70a0 Mon Sep 17 00:00:00 2001 From: rahkumar651991 Date: Mon, 11 Oct 2021 13:03:15 +0530 Subject: [PATCH 363/426] Pull request for roles being merged in collection --- .../module_utils/juniper_junos_common.py | 42 ++++++++++++++++--- .../juniper/device/plugins/modules/config.py | 33 ++++++++++++++- .../juniper/device/requirements.txt | 1 + 3 files changed, 70 insertions(+), 6 deletions(-) create mode 120000 ansible_collections/juniper/device/requirements.txt diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index bc27bbb4..1905d577 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -42,6 +42,7 @@ import jnpr from jnpr.junos.utils.sw import SW from jnpr.junos import exception as pyez_exception +from ncclient.operations.errors import TimeoutExpiredError # Standard library imports from argparse import ArgumentParser @@ -245,6 +246,12 @@ class ModuleDocFragment(object): type: str aliases: - console_password + huge_tree: + description: + - Parse XML with very deep trees and long text content. + required: false + type: bool + default: false ''' LOGGING_DOCUMENTATION = ''' @@ -417,6 +424,9 @@ class ModuleDocFragment(object): 'timeout': dict(type='int', required=False, default=30), + 'huge_tree': dict(type='bool', + required=False, + default=False), } # Connection arguments which are mutually exclusive. @@ -710,8 +720,13 @@ def exit_json(self, **kwargs): """ # Close the connection. if self.conn_type == "local": - self.close() - self.logger.debug("Exit JSON: %s", kwargs) + try: + self.close() + except TimeoutExpiredError: + if hasattr(self, 'logger'): + self.logger.debug("Ignoring dev.close() timeout error") + if hasattr(self, 'logger'): + self.logger.debug("Exit JSON: %s", kwargs) # Call the parent's exit_json() super(JuniperJunosModule, self).exit_json(**kwargs) @@ -726,7 +741,11 @@ def fail_json(self, **kwargs): self.close_configuration() # Close the connection. # if self.conn_type == "local": - self.close() + try: + self.close() + except TimeoutExpiredError: + if hasattr(self, 'logger'): + self.logger.debug("Ignoring dev.close() timeout error") if hasattr(self, 'logger'): self.logger.debug("Fail JSON: %s", kwargs) # Call the parent's fail_json() @@ -1204,6 +1223,17 @@ def get_configuration(self, database='committed', format='text', in CONFIG_DATABASE_CHOICES. format: The format of the configuration to return. Choices are defined in CONFIG_FORMAT_CHOICES. + model: The namespace of the configuration to return. Choices are defined + in CONFIG_MODEL_CHOICES. + namespace: User can have their own defined namespace in the + custom yang models, In such cases they need to provide that + namespace so that it can be used to fetch yang modeled configs + remove_ns: Flag to check if namespaces should be removed or not. + filter: A string of XML, or '/'-separated configuration hierarchies, + which specifies a filter used to restrict the portions of the + configuration which are retrieved. + options: Additional options, specified as a dictionary of key/value pairs, used + `when retrieving the configuration. Returns: A tuple containing: - The configuration in the requested format as a single @@ -1473,7 +1503,7 @@ def load_configuration(self, (str(ex))) def commit_configuration(self, ignore_warning=None, comment=None, - confirmed=None): + confirmed=None, full=False): """Commit the candidate configuration. Commit the configuration. Assumes the configuration is already opened. @@ -1482,6 +1512,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, ignore_warning - Which warnings to ignore. comment - The commit comment confirmed - Number of minutes for commit confirmed. + full - apply full commit Failures: - An error returned from committing the configuration. @@ -1497,7 +1528,8 @@ def commit_configuration(self, ignore_warning=None, comment=None, try: self.config.commit(ignore_warning=ignore_warning, comment=comment, - confirm=confirmed) + confirm=confirmed, + full=full) self.logger.debug("Configuration committed.") except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index 67fd555c..ce03f46a 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -178,6 +178,12 @@ required: false default: false type: bool + commit_full: + description: + - Coupled with commit=True, this will perform a C(commit full) operation. + required: false + default: false + type: bool config_mode: description: - The mode used to access the candidate configuration database. @@ -333,6 +339,26 @@ required: false default: none type: bool, str, or list of str + model: + description: + - Specifies yang model openconfig/custom/ietf to fetch. + - When model is True and filter_xml is None, xml is enclosed under + so that we get junos as well as other model configurations. + - In case of custom, user will have to provide the namespace to be fetched + using I(namespace) option. + required: false + default: none + choices: + - openconfig + - ietf + - custom + namespace: + description: + - Used with I(model) option. Specifies the custom namespace to be fetched + from the database. + required: false + default: none + type: str lines: description: - Used with the I(load) option. Specifies a list of list of @@ -864,6 +890,9 @@ def main(): commit_empty_changes=dict(required=False, type='bool', default=False), + commit_full=dict(required=False, + type='bool', + default=False), confirmed=dict(required=False, type='int', aliases=['confirm'], @@ -916,6 +945,7 @@ def main(): dest = junos_module.params.get('dest') commit = junos_module.params.get('commit') commit_empty_changes = junos_module.params.get('commit_empty_changes') + commit_full = junos_module.params.get('commit_full') confirmed = junos_module.params.get('confirmed') comment = junos_module.params.get('comment') check_commit_wait = junos_module.params.get('check_commit_wait') @@ -1143,7 +1173,8 @@ def main(): time.sleep(check_commit_wait) junos_module.commit_configuration(ignore_warning=ignore_warning, comment=comment, - confirmed=confirmed) + confirmed=confirmed, + full=commit_full) results['msg'] += ', committed' else: junos_module.logger.debug("Skipping commit. Nothing changed.") diff --git a/ansible_collections/juniper/device/requirements.txt b/ansible_collections/juniper/device/requirements.txt new file mode 120000 index 00000000..5f811608 --- /dev/null +++ b/ansible_collections/juniper/device/requirements.txt @@ -0,0 +1 @@ +../../../requirements.txt \ No newline at end of file From 49b09043b58706f216cef28d10938ddb8bae9ac7 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Mon, 10 Jan 2022 22:33:10 +0000 Subject: [PATCH 364/426] accounting for deprecation of yaml.load() function --- ansible_collections/juniper/device/plugins/modules/table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py index 953c2da1..b09a9e2a 100644 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -386,7 +386,7 @@ def main(): try: junos_module.logger.debug("Attempting to parse YAML from : " "%s.", file_name) - table_view = junos_module.yaml.load(fp) + table_view = junos_module.yaml.safe_load(fp) junos_module.logger.debug("YAML from %s successfully parsed.", file_name) except junos_module.yaml.YAMLError as ex: From 9f6f475611e3e6e3f71ac28c611a4737cc2d3bd3 Mon Sep 17 00:00:00 2001 From: Apurva Raghunath Date: Wed, 13 Jul 2022 21:29:04 -0700 Subject: [PATCH 365/426] fix timeout --- .../device/plugins/module_utils/juniper_junos_common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 1905d577..8e01b30d 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1503,7 +1503,7 @@ def load_configuration(self, (str(ex))) def commit_configuration(self, ignore_warning=None, comment=None, - confirmed=None, full=False): + confirmed=None, timeout=30, full=False): """Commit the candidate configuration. Commit the configuration. Assumes the configuration is already opened. @@ -1518,7 +1518,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, - An error returned from committing the configuration. """ if self.conn_type != "local": - self._pyez_conn.commit_configuration(ignore_warning, comment, confirmed) + self._pyez_conn.commit_configuration(ignore_warning, comment, timeout, confirmed) return if self.dev is None or self.config is None: @@ -1529,6 +1529,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, self.config.commit(ignore_warning=ignore_warning, comment=comment, confirm=confirmed, + timeout=timeout, full=full) self.logger.debug("Configuration committed.") except (self.pyez_exception.RpcError, From 0cdae0e4d0274c5826da5ba74dec0ecbf7c1e999 Mon Sep 17 00:00:00 2001 From: Apurva Raghunath Date: Wed, 13 Jul 2022 21:29:04 -0700 Subject: [PATCH 366/426] fix timeout --- .../device/plugins/module_utils/juniper_junos_common.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 1905d577..3955bdd9 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1503,7 +1503,7 @@ def load_configuration(self, (str(ex))) def commit_configuration(self, ignore_warning=None, comment=None, - confirmed=None, full=False): + confirmed=None, timeout=30, full=False): """Commit the candidate configuration. Commit the configuration. Assumes the configuration is already opened. @@ -1512,13 +1512,14 @@ def commit_configuration(self, ignore_warning=None, comment=None, ignore_warning - Which warnings to ignore. comment - The commit comment confirmed - Number of minutes for commit confirmed. + timeout - Timeout for commit configuration. Default timeout value is 30s. full - apply full commit Failures: - An error returned from committing the configuration. """ if self.conn_type != "local": - self._pyez_conn.commit_configuration(ignore_warning, comment, confirmed) + self._pyez_conn.commit_configuration(ignore_warning, comment, timeout, confirmed) return if self.dev is None or self.config is None: @@ -1529,6 +1530,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, self.config.commit(ignore_warning=ignore_warning, comment=comment, confirm=confirmed, + timeout=timeout, full=full) self.logger.debug("Configuration committed.") except (self.pyez_exception.RpcError, From 058ab6459ccfc7d724dbbd21ef1008c4ad3fa941 Mon Sep 17 00:00:00 2001 From: dineshbaburam91 Date: Mon, 10 Oct 2022 17:05:37 +0530 Subject: [PATCH 367/426] Added changelogs --- .../juniper/device/changelogs/config.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 ansible_collections/juniper/device/changelogs/config.yaml diff --git a/ansible_collections/juniper/device/changelogs/config.yaml b/ansible_collections/juniper/device/changelogs/config.yaml new file mode 100644 index 00000000..1052ffc5 --- /dev/null +++ b/ansible_collections/juniper/device/changelogs/config.yaml @@ -0,0 +1,32 @@ +changelog_filename_template: ../CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +ignore_other_fragment_extensions: true +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +prelude_section_name: release_summary +prelude_section_title: Release Summary +sanitize_changelog: true +sections: +- - major_changes + - Major Changes +- - minor_changes + - Minor Changes +- - breaking_changes + - Breaking Changes / Porting Guide +- - deprecated_features + - Deprecated Features +- - removed_features + - Removed Features (previously deprecated) +- - security_fixes + - Security Fixes +- - bugfixes + - Bugfixes +- - known_issues + - Known Issues +title: Juniper.Device +trivial_section_name: trivial +use_fqcn: true From e0a5e7984e496453438a6a2341c32d2230f180e7 Mon Sep 17 00:00:00 2001 From: dineshbaburam91 Date: Wed, 12 Oct 2022 15:35:32 +0530 Subject: [PATCH 368/426] "Introduced commit_sync and commit_force_sync paramater under juniper.device.config" --- .../module_utils/juniper_junos_common.py | 9 +++++-- .../juniper/device/plugins/modules/config.py | 27 ++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 3955bdd9..aee5fa42 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1503,7 +1503,8 @@ def load_configuration(self, (str(ex))) def commit_configuration(self, ignore_warning=None, comment=None, - confirmed=None, timeout=30, full=False): + confirmed=None, timeout=30, full=False, + sync=False, force_sync=False): """Commit the candidate configuration. Commit the configuration. Assumes the configuration is already opened. @@ -1514,6 +1515,8 @@ def commit_configuration(self, ignore_warning=None, comment=None, confirmed - Number of minutes for commit confirmed. timeout - Timeout for commit configuration. Default timeout value is 30s. full - apply full commit + sync - Check for commit syntax and sync between RE's + force_sync - Ignore syntax check and force to sync between RE's Failures: - An error returned from committing the configuration. @@ -1531,7 +1534,9 @@ def commit_configuration(self, ignore_warning=None, comment=None, comment=comment, confirm=confirmed, timeout=timeout, - full=full) + full=full, + force_sync=force_sync, + sync=sync) self.logger.debug("Configuration committed.") except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index ce03f46a..a0a7b4a5 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -184,6 +184,21 @@ required: false default: false type: bool + commit_sync: + description: + - On dual control plane systems, requests that the candidate configuration + on one control plane be copied to the other control plane, checked for + correct syntax, and committed on both Routing engines. + required: false + default: false + type: bool + commit_force_sync: + description: + - On dual control plane systems, forces the candidate configuration + on one control plane to be copied to the other control plane. + required: false + default: false + type: bool config_mode: description: - The mode used to access the candidate configuration database. @@ -893,6 +908,12 @@ def main(): commit_full=dict(required=False, type='bool', default=False), + commit_sync=dict(required=False, + type='bool', + default=False), + commit_force_sync=dict(required=False, + type='bool', + default=False), confirmed=dict(required=False, type='int', aliases=['confirm'], @@ -946,6 +967,8 @@ def main(): commit = junos_module.params.get('commit') commit_empty_changes = junos_module.params.get('commit_empty_changes') commit_full = junos_module.params.get('commit_full') + commit_sync = junos_module.params.get('commit_sync') + commit_force_sync = junos_module.params.get('commit_force_sync') confirmed = junos_module.params.get('confirmed') comment = junos_module.params.get('comment') check_commit_wait = junos_module.params.get('check_commit_wait') @@ -1174,7 +1197,9 @@ def main(): junos_module.commit_configuration(ignore_warning=ignore_warning, comment=comment, confirmed=confirmed, - full=commit_full) + full=commit_full, + sync=commit_sync, + force_sync=commit_force_sync) results['msg'] += ', committed' else: junos_module.logger.debug("Skipping commit. Nothing changed.") From e1a052309ed3018da9a08415286bf1c21c9141cb Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 31 Oct 2022 10:29:50 +0530 Subject: [PATCH 369/426] updated test playbooks (#598) * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks * updated test playbooks --- tests/pb.juniper_junos_command.yml | 144 +++++++++++++++++++++++++ tests/pb.juniper_junos_config.yml | 45 ++++++-- tests/pb.juniper_junos_facts.yml | 6 +- tests/pb.juniper_junos_jsnapy.yml | 43 ++++---- tests/pb.juniper_junos_ping.yml | 10 +- tests/pb.juniper_junos_pmtud.yml | 6 +- tests/pb.juniper_junos_rpc.yml | 18 ++-- tests/pb.juniper_junos_software.yml | 42 ++++++++ tests/pb.juniper_junos_srx_cluster.yml | 46 ++++++++ tests/pb.juniper_junos_system.yml | 21 ++++ tests/pb.juniper_junos_table.yml | 46 ++++++++ 11 files changed, 379 insertions(+), 48 deletions(-) create mode 100644 tests/pb.juniper_junos_command.yml create mode 100644 tests/pb.juniper_junos_software.yml create mode 100644 tests/pb.juniper_junos_srx_cluster.yml create mode 100644 tests/pb.juniper_junos_system.yml create mode 100644 tests/pb.juniper_junos_table.yml diff --git a/tests/pb.juniper_junos_command.yml b/tests/pb.juniper_junos_command.yml new file mode 100644 index 00000000..98e62d64 --- /dev/null +++ b/tests/pb.juniper_junos_command.yml @@ -0,0 +1,144 @@ +--- +- name: Test juniper.device.command module + hosts: all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: TEST 1 - Execute single "show version" command. + command: + commands: "show version" + register: test1 + + - name: Check TEST 1 + assert: + that: + test1.msg == "The command executed successfully." + + - name: Creates directory + file: + path: out + state: directory + + - name: TEST 2 - Execute three commands. + command: + commands: + - "show version" + - "show system uptime" + - "show interface terse" + register: test2 + + - name: Check TEST 2 + assert: + that: + - test2.results[0].command == "show version" + - test2.results[0].msg == "The command executed successfully." + - test2.results[1].command == "show system uptime" + - test2.results[1].msg == "The command executed successfully." + - test2.results[2].command == "show interface terse" + - test2.results[2].msg == "The command executed successfully." + + - name: Print the command output of each. + debug: + var: item.stdout + with_items: "{{ test2.results }}" + + - name: TEST 3 - Two commands with XML output. + command: + commands: + - "show route" + - "show lldp neighbors" + format: xml + register: test3 + + - name: Check TEST 3 + assert: + that: + - test3.results[0].command == "show route" + - test3.results[0].msg == "The command executed successfully." + - test3.results[1].command == "show lldp neighbors" + - test3.results[1].msg == "The command executed successfully." + + - name: TEST 4 - show route with XML output - show version with JSON output + command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + register: test4 + + - name: Check TEST 4 + assert: + that: + - test4.results[0].command == "show route" + - test4.results[0].msg == "The command executed successfully." + - test4.results[1].command == "show version" + - test4.results[1].msg == "The command executed successfully." + + - name: TEST 5 - save outputs in dest_dir + command: + commands: + - "show route" + - "show version" + dest_dir: "./out" + register: test5 + + - name: Check TEST 5 + assert: + that: + - test5.results[0].command == "show route" + - test5.results[0].msg == "The command executed successfully." + - test5.results[1].command == "show version" + - test5.results[1].msg == "The command executed successfully." + + - name: TEST 6 - save output to dest + command: + command: "show system uptime" + dest: "./out/{{ inventory_hostname }}.uptime.output" + register: test6 + + - name: Check TEST 6 + assert: + that: + - test6.command == "show system uptime" + - test6.msg == "The command executed successfully." + + - name: TEST 7 - save output to dest + command: + command: + - "show route" + - "show lldp neighbors" + dest: "./out/{{ inventory_hostname }}.commands.output" + register: test7 + + - name: Check TEST 7 + assert: + that: + - test7.results[0].command == "show route" + - test7.results[0].msg == "The command executed successfully." + - test7.results[1].command == "show lldp neighbors" + - test7.results[1].msg == "The command executed successfully." + + - name: TEST 8 - Multiple commands, save outputs, but don't return them + command: + commands: + - "show route" + - "show version" + formats: + - "xml" + - "json" + dest_dir: "./out/" + return_output: false + register: test8 + + - name: Check TEST 8 + assert: + that: + - test8.results[0].command == "show route" + - test8.results[0].msg == "The command executed successfully." + - test8.results[1].command == "show version" + - test8.results[1].msg == "The command executed successfully." diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index c0e4f8f9..4e95dee5 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -1,5 +1,5 @@ --- -- name: Test juniper_junos_config module +- name: Test juniper.device.config module hosts: all connection: local gather_facts: no @@ -8,7 +8,7 @@ tasks: ################# - name: Retrieve the committed configuration - juniper_junos_config: + config: retrieve: 'committed' diff: false check: false @@ -23,7 +23,7 @@ - test1.config ################# - name: Append .foo to the hostname using private config mode. - juniper_junos_config: + config: config_mode: 'private' load: 'merge' lines: @@ -39,7 +39,7 @@ - test2.diff_lines ################# - name: Rollback to the previous config. - juniper_junos_config: + config: config_mode: 'private' rollback: "1" register: test3 @@ -92,7 +92,7 @@ # state: absent ################ - name: Retrieve [edit system services] of current committed config. - juniper_junos_config: + config: retrieve: 'committed' filter: 'system/services' diff: true @@ -110,7 +110,7 @@ ################# - name: Confirm the previous commit with a commit check (but no commit) - juniper_junos_config: + config: check: true diff: false commit: false @@ -119,4 +119,35 @@ - name: Check TEST 6 assert: that: - - test6.changed == False \ No newline at end of file + test6.changed == False + +################# + + - name: Confirm the commit with a commit sync + config: + check: true + diff: false + comment: "Juniper Networks" + commit_sync: True + register: test7 + + - name: Check TEST 7 + assert: + that: + - test7.changed == False + +################# + + - name: Confirm the commit with a commit sync force + config: + check: true + diff: false + comment: "Juniper Networks" + commit_force_sync: True + register: test8 + + - name: Check TEST 8 + assert: + that: + test8.changed == False + diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index db5b12c1..1e9e09c4 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -1,5 +1,5 @@ --- -- name: Test juniper_junos_facts module +- name: Test juniper.device.facts module hosts: all connection: local gather_facts: no @@ -7,7 +7,7 @@ - juniper.device tasks: - name: "TEST 1 - Gather Facts" - juniper_junos_facts: + facts: ignore_errors: True register: test1 @@ -22,7 +22,7 @@ - test1.facts.fqdn - name: TEST 2 - get facts in xml format - juniper_junos_facts: + facts: config_format: xml register: test2 diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index a7f86240..0bcf749a 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -1,5 +1,5 @@ --- -- name: Test juniper_junos_ping module +- name: Test juniper.device.jsnapy module hosts: all connection: local gather_facts: no @@ -10,7 +10,7 @@ #### TEST 1 ## ################################################## - name: "TEST 1 - Execute SNAPCHECK with 1 test file / no dir" - juniper_junos_jsnapy: + jsnapy: test_files: junos_jsnapy/test_junos_storage.yml action: snapcheck register: test1 @@ -21,7 +21,8 @@ - name: Check TEST 1 assert: that: - - test1|succeeded + - test1.total_passed == 1 + - test1.total_failed == 0 - test1.passPercentage == 100 - test1.total_tests == 1 tags: [ test1 ] @@ -30,7 +31,7 @@ #### TEST 2 ## ################################################## - name: "TEST 2 - Execute SNAPCHECK with 2 test file & dir" - juniper_junos_jsnapy: + jsnapy: test_files: - test_junos_storage.yml - test_version.yml @@ -44,7 +45,8 @@ - name: Check TEST 2 assert: that: - - test2|succeeded + - test2.final_result == "Passed" + - test2.total_failed == 0 - test2.passPercentage == 100 - test2.total_tests == 2 tags: [ test2 ] @@ -53,7 +55,7 @@ #### TEST 3 ## ################################################## - name: "TEST 3 - Wrong test file" - juniper_junos_jsnapy: + jsnapy: test_files: file_that_doesnt_exist.yml action: snapcheck register: test3 @@ -64,14 +66,14 @@ - name: Check TEST 3 assert: that: - - test3|failed + - test3.failed == true tags: [ test3 ] ################################################## #### TEST 4 ## ################################################## - name: "TEST 4 - SNAP_PRE" - juniper_junos_jsnapy: + jsnapy: test_files: junos_jsnapy/test_loopback.yml action: snap_pre register: test4 @@ -83,14 +85,14 @@ - name: Check TEST 4 assert: that: - - test4|succeeded + - test4.msg == "The snap_pre action successfully executed." tags: [ test4 ] ################################################## #### TEST 5 ## ################################################## - name: "TEST 5 - SNAP_POST" - juniper_junos_jsnapy: + jsnapy: test_files: junos_jsnapy/test_loopback.yml action: snap_post register: test5 @@ -102,14 +104,14 @@ - name: Check TEST 5 assert: that: - - test5|succeeded + - test5.msg == "The snap_post action successfully executed." tags: [ test5 ] ################################################## #### TEST 6 ## ################################################## - name: "TEST 6 - CHECK" - juniper_junos_jsnapy: + jsnapy: test_files: junos_jsnapy/test_loopback.yml action: check register: test6 @@ -121,7 +123,7 @@ - name: Check TEST 6 assert: that: - - test6|succeeded + - test6.final_result == "Passed" - test6.passPercentage == 100 tags: [ test6 ] @@ -129,7 +131,7 @@ #### TEST 7 ## ################################################## - name: "PRE-TEST 7 - Add loopback address" - juniper_junos_config: + config: load: 'merge' file: junos_jsnapy/add_loopback.set register: test7_1 @@ -140,7 +142,7 @@ pause: seconds=15 - name: "TEST 7 - SNAP_POST with additional loopback" - juniper_junos_jsnapy: + jsnapy: test_files: junos_jsnapy/test_loopback.yml action: snap_post register: test7_2 @@ -149,7 +151,7 @@ # - debug: var=pretest7 - name: "TEST 7 - CHECK" - juniper_junos_jsnapy: + jsnapy: test_files: junos_jsnapy/test_loopback.yml action: check register: test7 @@ -158,7 +160,7 @@ - debug: var=test7 - name: "TEST 7 - Cleanup" - juniper_junos_config: + config: file: junos_jsnapy/delete_loopback.set load: 'merge' register: test7_3 @@ -168,10 +170,9 @@ - name: Check TEST 7 assert: that: - - test7_1|succeeded - - test7_2|succeeded - - test7_3|succeeded - - test7|succeeded + - test7.passPercentage == 50 + - test7.total_failed == 1 + - test7.total_passed == 1 - test7.passPercentage == 50 - test7.total_tests == 2 tags: [ test7 ] diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index dfc4293c..48269376 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -1,5 +1,5 @@ --- -- name: Test juniper_junos_ping module +- name: Test juniper.device.ping module hosts: all connection: local gather_facts: no @@ -7,7 +7,7 @@ - juniper.device tasks: - name: "TEST 1 - Ping Host DNS" - juniper_junos_ping: + ping: dest_ip: "{{ ansible_ssh_host }}" register: test1 ignore_errors: True @@ -21,7 +21,7 @@ ############ - name: "TEST 2 - Ping Wrong IP" - juniper_junos_ping: + ping: dest_ip: 8.8.1.1 register: test2 ignore_errors: True @@ -34,7 +34,7 @@ ################# - name: "TEST 3 - Change nbr packets" - juniper_junos_ping: + ping: dest_ip: "{{ ansible_ssh_host }}" count: 3 register: test3 @@ -49,7 +49,7 @@ ################# - name: "TEST 4 - Ping with DF-bit set" - juniper_junos_ping: + ping: dest_ip: "{{ ansible_ssh_host }}" count: 3 do_not_fragment: True diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index 61e74c4f..da6a19fc 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -1,5 +1,5 @@ --- -- name: Test juniper_junos_pmtud module +- name: Test juniper.device.pmtud module hosts: all connection: local gather_facts: no @@ -7,7 +7,7 @@ - juniper.device tasks: - name: "TEST 1 - Check path MTU to host DNS" - juniper_junos_pmtud: + pmtud: dest_ip: "{{ ansible_ssh_host }}" register: test1 ignore_errors: True @@ -19,7 +19,7 @@ - 768 <= test1.inet_mtu <= 1500 - name: "TEST 2 - Configure and check inet.mtu to host DNS" - juniper_junos_pmtud: + pmtud: dest_ip: "{{ ansible_ssh_host }}" max_size: 700 register: test1 diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 32553dca..9f108294 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -1,5 +1,5 @@ --- -- name: Test juniper_junos_rpc module +- name: Test juniper.device.rpc module hosts: all connection: local gather_facts: no @@ -9,7 +9,7 @@ tasks: ################# - name: "Execute single RPC get-software-information without any kwargs" - juniper_junos_rpc: + rpc: rpcs: - "get-software-information" register: test1 @@ -24,7 +24,7 @@ ################# - name: "Get Device Configuration with dest" - juniper_junos_rpc: + rpc: rpc: get-config dest: get_config.conf register: test2 @@ -50,7 +50,7 @@ ################# - name: "Get Device Configuration in text" - juniper_junos_rpc: + rpc: rpc: get-interface-information kwargs: "interface_name=em0" format: text @@ -67,7 +67,7 @@ ################# - name: "Execute multiple RPCs without any kwargs" - juniper_junos_rpc: + rpc: rpcs: - "get-software-information" - "get-interface-information" @@ -91,7 +91,7 @@ ################# - name: "Execute multiple RPCs with multiple kwargs" - juniper_junos_rpc: + rpc: rpcs: - "get-software-information" - "get-interface-information" @@ -116,7 +116,7 @@ state: directory - name: "Execute multiple RPCs with multiple kwargs and dest-dir" - juniper_junos_rpc: + rpc: rpcs: - "get-software-information" - "get-interface-information" @@ -154,7 +154,7 @@ ################# - name: Get Device Configuration for interface - juniper_junos_rpc: + rpc: rpc: get-config filter_xml: "" register: test7 @@ -169,7 +169,7 @@ ################# - name: "Execute wrong RPC to generate RPC error" - juniper_junos_rpc: + rpc: rpcs: - "wrong-rpc" register: test8 diff --git a/tests/pb.juniper_junos_software.yml b/tests/pb.juniper_junos_software.yml new file mode 100644 index 00000000..e1de7691 --- /dev/null +++ b/tests/pb.juniper_junos_software.yml @@ -0,0 +1,42 @@ +--- +- name: Test juniper.device.software module + hosts: all + collections: + - juniper.device + connection: local + gather_facts: no + vars: + wait_time: 3600 + pkg_dir: /var/tmp/ + OS_version: 14.1R1.10 + OS_package: jinstall-14.1R1.10-domestic-signed.tgz + log_dir: /var/log/ + + tasks: + - name: Checking NETCONF connectivity + wait_for: host={{ ansible_ssh_host }} port=830 timeout=5 + - name: Install Junos OS package + software: + reboot: yes + no_copy: True + all_re: False + version: "{{ OS_version }}" + package: "{{ pkg_dir }}/{{ OS_package }}" + logfile: "{{ log_dir }}/software.log" + register: test1 + notify: + - wait_reboot + + - name: Print response + debug: + var: test1 + + - name: Check TEST - 1 + assert: + that: + - test1.failed == "false" + + handlers: + - name: wait_reboot + wait_for: host={{ ansible_ssh_host }} port=830 timeout={{ wait_time }} + when: not test.check_mode diff --git a/tests/pb.juniper_junos_srx_cluster.yml b/tests/pb.juniper_junos_srx_cluster.yml new file mode 100644 index 00000000..97b1a95a --- /dev/null +++ b/tests/pb.juniper_junos_srx_cluster.yml @@ -0,0 +1,46 @@ +--- +- name: Test juniper.device.srx_cluster module + hosts: all + connection: local + gather_facts: no + collections: + - juniper.device + tasks: + - name: TEST 1 - Enable an SRX cluster + srx_cluster: + enable: true + cluster_id: 4 + node_id: 0 + register: test1 + tags: [ test1 ] + + - name: Print the response. + debug: + var: test1 + + - name: Check TEST 1 + assert: + that: + - test1.failed == false + tags: [ test1 ] + + - name: Sleep for 300 seconds and continue with play + ansible.builtin.wait_for: + timeout: 300 + delegate_to: localhost + + - name: TEST 2 - Disable an SRX cluster + srx_cluster: + enable: false + register: test2 + tags: [ test2 ] + + - name: Print the response. + debug: + var: test2 + + - name: Check TEST 2 + assert: + that: + - test2.failed == false + tags: [ test2 ] \ No newline at end of file diff --git a/tests/pb.juniper_junos_system.yml b/tests/pb.juniper_junos_system.yml new file mode 100644 index 00000000..94216973 --- /dev/null +++ b/tests/pb.juniper_junos_system.yml @@ -0,0 +1,21 @@ +--- +- name: Test juniper.device.system module + hosts: all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Reboot all REs of the device + system: + action: "reboot" + register: test1 + ignore_errors: True + tags: [ test1 ] + + - name: CHECK TEST 1 + assert: + that: + - test1.reboot == true + tags: [ test1 ] diff --git a/tests/pb.juniper_junos_table.yml b/tests/pb.juniper_junos_table.yml new file mode 100644 index 00000000..dca3716d --- /dev/null +++ b/tests/pb.juniper_junos_table.yml @@ -0,0 +1,46 @@ +--- +- name: Test juniper.device.table PyEZ table/view module. + hosts: all + connection: local + gather_facts: no + collections: + - juniper.device + + tasks: + - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table + table: + file: "lldp.yml" + register: test1 + ignore_errors: True + tags: [ test1 ] + + - name: check TEST 1 + assert: + that: + - test1.msg == "Successfully retrieved 0 items from LLDPNeighborTable." + + - name: Retrieve routes within 192.68.1/8 + table: + file: "routes.yml" + table: "RouteTable" + kwargs: + destination: "192.68.1.0/8" + response_type: "juniper_items" + register: test2 + + - name: check TEST 2 + assert: + that: + - test2.msg == "Successfully retrieved 1 items from RouteTable." + + - name: Retrieve ethernet devices + table: + file: "ethport.yml" + table: "EthPortTable" + kwargs: + interface_name: ge-0/0/0 + register: test3 + - name: check TEST 3 + assert: + that: + - test3.msg == "Successfully retrieved 1 items from EthPortTable." From b3a3490b28160fa6e496f04052ee5a95f06e515d Mon Sep 17 00:00:00 2001 From: chidanandpujar Date: Thu, 3 Nov 2022 17:14:09 +0530 Subject: [PATCH 370/426] updated test playbooks --- tests/junos_jsnapy/test_junos_storage.yml | 2 +- tests/pb.juniper_junos_config.yml | 8 ++++++++ tests/pb.juniper_junos_jsnapy.yml | 6 ++++-- tests/pb.juniper_junos_software.yml | 4 ++-- tests/pb.juniper_junos_system.yml | 4 ++++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/junos_jsnapy/test_junos_storage.yml b/tests/junos_jsnapy/test_junos_storage.yml index 3473c27e..1be2c82b 100644 --- a/tests/junos_jsnapy/test_junos_storage.yml +++ b/tests/junos_jsnapy/test_junos_storage.yml @@ -4,7 +4,7 @@ tests_include: check_storage: - command: show system storage - iterate: - xpath: //system-storage-information/filesystem[normalize-space(mounted-on)='/'] + xpath: //system-storage-information/filesystem[normalize-space(mounted-on)='/.mount'] tests: - is-lt: used-percent, 95 info: "File system {{post['mounted-on']}} use less than 95%" diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 4e95dee5..0a85a84c 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -91,6 +91,14 @@ # path: out # state: absent ################ + - name: Append .foo to the hostname using private config mode. + config: + config_mode: 'private' + load: 'merge' + lines: + - "set system services netconf ssh" + comment: "Configured system services" + - name: Retrieve [edit system services] of current committed config. config: retrieve: 'committed' diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index 0bcf749a..08f10004 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -138,8 +138,10 @@ ignore_errors: True tags: [ test7 ] - - name: Wait for loopback to come up - pause: seconds=15 + - name: Sleep for 15 seconds for loopback to come up + ansible.builtin.wait_for: + timeout: 15 + delegate_to: localhost - name: "TEST 7 - SNAP_POST with additional loopback" jsnapy: diff --git a/tests/pb.juniper_junos_software.yml b/tests/pb.juniper_junos_software.yml index e1de7691..2f8170ce 100644 --- a/tests/pb.juniper_junos_software.yml +++ b/tests/pb.juniper_junos_software.yml @@ -34,9 +34,9 @@ - name: Check TEST - 1 assert: that: - - test1.failed == "false" + - test1.failed == false handlers: - name: wait_reboot wait_for: host={{ ansible_ssh_host }} port=830 timeout={{ wait_time }} - when: not test.check_mode + when: not test1.check_mode diff --git a/tests/pb.juniper_junos_system.yml b/tests/pb.juniper_junos_system.yml index 94216973..f2428221 100644 --- a/tests/pb.juniper_junos_system.yml +++ b/tests/pb.juniper_junos_system.yml @@ -19,3 +19,7 @@ that: - test1.reboot == true tags: [ test1 ] + + - name: Checking NETCONF connectivity + wait_for: host={{ ansible_ssh_host }} port=830 timeout=360 + From 60547b608d916a7b7272c124350e33829aa3c7d9 Mon Sep 17 00:00:00 2001 From: chidanandpujar Date: Thu, 3 Nov 2022 17:20:30 +0530 Subject: [PATCH 371/426] updated test playbooks --- tests/pb.juniper_junos_config.yml | 2 +- tests/pb.juniper_junos_srx_cluster.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 0a85a84c..6baf119f 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -91,7 +91,7 @@ # path: out # state: absent ################ - - name: Append .foo to the hostname using private config mode. + - name: Configure system services. config: config_mode: 'private' load: 'merge' diff --git a/tests/pb.juniper_junos_srx_cluster.yml b/tests/pb.juniper_junos_srx_cluster.yml index 97b1a95a..7cbbcb80 100644 --- a/tests/pb.juniper_junos_srx_cluster.yml +++ b/tests/pb.juniper_junos_srx_cluster.yml @@ -29,6 +29,9 @@ timeout: 300 delegate_to: localhost + - name: Checking NETCONF connectivity + wait_for: host={{ ansible_ssh_host }} port=830 timeout=360 + - name: TEST 2 - Disable an SRX cluster srx_cluster: enable: false From 9375ab69b3396337e04bd5a3c9b682f952822ba2 Mon Sep 17 00:00:00 2001 From: chidanandpujar Date: Fri, 11 Nov 2022 15:56:14 +0530 Subject: [PATCH 372/426] updated test playbooks --- tests/pb.juniper_junos_rpc.yml | 42 ++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 9f108294..06b96227 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -52,7 +52,8 @@ - name: "Get Device Configuration in text" rpc: rpc: get-interface-information - kwargs: "interface_name=em0" + kwargs: + interface_name: "lo0.0" format: text register: test3 ignore_errors: True @@ -97,7 +98,7 @@ - "get-interface-information" kwargs: - {} - - "interface_name=em0" + - interface_name: "lo0.0" register: test5 ignore_errors: True tags: [ test5 ] @@ -122,7 +123,7 @@ - "get-interface-information" kwargs: - {} - - "interface_name=em0" + - interface_name: "lo0.0" dest_dir: "out" register: test6 ignore_errors: True @@ -130,12 +131,12 @@ - name: Check get-interface-information.xml exists stat: - path: "out/{{ ansible_ssh_host }}_get-interface-information.xml" + path: "out/test_get-interface-information.xml" register: stat_result_1 - name: Check get-software-information.xml exists stat: - path: "out/{{ ansible_ssh_host }}_get-software-information.xml" + path: "out/test_get-software-information.xml" register: stat_result_2 - name: Check TEST 6 @@ -183,3 +184,34 @@ tags: [ test8 ] ################# + - name: "Check configuration for errors" + rpc: + rpcs: + - "open-configuration" + - "load-configuration" + - "close-configuration" + attrs: + - {} + - action: 'set' + format: 'text' + - {} + kwargs: + - private: true + - configuration_set: 'set system syslog file test1 any any' + - {} + register: test9 + tags: [ test9 ] + + - name: Check TEST 9 + debug: + var: test9 + tags: [ test9 ] + + - name: Check TEST 9 + assert: + that: + - test9.results[0].msg == "The RPC executed successfully." + - test9.results[1].msg == "The RPC executed successfully." + - test9.results[2].msg == "The RPC executed successfully." + tags: [ test9 ] + From fb1d7682a74aaae7944c721d9edbe205fe1cfeb6 Mon Sep 17 00:00:00 2001 From: chidanandpujar Date: Fri, 11 Nov 2022 16:18:27 +0530 Subject: [PATCH 373/426] updated test playbooks --- tests/pb.juniper_junos_rpc.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 06b96227..92c5a712 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -215,3 +215,25 @@ - test9.results[2].msg == "The RPC executed successfully." tags: [ test9 ] +################# + - name: "Check huge xml/text data" + rpc: + rpcs: + - get-support-information + - file-archive + kwargs: + - {} + - destination: "support_info" + source: /var/log/* + compress: True + formats: text + timeout: 1200 + huge_tree: True + register: test10 + tags: [ test10 ] + + - name: Check TEST 10 + assert: + that: + test10.results[0].msg == "The RPC executed successfully." + tags: [ test10 ] From 90ccf26ab5f9bbbb1935337f15b3a13de2f5e7a0 Mon Sep 17 00:00:00 2001 From: chidanandpujar Date: Tue, 15 Nov 2022 11:02:40 +0530 Subject: [PATCH 374/426] Ansible v1.0.2 release update --- ansible_collections/juniper/device/version.py | 4 ++-- version.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 05686310..20a6d52f 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.1-collections" -DATE = "2021-Oct-05" +VERSION = "v1.0.2-collections" +DATE = "2022-Nov-16" diff --git a/version.py b/version.py index f97a894c..20a6d52f 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.0-collections_dev0" -DATE = "2021-Apr-23" +VERSION = "v1.0.2-collections" +DATE = "2022-Nov-16" From 518c9098d822e703a92e850e079fa384d37e29b9 Mon Sep 17 00:00:00 2001 From: chidanandpujar Date: Wed, 16 Nov 2022 20:22:11 +0530 Subject: [PATCH 375/426] Fix for sphinx build failure --- .../juniper/device/plugins/modules/rpc.py | 5 --- tests/pb.juniper_junos_rpc.yml | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index 2ab3d0b7..335136c3 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -166,11 +166,6 @@ shown in the :ref:`rpc-examples-label`. - By default "0" and "1" will be converted to boolean values. In case it doesn't need to be transformed to boolean pass first kwargs as - allow_bool_values : "0" - example - - kwargs: - allow_bool_values: "0" - data: "1" required: false default: none type: dict or list of dict diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 92c5a712..2a43affc 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -237,3 +237,34 @@ that: test10.results[0].msg == "The RPC executed successfully." tags: [ test10 ] + + - name: Check rollback info + rpc: + rpc: get-rollback-information + kwargs: + rollback: "3" + compare: "2" + register: test11 + tags: [ test11 ] + + - name: Check TEST 11 + assert: + that: + test11.msg == "The RPC executed successfully." + tags: [ test11 ] + + - name: Check rollback info with boolean values + rpc: + rpc: get-rollback-information + kwargs: + allow_bool_values : "0" + rollback: "1" + compare: "0" + register: test12 + tags: [ test12 ] + + - name: Check TEST 12 + assert: + that: + test12.msg == "The RPC executed successfully." + tags: [ test12 ] From e3f6fca99461b8f40d4d59bb7dcc8b3892722ad5 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Wed, 2 Aug 2023 09:34:30 +0530 Subject: [PATCH 376/426] Fix for issue #613 (#617) * Fix for issue #613 Support for software installation on specific members "member_id" of EX-VC * Fix for issue #613 Support for software installation on specific members "member_id" of EX-VC --- .../juniper/device/plugins/connection/pyez.py | 16 +++++++++---- .../device/plugins/modules/software.py | 23 +++++++++++++++++-- .../juniper/device/plugins/modules/system.py | 16 +++++++++++-- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 11f93923..b262fc23 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -754,7 +754,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, raise AnsibleError('Failure committing the configuraton: %s' % (str(ex))) - def system_api(self, action, in_min, at, all_re, vmhost, other_re, media): + def system_api(self, action, in_min, at, all_re, vmhost, other_re, media, member_id): """Triggers the system calls like reboot, shutdown, halt and zeroize to device. """ msg = None @@ -767,7 +767,11 @@ def system_api(self, action, in_min, at, all_re, vmhost, other_re, media): try: self.sw = jnpr.junos.utils.sw.SW(self.dev) if action == 'reboot': - got = self.sw.reboot(in_min, at, all_re, None, vmhost, other_re) + if member_id is not None: + for m_id in member_id: + got = self.sw.reboot(in_min, at, all_re, None, vmhost, other_re, member_id=m_id) + else: + got = self.sw.reboot(in_min, at, all_re, None, vmhost, other_re) elif action == 'shutdown': got = self.sw.poweroff(in_min, at, None, all_re, other_re) elif action == 'halt': @@ -815,7 +819,7 @@ def software_api(self, install_params): self.pyez_exception.RpcError) as ex: raise AnsibleError('Installation failed. Error: %s' % str(ex)) - def reboot_api(self, all_re, vmhost): + def reboot_api(self, all_re, vmhost, member_id): """reboots the device. """ msg = None @@ -824,7 +828,11 @@ def reboot_api(self, all_re, vmhost): if self.dev.timeout > 5: self.dev.timeout = 5 try: - got = self.sw.reboot(0, None, all_re, None, vmhost) + if member_id is not None: + for m_id in member_id: + got = self.sw.reboot(0, None, all_re, None, vmhost, member_id=m_id) + else: + got = self.sw.reboot(0, None, all_re, None, vmhost) self.dev.timeout = restore_timeout except Exception: # pylint: disable=broad-except self.dev.timeout = restore_timeout diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index a6c244af..54a5b814 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -88,6 +88,12 @@ required: false default: true type: bool + member_id: + description: + - install software on the specified members ids of VC. + required: false + default: none + type: list checksum: description: - The pre-calculated checksum, using the I(checksum_algorithm) of the @@ -492,6 +498,9 @@ def main(): all_re=dict(required=False, type='bool', default=True), + member_id=dict(required=False, + type='list', + default=None), vmhost=dict(required=False, type='bool', default=False), @@ -541,6 +550,7 @@ def main(): install_timeout = junos_module.params.pop('install_timeout') cleanfs = junos_module.params.pop('cleanfs') all_re = junos_module.params.pop('all_re') + member_id = junos_module.params.pop('member_id') kwargs = junos_module.params.pop('kwargs') url = None @@ -684,6 +694,7 @@ def main(): install_params['no_copy'] = no_copy install_params['timeout'] = install_timeout install_params['all_re'] = all_re + install_params['member_id'] = member_id for key in option_keys: value = junos_module.params.get(key) if value is not None: @@ -715,7 +726,11 @@ def main(): if reboot is True: junos_module.logger.debug('Initiating reboot.') if junos_module.conn_type != "local": - results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost')) + if member_id is not None: + for m_id in member_id: + results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost'), member_id=m_id) + else: + results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost')) else: try: # Try to deal with the fact that we might not get the closing @@ -728,7 +743,11 @@ def main(): "to 5 seconds.") junos_module.dev.timeout = 5 try: - got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) + if member_id is not None: + for m_id in member_id: + got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost'), member_id=m_id) + else: + got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) junos_module.dev.timeout = restore_timeout except Exception: # pylint: disable=broad-except junos_module.dev.timeout = restore_timeout diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index b8aa3c86..6dd40178 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -273,6 +273,9 @@ def main(): all_re=dict(type='bool', required=False, default=True), + member_id=dict(type='list', + required=False, + default=None), other_re=dict(type='bool', required=False, default=False), @@ -297,6 +300,7 @@ def main(): other_re = params.get('other_re') media = params.get('media') vmhost = params.get('vmhost') + member_id = params.get('member_id') # Synonymn for shutdown if action == 'off' or action == 'power_off' or action == 'power-off': @@ -331,7 +335,11 @@ def main(): if not junos_module.check_mode: if junos_module.conn_type != "local": - results['msg'] = junos_module._pyez_conn.system_api(action, in_min, at, all_re, vmhost, other_re, media) + if member_id is not None: + for m_id in member_id: + results['msg'] = junos_module._pyez_conn.system_api(action, in_min, at, all_re, vmhost, other_re, media, member_id=m_id) + else: + results['msg'] = junos_module._pyez_conn.system_api(action, in_min, at, all_re, vmhost, other_re, media) results['failed'] = False else: if action != 'zeroize': @@ -351,7 +359,11 @@ def main(): junos_module.logger.debug("Executing RPC") junos_module.add_sw() if action == 'reboot': - got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) + if member_id is not None: + for m_id in member_id: + got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re, member_id=m_id) + else: + got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) elif action == 'shutdown': got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) elif action == 'halt': From df8c83419208f158cbf689f96e26386389960c6b Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:31:50 +0530 Subject: [PATCH 377/426] fix for issue #612 (#628) --- ansible_collections/juniper/device/docs/jsnapy.rst | 2 +- ansible_collections/juniper/device/plugins/modules/jsnapy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/docs/jsnapy.rst b/ansible_collections/juniper/device/docs/jsnapy.rst index a2c506ec..63e1bc75 100644 --- a/ansible_collections/juniper/device/docs/jsnapy.rst +++ b/ansible_collections/juniper/device/docs/jsnapy.rst @@ -15,7 +15,7 @@ Synopsis -------- -* Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. JSNAPy is documented on `Github `_ and this `Day One Book `_ +* Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. JSNAPy is documented on `Github `_ and this `Day One Book `_ * This module only reports ``failed`` if the module encounters an error and fails to execute the JSNAPy tests. If does NOT report ``failed`` if one or more of the JSNAPy tests fail. To check the test results, register the module's response and use the assert module to verify the expected result in the response. (See :ref:`jsnapy-examples-label`.) * A callback plugin which formats and prints JSNAPy test results for human consumption is also available. This callback plugin is enabled by adding ``callback_whitelist = jsnapy`` to the Ansible configuration file. diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 43f87754..28f94e5b 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -53,7 +53,7 @@ - Execute Junos SNAPshot Adminsitrator (JSNAPy) tests against a Junos device. JSNAPy is documented on U(Github|https://github.com/Juniper/jsnapy) and this - U(Day One Book|https://www.juniper.net/uk/en/training/jnbooks/day-one/automation-series/jsnapy/) + U(Day One Book|https://www.juniper.net/documentation/en_US/day-one-books/DO_JSNAPy.zip) - This module only reports C(failed) if the module encounters an error and fails to execute the JSNAPy tests. If does NOT report C(failed) if one or more of the JSNAPy tests fail. To check the test results, register the From efff541780476d807c778eb30c36dc21915b5784 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Thu, 28 Sep 2023 17:39:35 +0530 Subject: [PATCH 378/426] fix for issue #621 (#627) * fix for issue #621 * fix for issue #621 --- .../juniper/device/docs/.readthedocs.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 ansible_collections/juniper/device/docs/.readthedocs.yaml diff --git a/ansible_collections/juniper/device/docs/.readthedocs.yaml b/ansible_collections/juniper/device/docs/.readthedocs.yaml new file mode 100644 index 00000000..f3aa1f5a --- /dev/null +++ b/ansible_collections/juniper/device/docs/.readthedocs.yaml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +# python: +# install: +# - requirements: docs/requirements.txt From 4f47c505f476c445ed8304f916b00e505c8dc670 Mon Sep 17 00:00:00 2001 From: Chris Mason <57402469+cmason3@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:50:13 +0100 Subject: [PATCH 379/426] apply fix for issue #592 - pass through auth command line arguments (#615) --- .../juniper/device/plugins/action/extract_data.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py index 6b4df350..faff5a2b 100644 --- a/ansible_collections/juniper/device/plugins/action/extract_data.py +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -93,6 +93,20 @@ def extract(self, tmp=None, task_vars=None): new_connection_args[key] = task_vars[tempKey] break + # Fix for ansible-core>=2.13 when -u, -k or --private-key are command line arguments + if 'user' not in connection_args: + if self._play_context.remote_user is not None: + new_connection_args['user'] = self._play_context.remote_user + connection_args['user'] = self._play_context.remote_user + if 'passwd' not in connection_args: + if self._play_context.password is not None: + new_connection_args['passwd'] = self._play_context.password + connection_args['passwd'] = self._play_context.password + if 'ssh_private_key_file' not in connection_args: + if self._play_context.private_key_file is not None: + new_connection_args['ssh_private_key_file'] = self._play_context.private_key_file + connection_args['ssh_private_key_file'] = self._play_context.private_key_file + # Backwards compatible behavior to fallback to USER env. variable. if 'user' not in connection_args and 'user' not in new_connection_args: user = os.getenv('USER') From bd6d0b3b9f8318a2a197dd5c0af2a3b4f6a6aa82 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:26:39 +0530 Subject: [PATCH 380/426] Fix for issue #620 (#632) --- .../device/plugins/module_utils/juniper_junos_common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index aee5fa42..f591b74e 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -43,6 +43,7 @@ from jnpr.junos.utils.sw import SW from jnpr.junos import exception as pyez_exception from ncclient.operations.errors import TimeoutExpiredError +from ansible.module_utils.common.validation import check_type_dict # Standard library imports from argparse import ArgumentParser @@ -932,7 +933,7 @@ def parse_arg_to_list_of_dicts(self, # This might be a keyword1=value1 keyword2=value2 type string. # The _check_type_dict method will parse this into a dict for us. try: - kwargs = self._check_type_dict(kwargs) + kwargs = check_type_dict(kwargs) except TypeError as exc: self.fail_json(msg="The value of the %s option (%s) is " "invalid. Unable to translate into " @@ -955,7 +956,7 @@ def parse_arg_to_list_of_dicts(self, # This might be a keyword1=value1 keyword2=value2 type string. # The _check_type_dict method will parse this into a dict. try: - kwarg = self._check_type_dict(kwarg) + kwarg = check_type_dict(kwarg) except TypeError as exc: self.fail_json(msg="The value of the %s option (%s) is " "invalid. Unable to translate into a " From 9050a2e84287282735940de779fb2775dc022f8c Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:26:56 +0530 Subject: [PATCH 381/426] Fix for issue #630 (#631) --- ansible_collections/juniper/device/docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/docs/conf.py b/ansible_collections/juniper/device/docs/conf.py index 0d460fe5..cb4397e1 100644 --- a/ansible_collections/juniper/device/docs/conf.py +++ b/ansible_collections/juniper/device/docs/conf.py @@ -18,7 +18,7 @@ def setup(app): - app.add_stylesheet("juniper-junos-modules.css") + app.add_css_file("juniper-junos-modules.css") # If extensions (or modules to document with autodoc) are in another directory, From 540d40ba64180d44dbbb4a458f9bcfd927b07a0d Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:58:46 +0530 Subject: [PATCH 382/426] playbook to install image on VC member id (#629) * playbook to install image on VC member id * playbook to install image on VC member id * playbook to install image on VC member id * test playbooks --- tests/pb.juniper_junos_persistent_conn.yml | 25 +++++++++++++ tests/pb.juniper_junos_software_member.yml | 43 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 tests/pb.juniper_junos_persistent_conn.yml create mode 100644 tests/pb.juniper_junos_software_member.yml diff --git a/tests/pb.juniper_junos_persistent_conn.yml b/tests/pb.juniper_junos_persistent_conn.yml new file mode 100644 index 00000000..4494a3cf --- /dev/null +++ b/tests/pb.juniper_junos_persistent_conn.yml @@ -0,0 +1,25 @@ +--- +- name: Test juniper.device.rpc module + hosts: all + connection: juniper.device.pyez + gather_facts: no + collections: + - juniper.device + + tasks: +################# + - name: Get Device Configuration with filter and attr + rpc: + rpc: get-config + format: xml + filter: re0 + attr: name=re0 + register: test7 + ignore_errors: True + tags: [ test7 ] + + - name: Check TEST 7 + assert: + that: + - test7.msg == "The \"get-config\" RPC executed successfully." + tags: [ test7 ] diff --git a/tests/pb.juniper_junos_software_member.yml b/tests/pb.juniper_junos_software_member.yml new file mode 100644 index 00000000..56c81a09 --- /dev/null +++ b/tests/pb.juniper_junos_software_member.yml @@ -0,0 +1,43 @@ +--- +- name: Test juniper.device.software module + hosts: all + collections: + - juniper.device + connection: local + gather_facts: no + vars: + wait_time: 3600 + pkg_dir: /var/tmp/ + OS_version: 22.4 + OS_package: junos-install-ex-x86-64-22.4I62500TB237700_cd-builder.tgz + log_dir: /var/log/ + + tasks: + - name: Checking NETCONF connectivity + wait_for: host={{ ansible_ssh_host }} port=830 timeout=5 + - name: Install Junos OS package on VC member + software: + reboot: False + no_copy: True + version: "{{ OS_version }}" + package: "{{ pkg_dir }}/{{ OS_package }}" + logfile: "{{ log_dir }}/software.log" + all_re: False + member_id: ['1','2'] + register: test1 + notify: + - wait_reboot + + - name: Print response + debug: + var: test1 + + - name: Check TEST - 1 + assert: + that: + - test1.failed == false + + handlers: + - name: wait_reboot + wait_for: host={{ ansible_ssh_host }} port=830 timeout={{ wait_time }} + when: not test1.check_mode From 290e3390b32de531b9b1a1cac0df352f525b64ed Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Mon, 8 Jan 2024 16:33:52 +0530 Subject: [PATCH 383/426] 1. Supported config mode : private, batch, dynamic, exclusive. (#635) 2. By default, config mode is exclusive. 3. Supported config mode : ephemeral (default) and ephemeral (user defined instance). 4. Ephemeral db doesn't support "show | compare" command. So, set diff as False in case of config mode ephemeral. root@> configure ephemeral warning: uncommitted changes will be discarded on exit Entering configuration mode root@# show | compare ^ syntax error, expecting . --- .../module_utils/juniper_junos_common.py | 27 ++++++++++++--- .../juniper/device/plugins/modules/config.py | 34 ++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index f591b74e..06768bdd 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -485,7 +485,7 @@ class ModuleDocFragment(object): CONFIG_ACTION_CHOICES = ['set', 'merge', 'update', 'replace', 'override', 'overwrite', 'patch'] # Supported configuration modes -CONFIG_MODE_CHOICES = ['exclusive', 'private'] +CONFIG_MODE_CHOICES = ['exclusive', 'private', 'dynamic', 'batch', 'ephemeral'] # Supported configuration models CONFIG_MODEL_CHOICES = ['openconfig', 'custom', 'ietf', 'True'] @@ -513,7 +513,7 @@ class JuniperJunosModule(AnsibleModule): open: Open self.dev. close: Close self.dev. add_sw: Add an instance of jnp.junos.utils.sw.SW() to self. - open_configuration: Open cand. conf. db in exclusive or private mode. + open_configuration: Open cand. conf. db in exclusive/private/dynamic/batch/ephemeral mode. close_configuration: Close candidate configuration database. get_configuration: Return the device config. in the specified format. rollback_configuration: Rollback device config. to the specified id. @@ -1137,7 +1137,7 @@ def add_sw(self): """ self.sw = SW(self.dev) - def open_configuration(self, mode, ignore_warning=None): + def open_configuration(self, mode, ignore_warning=None, ephemeral_instance=None): """Open candidate configuration database in exclusive or private mode. Failures: @@ -1164,6 +1164,8 @@ def open_configuration(self, mode, ignore_warning=None): if self.config is None: if mode not in CONFIG_MODE_CHOICES: self.fail_json(msg='Invalid configuration mode: %s' % (mode)) + if mode != 'ephemeral' and ephemeral_instance is not None: + self.fail_json(msg='configuration mode ephemeral is required') if self.dev is None: self.open() config = jnpr.junos.utils.config.Config(self.dev, mode=mode) @@ -1174,6 +1176,23 @@ def open_configuration(self, mode, ignore_warning=None): self.dev.rpc.open_configuration( private=True, ignore_warning=ignore_warn) + elif config.mode == 'dynamic': + self.dev.rpc.open_configuration( + dynamic=True, + ignore_warning=ignore_warn) + elif config.mode == 'batch': + self.dev.rpc.open_configuration( + batch=True, + ignore_warning=ignore_warn) + elif config.mode == 'ephemeral': + if ephemeral_instance is None: + self.dev.rpc.open_configuration( + ephemeral=True, + ignore_warning=ignore_warn) + else: + self.dev.rpc.open_configuration( + ephemeral_instance = ephemeral_instance, + ignore_warning=ignore_warn) except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: self.fail_json(msg='Unable to open the configuration in %s ' @@ -1202,7 +1221,7 @@ def close_configuration(self): try: if config.mode == 'exclusive': config.unlock() - elif config.mode == 'private': + elif config.mode in ['batch', 'dynamic', 'private', 'ephemeral']: self.dev.rpc.close_configuration() self.logger.debug("Configuration closed.") except (pyez_exception.ConnectError, diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index a0a7b4a5..abba237f 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -60,6 +60,20 @@ * If the I(config_mode) option has a value of C(private), open a private candidate configuration database. If opening the private configuration database fails the module fails and reports an error. + * If the I(config_mode) option has a value of C(dynamic), open a dynamic + candidate configuration database. If opening the dynamic configuration + database fails the module fails and reports an error. + * If the I(config_mode) option has a value of C(batch), open a batch + candidate configuration database. If opening the batch configuration + database fails the module fails and reports an error. + * If the I(config_mode) option has a value of C(ephemeral), open a default + ephemeral candidate configuration database. If opening the ephemeral + configuration database fails the module fails and reports an error. + * If the I(ephemeral_instance) option has a value of C(ephemeral instance + name) along with I(config_mode) option has a value of C(ephemeral), open + a user defined ephemeral instance candidate configuration database. If + opening the ephemeral onfiguration database fails the module fails + and reports an error. #. Load configuration data into the candidate configuration database. * Configuration data may be loaded using the I(load) or I(rollback) @@ -208,10 +222,19 @@ choices: - exclusive - private + - dynamic + - batch + - ephemeral aliases: - config_access - edit_mode - edit_access + ephemeral_instance: + description: + - To open a user-defined instance of the ephemeral database + required: false + default: None + type: str confirmed: description: - Provide a confirmed timeout, in minutes, to be used with the commit @@ -826,6 +849,9 @@ def main(): aliases=['config_access', 'edit_mode', 'edit_access'], default='exclusive'), + ephemeral_instance=dict(type='str', + required=False, + default=None), rollback=dict(type='str', required=False, default=None), @@ -943,6 +969,7 @@ def main(): # Straight from params config_mode = junos_module.params.get('config_mode') + ephemeral_instance = junos_module.params.get('ephemeral_instance') # Parse rollback value rollback = junos_module.parse_rollback_option() @@ -976,6 +1003,10 @@ def main(): remove_ns = junos_module.params.get('remove_ns') namespace = junos_module.params.get('namespace') + # Ephemeral database doesn't support "show | compare", + # so setting diff to False. + if config_mode == 'ephemeral': + diff = False # If retrieve is set and load and rollback are not set, then # check, diff, and commit default to False. @@ -1092,7 +1123,8 @@ def main(): junos_module.logger.debug("Step 1 - Open a candidate configuration " "database.") - junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning) + junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning, + ephemeral_instance=ephemeral_instance) results['msg'] += 'opened' junos_module.logger.debug("Step 2 - Load configuration data into the " From cf07c23c16f0397c8efdcae278b131112acf8116 Mon Sep 17 00:00:00 2001 From: apurvaraghu <100869315+apurvaraghu@users.noreply.github.com> Date: Wed, 24 Jan 2024 01:32:16 -0800 Subject: [PATCH 384/426] issue587 (#636) * implement action:shutdown in vmhost devices * implem --- .../juniper/device/plugins/connection/pyez.py | 14 +++++++------- .../juniper/device/plugins/modules/system.py | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index b262fc23..dbc50281 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -113,7 +113,7 @@ env: - name: ANSIBLE_PYEZ_CONSOLE vars: - - name: ansible_pyez_console + - name: ansible_pyez_console private_key_file: description: - The private SSH key or certificate file used to authenticate to the remote device @@ -202,7 +202,7 @@ vars: - name: ansible_pyez_ssh_config - name: ssh_config - + """ import pickle @@ -453,7 +453,7 @@ def get_config(self, filter_xml=None, options=None, model=None, """ resp = self.dev.rpc.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) return etree.tostring(resp) - + def get_rpc_resp(self,rpc, ignore_warning, format): """Execute rpc on the device and get response. @@ -469,8 +469,8 @@ def get_rpc_resp(self,rpc, ignore_warning, format): Fails: - If the RPC produces an exception. """ - # data comes in JSON format, needs to be converted - rpc_val = xmltodict.unparse(rpc) + # data comes in JSON format, needs to be converted + rpc_val = xmltodict.unparse(rpc) rpc_val = rpc_val.encode('utf-8') parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8') rpc_etree = etree.fromstring(rpc_val, parser=parser) @@ -478,7 +478,7 @@ def get_rpc_resp(self,rpc, ignore_warning, format): if(format == 'json'): return resp return etree.tostring(resp) - + def get_facts(self): """Get device facts. """ @@ -773,7 +773,7 @@ def system_api(self, action, in_min, at, all_re, vmhost, other_re, media, member else: got = self.sw.reboot(in_min, at, all_re, None, vmhost, other_re) elif action == 'shutdown': - got = self.sw.poweroff(in_min, at, None, all_re, other_re) + got = self.sw.poweroff(in_min, at, None, all_re, other_re, vmhost) elif action == 'halt': got = self.sw.halt(in_min, at, all_re, other_re) elif action == 'zeroize': diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index 6dd40178..6834cf42 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -39,7 +39,7 @@ DOCUMENTATION = ''' --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: system @@ -81,7 +81,7 @@ - power_off at: description: - - The time at which to shutdown, halt, or reboot the system. + - The time at which to shutdown, halt, or reboot the system. - > The value may be specified in one of the following ways: - B(now) - The action takes effect immediately. @@ -365,7 +365,7 @@ def main(): else: got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) elif action == 'shutdown': - got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re) + got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re, vmhost) elif action == 'halt': got = junos_module.sw.halt(in_min, at, all_re, other_re) elif action == 'zeroize': From 0cef75e862dc58e5bc86f079ba18eab2695fddfe Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Thu, 25 Jan 2024 11:56:00 +0530 Subject: [PATCH 385/426] =?UTF-8?q?Issue:=E2=80=A8=E2=80=A8commit=5Fconfig?= =?UTF-8?q?uration=20API=20parameters=20are=20missing=20in=20ansible=20Py?= =?UTF-8?q?=E2=80=A6=20(#638)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Issue:

commit_configuration API parameters are missing in ansible PyEZ connection API and also formatted error was noticed. This impact committing the configuration using ansible PyEZ connection. Fix: Supported valid parameter in commit_configuration API parameter. Fixed format string issue Fixed configuration mode support for ansible PyEZ connection. * Issue:

commit_configuration API parameters are missing in ansible PyEZ connection API and also formatted error was noticed. This impact committing the configuration using ansible PyEZ connection. Fix: Supported valid parameter in commit_configuration API parameter. Fixed format string issue Fixed configuration mode support for ansible PyEZ connection. * Fix --- .../juniper/device/plugins/connection/pyez.py | 38 ++++++++++++++++--- .../module_utils/juniper_junos_common.py | 6 ++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index dbc50281..47a6d3df 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -302,7 +302,7 @@ ) # Supported configuration modes -CONFIG_MODE_CHOICES = ['exclusive', 'private'] +CONFIG_MODE_CHOICES = ['exclusive', 'private', 'dynamic', 'batch', 'ephemeral'] class Connection(NetworkConnectionBase): @@ -590,7 +590,7 @@ def invoke_jsnapy(self, data, action): (type(responses), str(responses))) return results - def open_configuration(self, mode, ignore_warn=None): + def open_configuration(self, mode, ignore_warn=None, ephemeral_instance=None): """Open candidate configuration database in exclusive or private mode. Failures: @@ -601,6 +601,10 @@ def open_configuration(self, mode, ignore_warn=None): if self.config is None: if mode not in CONFIG_MODE_CHOICES: raise AnsibleError("Invalid configuration mode: %s" % mode) + if mode != 'ephemeral' and ephemeral_instance is not None: + self.fail_json(msg='Ephemeral instance is specified while the mode ' + 'is not ephemeral. Specify the mode as ephemeral ' + 'or do not specify the instance.') if self.dev is None: self.open() config = jnpr.junos.utils.config.Config(self.dev, mode=mode) @@ -611,6 +615,23 @@ def open_configuration(self, mode, ignore_warn=None): self.dev.rpc.open_configuration( private=True, ignore_warning=ignore_warn) + elif config.mode == 'dynamic': + self.dev.rpc.open_configuration( + dynamic=True, + ignore_warning=ignore_warn) + elif config.mode == 'batch': + self.dev.rpc.open_configuration( + batch=True, + ignore_warning=ignore_warn) + elif config.mode == 'ephemeral': + if ephemeral_instance is None: + self.dev.rpc.open_configuration( + ephemeral=True, + ignore_warning=ignore_warn) + else: + self.dev.rpc.open_configuration( + ephemeral_instance = ephemeral_instance, + ignore_warning=ignore_warn) except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: raise AnsibleError('Unable to open the configuration in %s ' @@ -631,7 +652,7 @@ def close_configuration(self): try: if config.mode == 'exclusive': config.unlock() - elif config.mode == 'private': + elif config.mode in ['batch', 'dynamic', 'private', 'ephemeral']: self.dev.rpc.close_configuration() self.queue_message("log", "Configuration closed.") except (pyez_exception.ConnectError, @@ -723,7 +744,7 @@ def load_configuration(self, config, load_args): if config is not None: self.config.load(config, **load_args) else: - self.queue_message("log", "Load args %s.", str(load_args)) + self.queue_message("log", "Load args %s." %str(load_args)) self.config.load(**load_args) self.queue_message("log", "Configuration loaded.") except (self.pyez_exception.RpcError, @@ -732,7 +753,8 @@ def load_configuration(self, config, load_args): (str(ex))) def commit_configuration(self, ignore_warning=None, comment=None, - confirmed=None): + confirmed=None, timeout=30, full=False, + force_sync=False, sync=False): """Commit the candidate configuration. Assumes the configuration is already opened. @@ -747,7 +769,11 @@ def commit_configuration(self, ignore_warning=None, comment=None, try: self.config.commit(ignore_warning=ignore_warning, comment=comment, - confirm=confirmed) + confirm=confirmed, + timeout=timeout, + full=full, + force_sync=force_sync, + sync=sync) self.queue_message("log", "Configuration committed.") except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 06768bdd..8e999165 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1165,7 +1165,9 @@ def open_configuration(self, mode, ignore_warning=None, ephemeral_instance=None) if mode not in CONFIG_MODE_CHOICES: self.fail_json(msg='Invalid configuration mode: %s' % (mode)) if mode != 'ephemeral' and ephemeral_instance is not None: - self.fail_json(msg='configuration mode ephemeral is required') + self.fail_json(msg='Ephemeral instance is specified while the mode ' + 'is not ephemeral.Specify the mode as ephemeral or ' + 'do not specify the instance.') if self.dev is None: self.open() config = jnpr.junos.utils.config.Config(self.dev, mode=mode) @@ -1542,7 +1544,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, - An error returned from committing the configuration. """ if self.conn_type != "local": - self._pyez_conn.commit_configuration(ignore_warning, comment, timeout, confirmed) + self._pyez_conn.commit_configuration(ignore_warning, comment, timeout, confirmed, full, sync, force_sync) return if self.dev is None or self.config is None: From 9e39e0023f25ffa4e4d430d5905e60299018ad9a Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Thu, 25 Jan 2024 20:30:08 +0530 Subject: [PATCH 386/426] Ansible collection v1.0.3 release (#640) --- ansible_collections/juniper/device/galaxy.yml | 2 +- ansible_collections/juniper/device/version.py | 4 ++-- version.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index d1d6f7d5..2fb20eba 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -9,7 +9,7 @@ namespace: juniper name: device # The version of the collection. Must be compatible with semantic versioning -version: 1.0.1 +version: 1.0.3 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 20a6d52f..57525f77 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.2-collections" -DATE = "2022-Nov-16" +VERSION = "v1.0.3-collections" +DATE = "2024-Jan-25" diff --git a/version.py b/version.py index 20a6d52f..57525f77 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.2-collections" -DATE = "2022-Nov-16" +VERSION = "v1.0.3-collections" +DATE = "2024-Jan-25" From 572d81bd2e74ffea501888433a277c7db133a135 Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Sat, 27 Apr 2024 11:33:36 +0530 Subject: [PATCH 387/426] Updated ansible rpc testcase playbook (#651) --- tests/pb.juniper_junos_rpc.yml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/pb.juniper_junos_rpc.yml b/tests/pb.juniper_junos_rpc.yml index 2a43affc..e21d1284 100644 --- a/tests/pb.juniper_junos_rpc.yml +++ b/tests/pb.juniper_junos_rpc.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.rpc module hosts: all - connection: local gather_facts: no collections: - juniper.device @@ -131,12 +130,12 @@ - name: Check get-interface-information.xml exists stat: - path: "out/test_get-interface-information.xml" + path: "out/{{ inventory_hostname }}_get-interface-information.xml" register: stat_result_1 - name: Check get-software-information.xml exists stat: - path: "out/test_get-software-information.xml" + path: "out/{{ inventory_hostname }}_get-software-information.xml" register: stat_result_2 - name: Check TEST 6 @@ -180,25 +179,19 @@ - name: Check TEST 8 assert: that: - - '"Unable to execute the RPC" in test8.msg' + - '"Unable to execute the RPC" or "MODULE FAILURE" in test8.msg' tags: [ test8 ] ################# - name: "Check configuration for errors" rpc: rpcs: - - "open-configuration" - "load-configuration" - - "close-configuration" attrs: - - {} - action: 'set' format: 'text' - - {} kwargs: - - private: true - configuration_set: 'set system syslog file test1 any any' - - {} register: test9 tags: [ test9 ] @@ -210,9 +203,7 @@ - name: Check TEST 9 assert: that: - - test9.results[0].msg == "The RPC executed successfully." - - test9.results[1].msg == "The RPC executed successfully." - - test9.results[2].msg == "The RPC executed successfully." + - test9.msg == "The RPC executed successfully." tags: [ test9 ] ################# @@ -238,6 +229,7 @@ test10.results[0].msg == "The RPC executed successfully." tags: [ test10 ] +##################### - name: Check rollback info rpc: rpc: get-rollback-information @@ -253,6 +245,7 @@ test11.msg == "The RPC executed successfully." tags: [ test11 ] +#################### - name: Check rollback info with boolean values rpc: rpc: get-rollback-information From a0c04b5306f25eef693937d04a2870d52993c555 Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Sat, 27 Apr 2024 11:35:12 +0530 Subject: [PATCH 388/426] Fixed Ansible Functional testcases (#645) * Fixed Ansible Functional testcases * Update inventory * Update pb.juniper_junos_config.yml * Update pyez.py * Update inventory * Update inventory --- .../juniper/device/plugins/connection/pyez.py | 6 +- tests/inventory | 7 ++ tests/pb.juniper_junos_command.yml | 5 +- tests/pb.juniper_junos_config.yml | 70 +++++++------------ 4 files changed, 39 insertions(+), 49 deletions(-) create mode 100644 tests/inventory diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 47a6d3df..e9d30a8e 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -687,10 +687,10 @@ def rollback_configuration(self, id): raise AnsibleError('Unable to load the rescue configuraton: ' '%s' % (str(ex))) elif id >= 0 and id <= 49: - self.queue_message("log", "Loading rollback %d configuration.", id) + self.queue_message("log", "Loading rollback {} configuration.".format(id)) try: self.config.rollback(rb_id=id) - self.queue_message("log", "Rollback %d configuration loaded.", id) + self.queue_message("log", "Rollback {} configuration loaded.".format(id)) except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: raise AnsibleError('Unable to load the rollback %d ' @@ -839,7 +839,7 @@ def software_api(self, install_params): install_params.get('package') or install_params.get('pkg_set'), msg_ret) - self.queue_message("log", "%s" % msg) + self.queue_message("log",str(msg)) return msg except (self.pyez_exception.ConnectError, self.pyez_exception.RpcError) as ex: diff --git a/tests/inventory b/tests/inventory new file mode 100644 index 00000000..eb53d4ed --- /dev/null +++ b/tests/inventory @@ -0,0 +1,7 @@ +[junos] +local_connection_testcases ansible_host=x.x.x.x ansible_user=xxxx ansible_pass=xxxx ansible_port=22 ansible_connection=local ansible_command_timeout=300 + +pyez_connection_testcases ansible_host=x.x.x.x ansible_user=xxx ansible_ssh_pass=xxxx ansible_port=22 ansible_connection=juniper.device.pyez ansible_command_timeout=300 + +[all:vars] +ansible_python_interpreter= diff --git a/tests/pb.juniper_junos_command.yml b/tests/pb.juniper_junos_command.yml index 98e62d64..cec31b57 100644 --- a/tests/pb.juniper_junos_command.yml +++ b/tests/pb.juniper_junos_command.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.command module hosts: all - connection: local gather_facts: no collections: - juniper.device @@ -111,7 +110,7 @@ command: command: - "show route" - - "show lldp neighbors" + - "show version" dest: "./out/{{ inventory_hostname }}.commands.output" register: test7 @@ -120,7 +119,7 @@ that: - test7.results[0].command == "show route" - test7.results[0].msg == "The command executed successfully." - - test7.results[1].command == "show lldp neighbors" + - test7.results[1].command == "show version" - test7.results[1].msg == "The command executed successfully." - name: TEST 8 - Multiple commands, save outputs, but don't return them diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 6baf119f..1179847b 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -1,12 +1,11 @@ --- - name: Test juniper.device.config module hosts: all - connection: local gather_facts: no collections: - juniper.device tasks: -################# +################ - name: Retrieve the committed configuration config: retrieve: 'committed' @@ -21,7 +20,7 @@ assert: that: - test1.config -################# +################ - name: Append .foo to the hostname using private config mode. config: config_mode: 'private' @@ -37,11 +36,11 @@ assert: that: - test2.diff_lines -################# +################ - name: Rollback to the previous config. config: config_mode: 'private' - rollback: "1" + rollback: 1 register: test3 ignore_errors: True tags: [ test3 ] @@ -51,46 +50,31 @@ that: - test3.diff_lines ################# - - name: Creates directory + - name: Save rescue configuration + command: + commands: "request system configuration rescue save" + + + - name: Configure syslog configuration + config: + load: 'merge' + lines: + - "set system syslog file TEST any any" + comment: "Configured system services" + + - name: Rollback to the rescue config. + config: + rollback: 'rescue' + register: test4 + - name: Check TEST 4 + assert: + that: + - test4.diff_lines + - name: Clean up TEST 4 file: path: out - state: directory - -# - name: Configure LLDP -# juniper_junos_config: -# load: 'merge' -# lines: -# - "set protocols lldp advertisement-interval 30" -# - "set protocols lldp transmit-delay 2" -# - "set protocols lldp hold-multiplier 4" -# - "set protocols lldp ptopo-configuration-trap-interval 30" -# - "set protocols lldp ptopo-configuration-maximum-hold-time 300" -# - "set protocols lldp lldp-configuration-notification-interval 30" -# - "set protocols lldp interface all disable" -# - "set protocols lldp interface ge-1/1/1" -# format: 'set' -# comment: 'Start LLDP with given options' -# dest_dir: './out' -# register: test4 -# ignore_errors: True -# tags: [ test4 ] -# -# - name: Rollback to the rescue config. -# juniper_junos_config: -# rollback: 'rescue' -# -# - name: Check TEST 4 -# assert: -# that: -# - stat_result_1.stat.exists == True -# - test4.diff_lines -# - "'+ interface ge-1/1/1;' in test4.diff_lines" -# -# - name: Clean up TEST 4 -# file: -# path: out -# state: absent -################ + state: absent +############### - name: Configure system services. config: config_mode: 'private' From a1036e6987eaf2b0e0abcba4e84a91a49735fb93 Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Mon, 29 Apr 2024 09:51:30 +0530 Subject: [PATCH 389/426] Fixed functional testcases for ping and pmutd playbook (#646) * Fixed functional testcases for ping and pmutd playbook * Update pyez.py --- ansible_collections/juniper/device/plugins/connection/pyez.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index e9d30a8e..7f567277 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -484,7 +484,7 @@ def get_facts(self): """ return dict(self.dev.facts) - def ping_device(self, normalize=True, **params): + def ping_device(self, normalize, **params): """Ping the device. Args: @@ -497,7 +497,7 @@ def ping_device(self, normalize=True, **params): Fails: - If the ping RPC produces an exception. """ - resp = self.dev.rpc.ping(normalize, **params) + resp = self.dev.rpc.ping(normalize=normalize, **params) rpc_str = etree.tostring(resp) return rpc_str From e28c8e4eb6a5493a0ad5b42311193365ee0d4a13 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 29 Apr 2024 10:15:39 +0530 Subject: [PATCH 390/426] Fix for pb.juniper_junos_jsnapy.yml (#650) * Fix for pb.juniper_junos_jsnapy.yml * Fix for pb.juniper_junos_jsnapy.yml --- .../juniper/device/plugins/connection/pyez.py | 2 +- .../juniper/device/plugins/modules/jsnapy.py | 68 +++++++++++++++---- tests/pb.juniper_junos_jsnapy.yml | 29 +++++++- 3 files changed, 82 insertions(+), 17 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 7f567277..68cc7a69 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -541,7 +541,7 @@ def invoke_jsnapy(self, data, action): elif action == 'snapcheck': responses = jsa.snapcheck(data=data, dev=self.dev, - file_name='PRE') + pre_file='PRE') elif action == 'snap_pre': responses = jsa.snap(data=data, dev=self.dev, diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 28f94e5b..1531d0c3 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -285,22 +285,38 @@ def main(): jsa = junos_module.jsnapy.SnapAdmin() junos_module.logger.debug('Executing %s action.', action) if action == 'check': - responses = jsa.check(data=data, - dev=junos_module.dev, - pre_file='PRE', - post_file='POST') - elif action == 'snapcheck': - responses = jsa.snapcheck(data=data, + if junos_module.conn_type != "local": + responses = junos_module._pyez_conn.invoke_jsnapy(data=data, + action='check') + else: + responses = jsa.check(data=data, dev=junos_module.dev, - pre_file='PRE') + pre_file='PRE', + post_file='POST') + elif action == 'snapcheck': + if junos_module.conn_type != "local": + responses = junos_module._pyez_conn.invoke_jsnapy(data=data, + action='snapcheck') + else: + responses = jsa.snapcheck(data=data, + dev=junos_module.dev, + pre_file='PRE') elif action == 'snap_pre': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='PRE') + if junos_module.conn_type != "local": + responses = junos_module._pyez_conn.invoke_jsnapy(data=data, + action='snap_pre') + else: + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='PRE') elif action == 'snap_post': - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='POST') + if junos_module.conn_type != "local": + responses = junos_module._pyez_conn.invoke_jsnapy(data=data, + action='snap_post') + else: + responses = jsa.snap(data=data, + dev=junos_module.dev, + file_name='POST') else: junos_module.fail_json(msg="Unexpected action: %s." % (action)) junos_module.logger.debug('The %s action executed successfully.', @@ -340,6 +356,32 @@ def main(): results['total_failed']) elif action in ('snap_pre', 'snap_post'): results['msg'] = "The %s action successfully executed." % (action) + elif isinstance(responses, dict) and len(responses) >= 1: + if action in ('snapcheck', 'check'): + results['device'] = responses["device"] + results['router'] = responses["router"] + results['final_result'] = responses["final_result"] + results['total_passed'] = responses["total_passed"] + results['total_failed'] = responses["total_failed"] + results['test_results'] = responses["test_results"] + total_tests = int(responses["total_passed"]) + int(responses["total_failed"]) + results['total_tests'] = total_tests + pass_percentage = 0 + if total_tests > 0: + pass_percentage = ((int(responses["total_passed"]) * 100) // + total_tests) + results['passPercentage'] = pass_percentage + results['pass_percentage'] = pass_percentage + if results['final_result'] == 'Failed': + results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + else: + results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ + (results['total_passed'], + results['total_failed']) + elif action in ('snap_pre', 'snap_post'): + results['msg'] = "The %s action successfully executed." % (action) else: junos_module.fail_json(msg="Unexpected JSNAPy responses. Type: %s." "Responses: %s" % diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index 08f10004..03601e26 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.jsnapy module hosts: all - connection: local gather_facts: no collections: - juniper.device @@ -127,13 +126,30 @@ - test6.passPercentage == 100 tags: [ test6 ] +################################################## +#### TEST 7 PREREQUSITE ## +################################################## + - name: "PRE-TEST 7 - Add loopback address" + config: + load: 'merge' + lines: + - "set routing-instances LO10 interface lo0.10" + - "set routing-instances LO10 instance-type virtual-router" + - "set interfaces lo0 unit 10 family inet" + register: test7_0 + ignore_errors: True + tags: [ test7 ] + ################################################## #### TEST 7 ## ################################################## - name: "PRE-TEST 7 - Add loopback address" config: load: 'merge' - file: junos_jsnapy/add_loopback.set + lines: + - "set routing-instances LO10 interface lo0.10" + - "set routing-instances LO10 instance-type virtual-router" + - "set interfaces lo0 unit 10 family inet" register: test7_1 ignore_errors: True tags: [ test7 ] @@ -163,12 +179,19 @@ - name: "TEST 7 - Cleanup" config: - file: junos_jsnapy/delete_loopback.set load: 'merge' + lines: + - "delete routing-instances LO10" + - "delete interfaces lo0 unit 10" register: test7_3 ignore_errors: True tags: [ test7 ] + - name: Sleep for 15 seconds for loopback to come up + ansible.builtin.wait_for: + timeout: 15 + delegate_to: localhost + - name: Check TEST 7 assert: that: From da7a2fbc105c046f0db0a033b7528810f56dab81 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:07:58 +0530 Subject: [PATCH 391/426] Fix for play pb.juniper_junos_software_member.yml (#649) * Fix for play pb.juniper_junos_software_member.yml * Fix for play pb.juniper_junos_software_member.yml * Fix for play pb.juniper_junos_software_member.yml * Fix for play pb.juniper_junos_software_member.yml * Fix for play pb.juniper_junos_software_member.yml --- .../juniper/device/plugins/connection/pyez.py | 4 +-- .../device/plugins/modules/software.py | 30 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 68cc7a69..4dac432a 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -780,7 +780,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, raise AnsibleError('Failure committing the configuraton: %s' % (str(ex))) - def system_api(self, action, in_min, at, all_re, vmhost, other_re, media, member_id): + def system_api(self, action, in_min, at, all_re, vmhost, other_re, media, member_id=None): """Triggers the system calls like reboot, shutdown, halt and zeroize to device. """ msg = None @@ -845,7 +845,7 @@ def software_api(self, install_params): self.pyez_exception.RpcError) as ex: raise AnsibleError('Installation failed. Error: %s' % str(ex)) - def reboot_api(self, all_re, vmhost, member_id): + def reboot_api(self, all_re, vmhost, member_id=None): """reboots the device. """ msg = None diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 54a5b814..742adf38 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -726,11 +726,23 @@ def main(): if reboot is True: junos_module.logger.debug('Initiating reboot.') if junos_module.conn_type != "local": - if member_id is not None: - for m_id in member_id: - results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost'), member_id=m_id) - else: - results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost')) + try: + #Handling reboot of specific VC members + if member_id is not None: + results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost'), member_id=member_id) + else: + results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost')) + except Exception as err: # pylint: disable=broad-except + if "ConnectionError" in str(type(err)): + # If Exception is ConnectionError, it is excpected + # Device reboot inititated succesfully + junos_module.logger.debug("Reboot RPC executed.") + results['msg'] += ' Reboot succeeded.' + else: + # If exception is not ConnectionError + # we will raise the exception + raise + junos_module.logger.debug("Reboot RPC successfully initiated.") else: try: # Try to deal with the fact that we might not get the closing @@ -744,8 +756,12 @@ def main(): junos_module.dev.timeout = 5 try: if member_id is not None: - for m_id in member_id: - got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost'), member_id=m_id) + got = junos_module.sw.reboot(0, + None, + all_re, + None, + install_params.get('vmhost'), + member_id=member_id) else: got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) junos_module.dev.timeout = restore_timeout From 7df7dcf1d79937ca1ef2a6281a8e69723d144e4e Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:08:19 +0530 Subject: [PATCH 392/426] Fix for Object of type function is not JSON serializable (#647) * Fix for JSON encoder error Object of type function is not JSON serializable * Fix for JSON encoder error Object of type function is not JSON serializable --- .../juniper/device/plugins/modules/software.py | 5 ++++- tests/ansible.cfg | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 742adf38..4160d2c1 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -689,7 +689,10 @@ def main(): install_params['package'] = remote_filename if remote_dir is not None: install_params['remote_path'] = remote_dir - install_params['progress'] = define_progress_callback(junos_module) + if junos_module.conn_type != "local": + install_params['progress'] = True + else: + install_params['progress'] = define_progress_callback(junos_module) install_params['cleanfs'] = cleanfs install_params['no_copy'] = no_copy install_params['timeout'] = install_timeout diff --git a/tests/ansible.cfg b/tests/ansible.cfg index ef7c4505..56cad009 100644 --- a/tests/ansible.cfg +++ b/tests/ansible.cfg @@ -3,3 +3,6 @@ hash_behaviour=merge roles_path = ~/.ansible/roles +[persistent_connection] +command_timeout = 300 + From de15a23a319cd501bed4f10468e2b6ae994b87c7 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:20:29 +0530 Subject: [PATCH 393/426] Fixed pb.juniper_junos_srx_cluster.yml (#652) * Fixed pb.juniper_junos_srx_cluster.yml * Fixed pb.juniper_junos_srx_cluster.yml --- .../device/plugins/modules/srx_cluster.py | 19 +++++++++++--- tests/pb.juniper_junos_facts.yml | 1 - tests/pb.juniper_junos_persistent_conn.yml | 25 ------------------- tests/pb.juniper_junos_ping.yml | 1 - tests/pb.juniper_junos_pmtud.yml | 1 - tests/pb.juniper_junos_software.yml | 1 - tests/pb.juniper_junos_software_member.yml | 1 - tests/pb.juniper_junos_srx_cluster.yml | 1 - tests/pb.juniper_junos_system.yml | 1 - tests/pb.juniper_junos_table.yml | 1 - 10 files changed, 15 insertions(+), 37 deletions(-) delete mode 100644 tests/pb.juniper_junos_persistent_conn.yml diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index f62ee961..6a912005 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -196,9 +196,14 @@ def main(): 'reboot': False, 'failed': True} + if junos_module.conn_type == "local": + facts = dict(junos_module.dev.facts) + else: + facts = junos_module.get_facts() + # facts checking has been done as part of persitent connection itself. junos_module.logger.debug("Check current SRX cluster operational state.") - current_cluster_state = junos_module.dev.facts['srx_cluster'] - current_cluster_id = junos_module.dev.facts['srx_cluster_id'] + current_cluster_state = facts['srx_cluster'] + current_cluster_id = facts['srx_cluster_id'] if current_cluster_id is not None: current_cluster_id = int(current_cluster_id) if junos_module.conn_type == "local": @@ -275,8 +280,14 @@ def main(): reboot=True, normalize=True ) else: - resp = junos_module._pyez_conn.set_chassis_cluster_disable() - + try: + resp = junos_module._pyez_conn.set_chassis_cluster_disable() + except Exception as err: + # Reboot initiated + # We got Exception ConnectionError + # so handling the exception + resp = None + output = None if resp is not None: output = resp.getparent().findtext('.//output') if output is None: diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 1e9e09c4..83998f03 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.facts module hosts: all - connection: local gather_facts: no collections: - juniper.device diff --git a/tests/pb.juniper_junos_persistent_conn.yml b/tests/pb.juniper_junos_persistent_conn.yml deleted file mode 100644 index 4494a3cf..00000000 --- a/tests/pb.juniper_junos_persistent_conn.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -- name: Test juniper.device.rpc module - hosts: all - connection: juniper.device.pyez - gather_facts: no - collections: - - juniper.device - - tasks: -################# - - name: Get Device Configuration with filter and attr - rpc: - rpc: get-config - format: xml - filter: re0 - attr: name=re0 - register: test7 - ignore_errors: True - tags: [ test7 ] - - - name: Check TEST 7 - assert: - that: - - test7.msg == "The \"get-config\" RPC executed successfully." - tags: [ test7 ] diff --git a/tests/pb.juniper_junos_ping.yml b/tests/pb.juniper_junos_ping.yml index 48269376..c565d153 100644 --- a/tests/pb.juniper_junos_ping.yml +++ b/tests/pb.juniper_junos_ping.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.ping module hosts: all - connection: local gather_facts: no collections: - juniper.device diff --git a/tests/pb.juniper_junos_pmtud.yml b/tests/pb.juniper_junos_pmtud.yml index da6a19fc..cc08bcf0 100644 --- a/tests/pb.juniper_junos_pmtud.yml +++ b/tests/pb.juniper_junos_pmtud.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.pmtud module hosts: all - connection: local gather_facts: no collections: - juniper.device diff --git a/tests/pb.juniper_junos_software.yml b/tests/pb.juniper_junos_software.yml index 2f8170ce..1c7a7366 100644 --- a/tests/pb.juniper_junos_software.yml +++ b/tests/pb.juniper_junos_software.yml @@ -3,7 +3,6 @@ hosts: all collections: - juniper.device - connection: local gather_facts: no vars: wait_time: 3600 diff --git a/tests/pb.juniper_junos_software_member.yml b/tests/pb.juniper_junos_software_member.yml index 56c81a09..3b139577 100644 --- a/tests/pb.juniper_junos_software_member.yml +++ b/tests/pb.juniper_junos_software_member.yml @@ -3,7 +3,6 @@ hosts: all collections: - juniper.device - connection: local gather_facts: no vars: wait_time: 3600 diff --git a/tests/pb.juniper_junos_srx_cluster.yml b/tests/pb.juniper_junos_srx_cluster.yml index 7cbbcb80..6420e782 100644 --- a/tests/pb.juniper_junos_srx_cluster.yml +++ b/tests/pb.juniper_junos_srx_cluster.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.srx_cluster module hosts: all - connection: local gather_facts: no collections: - juniper.device diff --git a/tests/pb.juniper_junos_system.yml b/tests/pb.juniper_junos_system.yml index f2428221..bc13516a 100644 --- a/tests/pb.juniper_junos_system.yml +++ b/tests/pb.juniper_junos_system.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.system module hosts: all - connection: local gather_facts: no collections: - juniper.device diff --git a/tests/pb.juniper_junos_table.yml b/tests/pb.juniper_junos_table.yml index dca3716d..b729b104 100644 --- a/tests/pb.juniper_junos_table.yml +++ b/tests/pb.juniper_junos_table.yml @@ -1,7 +1,6 @@ --- - name: Test juniper.device.table PyEZ table/view module. hosts: all - connection: local gather_facts: no collections: - juniper.device From cad7da7c4037906a5c187b795e8a28ee9dc688a8 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:58:08 +0530 Subject: [PATCH 394/426] Fix for ReadMe (#653) * Fix for ReadMe * Fix for ReadMe * Fix for ReadMe * Fix for ReadMe --- README.md | 61 +++++++++++++++++++------------------------------ tests/README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 37 deletions(-) create mode 100644 tests/README.md diff --git a/README.md b/README.md index f819cbaf..885ec4dc 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,30 @@ [![Documentation Status](https://readthedocs.org/projects/junos-ansible-modules/badge/?version=stable)](https://junos-ansible-modules.readthedocs.io/en/2.3.0/) -NOTE : The collection for Ansible is under development and changes are expected in the namespace/module implementation. -One may use it but it is recommended to currently use juniper.junos roles for professional implementation. -Refer - https://github.com/Juniper/ansible-junos-stdlib/tree/roles for more info. - # Juniper Ansible collection for Junos -The repo is under active development. If you take a clone, you are getting the latest, and perhaps not entirely stable code. - ## About -Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS). +Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS and Junos Evolved). This collection is hosted on the Ansible Galaxy website under the collection -[juniper.device](https://galaxy.ansible.com/Juniper/junos/). The juniper.device collection includes -a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. -These tasks include: -installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, +[juniper.device](https://galaxy.ansible.com/ui/repo/published/juniper/device/). + +The `juniper.device` collection includes a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. +These tasks include: installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to the [INSTALLATION](#installation) section for instructions on installing this collection. -## juniper.junos roles by Juniper Networks - -Ansible galaxy is upgrading to collections and plans to deprecate roles in future. The master branch will now have -juniper.device collection support. Juniper.junos roles have been moved to roles branch. -For more information for roles, check: -https://github.com/Juniper/ansible-junos-stdlib/tree/roles - ## Two Sets of Ansible Modules for Junos devices Since Ansible version >= 2.1, Ansible also natively includes -[core modules for Junos](http://docs.ansible.com/ansible/list_of_network_modules.html#junos). The Junos modules included -in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this Juniper.device +[core modules for Junos](https://docs.ansible.com/ansible/latest/collections/junipernetworks/junos/index.html#plugins-in-junipernetworks-junos). The Junos modules included +in Ansible core have names which begin with the prefix `junos_`. The Junos modules included in this `Juniper.device` collection have names starting with module types. These two sets of Junos modules can coexist on the same -Ansible control machine, and an Ansible play may invoke a module from either (or both) sets. Juniper Networks recommends -using the modules in this collection when writing new playbooks that manage Junos devices. +Ansible control machine, and an Ansible playbook may invoke a module from either (or both) sets. Juniper Networks recommends +using the modules in `juniper.device` collection when writing new playbooks that manage Junos devices. ## Overview of Modules -This juniper.device collection includes the following modules: +This `juniper.device` collection includes the following modules: - **command** — Execute one or more CLI commands on a Junos device. - **config** — Manipulate the configuration of a Junos device. @@ -53,11 +40,11 @@ This juniper.device collection includes the following modules: ### PyEZ Version Requirement -For ansible collection juniper.device we will need to install junos-eznc(PyEZ) version 2.6.0 or higher. +For ansible collection `juniper.device` we will need to install [junos-eznc](https://github.com/Juniper/py-junos-eznc) version 2.6.0 or higher. ### Overview of Plugins -In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module `jsnapy`. +In addition to the modules listed above, a callback_plugin `jsnapy` is available for the module [jsnapy](https://github.com/Juniper/jsnapy). The callback_plugin `jsnapy` helps to print on the screen additional information regarding jsnapy failed tests. For each failed test, a log will be printed after the RECAP of the playbook as shown in this example: @@ -75,8 +62,6 @@ For each failed test, a log will be printed after the RECAP of the playbook as s JSNAPy Results for: qfx10002-02 ************************************************ Value of 'peer-state' not 'is-equal' at '//bgp-information/bgp-peer' with {"peer-as": "65200", "peer-state": "Active", "peer-address": "100.0.0.21"} -The `jsnapy` plugin is currently in **Experimental** stage, please provide feedback. - Callback plugins are not activated by default. They must be manually added to the Ansible configuration file under the `[defaults]` section using the variable `callback_whitelist`. Specifically, these lines should be added to the Ansible configuration file in order to allow the jsnapy callback plugin: @@ -88,7 +73,7 @@ should be added to the Ansible configuration file in order to allow the jsnapy c [Official Juniper documentation](http://www.juniper.net/techpubs/en_US/release-independent/junos-ansible/information-products/pathway-pages/index.html) (detailed information, including examples) -[Ansible style documentation](http://junos-ansible-modules.readthedocs.org) +[Ansible style documentation](https://ansible-juniper-collection.readthedocs.io/en/latest/) ## INSTALLATION @@ -108,13 +93,13 @@ In MacOS Mojave and newer (>=10.14), ssh keys created with the system `ssh-keyge ### Ansible Galaxy collection You can use the ansible-galaxy install command to install the latest -version of the juniper.device collection. +version of the `juniper.device` collection. ```bash sudo ansible-galaxy collection install juniper.device ``` -You can also use the ansible-galaxy install command to install the latest development version of the junos role directly from GitHub. +You can also use the ansible-galaxy install command to install the latest development version of the junos collections directly from GitHub. ```bash sudo ansible-galaxy collection install git+https://github.com/Juniper/ansible-junos-stdlib.git#/ansible_collections/juniper/device @@ -167,7 +152,7 @@ You may have noticed that the base command is almost always the same. We can als ### Extending the container with additional packages -It's possible to install additional OS (Alpine) packages, Python packages (via pip), and Ansible roles or collections at container instantiation. This can be done by passing in environment variables or bind mounting files. +It's possible to install additional OS (Alpine) packages, Python packages (via pip), and Ansible collections at container instantiation. This can be done by passing in environment variables or binding mount files. #### OS Packages @@ -188,7 +173,7 @@ As a bind mount. Environment Variable: `$REQ` Bind Mount: `/extras/requirements.txt` -File Format: pip [requirements](https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format) file +File Format: pip [requirements](https://pip.pypa.io/en/stable/reference/requirements-file-format/) file Examples: @@ -256,10 +241,12 @@ This example outlines how to use Ansible to install or upgrade the software imag This modules requires the following to be installed on the Ansible control machine: -- Python >= 3.7 +- Python >= 3.8 - [Ansible](http://www.ansible.com) 2.9 or later - Junos [py-junos-eznc](https://github.com/Juniper/py-junos-eznc) 2.6.0 or later - [jxmlease](https://github.com/Juniper/jxmlease) 1.0.1 or later +- [xmltodict](https://pypi.org/project/xmltodict/) 0.13.0 or later +- [jsnapy](https://github.com/Juniper/jsnapy) 1.3.7 or later ## LICENSE @@ -267,8 +254,8 @@ Apache 2.0 ## SUPPORT -Support for this juniper.device collection is provided by the community and Juniper Networks. If you have an -issue with a module in the juniper.device collection, you may: +Support for this `juniper.device` collection is provided by the community and Juniper Networks. If you have an +issue with a module in the `juniper.device` collection, you may: - Open a [GitHub issue](https://github.com/Juniper/ansible-junos-stdlib/issues). - Post a question on our [Google Group](https://groups.google.com/forum/#!forum/junos-python-ez) @@ -284,8 +271,8 @@ Juniper Networks is actively contributing to and maintaining this repo. Please c [jnpr-community-netdev@juniper.net](jnpr-community-netdev@juniper.net) for any queries. *Contributors:* -[Nitin Kumar](https://github.com/vnitinv), [Rahul Kumar](https://github.com/rahkumar651991), [Stephen Steiner](https://github.com/ntwrkguru) +[Stephen Steiner](https://github.com/ntwrkguru), [Dinesh Babu](https://github.com/dineshbaburam91), [Chidanand Pujar](https://github.com/chidanandpujar) *Former Contributors:* -[Stacy W Smith](https://github.com/stacywsmith), [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr) +[Stacy W Smith](https://github.com/stacywsmith), [Jeremy Schulman](https://github.com/jeremyschulman), [Rick Sherman](https://github.com/shermdog), [Damien Garros](https://github.com/dgarros), [David Gethings](https://github.com/dgjnpr), [Nitin Kumar](https://github.com/vnitinv), [Rahul Kumar](https://github.com/rahkumar651991) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..0bd2424f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,61 @@ +# Functional Tests for Juniper Ansible collection for Junos + +Following are the steps to execute Ansible Functional test playbooks + +## Steps to execute Ansible Functional test playbooks + +1. Git clone the functional tests +``` +git clone https://github.com/Juniper/ansible-junos-stdlib.git +``` +2. Change directory to ansible-junos-stdlib/tests +``` +cd ansible-junos-stdlib/tests/ +``` +3. Update the ansible.cfg under tests directory +``` +[defaults] +hash_behaviour=merge +inventory = inventory +host_key_checking = False +log_path = ./ansible.log + +[persistent_connection] +command_timeout = 300 +``` +4. To execute test playbook with Local connection, you need to update the inventory file for local connection test cases by setting ansible_connection=local +``` +[junos] +local_connection_testcases ansible_host=xx.xx.xx.xx ansible_user=xyz ansible_pass=xyz ansible_port=22 ansible_connection=local ansible_command_timeout=300 + +[all:vars] +ansible_python_interpreter= +``` +5. To execute test playbook with PyEZ persistent connection, you need to update the inventory file for persistent connection test cases by setting ansible_connection=juniper.device.pyez +``` +[junos] +pyez_connection_testcases ansible_host=xx.xx.xx.xx ansible_user=xyz ansible_ssh_pass=xyz ansible_port=22 ansible_connection=juniper.device.pyez ansible_command_timeout=300 + +[all:vars] +ansible_python_interpreter= +``` +6. To execute Functional test playbooks +ansible-playbook +``` +ansible-playbook pb.juniper_junos_system.yml +``` +### NOTE: + +To execute pb.juniper_junos_software_member.yml playbook, you have to use the following devices + +Virtual Chassis - EX-VC, MX-VC + +To execute pb.juniper_junos_srx_cluster.yml playbook, you have to use the following devices + +SRX HA Cluster enabled devices + +### Ansible Fixes : +For the PyEZ persistent connection support, you need to apply the following fix for Ansible issue +``` +https://github.com/Juniper/ansible-junos-stdlib/issues/606 +``` \ No newline at end of file From f40bf37b2a95b9bd65be4f5e789e131360b31b9b Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 30 Apr 2024 17:12:23 +0530 Subject: [PATCH 395/426] Ansible Collection v1.0.4 release update (#654) * Ansible v1.0.4 release update * Ansible v1.0.4 release update --- ansible_collections/juniper/device/version.py | 4 ++-- version.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 57525f77..45259437 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.3-collections" -DATE = "2024-Jan-25" +VERSION = "v1.0.4-collections" +DATE = "2024-Apr-30" diff --git a/version.py b/version.py index 57525f77..45259437 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.3-collections" -DATE = "2024-Jan-25" +VERSION = "v1.0.4-collections" +DATE = "2024-Apr-30" From abd02397c52a7ce42306eb2c2399f56b328a1bbb Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 30 Apr 2024 17:28:04 +0530 Subject: [PATCH 396/426] v1.0.4 release update (#655) --- ansible_collections/juniper/device/galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index 2fb20eba..70050839 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -9,7 +9,7 @@ namespace: juniper name: device # The version of the collection. Must be compatible with semantic versioning -version: 1.0.3 +version: 1.0.4 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md From 88789405912b8158ccef2d47a7e900cdea237233 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:08:38 +0530 Subject: [PATCH 397/426] Fix for readthedoc build failures (#656) --- ansible_collections/juniper/device/docs/.readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/docs/.readthedocs.yaml b/ansible_collections/juniper/device/docs/.readthedocs.yaml index f3aa1f5a..b8c1bd36 100644 --- a/ansible_collections/juniper/device/docs/.readthedocs.yaml +++ b/ansible_collections/juniper/device/docs/.readthedocs.yaml @@ -13,7 +13,7 @@ build: # Build documentation in the docs/ directory with Sphinx sphinx: - configuration: docs/conf.py + configuration: ansible_collections/juniper/device/docs/conf.py # We recommend specifying your dependencies to enable reproducible builds: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html From 4a90660e11cf92af7eb7fc9f196cf0a8495a1e64 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:17:57 +0530 Subject: [PATCH 398/426] Doc build fix2 (#658) * Fix for readthedoc build failures * Fix for readthedoc build failures --- ansible_collections/juniper/device/docs/.readthedocs.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible_collections/juniper/device/docs/.readthedocs.yaml b/ansible_collections/juniper/device/docs/.readthedocs.yaml index b8c1bd36..0d1af2f2 100644 --- a/ansible_collections/juniper/device/docs/.readthedocs.yaml +++ b/ansible_collections/juniper/device/docs/.readthedocs.yaml @@ -17,6 +17,6 @@ sphinx: # We recommend specifying your dependencies to enable reproducible builds: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -# python: -# install: -# - requirements: docs/requirements.txt +python: + install: + - requirements: ansible_collections/juniper/device/docs/docreq.txt From 485b3c366d6778189dfcc8bbfdf05d2ffd7fcf05 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:25:35 +0530 Subject: [PATCH 399/426] Doc build fix3 (#659) * Fix for readthedoc build failures * Fix for readthedoc build failures * Fix for readthedoc build failures --- ansible_collections/juniper/device/docs/.readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/docs/.readthedocs.yaml b/ansible_collections/juniper/device/docs/.readthedocs.yaml index 0d1af2f2..ce9e7926 100644 --- a/ansible_collections/juniper/device/docs/.readthedocs.yaml +++ b/ansible_collections/juniper/device/docs/.readthedocs.yaml @@ -9,7 +9,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.8" # Build documentation in the docs/ directory with Sphinx sphinx: From 60731061366ef0c1c7bd22980040356ae18f11d6 Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Wed, 8 May 2024 10:13:39 +0530 Subject: [PATCH 400/426] Updated commit_configuration API parameter for PyEZ connection. (#661) --- .../device/plugins/module_utils/juniper_junos_common.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 8e999165..1c1cd92b 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1544,7 +1544,13 @@ def commit_configuration(self, ignore_warning=None, comment=None, - An error returned from committing the configuration. """ if self.conn_type != "local": - self._pyez_conn.commit_configuration(ignore_warning, comment, timeout, confirmed, full, sync, force_sync) + self._pyez_conn.commit_configuration(ignore_warning=ignore_warning, + comment=comment, + confirm=confirmed, + timeout=timeout, + full=full, + force_sync=force_sync, + sync=sync) return if self.dev is None or self.config is None: From 0b6195efa4b2df74c165fd3157d842ff1c9b8243 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 14 May 2024 09:38:53 +0530 Subject: [PATCH 401/426] handling ansible.module_utils.connection.ConnectionError (#662) --- .../juniper/device/plugins/modules/software.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 4160d2c1..b9c7fc9a 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -708,7 +708,19 @@ def main(): junos_module.logger.debug("Install parameters are: %s", str(install_params)) if junos_module.conn_type != "local": - results['msg'] = junos_module._pyez_conn.software_api(install_params) + try: + results['msg'] = junos_module._pyez_conn.software_api(install_params) + except Exception as err: # pylint: disable=broad-except + if "ConnectionError" in str(type(err)): + # If Exception is ConnectionError, it is excpected + # Device installation inititated succesfully + junos_module.logger.debug("Package successfully installed.") + results['msg'] += 'Package successfully installed.' + else: + # If exception is not ConnectionError + # we will raise the exception + raise + junos_module.logger.debug("Package successfully installed") else: try: junos_module.add_sw() From 9fa948ce0158bb7fe757407da39e9c2453024aca Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 14 May 2024 15:27:39 +0530 Subject: [PATCH 402/426] Fix for issue #580 (#663) --- .../device/plugins/module_utils/juniper_junos_common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 1c1cd92b..fac1a1da 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1497,7 +1497,8 @@ def load_configuration(self, config = '\n'.join(map(lambda line: line.rstrip('\n'), lines)) self.logger.debug("Loading the supplied configuration.") if src is not None: - load_args['path'] = src + abs_path_src = os.path.abspath(src) # For PyEZ persistent + load_args['path'] = abs_path_src self.logger.debug("Loading the configuration from: %s.", src) if template is not None: load_args['template_path'] = template From 3670c215bc28a1be1f9e7b2e7add71ec8d9c81ce Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Thu, 16 May 2024 10:04:05 +0530 Subject: [PATCH 403/426] Fixed ansible playbook coding style issue using ansible-lint tool (#664) * Fixed ansible playbook code style issue using ansible-lint tool * Fixed typo error in argument --- README.md | 30 +-- Samples/inventory | 5 - Samples/persistent_conn.yml | 64 ------ Samples/persistent_conn_with_vars.yml | 38 ---- Samples/sample_command.yaml | 75 ------- Samples/sample_config.yaml | 186 ------------------ Samples/sample_facts.yaml | 26 --- Samples/sample_rpc.yaml | 74 ------- Samples/sample_software.yaml | 31 --- Samples/sample_system.yaml | 47 ----- Samples/test_jsnapy.yaml | 12 -- .../module_utils/juniper_junos_common.py | 5 +- .../juniper/device/plugins/modules/command.py | 18 +- .../juniper/device/plugins/modules/config.py | 58 +++--- .../juniper/device/plugins/modules/facts.py | 10 +- .../juniper/device/plugins/modules/jsnapy.py | 26 ++- .../juniper/device/plugins/modules/ping.py | 38 ++-- .../juniper/device/plugins/modules/pmtud.py | 26 ++- .../juniper/device/plugins/modules/rpc.py | 22 +-- .../device/plugins/modules/software.py | 10 +- .../device/plugins/modules/srx_cluster.py | 10 +- .../juniper/device/plugins/modules/system.py | 20 +- .../juniper/device/plugins/modules/table.py | 16 +- tests/pb.juniper_junos_command.yml | 53 +++-- tests/pb.juniper_junos_config.yml | 66 +++---- tests/pb.juniper_junos_facts.yml | 15 +- tests/pb.juniper_junos_jsnapy.yml | 109 +++++----- tests/pb.juniper_junos_ping.yml | 36 ++-- tests/pb.juniper_junos_pmtud.yml | 20 +- tests/pb.juniper_junos_rpc.yml | 143 +++++++------- tests/pb.juniper_junos_software.yml | 38 ++-- tests/pb.juniper_junos_software_member.yml | 36 ++-- tests/pb.juniper_junos_srx_cluster.yml | 34 ++-- tests/pb.juniper_junos_system.yml | 20 +- tests/pb.juniper_junos_table.yml | 26 ++- 35 files changed, 420 insertions(+), 1023 deletions(-) delete mode 100644 Samples/inventory delete mode 100644 Samples/persistent_conn.yml delete mode 100644 Samples/persistent_conn_with_vars.yml delete mode 100644 Samples/sample_command.yaml delete mode 100644 Samples/sample_config.yaml delete mode 100644 Samples/sample_facts.yaml delete mode 100644 Samples/sample_rpc.yaml delete mode 100644 Samples/sample_software.yaml delete mode 100644 Samples/sample_system.yaml delete mode 100644 Samples/test_jsnapy.yaml diff --git a/README.md b/README.md index 885ec4dc..84bc4519 100644 --- a/README.md +++ b/README.md @@ -207,33 +207,37 @@ This example outlines how to use Ansible to install or upgrade the software imag --- - name: Install Junos OS hosts: dc1 - collections: - - juniper.device connection: local - gather_facts: no + gather_facts: false vars: wait_time: 3600 pkg_dir: /var/tmp/junos-install - OS_version: 14.1R1.10 - OS_package: jinstall-14.1R1.10-domestic-signed.tgz + os_version: 14.1R1.10 + os_package: jinstall-14.1R1.10-domestic-signed.tgz log_dir: /var/log/ansible tasks: - name: Checking NETCONF connectivity - wait_for: host={{ inventory_hostname }} port=830 timeout=5 + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: 5 - name: Install Junos OS package - software: - reboot: yes - version: "{{ OS_version }}" - package: "{{ pkg_dir }}/{{ OS_package }}" + juniper.device.software: + reboot: true + version: "{{ os_version }}" + package: "{{ pkg_dir }}/{{ os_package }}" logfile: "{{ log_dir }}/software.log" register: sw notify: - - wait_reboot + - Wait_reboot handlers: - - name: wait_reboot - wait_for: host={{ inventory_hostname }} port=830 timeout={{ wait_time }} + - name: Wait_reboot + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: "{{ wait_time }}" when: not sw.check_mode ``` diff --git a/Samples/inventory b/Samples/inventory deleted file mode 100644 index 91232e2e..00000000 --- a/Samples/inventory +++ /dev/null @@ -1,5 +0,0 @@ -[junos] -vm ansible_host=10.x.x.x ansible_user=user ansible_password=user123 ansible_port=22 - -[all:vars] -ansible_python_interpreter=/usr/local/bin/python3.8 diff --git a/Samples/persistent_conn.yml b/Samples/persistent_conn.yml deleted file mode 100644 index 23c7c935..00000000 --- a/Samples/persistent_conn.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -- name: 'Explicit host argument' - hosts: junos -# connection: local - connection: juniper.device.pyez - - gather_facts: no - collections: - - juniper.device - -# The example here uses inventory file for user-authentication. -# the command to be used will look something like below -# ansible-playbook persistent_conn.yml -i inventory - - tasks: - # Command module executed - - name: show version with command - command: - commands: - - "show version" - register: junos_result - - # Jsnapy module executed - - name: 'Performa a snap_check of storage' - jsnapy: - action: "check" - test_files: "test_jsnapy.yaml" - register: response - - - name: "Print the response" - debug: - var: response - - # Facts module executed - - name: "Get facts" - facts: - register: response - - # Rpc module executed - - name: "Test RPC" - rpc: - rpcs: - - "get-config" - - "get-software-information" - - # Config module executed - - name: SET JUNOS CONFIG - config: - load: "set" - format: "set" - lines: 'set system login message "This is login message"' - comment: "comment" - ignore_warning: "True" - config_mode: private - register: junos_response - - # System module executed - - name: Reboot switch after upgrade - system: - all_re: false - action: "reboot" - register: response - -# close connection diff --git a/Samples/persistent_conn_with_vars.yml b/Samples/persistent_conn_with_vars.yml deleted file mode 100644 index bb13d240..00000000 --- a/Samples/persistent_conn_with_vars.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -- name: 'Explicit host argument' - hosts: junos -# connection: local - connection: juniper.device.pyez - - gather_facts: no - collections: - - juniper.device - - vars: - host: 10.x.x.x - user: user - passwd: user123 - timeout: 300 - -# The example here uses vars for user-authentication. - - tasks: - # Command module executed - - name: show version with command - command: - commands: - - "show version" - register: response - - - name: "Print the response" - debug: - var: response - - # Rpc module executed - - name: "Test RPC" - rpc: - rpcs: - - "get-config" - - "get-software-information" - -# close connection diff --git a/Samples/sample_command.yaml b/Samples/sample_command.yaml deleted file mode 100644 index 9a251bf1..00000000 --- a/Samples/sample_command.yaml +++ /dev/null @@ -1,75 +0,0 @@ -- name: 'Explicit host argument' - hosts: junos - connection: local - gather_facts: no - collections: - - juniper.device - -# the user-credentials and other task specific parameter can be specified with vars -# which will be applicable to all the tasks. -# Uncomment the below lines to use them. - -# vars: -# host: 10.x.x.x -# user: user -# passwd: user123 -# timeout: 300 - - tasks: - - name: "Execute single command in text format" - command: - commands: "show configuration system services netconf traceoptions" - format: text - - - name: "Execute command with login credentials" - command: - host: "10.x.x.x" - user: "user" - passwd: "user123" - port: "22" - commands: - - "show system storage" - register: junos_result - - - name: Execute three commands. - command: - commands: - - "show version" - - "show system uptime" - - "show interface terse" - register: response - - - name: "Print the response" - debug: - var: response - - - name: Print the command output of each. - debug: - var: item.stdout - with_items: "{{ response.results }}" - - - name: show route with XML output - show version with JSON output - command: - commands: - - "show route" - - "show version" - formats: - - "xml" - - "json" - - - name: Multiple commands, save outputs, but don't return them - command: - commands: - - "show route" - - "show version" - formats: - - "xml" - dest_dir: "../Output" - return_output: false - - - name: save output to dest - command: - command: - - "show route" - - "show lldp neighbors" - dest: "/tmp/{{ inventory_hostname }}.commands.output" diff --git a/Samples/sample_config.yaml b/Samples/sample_config.yaml deleted file mode 100644 index 479ecbb8..00000000 --- a/Samples/sample_config.yaml +++ /dev/null @@ -1,186 +0,0 @@ -- name: 'Explicit host argument' - hosts: junos - connection: local - gather_facts: no - collections: - - juniper.device - -# the user-credentials and other task specific parameter can be specified with vars -# which will be applicable to all the tasks. -# Uncomment the below lines to use them. - -# vars: -# host: 10.x.x.x -# user: user -# passwd: user123 -# timeout: 300 - - tasks: - - name: Retrieve the committed configuration - config: - retrieve: 'committed' - diff: false - check: false - commit: false - register: response - - - name: Print the lines in the config. - debug: - var: response.config_lines - - - name: Append .foo to the hostname using private config mode. - config: - config_mode: 'private' - load: 'merge' - lines: - - "set system host-name {{ inventory_hostname }}.foo" - register: response - - - name: Print the config changes. - debug: - var: response.diff_lines - - - name: Rollback to the previous config. - config: - config_mode: 'private' - rollback: 1 - register: response - - - name: Print the config changes. - debug: - var: response.diff_lines - - - name: Rollback to the rescue config. - config: - rollback: 'rescue' - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load override from a file. - config: - load: 'override' - src: "{{ inventory_hostname }}.conf" - register: response - - - name: Print the complete response. - debug: - var: response - - - name: Load from a Jinja2 template. - config: - load: 'merge' - format: 'xml' - template: "{{ inventory_hostname }}.j2" - vars: - host: "{{ inventory_hostname }}" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a file on the Junos device. - config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - register: response - - name: Print the complete response. - debug: - var: response - - - name: Load from a file on the Junos device, skip the commit check - config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - check: false - register: response - - name: Print the msg. - debug: - var: response.msg - - - name: Print diff between current and rollback 10. No check. No commit. - config: - rollback: 11 - diff: true - check: false - commit: false - register: response - - - name: Print the msg. - debug: - var: response - - - name: Retrieve [edit system services] of current committed config. - config: - retrieve: 'committed' - filter: 'system/services' - diff: true - check: false - commit: false - register: response - - - name: Print the resulting config lines. - debug: - var: response.config_lines - - - name: Enable NETCONF SSH and traceoptions, save config, and diffs. - config: - load: 'merge' - lines: - - 'set system services netconf ssh' - - 'set system services netconf traceoptions flag all' - - 'set system services netconf traceoptions file netconf.log' - format: 'set' - retrieve: 'candidate' - filter: 'system/services' - comment: 'Enable NETCONF with traceoptions' - dest_dir: './output' - register: response - - - name: Print the complete response - debug: - var: response - - - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit - config: - load: 'merge' - url: "{{ inventory_hostname }}.conf" - confirm: 5 - check_commit_wait: 3 - register: response - - - name: Print the complete response - debug: - var: response - - - name: Confirm the previous commit with a commit check (but no commit) - config: - check: true - diff: false - commit: false - register: response - - - name: Print the complete response - debug: - var: response - - - name: fetch config from the device with filter and login credentials - config: - host: "10.x.x.x" - user: "user" - passwd: "user123" - port: "22" - retrieve: 'committed' - format: xml - commit: no - check: no - diff: no - dest_dir: "/tmp/" - filter: re0 - return_output: True - register: config_output - - - name: "Print the response" - debug: - var: response diff --git a/Samples/sample_facts.yaml b/Samples/sample_facts.yaml deleted file mode 100644 index 2fd366d4..00000000 --- a/Samples/sample_facts.yaml +++ /dev/null @@ -1,26 +0,0 @@ -- name: 'Explicit host argument' - hosts: junos - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: "Get facts" - facts: - register: response - - - name: Facts with login credentials - facts: - host: "10.x.x.x" - user: "user" - passwd: "user123" - port: "22" - - - name: Facts in telnet mode - facts: - host: "10.x.x.x" - user: "user" - passwd: "user123" - port: "23" - mode: "telnet" diff --git a/Samples/sample_rpc.yaml b/Samples/sample_rpc.yaml deleted file mode 100644 index f3656586..00000000 --- a/Samples/sample_rpc.yaml +++ /dev/null @@ -1,74 +0,0 @@ -- name: 'Explicit host argument' - hosts: junos - connection: local - gather_facts: no - collections: - - juniper.device - -# the user-credentials and other task specific parameter can be specified with vars -# which will be applicable to all the tasks. -# Uncomment the below lines to use them. - -# vars: -# host: 10.x.x.x -# user: user -# passwd: user123 -# timeout: 300 - - tasks: - - name: "Execute RPC with filters" - rpc: - rpcs: - - "get-config" - format: xml - filter: re0 - attr: name=re0 - register: test1 - ignore_errors: True - - - name: Check TEST 1 - debug: - var: test1 - - - name: "Execute RPC with host data and store logging" - rpc: - host: "10.x.x.x" - user: "user" - passwd: "user123" - port: "22" - rpcs: - - "get-software-information" - logfile: "/var/tmp/rpc.log" - ignore_warning: true - register: test1 - ignore_errors: True - - - name: "Print results - summary" - debug: - var: test1.stdout_lines - - - name: "Execute multiple RPC" - rpc: - rpcs: - - "get-config" - - "get-software-information" - - - name: Get Device Configuration for vlan - 1 - rpc: - rpc: "get-config" - filter_xml: "" - dest: "get_config_vlan.conf" - register: junos - - - name: "Print the response" - debug: - var: junos - - - name: Get interface information with kwargs - rpc: - rpc: get-interface-information - kwargs: - interface_name: em1 - media: True - format: json - dest: get_interface_information.conf diff --git a/Samples/sample_software.yaml b/Samples/sample_software.yaml deleted file mode 100644 index ac5190df..00000000 --- a/Samples/sample_software.yaml +++ /dev/null @@ -1,31 +0,0 @@ -- name: 'Explicit host argument' - hosts: junos - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Execute a basic Junos software upgrade. - software: - local_package: "./images/" - register: response - - - name: Print the complete response. - debug: - var: response - - - name: Upgrade Junos OS from package copied at device - software: - host: "10.x.x.x" - user: "user" - passwd: "user123" - remote_package: "/var/tmp/junos-install-mx-x86-64-20.1R1.5.tgz" - no_copy: false - cleanfs: false - validate: true - register: response - - - name: "Print the response" - debug: - var: response \ No newline at end of file diff --git a/Samples/sample_system.yaml b/Samples/sample_system.yaml deleted file mode 100644 index 960c23f1..00000000 --- a/Samples/sample_system.yaml +++ /dev/null @@ -1,47 +0,0 @@ -- name: 'Explicit host argument' - hosts: junos - connection: local - gather_facts: no - collections: - - juniper.device - - tasks: - - name: Reboot all REs of the device - system: - action: "reboot" - - - name: Power off the other RE of the device. - system: - action: "shutdown" - othe_re: True - - - name: Reboot this RE at 8pm today. - system: - action: "reboot" - all_re: False - at: "20:00" - - - name: Halt the system on 25 January 2018 at 4pm. - system: - action: "halt" - at: "1801251600" - - - name: Reboot the system in 30 minutes. - system: - action: "reboot" - in_min: 30 - - - name: Reboot the system in 30 minutes. - system: - action: "reboot" - at: "+30m" - - - name: Zeroize the local RE only. - system: - action: "zeroize" - all_re: False - - - name: Zeroize all REs and overwrite medea. - system: - action: "zeroize" - media: True diff --git a/Samples/test_jsnapy.yaml b/Samples/test_jsnapy.yaml deleted file mode 100644 index 0edd2c98..00000000 --- a/Samples/test_jsnapy.yaml +++ /dev/null @@ -1,12 +0,0 @@ -tests_include: - - test_rpc_version - -test_rpc_version: - - rpc: get-software-information - - iterate: - xpath: package-information - id: './name' - tests: - - no-diff: comment - info: "Test Succeeded!!, comment is <{{post['comment']}}>" - err: "Test Failed!!!, comment is <{{post['comment']}}>" diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index fac1a1da..d32f0e7b 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -462,13 +462,10 @@ class ModuleDocFragment(object): # Junos module, but which should not be visible to users. internal_spec = { '_module_utils_path': dict(type='path', - required=True, default=None), '_module_name': dict(type='str', - required=True, default=None), '_inventory_hostname': dict(type='str', - required=True, default=None), '_connection': dict(type='str', default=None), @@ -1547,7 +1544,7 @@ def commit_configuration(self, ignore_warning=None, comment=None, if self.conn_type != "local": self._pyez_conn.commit_configuration(ignore_warning=ignore_warning, comment=comment, - confirm=confirmed, + confirmed=confirmed, timeout=timeout, full=full, force_sync=force_sync, diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index 67996228..8927e852 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -147,18 +147,16 @@ - name: 'Explicit host argument' hosts: junos connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: "Execute single command in text format" - command: + juniper.device.command: commands: "show configuration system services netconf traceoptions" format: text - name: "Execute command with login credentials" - command: + juniper.device.command: host: "10.x.x.x." user: "user" passwd: "user123" @@ -167,7 +165,7 @@ register: junos_result - name: Execute three commands. - command: + juniper.device.command: commands: - "show version" - "show system uptime" @@ -175,12 +173,12 @@ register: response - name: Print the command output of each. - debug: + ansible.builtin.debug: var: item.stdout with_items: "{{ response.results }}" - name: show route with XML output - show version with JSON output - command: + juniper.device.command: commands: - "show route" - "show version" @@ -189,7 +187,7 @@ - "json" - name: Multiple commands, save outputs, but don't return them - command: + juniper.device.command: commands: - "show route" - "show version" @@ -199,7 +197,7 @@ return_output: false - name: save output to dest - command: + juniper.device.command: command: - "show route" - "show lldp neighbors" diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index abba237f..2931573a 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -578,13 +578,11 @@ - name: 'Explicit host argument' hosts: junos connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Retrieve the committed configuration - config: + juniper.device.config: retrieve: 'committed' diff: false check: false @@ -592,11 +590,11 @@ register: response - name: Print the lines in the config. - debug: + ansible.builtin.debug: var: response.config_lines - name: Append .foo to the hostname using private config mode. - config: + juniper.device.config: config_mode: 'private' load: 'merge' lines: @@ -604,39 +602,39 @@ register: response - name: Print the config changes. - debug: + ansible.builtin.debug: var: response.diff_lines - name: Rollback to the previous config. - config: + juniper.device.config: config_mode: 'private' rollback: 1 register: response - name: Print the config changes. - debug: + ansible.builtin.debug: var: response.diff_lines - name: Rollback to the rescue config. - config: + juniper.device.config: rollback: 'rescue' register: response - name: Print the complete response. - debug: + ansible.builtin.debug: var: response - name: Load override from a file. - config: + juniper.device.config: load: 'override' src: "{{ inventory_hostname }}.conf" register: response - name: Print the complete response. - debug: + ansible.builtin.debug: var: response - name: Load from a Jinja2 template. - config: + juniper.device.config: load: 'merge' format: 'xml' template: "{{ inventory_hostname }}.j2" @@ -644,30 +642,30 @@ host: "{{ inventory_hostname }}" register: response - name: Print the complete response. - debug: + ansible.builtin.debug: var: response - name: Load from a file on the Junos device. - config: + juniper.device.config: load: 'merge' url: "{{ inventory_hostname }}.conf" register: response - name: Print the complete response. - debug: + ansible.builtin.debug: var: response - name: Load from a file on the Junos device, skip the commit check - config: + juniper.device.config: load: 'merge' url: "{{ inventory_hostname }}.conf" check: false register: response - name: Print the msg. - debug: + ansible.builtin.debug: var: response.msg - name: Print diff between current and rollback 10. No check. No commit. - config: + juniper.device.config: rollback: 11 diff: true check: false @@ -675,11 +673,11 @@ register: response - name: Print the msg. - debug: + ansible.builtin.debug: var: response - name: Retrieve [edit system services] of current committed config. - config: + juniper.device.config: retrieve: 'committed' filter: 'system/services' diff: true @@ -688,11 +686,11 @@ register: response - name: Print the resulting config lines. - debug: + ansible.builtin.debug: var: response.config_lines - name: Enable NETCONF SSH and traceoptions, save config, and diffs. - config: + juniper.device.config: load: 'merge' lines: - 'set system services netconf ssh' @@ -706,11 +704,11 @@ register: response - name: Print the complete response - debug: + ansible.builtin.debug: var: response - name: Load conf. Confirm within 5 min. Wait 3 secs between chk and commit - config: + juniper.device.config: load: 'merge' url: "{{ inventory_hostname }}.conf" confirm: 5 @@ -718,22 +716,22 @@ register: response - name: Print the complete response - debug: + ansible.builtin.debug: var: response - name: Confirm the previous commit with a commit check (but no commit) - config: + juniper.device.config: check: true diff: false commit: false register: response - name: Print the complete response - debug: + ansible.builtin.debug: var: response - name: fetch config from the device with filter and login credentials - config: + juniper.device.config: host: "10.x.x.x" user: "user" passwd: "user123" diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py index e6082ccc..8073c9b5 100644 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -85,24 +85,22 @@ - name: 'Explicit host argument' hosts: junos connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: "Get facts" - facts: + juniper.device.facts: register: response - name: Facts with login credentials - facts: + juniper.device.facts: host: "10.x.x.x" user: "user" passwd: "user123" port: "22" - name: Facts in telnet mode - facts: + juniper.device.facts: host: "10.x.x.x" user: "user" passwd: "user123" diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 1531d0c3..3e0ee467 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -110,60 +110,58 @@ - name: Examples of jsnapy hosts: junos-all connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: JUNOS Post Checklist - jsnapy: + juniper.device.jsnapy: action: "snap_post" config_file: "first_test.yml" logfile: "migration_post.log" register: test1 - name: Verify all JSNAPy tests passed - assert: + ansible.builtin.assert: that: - "test1.passPercentage == 100" - name: Print the full test response - debug: + ansible.builtin.debug: var: test1 - name: Test based on a test_file directly - jsnapy: + juniper.device.jsnapy: action: "snapcheck" test_files: "tests/test_junos_interface.yaml" register: test2 - name: Verify all JSNAPy tests passed - assert: + ansible.builtin.assert: that: - "test2.passPercentage == 100" - name: Print the full test response - debug: + ansible.builtin.debug: var: test2 - name: "Collect Pre Snapshot" - jsnapy: + juniper.device.jsnapy: action: "snap_pre" test_files: "tests/test_loopback.yml" - name: "Collect Post Snapshot" - jsnapy: + juniper.device.jsnapy: action: "snap_post" test_files: "tests/test_loopback.yml" - name: "Check after Pre and Post Snapshots" - jsnapy: + juniper.device.jsnapy: action: "check" test_files: "tests/test_loopback.yml" register: test3 - name: Verify all JSNAPy tests passed - assert: + ansible.builtin.assert: that: - "test3.|succeeded" - "test3.passPercentage == 100" - name: Print the full test response - debug: + ansible.builtin.debug: var: test3 ''' diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py index e1010310..fa68861a 100644 --- a/ansible_collections/juniper/device/plugins/modules/ping.py +++ b/ansible_collections/juniper/device/plugins/modules/ping.py @@ -141,86 +141,84 @@ - name: Examples of ping hosts: junos-all connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Ping 192.68.1.1 with default parameters. Fails if any packets lost. - ping: + juniper.device.ping: dest: "192.68.1.1" - name: Ping 192.68.1.1 Allow 50% packet loss. Register response. - ping: + juniper.device.ping: dest: "192.68.1.1" acceptable_percent_loss: 50 register: response - name: Print all keys in the response. - debug: + ansible.builtin.debug: var: response - name: Ping 192.68.1.1. Send 20 packets. Register response. - ping: + juniper.device.ping: dest: "192.68.1.1" count: 20 register: response - name: Print packet sent from the response. - debug: + ansible.builtin.debug: var: response.packets_sent - name: Ping 192.68.1.1. Send 10 packets wihtout rapid. Register response. - ping: + juniper.device.ping: dest: "192.68.1.1" count: 10 rapid: false register: response - name: Print the average round-trip-time from the response. - debug: + ansible.builtin.debug: var: response.rtt_average - name: Ping www.juniper.net with ttl 15. Register response. - ping: + juniper.device.ping: dest: "www.juniper.net" ttl: 15 register: response - name: Print the packet_loss percentage from the response. - debug: + ansible.builtin.debug: var: response.packet_loss - name: Ping 192.68.1.1 with IP packet size of 1500. Register response. - ping: + juniper.device.ping: dest: "192.68.1.1" size: 1472 register: response - name: Print the packets_received from the response. - debug: + ansible.builtin.debug: var: response.packets_received - name: Ping 192.68.1.1 with do-not-fragment bit set. Register response. - ping: + juniper.device.ping: dest: "192.68.1.1" do_not_fragment: true register: response - name: Print the maximum round-trip-time from the response. - debug: + ansible.builtin.debug: var: response.rtt_maximum - name: Ping 192.68.1.1 with source set to 192.68.1.2. Register response. - ping: + juniper.device.ping: dest: "192.68.1.1" source: "192.68.1.2" register: response - name: Print the source from the response. - debug: + ansible.builtin.debug: var: response.source - name: Ping 192.168.1.1 from the red routing-instance. - ping: + juniper.device.ping: dest: "192.168.1.1" routing_instance: "red" - name: Ping the all-hosts multicast address from the ge-0/0/0.0 interface - ping: + juniper.device.ping: dest: "224.0.0.1" interface: "ge-0/0/0.0" ''' diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py index a6bfb450..5e9450cc 100644 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -127,58 +127,56 @@ - name: Examples of pmtud hosts: junos-all connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Perform PMTUD to 192.68.1.1 with default parameters. - pmtud: + juniper.device.pmtud: dest: "192.68.1.1" - name: Perform PMTUD to 192.68.1.1. Register response. - pmtud: + juniper.device.pmtud: dest: "192.68.1.1" register: response - name: Print the discovered MTU. - debug: + ansible.builtin.debug: var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Search all possible MTU values. - pmtud: + juniper.device.pmtud: dest: "192.68.1.1" max_size: 65496 max_range: 65536 register: response - name: Print the discovered MTU. - debug: + ansible.builtin.debug: var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Source from ge-0/0/0.0 interface. - pmtud: + juniper.device.pmtud: dest: "192.68.1.1" interface: "ge-0/0/0.0" register: response - name: Print the discovered MTU. - debug: + ansible.builtin.debug: var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Source from 192.168.1.2. - pmtud: + juniper.device.pmtud: dest: "192.68.1.1" source: "192.168.1.2" register: response - name: Print the discovered MTU. - debug: + ansible.builtin.debug: var: response.inet_mtu - name: Perform PMTUD to 192.68.1.1. Source from the red routing-instance. - pmtud: + juniper.device.pmtud: dest: "192.68.1.1" routing_instance: "red" register: response - name: Print the discovered MTU. - debug: + ansible.builtin.debug: var: response.inet_mtu ''' diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index 335136c3..d1f08542 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -198,27 +198,25 @@ - name: 'Explicit host argument' hosts: junos connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: "Execute RPC with filters" - rpc: + juniper.device.rpc: rpcs: - "get-config" format: xml filter: re0 attr: name=re0 register: test1 - ignore_errors: True + ignore_errors: true - name: Check TEST 1 - debug: + ansible.builtin.debug: var: test1 - name: "Execute RPC with host data and store logging" - rpc: + juniper.device.rpc: host: "10.x.x.x" user: "user" passwd: "user123" @@ -228,27 +226,27 @@ logfile: "/var/tmp/rpc.log" ignore_warning: true register: test1 - ignore_errors: True + ignore_errors: true - name: "Print results - summary" - debug: + ansible.builtin.debug: var: test1.stdout_lines - name: "Execute multiple RPC" - rpc: + juniper.device.rpc: rpcs: - "get-config" - "get-software-information" - name: Get Device Configuration for vlan - 1 - rpc: + juniper.device.rpc: rpc: "get-config" filter_xml: "" dest: "get_config_vlan.conf" register: junos - name: Get interface information with kwargs - rpc: + juniper.device.rpc: rpc: get-interface-information kwargs: interface_name: em1 diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index b9c7fc9a..bfc4abbb 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -323,22 +323,20 @@ - name: 'Explicit host argument' hosts: junos connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Execute a basic Junos software upgrade. - software: + juniper.device.software: local_package: "./images/" register: response - name: Print the complete response. - debug: + ansible.builtin.debug: var: response - name: Upgrade Junos OS from package copied at device - software: + juniper.device.software: host: "10.x.x.x" user: "user" passwd: "user123" diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index 6a912005..c5baf36a 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -90,25 +90,23 @@ hosts: junos-all connection: local gather_facts: no - collections: - - juniper.device tasks: - name: Enable an SRX cluster - srx_cluster: + juniper.device.srx_cluster: enable: true cluster_id: 4 node_id: 0 register: response - name: Print the response. - debug: + ansible.builtin.debug: var: response.config_lines - name: Disable an SRX cluster - srx_cluster: + juniper.device.srx_cluster: enable: false register: response - name: Print the response. - debug: + ansible.builtin.debug: var: response.config_lines ''' diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index 6834cf42..cc891ffd 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -158,48 +158,46 @@ - name: 'Explicit host argument' hosts: junos connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Reboot all REs of the device - system: + juniper.device.system: action: "reboot" - name: Power off the other RE of the device. - system: + juniper.device.system: action: "shutdown" othe_re: True - name: Reboot this RE at 8pm today. - system: + juniper.device.system: action: "reboot" all_re: False at: "20:00" - name: Halt the system on 25 January 2018 at 4pm. - system: + juniper.device.system: action: "halt" at: "1801251600" - name: Reboot the system in 30 minutes. - system: + juniper.device.system: action: "reboot" in_min: 30 - name: Reboot the system in 30 minutes. - system: + juniper.device.system: action: "reboot" at: "+30m" - name: Zeroize the local RE only. - system: + juniper.device.system: action: "zeroize" all_re: False - name: Zeroize all REs and overwrite medea. - system: + juniper.device.system: action: "zeroize" media: True ''' diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py index b09a9e2a..38c1541f 100644 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -124,21 +124,19 @@ - name: Retrieve data from a Junos device using a PyEZ table/view. hosts: junos-all connection: local - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table - table: + juniper.device.table: file: "lldp.yml" register: response - name: Print response - debug: + ansible.builtin.debug: var: response - name: Retrieve routes within 192.68.1/8 - table: + juniper.device.table: file: "routes.yml" table: "RouteTable" kwargs: @@ -146,16 +144,16 @@ response_type: "juniper_items" register: response - name: Print response - debug: + ansible.builtin.debug: var: response - name: Retrieve from custom table in playbook directory - table: + juniper.device.table: file: "fpc.yaml" path: "." register: response - name: Print response - debug: + ansible.builtin.debug: var: response ''' diff --git a/tests/pb.juniper_junos_command.yml b/tests/pb.juniper_junos_command.yml index cec31b57..d51c1725 100644 --- a/tests/pb.juniper_junos_command.yml +++ b/tests/pb.juniper_junos_command.yml @@ -1,28 +1,27 @@ --- - name: Test juniper.device.command module hosts: all - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: TEST 1 - Execute single "show version" command. - command: + juniper.device.command: commands: "show version" register: test1 - name: Check TEST 1 - assert: + ansible.builtin.assert: that: test1.msg == "The command executed successfully." - name: Creates directory - file: + ansible.builtin.file: path: out state: directory + mode: '0644' - name: TEST 2 - Execute three commands. - command: + juniper.device.command: commands: - "show version" - "show system uptime" @@ -30,7 +29,7 @@ register: test2 - name: Check TEST 2 - assert: + ansible.builtin.assert: that: - test2.results[0].command == "show version" - test2.results[0].msg == "The command executed successfully." @@ -40,28 +39,28 @@ - test2.results[2].msg == "The command executed successfully." - name: Print the command output of each. - debug: + ansible.builtin.debug: var: item.stdout with_items: "{{ test2.results }}" - name: TEST 3 - Two commands with XML output. - command: + juniper.device.command: commands: - "show route" - - "show lldp neighbors" + - "show version" format: xml register: test3 - name: Check TEST 3 - assert: + ansible.builtin.assert: that: - test3.results[0].command == "show route" - test3.results[0].msg == "The command executed successfully." - - test3.results[1].command == "show lldp neighbors" + - test3.results[1].command == "show version" - test3.results[1].msg == "The command executed successfully." - name: TEST 4 - show route with XML output - show version with JSON output - command: + juniper.device.command: commands: - "show route" - "show version" @@ -71,7 +70,7 @@ register: test4 - name: Check TEST 4 - assert: + ansible.builtin.assert: that: - test4.results[0].command == "show route" - test4.results[0].msg == "The command executed successfully." @@ -79,7 +78,7 @@ - test4.results[1].msg == "The command executed successfully." - name: TEST 5 - save outputs in dest_dir - command: + juniper.device.command: commands: - "show route" - "show version" @@ -87,7 +86,7 @@ register: test5 - name: Check TEST 5 - assert: + ansible.builtin.assert: that: - test5.results[0].command == "show route" - test5.results[0].msg == "The command executed successfully." @@ -95,19 +94,19 @@ - test5.results[1].msg == "The command executed successfully." - name: TEST 6 - save output to dest - command: + juniper.device.command: command: "show system uptime" dest: "./out/{{ inventory_hostname }}.uptime.output" register: test6 - name: Check TEST 6 - assert: + ansible.builtin.assert: that: - test6.command == "show system uptime" - test6.msg == "The command executed successfully." - name: TEST 7 - save output to dest - command: + juniper.device.command: command: - "show route" - "show version" @@ -115,7 +114,7 @@ register: test7 - name: Check TEST 7 - assert: + ansible.builtin.assert: that: - test7.results[0].command == "show route" - test7.results[0].msg == "The command executed successfully." @@ -123,7 +122,7 @@ - test7.results[1].msg == "The command executed successfully." - name: TEST 8 - Multiple commands, save outputs, but don't return them - command: + juniper.device.command: commands: - "show route" - "show version" @@ -135,9 +134,9 @@ register: test8 - name: Check TEST 8 - assert: + ansible.builtin.assert: that: - - test8.results[0].command == "show route" - - test8.results[0].msg == "The command executed successfully." - - test8.results[1].command == "show version" - - test8.results[1].msg == "The command executed successfully." + - test8.results[0].command == "show route" + - test8.results[0].msg == "The command executed successfully." + - test8.results[1].command == "show version" + - test8.results[1].msg == "The command executed successfully." diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 1179847b..2b691067 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -1,82 +1,81 @@ --- - name: Test juniper.device.config module hosts: all - gather_facts: no - collections: - - juniper.device + gather_facts: false + tasks: ################ - name: Retrieve the committed configuration - config: + juniper.device.config: retrieve: 'committed' diff: false check: false commit: false register: test1 - ignore_errors: True - tags: [ test1 ] + ignore_errors: true + tags: [test1] - name: Check TEST 1 - assert: + ansible.builtin.assert: that: - test1.config ################ - name: Append .foo to the hostname using private config mode. - config: + juniper.device.config: config_mode: 'private' load: 'merge' lines: - "set system host-name {{ inventory_hostname }}.foo" comment: "Append .foo to the hostname" register: test2 - ignore_errors: True - tags: [ test2 ] + ignore_errors: true + tags: [test2] - name: Check TEST 2 - assert: + ansible.builtin.assert: that: - test2.diff_lines ################ - name: Rollback to the previous config. - config: + juniper.device.config: config_mode: 'private' rollback: 1 register: test3 - ignore_errors: True - tags: [ test3 ] + ignore_errors: true + tags: [test3] - name: Check TEST 3 - assert: + ansible.builtin.assert: that: - test3.diff_lines ################# - name: Save rescue configuration - command: + juniper.device.command: commands: "request system configuration rescue save" - name: Configure syslog configuration - config: + juniper.device.config: load: 'merge' lines: - "set system syslog file TEST any any" comment: "Configured system services" - name: Rollback to the rescue config. - config: + juniper.device.config: rollback: 'rescue' register: test4 - name: Check TEST 4 - assert: + ansible.builtin.assert: that: - test4.diff_lines - name: Clean up TEST 4 - file: + ansible.builtin.file: path: out state: absent ############### - name: Configure system services. - config: + juniper.device.config: config_mode: 'private' load: 'merge' lines: @@ -84,62 +83,61 @@ comment: "Configured system services" - name: Retrieve [edit system services] of current committed config. - config: + juniper.device.config: retrieve: 'committed' filter: 'system/services' diff: true check: false commit: false register: test5 - ignore_errors: True - tags: [ test5 ] + ignore_errors: true + tags: [test5] - name: Check TEST 5 - assert: + ansible.builtin.assert: that: - test5.failed == False - "'system {' in test5.config_lines" ################# - name: Confirm the previous commit with a commit check (but no commit) - config: + juniper.device.config: check: true diff: false commit: false register: test6 - name: Check TEST 6 - assert: + ansible.builtin.assert: that: test6.changed == False ################# - name: Confirm the commit with a commit sync - config: + juniper.device.config: check: true diff: false comment: "Juniper Networks" - commit_sync: True + commit_sync: true register: test7 - name: Check TEST 7 - assert: + ansible.builtin.assert: that: - test7.changed == False ################# - name: Confirm the commit with a commit sync force - config: + juniper.device.config: check: true diff: false comment: "Juniper Networks" - commit_force_sync: True + commit_force_sync: true register: test8 - name: Check TEST 8 - assert: + ansible.builtin.assert: that: test8.changed == False - diff --git a/tests/pb.juniper_junos_facts.yml b/tests/pb.juniper_junos_facts.yml index 83998f03..7d2fb042 100644 --- a/tests/pb.juniper_junos_facts.yml +++ b/tests/pb.juniper_junos_facts.yml @@ -1,19 +1,16 @@ --- - name: Test juniper.device.facts module hosts: all - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: "TEST 1 - Gather Facts" - facts: - ignore_errors: True + juniper.device.facts: + ignore_errors: true register: test1 - # - debug: var=test1 - name: Check TEST 1 - assert: + ansible.builtin.assert: that: - test1.facts.hostname - test1.facts.serialnumber @@ -21,11 +18,11 @@ - test1.facts.fqdn - name: TEST 2 - get facts in xml format - facts: + juniper.device.facts: config_format: xml register: test2 - name: Check TEST 2 - assert: + ansible.builtin.assert: that: - "'" register: test7 - ignore_errors: True - tags: [ test7 ] + ignore_errors: true + tags: [test7] - name: Check TEST 7 - assert: + ansible.builtin.assert: that: - test7.msg == "The \"get-config\" RPC executed successfully." - tags: [ test7 ] + tags: [test7] ################# - name: "Execute wrong RPC to generate RPC error" - rpc: + juniper.device.rpc: rpcs: - "wrong-rpc" register: test8 - ignore_errors: True - tags: [ test8 ] + ignore_errors: true + tags: [test8] - name: Check TEST 8 - assert: + ansible.builtin.assert: that: - '"Unable to execute the RPC" or "MODULE FAILURE" in test8.msg' - tags: [ test8 ] + tags: [test8] ################# - name: "Check configuration for errors" - rpc: + juniper.device.rpc: rpcs: - "load-configuration" attrs: @@ -193,22 +192,22 @@ kwargs: - configuration_set: 'set system syslog file test1 any any' register: test9 - tags: [ test9 ] + tags: [test9] - name: Check TEST 9 - debug: + ansible.builtin.debug: var: test9 - tags: [ test9 ] + tags: [test9] - name: Check TEST 9 - assert: + ansible.builtin.assert: that: - test9.msg == "The RPC executed successfully." - tags: [ test9 ] + tags: [test9] ################# - name: "Check huge xml/text data" - rpc: + juniper.device.rpc: rpcs: - get-support-information - file-archive @@ -216,48 +215,48 @@ - {} - destination: "support_info" source: /var/log/* - compress: True + compress: true formats: text timeout: 1200 - huge_tree: True + huge_tree: true register: test10 - tags: [ test10 ] + tags: [test10] - name: Check TEST 10 - assert: + ansible.builtin.assert: that: test10.results[0].msg == "The RPC executed successfully." - tags: [ test10 ] + tags: [test10] ##################### - name: Check rollback info - rpc: + juniper.device.rpc: rpc: get-rollback-information kwargs: rollback: "3" compare: "2" register: test11 - tags: [ test11 ] + tags: [test11] - name: Check TEST 11 - assert: + ansible.builtin.assert: that: test11.msg == "The RPC executed successfully." - tags: [ test11 ] + tags: [test11] #################### - name: Check rollback info with boolean values - rpc: + juniper.device.rpc: rpc: get-rollback-information kwargs: - allow_bool_values : "0" + allow_bool_values: "0" rollback: "1" compare: "0" register: test12 - tags: [ test12 ] + tags: [test12] - name: Check TEST 12 - assert: + ansible.builtin.assert: that: test12.msg == "The RPC executed successfully." - tags: [ test12 ] + tags: [test12] diff --git a/tests/pb.juniper_junos_software.yml b/tests/pb.juniper_junos_software.yml index 1c7a7366..2e9a9b9e 100644 --- a/tests/pb.juniper_junos_software.yml +++ b/tests/pb.juniper_junos_software.yml @@ -1,41 +1,45 @@ --- - name: Test juniper.device.software module hosts: all - collections: - - juniper.device - gather_facts: no + gather_facts: false vars: wait_time: 3600 pkg_dir: /var/tmp/ - OS_version: 14.1R1.10 - OS_package: jinstall-14.1R1.10-domestic-signed.tgz + os_version: 14.1R1.10 + os_package: jinstall-14.1R1.10-domestic-signed.tgz log_dir: /var/log/ tasks: - name: Checking NETCONF connectivity - wait_for: host={{ ansible_ssh_host }} port=830 timeout=5 + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: 5 - name: Install Junos OS package - software: - reboot: yes - no_copy: True - all_re: False - version: "{{ OS_version }}" - package: "{{ pkg_dir }}/{{ OS_package }}" + juniper.device.software: + reboot: true + no_copy: true + all_re: false + version: "{{ os_version }}" + package: "{{ pkg_dir }}/{{ os_package }}" logfile: "{{ log_dir }}/software.log" register: test1 notify: - - wait_reboot + - Wait_reboot - name: Print response - debug: + ansible.builtin.debug: var: test1 - name: Check TEST - 1 - assert: + ansible.builtin.assert: that: - test1.failed == false handlers: - - name: wait_reboot - wait_for: host={{ ansible_ssh_host }} port=830 timeout={{ wait_time }} + - name: Wait_reboot + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: "{{ wait_time }}" when: not test1.check_mode diff --git a/tests/pb.juniper_junos_software_member.yml b/tests/pb.juniper_junos_software_member.yml index 3b139577..becc533b 100644 --- a/tests/pb.juniper_junos_software_member.yml +++ b/tests/pb.juniper_junos_software_member.yml @@ -1,42 +1,46 @@ --- - name: Test juniper.device.software module hosts: all - collections: - - juniper.device - gather_facts: no + gather_facts: false vars: wait_time: 3600 pkg_dir: /var/tmp/ - OS_version: 22.4 - OS_package: junos-install-ex-x86-64-22.4I62500TB237700_cd-builder.tgz + os_version: 22.4 + os_package: junos-install-ex-x86-64-22.4I62500TB237700_cd-builder.tgz log_dir: /var/log/ tasks: - name: Checking NETCONF connectivity - wait_for: host={{ ansible_ssh_host }} port=830 timeout=5 + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: 5 - name: Install Junos OS package on VC member - software: - reboot: False - no_copy: True + juniper.device.software: + reboot: false + no_copy: true version: "{{ OS_version }}" package: "{{ pkg_dir }}/{{ OS_package }}" logfile: "{{ log_dir }}/software.log" - all_re: False - member_id: ['1','2'] + all_re: false + member_id: ['1', '2'] register: test1 notify: - - wait_reboot + - Wait_reboot - name: Print response - debug: + ansible.builtin.debug: var: test1 - name: Check TEST - 1 - assert: + ansible.builtin.assert: that: - test1.failed == false handlers: - - name: wait_reboot - wait_for: host={{ ansible_ssh_host }} port=830 timeout={{ wait_time }} + - name: Wait_reboot + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: "{{ wait_time }}" when: not test1.check_mode diff --git a/tests/pb.juniper_junos_srx_cluster.yml b/tests/pb.juniper_junos_srx_cluster.yml index 6420e782..d7952a8d 100644 --- a/tests/pb.juniper_junos_srx_cluster.yml +++ b/tests/pb.juniper_junos_srx_cluster.yml @@ -1,27 +1,26 @@ --- - name: Test juniper.device.srx_cluster module hosts: all - gather_facts: no - collections: - - juniper.device + gather_facts: false + tasks: - - name: TEST 1 - Enable an SRX cluster - srx_cluster: + - name: Enable an SRX cluster + juniper.device.srx_cluster: enable: true cluster_id: 4 node_id: 0 register: test1 - tags: [ test1 ] + tags: [test1] - name: Print the response. - debug: + ansible.builtin.debug: var: test1 - name: Check TEST 1 - assert: + ansible.builtin.assert: that: - test1.failed == false - tags: [ test1 ] + tags: [test1] - name: Sleep for 300 seconds and continue with play ansible.builtin.wait_for: @@ -29,20 +28,23 @@ delegate_to: localhost - name: Checking NETCONF connectivity - wait_for: host={{ ansible_ssh_host }} port=830 timeout=360 + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: 5 - - name: TEST 2 - Disable an SRX cluster - srx_cluster: + - name: Disable an SRX cluster + juniper.device.srx_cluster: enable: false register: test2 - tags: [ test2 ] + tags: [test2] - name: Print the response. - debug: + ansible.builtin.debug: var: test2 - name: Check TEST 2 - assert: + ansible.builtin.assert: that: - test2.failed == false - tags: [ test2 ] \ No newline at end of file + tags: [test2] diff --git a/tests/pb.juniper_junos_system.yml b/tests/pb.juniper_junos_system.yml index bc13516a..7ec0fece 100644 --- a/tests/pb.juniper_junos_system.yml +++ b/tests/pb.juniper_junos_system.yml @@ -1,24 +1,24 @@ --- - name: Test juniper.device.system module hosts: all - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Reboot all REs of the device - system: + juniper.device.system: action: "reboot" register: test1 - ignore_errors: True - tags: [ test1 ] + ignore_errors: true + tags: [test1] - name: CHECK TEST 1 - assert: + ansible.builtin.assert: that: - test1.reboot == true - tags: [ test1 ] + tags: [test1] - name: Checking NETCONF connectivity - wait_for: host={{ ansible_ssh_host }} port=830 timeout=360 - + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: 830 + timeout: 5 diff --git a/tests/pb.juniper_junos_table.yml b/tests/pb.juniper_junos_table.yml index b729b104..aba778a5 100644 --- a/tests/pb.juniper_junos_table.yml +++ b/tests/pb.juniper_junos_table.yml @@ -1,25 +1,23 @@ --- - name: Test juniper.device.table PyEZ table/view module. hosts: all - gather_facts: no - collections: - - juniper.device + gather_facts: false tasks: - name: Retrieve LLDP Neighbor Information Using PyEZ-included Table - table: + juniper.device.table: file: "lldp.yml" register: test1 - ignore_errors: True - tags: [ test1 ] + ignore_errors: true + tags: [test1] - - name: check TEST 1 - assert: + - name: Check TEST 1 + ansible.builtin.assert: that: - test1.msg == "Successfully retrieved 0 items from LLDPNeighborTable." - name: Retrieve routes within 192.68.1/8 - table: + juniper.device.table: file: "routes.yml" table: "RouteTable" kwargs: @@ -27,19 +25,19 @@ response_type: "juniper_items" register: test2 - - name: check TEST 2 - assert: + - name: Check TEST 2 + ansible.builtin.assert: that: - test2.msg == "Successfully retrieved 1 items from RouteTable." - name: Retrieve ethernet devices - table: + juniper.device.table: file: "ethport.yml" table: "EthPortTable" kwargs: interface_name: ge-0/0/0 register: test3 - - name: check TEST 3 - assert: + - name: Check TEST 3 + ansible.builtin.assert: that: - test3.msg == "Successfully retrieved 1 items from EthPortTable." From ff81c02336f9e1ccf41c30770dec9169ae899f5b Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Fri, 17 May 2024 13:11:27 +0530 Subject: [PATCH 404/426] Fix for the issue #607 (#665) * Fix for the issue #607 * Fix for the issue #607 * Fix for the issue #607 * Fix for the issue #607 --- .../juniper/device/plugins/connection/pyez.py | 2 ++ .../plugins/module_utils/juniper_junos_common.py | 4 ++++ .../juniper/device/plugins/modules/config.py | 5 +++++ tests/pb.juniper_junos_config.yml | 16 ++++++++++++++++ 4 files changed, 27 insertions(+) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 4dac432a..fbcf36a3 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -766,6 +766,8 @@ def commit_configuration(self, ignore_warning=None, comment=None, Failures: - An error returned from committing the configuration. """ + if self.dev.timeout: + timeout = self.dev.timeout try: self.config.commit(ignore_warning=ignore_warning, comment=comment, diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index d32f0e7b..bbc3ddab 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1541,6 +1541,10 @@ def commit_configuration(self, ignore_warning=None, comment=None, Failures: - An error returned from committing the configuration. """ + if self.conn_type == "local": + if self.dev.timeout: + timeout = self.dev.timeout + if self.conn_type != "local": self._pyez_conn.commit_configuration(ignore_warning=ignore_warning, comment=comment, diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index 2931573a..4ea55c23 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -942,6 +942,9 @@ def main(): type='int', aliases=['confirm'], default=None), + timeout=dict(required=False, + type='int', + default=30), comment=dict(required=False, type='str', default=None), @@ -995,6 +998,7 @@ def main(): commit_sync = junos_module.params.get('commit_sync') commit_force_sync = junos_module.params.get('commit_force_sync') confirmed = junos_module.params.get('confirmed') + timeout = junos_module.params.get('timeout') comment = junos_module.params.get('comment') check_commit_wait = junos_module.params.get('check_commit_wait') model = junos_module.params.get('model') @@ -1227,6 +1231,7 @@ def main(): junos_module.commit_configuration(ignore_warning=ignore_warning, comment=comment, confirmed=confirmed, + timeout=timeout, full=commit_full, sync=commit_sync, force_sync=commit_force_sync) diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index 2b691067..ed80da34 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -141,3 +141,19 @@ ansible.builtin.assert: that: test8.changed == False +################ + - name: Test commit timeout . + juniper.device.config: + load: 'merge' + lines: + - "set system host-name {{ inventory_hostname }}.foo" + comment: "Append .foo to the hostname" + timeout: 300 + register: test2 + ignore_errors: true + tags: [test2] + + - name: Check TEST 2 + ansible.builtin.assert: + that: + - test2.diff_lines From 08e3376dcdbe785591b92ce3ffe5d01b8eb35b66 Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Mon, 20 May 2024 15:26:26 +0530 Subject: [PATCH 405/426] Introduced a new module file_copy (#666) * Introduced new module file_copy Introduced new module file_copy to put and get the file to and from junos device. * Update file_copy.py --- README.md | 1 + .../device/plugins/action/file_copy.py | 50 +++++ .../juniper/device/plugins/connection/pyez.py | 42 +++++ .../module_utils/juniper_junos_common.py | 118 ++++++++++++ .../device/plugins/modules/file_copy.py | 172 ++++++++++++++++++ tests/pb.juniper_junos_file.yml | 31 ++++ 6 files changed, 414 insertions(+) create mode 100755 ansible_collections/juniper/device/plugins/action/file_copy.py create mode 100644 ansible_collections/juniper/device/plugins/modules/file_copy.py create mode 100644 tests/pb.juniper_junos_file.yml diff --git a/README.md b/README.md index 84bc4519..f2c4aafa 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ This `juniper.device` collection includes the following modules: - **command** — Execute one or more CLI commands on a Junos device. - **config** — Manipulate the configuration of a Junos device. - **facts** — Retrieve facts from a Junos device. +- **file_copy** - Copy the files from and to a Junos device. - **jsnapy** — Execute JSNAPy tests on a Junos device. - **ping** — Execute ping from a Junos device. - **pmtud** — Perform path MTU discovery from a Junos device to a destination. diff --git a/ansible_collections/juniper/device/plugins/action/file_copy.py b/ansible_collections/juniper/device/plugins/action/file_copy.py new file mode 100755 index 00000000..6031d6e5 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/action/file_copy.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2017-2024, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from ansible.plugins.action.normal import ActionModule as ActionNormal +from ansible_collections.juniper.device.plugins.action.extract_data import ExtractData +import os + +# The Ansible core engine will call ActionModule.run() +class ActionModule(ExtractData, ActionNormal): + """A subclass of ansible.plugins.action.network.ActionModule used by all modules. + + All modules share common behavior which is implemented in + this class. This includes specific option fallback/default behavior and + passing the "hidden" _module_utils_path option to the module. + + """ + def run(self, tmp=None, task_vars=None): + super().extract(tmp,task_vars) + # Call the parent action module. + return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index fbcf36a3..071cc6f2 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -234,6 +234,13 @@ except ImportError: HAS_PYEZ_SW = False +try: + from jnpr.junos.utils.scp import SCP + + HAS_PYEZ_SCP = True +except ImportError: + HAS_PYEZ_SCP = False + try: import jnpr.junos.utils.config @@ -508,6 +515,11 @@ def get_chassis_inventory(self): resp = self.dev.rpc.get_chassis_inventory() return etree.tostring(resp) + def get_checksum_information(self, remote_file): + """Get checksum information of the file""" + resp = self.dev.rpc.get_checksum_information(path=remote_file) + return etree.tostring(resp) + def get_re_name(self): """Get re name from the device. """ @@ -900,3 +912,33 @@ def reboot_api(self, all_re, vmhost, member_id=None): self.queue_message("log", "Reboot RPC successfully initiated.") return msg + + def scp_file_copy_put(self, local_file, remote_file): + """Copy the file using scp. + + Args: + local_file local file path/name. + remote_file: remote file path/name. + """ + try: + with SCP(self.dev, progress=True) as scp: + scp.put(local_file, remote_file) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError( + "Failure checking the configuraton: {0}".format(str(ex)) + ) from ex + + def scp_file_copy_get(self, remote_file, local_file): + """Copy the file using scp. + + Args: + local_file local file path/name. + remote_file: remote file path/name. + """ + try: + with SCP(self.dev, progress=True) as scp: + scp.get(remote_file, local_file) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError( + "Failure checking the configuraton: {0}".format(str(ex)) + ) from ex diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index bbc3ddab..dca04576 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -41,6 +41,8 @@ from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg import jnpr from jnpr.junos.utils.sw import SW +from jnpr.junos.utils.scp import SCP + from jnpr.junos import exception as pyez_exception from ncclient.operations.errors import TimeoutExpiredError from ansible.module_utils.common.validation import check_type_dict @@ -52,6 +54,7 @@ import xmltodict import logging import os +import hashlib try: # Python 2 @@ -1797,3 +1800,118 @@ def get_facts(self): def get_chassis_inventory(self): chassis = self._pyez_conn.get_chassis_inventory() return self.etree.fromstring(chassis) + + def _hashfile(self, afile, hasher, blocksize=65536): + buf = afile.read(blocksize) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(blocksize) + return hasher.hexdigest() + + def local_md5(self, package, action): + """ + Computes the MD5 checksum value on the local package file. + :param str package: + File-path to the package (\*.tgz) file on the local server + :returns: MD5 checksum (str) + :raises IOError: when **package** file does not exist + """ + try: + checksum= self._hashfile(open(package, 'rb'), hashlib.md5()) + except Exception as err: + self.logger.error("unable to get the hash due to:{0}".format(err)) + if (("No such file" in format(err)) and action == "get"): + checksum="no_file" + else: + raise err + return checksum + + self.logger.info("local hash calculated") + return checksum + + def remote_md5(self, remote_file, action): + try: + if self.conn_type == "local": + rpc_reply = self.dev.rpc.get_checksum_information(path=remote_file) + checksum = rpc_reply.findtext('.//checksum').strip() + else: + rpc_reply = self._pyez_conn.get_checksum_information(remote_file) + resp = self.etree.fromstring(rpc_reply) + checksum = resp.findtext('.//checksum').strip() + except Exception as err: + self.logger.error("unable to get rpc due to:{0}".format(str(err))) + if (("No such file or directory" in format(err)) and (action=="put")): + checksum="no_file" + else: + raise err + return checksum + self.logger.info("rpc response received") + return checksum + + def scp_file_copy_put(self, local_file, remote_file): + self.logger.info("Computing local MD5 checksum on: {0}".format(local_file)) + local_checksum = self.local_md5(local_file, "put") + self.logger.info("Local checksum: {0}".format(local_checksum)) + remote_checksum = self.remote_md5(remote_file, "put") + if remote_checksum == "no_file" or remote_checksum != local_checksum: + status = "File not present, need to transfer" + self.logger.info(status) + if self.conn_type == "local": + with SCP(self.dev, progress=True) as scp1: + scp1.put(local_file, remote_file) + else: + self._pyez_conn.scp_file_copy_put(local_file, remote_file) + self.logger.info("computing remote MD5 checksum on: {0}".format(remote_file)) + remote_checksum = self.remote_md5(remote_file, "put") + self.logger.info("Remote checksum: {0}".format(remote_checksum)) + if remote_checksum != local_checksum: + status = "Transfer failed (different MD5 between local and remote) {0} | {1}".format( + local_checksum, + remote_checksum + ) + self.logger.error(status) + self.fail_json(msg=status) + return [status, False] + else: + status = "File pushed OK" + self.logger.info("Checksum check passed.") + self.logger.info(status) + return [status, True] + else: + status = "File already present, skipping the scp" + self.logger.info(status) + return [status, False] + + def scp_file_copy_get(self, remote_file, local_file): + self.logger.info("Computing remote MD5 checksum on: {0}".format(remote_file)) + remote_checksum = self.remote_md5(remote_file, "get") + self.logger.info("Remote checksum: {0}".format(remote_checksum)) + local_checksum = self.local_md5(local_file, "get") + if (local_checksum == "no_file" or local_checksum != remote_checksum): + status = "File not present, need to transfer" + self.logger.info(status) + if self.conn_type == "local": + with SCP(self.dev, progress=True) as scp1: + scp1.get(remote_file, local_file) + else: + self._pyez_conn.scp_file_copy_get(remote_file, local_file) + self.logger.info("computing local MD5 checksum on: {0}".format(local_file)) + local_checksum = self.local_md5(local_file, "get") + self.logger.info("Local checksum: {0}".format(local_checksum)) + if remote_checksum != local_checksum: + status = "Transfer failed (different MD5 between local and remote) {0} | {1}".format( + local_checksum, + remote_checksum + ) + self.logger.error(status) + self.fail_json(msg=status) + return [status, False] + else: + status = "File pushed OK" + self.logger.info("Checksum check passed.") + self.logger.info(status) + return [status, True] + else: + status = "File already present, skipping the scp" + self.logger.info(status) + return [status, False] diff --git a/ansible_collections/juniper/device/plugins/modules/file_copy.py b/ansible_collections/juniper/device/plugins/modules/file_copy.py new file mode 100644 index 00000000..884b40b1 --- /dev/null +++ b/ansible_collections/juniper/device/plugins/modules/file_copy.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017-2024, Juniper Networks Inc. All rights reserved. +# +# License: Apache 2.0 +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the Juniper Networks nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Juniper Networks, Inc. ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Juniper Networks, Inc. BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +from __future__ import absolute_import, division, print_function + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'supported_by': 'community', + 'status': ['stableinterface']} + +DOCUMENTATION = ''' +--- +extends_documentation_fragment: + - juniper_junos_common.connection_documentation + - juniper_junos_common.logging_documentation +module: file_copy +author: Juniper Networks - Dinesh babu (@dineshbaburam91) +description: + - Copy file over SCP to and from a Juniper device +options: + local_dir=dict(type='str', + required=True, + default=None), + remote_dir=dict(type='str', + required=True, + default=None), + file=dict(type='str', + required=True, + default=None), + action=dict(type='str', + choices=['put', 'get'], + required=True, + default=None) + local_dir: + description: + - path of the local directory where the file is located + or needs to be copied to + required: True + type: str + remote_dir: + description: + - path of the directory on the remote device where the file is located + or needs to be copied to + required: True + type: str + file: + description: + - Name of the file to copy to/from the remote device. + required: true + type: str + Action: + description: + - Type of operation to execute, currently only support get and put + required: True + type: str +''' + +EXAMPLES = ''' +--- +- name: Examples of juniper_device_file_copy + hosts: all + connection: local + gather_facts: false + tasks: + - name: Copy a log file on a remote device locally + juniper.device.file_copy: + remote_dir: /var/log + local_dir: /tmp + action: get + file: log.txt + - name: Copy a local file into /var/tmp on the remote device + juniper.device.file_copy: + remote_dir: /var/tmp + local_dir: /tmp + action: put + file: license.txt +''' + +RETURN = ''' +changed: + description: + - Indicates if the device's state has changed. + returned: when the file has been successfully copied. + type: bool +''' + +"""From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules +But custom module_utils directory is supported from Ansible 2.3 +Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ + +# Ansiballz packages module_utils into ansible.module_utils +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg + +def main(): + + # The argument spec for the module. + junos_module = juniper_junos_common.JuniperJunosModule( + argument_spec = dict( + local_dir=dict(type='str', + required=True, + default=None), + remote_dir=dict(type='str', + required=True, + default=None), + file=dict(type='str', + required=True, + default=None), + action=dict(type='str', + choices=['put', 'get'], + required=True, + default=None) + ), + supports_check_mode=True, + min_jxmlease_version=cfg.MIN_JXMLEASE_VERSION, + ) + + # Set initial results values. Assume failure until we know it's success. + results = {'msg': '', 'changed': False, 'failed': False} + output = [] + # We're going to be using params a lot + params = junos_module.params + + remote_path = params['remote_dir'] + local_file=params['local_dir']+"/"+params['file'] + remote_file=params['remote_dir']+"/"+params['file'] + + if (params['action'] == "put"): + output = junos_module.scp_file_copy_put(local_file, remote_file) + results['msg'] = output[0] + results['changed'] = output[1] + elif (params['action'] == "get"): + output = junos_module.scp_file_copy_get(remote_file, local_file) + results['msg'] = output[0] + results['changed'] = output[1] + + # Return results. + junos_module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/tests/pb.juniper_junos_file.yml b/tests/pb.juniper_junos_file.yml new file mode 100644 index 00000000..7f001e71 --- /dev/null +++ b/tests/pb.juniper_junos_file.yml @@ -0,0 +1,31 @@ +--- +- name: Test juniper.device.file_copy module + hosts: all + gather_facts: false + tasks: + - name: "File copy" + juniper.device.file_copy: + local_dir: /var/tmp + remote_dir: /var/log + file: log.txt + action: put + register: test1 + ignore_errors: true + + - name: Check file copy put + ansible.builtin.assert: + that: + - test1.msg == 'File pushed OK' + + - name: "File copy get" + juniper.device.file_copy: + local_dir: /tmp + remote_dir: /var/log + file: log.txt + action: get + register: test2 + + - name: Check file copy get + ansible.builtin.assert: + that: + - test2.msg == 'File pushed OK' From 5c50735462ac25e5fcc418d2684a59879713f0b8 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Wed, 22 May 2024 10:16:08 +0530 Subject: [PATCH 406/426] Fix for netconf timeout (#667) * Fix for netconf timeout * Fix for netconf timeout * Fix for netconf timeout * Fix for netconf timeout * Fix for test playbooks * Fix for test playbooks * Fix for test playbooks * Fix for test playbooks --- tests/pb.juniper_junos_file.yml | 11 +++++++++++ tests/pb.juniper_junos_software.yml | 4 ++-- tests/pb.juniper_junos_software_member.yml | 8 ++++---- tests/pb.juniper_junos_srx_cluster.yml | 2 +- tests/pb.juniper_junos_system.yml | 4 ++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/tests/pb.juniper_junos_file.yml b/tests/pb.juniper_junos_file.yml index 7f001e71..d78e08e1 100644 --- a/tests/pb.juniper_junos_file.yml +++ b/tests/pb.juniper_junos_file.yml @@ -3,6 +3,17 @@ hosts: all gather_facts: false tasks: + - name: Creates file log.txt + ansible.builtin.file: + path: /var/tmp/log.txt + state: touch + mode: '0644' + + - name: Delete local file log.txt + ansible.builtin.file: + path: /tmp/log.txt + state: absent + - name: "File copy" juniper.device.file_copy: local_dir: /var/tmp diff --git a/tests/pb.juniper_junos_software.yml b/tests/pb.juniper_junos_software.yml index 2e9a9b9e..96e53e05 100644 --- a/tests/pb.juniper_junos_software.yml +++ b/tests/pb.juniper_junos_software.yml @@ -12,7 +12,7 @@ tasks: - name: Checking NETCONF connectivity ansible.builtin.wait_for: - host: "{{ inventory_hostname }}" + host: "{{ ansible_ssh_host }}" port: 830 timeout: 5 - name: Install Junos OS package @@ -39,7 +39,7 @@ handlers: - name: Wait_reboot ansible.builtin.wait_for: - host: "{{ inventory_hostname }}" + host: "{{ ansible_ssh_host }}" port: 830 timeout: "{{ wait_time }}" when: not test1.check_mode diff --git a/tests/pb.juniper_junos_software_member.yml b/tests/pb.juniper_junos_software_member.yml index becc533b..89429af5 100644 --- a/tests/pb.juniper_junos_software_member.yml +++ b/tests/pb.juniper_junos_software_member.yml @@ -5,14 +5,14 @@ vars: wait_time: 3600 pkg_dir: /var/tmp/ - os_version: 22.4 - os_package: junos-install-ex-x86-64-22.4I62500TB237700_cd-builder.tgz + OS_version: 22.4 + OS_package: junos-install-ex-x86-64-22.4I62500TB237700_cd-builder.tgz log_dir: /var/log/ tasks: - name: Checking NETCONF connectivity ansible.builtin.wait_for: - host: "{{ inventory_hostname }}" + host: "{{ ansible_ssh_host }}" port: 830 timeout: 5 - name: Install Junos OS package on VC member @@ -40,7 +40,7 @@ handlers: - name: Wait_reboot ansible.builtin.wait_for: - host: "{{ inventory_hostname }}" + host: "{{ ansible_ssh_host }}" port: 830 timeout: "{{ wait_time }}" when: not test1.check_mode diff --git a/tests/pb.juniper_junos_srx_cluster.yml b/tests/pb.juniper_junos_srx_cluster.yml index d7952a8d..441eafba 100644 --- a/tests/pb.juniper_junos_srx_cluster.yml +++ b/tests/pb.juniper_junos_srx_cluster.yml @@ -29,7 +29,7 @@ - name: Checking NETCONF connectivity ansible.builtin.wait_for: - host: "{{ inventory_hostname }}" + host: "{{ ansible_ssh_host }}" port: 830 timeout: 5 diff --git a/tests/pb.juniper_junos_system.yml b/tests/pb.juniper_junos_system.yml index 7ec0fece..50b225d1 100644 --- a/tests/pb.juniper_junos_system.yml +++ b/tests/pb.juniper_junos_system.yml @@ -19,6 +19,6 @@ - name: Checking NETCONF connectivity ansible.builtin.wait_for: - host: "{{ inventory_hostname }}" + host: "{{ ansible_ssh_host }}" port: 830 - timeout: 5 + timeout: 360 From b15439ea6b1ede9e0da67b84348f8acf93547a1c Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Wed, 22 May 2024 18:16:26 +0530 Subject: [PATCH 407/426] Ansible Collection v1.0.5 Release update (#669) * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update --- README.md | 2 +- .../juniper/device/docs/file_copy.rst | 130 ++++++++++++++++++ ansible_collections/juniper/device/galaxy.yml | 2 +- .../device/plugins/modules/file_copy.py | 36 ++--- ansible_collections/juniper/device/version.py | 4 +- version.py | 4 +- 6 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 ansible_collections/juniper/device/docs/file_copy.rst diff --git a/README.md b/README.md index f2c4aafa..ad3ae6be 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ should be added to the Ansible configuration file in order to allow the jsnapy c [Official Juniper documentation](http://www.juniper.net/techpubs/en_US/release-independent/junos-ansible/information-products/pathway-pages/index.html) (detailed information, including examples) -[Ansible style documentation](https://ansible-juniper-collection.readthedocs.io/en/latest/) +[Ansible style documentation](https://ansible-juniper-collection.readthedocs.io) ## INSTALLATION diff --git a/ansible_collections/juniper/device/docs/file_copy.rst b/ansible_collections/juniper/device/docs/file_copy.rst new file mode 100644 index 00000000..38b9d12a --- /dev/null +++ b/ansible_collections/juniper/device/docs/file_copy.rst @@ -0,0 +1,130 @@ +.. _file_copy: + +file_copy ++++++++++ +File put and get module + + + +.. contents:: + :local: + :depth: 2 + + +Synopsis +-------- + + +* Copy file over SCP to and from a Juniper device + + +.. _module-specific-options-label: + +Module-specific Options +----------------------- +The following options may be specified for this module: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
parametertyperequireddefaultchoicescomments
action
stryes +
Type of operation to execute, currently only support get and put
+
file
stryes +
Name of the file to copy to/from the remote device
+
local_dir
stryes +
path of the local directory where the file is located or needs to be copied to
+
remote_dir
stryes +
path of the directory on the remote device where the file is located or needs to be copied to
+
+
+ +.. _file_copy-examples-label: + +Examples +-------- + +:: + + + --- + - name: Examples of juniper_device_file_copy + hosts: all + connection: local + gather_facts: false + tasks: + - name: Copy a log file on a remote device locally + juniper.device.file_copy: + remote_dir: /var/log + local_dir: /tmp + action: get + file: log.txt + - name: Copy a local file into /var/tmp on the remote device + juniper.device.file_copy: + remote_dir: /var/tmp + local_dir: /tmp + action: put + file: license.txt + + + + + +Author +~~~~~~ + +* Juniper Networks - Dinesh Babu (@dineshbaburam91) + + + + +Status +~~~~~~ + +This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. + diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index 70050839..0a8e3e40 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -9,7 +9,7 @@ namespace: juniper name: device # The version of the collection. Must be compatible with semantic versioning -version: 1.0.4 +version: 1.0.5 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/ansible_collections/juniper/device/plugins/modules/file_copy.py b/ansible_collections/juniper/device/plugins/modules/file_copy.py index 884b40b1..9b020cb8 100644 --- a/ansible_collections/juniper/device/plugins/modules/file_copy.py +++ b/ansible_collections/juniper/device/plugins/modules/file_copy.py @@ -43,44 +43,32 @@ - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: file_copy -author: Juniper Networks - Dinesh babu (@dineshbaburam91) +author: "Juniper Networks - Dinesh Babu (@dineshbaburam91)" +short_description: File put and get over SCP module description: - - Copy file over SCP to and from a Juniper device + - Copy file over SCP to and from a Juniper device options: - local_dir=dict(type='str', - required=True, - default=None), - remote_dir=dict(type='str', - required=True, - default=None), - file=dict(type='str', - required=True, - default=None), - action=dict(type='str', - choices=['put', 'get'], - required=True, - default=None) local_dir: description: - - path of the local directory where the file is located - or needs to be copied to - required: True + - path of the local directory where the file is located + or needs to be copied to + required: true type: str remote_dir: description: - - path of the directory on the remote device where the file is located - or needs to be copied to - required: True + - path of the directory on the remote device where the file is located + or needs to be copied to + required: true type: str file: description: - - Name of the file to copy to/from the remote device. + - Name of the file to copy to/from the remote device required: true type: str - Action: + action: description: - Type of operation to execute, currently only support get and put - required: True + required: true type: str ''' diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 45259437..8fd4c05f 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.4-collections" -DATE = "2024-Apr-30" +VERSION = "v1.0.5-collections" +DATE = "2024-May-22" diff --git a/version.py b/version.py index 45259437..8fd4c05f 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.4-collections" -DATE = "2024-Apr-30" +VERSION = "v1.0.5-collections" +DATE = "2024-May-22" From 9e1c8bcb4789754cb5eb01e2c6e369f1d65540cc Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Wed, 22 May 2024 18:23:27 +0530 Subject: [PATCH 408/426] Release v105 update1 (#670) * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update * Ansible Collection v1.0.5 Release update --------- Co-authored-by: Dinesh babu --- ansible_collections/juniper/device/version.py | 2 +- version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 8fd4c05f..64385de7 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.5-collections" +VERSION = "v1.0.5" DATE = "2024-May-22" diff --git a/version.py b/version.py index 8fd4c05f..64385de7 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.5-collections" +VERSION = "v1.0.5" DATE = "2024-May-22" From e19dc9f7f7d80a4e27b54e9b9db20035b887983b Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:52:55 +0530 Subject: [PATCH 409/426] Fix for issue #674 syntax warnings (#675) --- .../juniper/device/plugins/module_utils/juniper_junos_common.py | 2 +- ansible_collections/juniper/device/plugins/modules/software.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index dca04576..fbe00114 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1812,7 +1812,7 @@ def local_md5(self, package, action): """ Computes the MD5 checksum value on the local package file. :param str package: - File-path to the package (\*.tgz) file on the local server + File-path to the package (tgz) file on the local server :returns: MD5 checksum (str) :raises IOError: when **package** file does not exist """ diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index bfc4abbb..8a921e94 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -423,7 +423,7 @@ def parse_version_from_filename(filename): # Assumes the version string will be prefixed by -. # Assume major version will begin with two digits followed by dot. # Assume the version string ends with the last digit in filename. - match = re.search('-(\d{2}\..*\d).*', filename) + match = re.search(r'-(\d{2}\..*\d).*', filename) if match is not None: return match.group(1) # Not a known Junos package name. From 9075bf56f8eda52ad0979eb6bdc8a5d36fc19c59 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:04:28 +0530 Subject: [PATCH 410/426] fix for issue #672 to handle float values (#673) * fix for issue #672 to handle float values * fix for issue #672 to handle float values --- .../juniper/device/plugins/module_utils/juniper_junos_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index fbe00114..faf4a8b2 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1686,7 +1686,7 @@ def ping(self, params, acceptable_percent_loss=0, results={}): packet_loss = 100 if results['packet_loss'] is not None: try: - packet_loss = int(results['packet_loss']) + packet_loss = round(float(results['packet_loss'])) except ValueError: results['msg'] = 'Packet loss %s not an integer. ' \ 'Response: %s' % \ From b1aa8c4178c3123c96ec9f3045801770e0bf3eaa Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Wed, 17 Jul 2024 08:29:08 +0530 Subject: [PATCH 411/426] Fixed rpd module related issue (#677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When performing an RPC get with an RPC that is not implemented for a platform, it fails when using a PyEZ connection. With a local connection, the response is “Unable to execute the RPC” and it continues with the next RPC get. PyEZ behavior should be the same as with the local connection. --- ansible_collections/juniper/device/plugins/modules/rpc.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index d1f08542..ef15549d 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -546,8 +546,14 @@ def main(): resp = junos_module.dev.rpc(rpc, normalize=bool(format == 'xml')) else: - resp = junos_module.get_rpc(rpc, + try: + resp = junos_module.get_rpc(rpc, ignore_warning=ignore_warning, format=format) + except Exception as ex: + if "RpcError" in (str(ex)): + raise junos_module.pyez_exception.RpcError + if "ConnectError" in (str(ex)): + raise junos_module.pyez_exception.ConnectError result['msg'] = 'The RPC executed successfully.' junos_module.logger.debug('RPC "%s" executed successfully.', junos_module.etree.tostring( From c9fbb094d4716a46ff618dffa5497e386bcfab6d Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 5 Aug 2024 09:25:57 +0530 Subject: [PATCH 412/426] tests for jsnapy dest_dir support (#682) * tests for JSNAPy dest_dir * tests for JSNAPy dest_dir * tests for JSNAPy dest_dir --- tests/junos_jsnapy/test_snmp.yml | 11 +++++++++ tests/pb.juniper_junos_jsnapy.yml | 37 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/junos_jsnapy/test_snmp.yml diff --git a/tests/junos_jsnapy/test_snmp.yml b/tests/junos_jsnapy/test_snmp.yml new file mode 100644 index 00000000..6967c959 --- /dev/null +++ b/tests/junos_jsnapy/test_snmp.yml @@ -0,0 +1,11 @@ +tests_include: + - test_snmp_config + +test_snmp_config: + - rpc: get-configuration + - iterate: + xpath: /configuration/snmp + tests: + - exists: community[name='mycommunity'] + info: SNMP community ''mycommunity' is configured. + err: SNMP community ''mycommunity' is not configured! diff --git a/tests/pb.juniper_junos_jsnapy.yml b/tests/pb.juniper_junos_jsnapy.yml index 19015e35..9427ab0a 100644 --- a/tests/pb.juniper_junos_jsnapy.yml +++ b/tests/pb.juniper_junos_jsnapy.yml @@ -2,6 +2,11 @@ - name: Test juniper.device.jsnapy module hosts: all gather_facts: false + + vars: + backup_dir: "{{ playbook_dir }}/backup_dir" + + tasks: ################################################## #### TEST 1 ## @@ -188,3 +193,35 @@ - test7.passPercentage == 50 - test7.total_tests == 2 tags: [test7] + +################################################## +#### TEST 8 ## +################################################## + - name: Create local directory if it does not exist + ansible.builtin.file: + path: "{{ backup_dir }}" + state: directory + register: local_dir + delegate_to: localhost + + - name: "TEST 8 - Execute SNAPCHECK with test file & dest_dir" + juniper.device.jsnapy: + test_files: + - test_snmp.yml + dir: junos_jsnapy + dest_dir: "{{ backup_dir }}" + action: snapcheck + register: test8 + ignore_errors: true + tags: [test2] + + - debug: var=test8 + + - name: Check TEST 8 + ansible.builtin.assert: + that: + - test8.passPercentage == 0 + - test8.total_failed == 1 + - test8.total_passed == 0 + - test8.total_tests == 1 + tags: [test8] From b90771b31fb7c5fe9ef02f33544406cfb0a3b2f4 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:31:11 +0530 Subject: [PATCH 413/426] support to save the failed tests to dest_dir for local and pyez connection (#678) * support to save the failed tests to dest_dir * support to save the failed tests to dest_dir * support to save the failed tests to dest_dir --- .../juniper/device/plugins/modules/jsnapy.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 3e0ee467..1e5e7a3e 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -222,6 +222,10 @@ def main(): config_file=dict(required=False, type='path', default=None), + dest_dir=dict(required=False, + type='path', + aliases=['destination_dir', 'destdir'], + default=None), dir=dict(required=False, type='path', aliases=['directory'], @@ -239,6 +243,7 @@ def main(): test_files = junos_module.params.get('test_files') config_file = junos_module.params.get('config_file') dir = junos_module.params.get('dir') + dest_dir = junos_module.params.get('dest_dir') # Initialize the results. Assume failure until we know otherwise. results = {'msg': '', @@ -388,6 +393,31 @@ def main(): # If we made it this far, it's success. results['failed'] = False + # To save the failed test_name details to dest_dir + # for connection local + if dest_dir is not None: + if action in ('snapcheck', 'check'): + if junos_module.conn_type == "local": + for response_loc in responses: + results_loc = response_loc.test_details + for cmd, data in results.items(): + for data1 in data: + if (('test_name' in data1.keys()) and ('result' in data1.keys())): + if data1['result'] == False: + test_name = str(data1['test_name']) + "_" + str(data1['result']) + text_msg = str(data) + junos_module.save_text_output(test_name, "text", text_msg) + else: + # For connection pyez + for res in results["test_results"]: + for data in results['test_results'][res]: + for key, value in data.items(): + if (('test_name' in data.keys()) and ('result' in data.keys())): + if data['result'] == False: + test_name = str(data['test_name']) + "_" + str(data['result']) + text_msg = str(data) + junos_module.save_text_output(test_name, "text", text_msg) + junos_module.exit_json(**results) From af07e2443e437dbec87ce88af1847c591227e685 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:31:43 +0530 Subject: [PATCH 414/426] Release v1.0.6 update (#683) --- ansible_collections/juniper/device/version.py | 4 ++-- version.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index 64385de7..bf9f997b 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.5" -DATE = "2024-May-22" +VERSION = "v1.0.6" +DATE = "2024-Aug-5" diff --git a/version.py b/version.py index 64385de7..bf9f997b 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.5" -DATE = "2024-May-22" +VERSION = "v1.0.6" +DATE = "2024-Aug-5" From 97e40eaaff5d26d24c9ae6ae48d8511128af2a27 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:16:01 +0530 Subject: [PATCH 415/426] Fixed typo results changed results_loc (#684) --- ansible_collections/juniper/device/plugins/modules/jsnapy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 1e5e7a3e..11efcc7c 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -400,7 +400,7 @@ def main(): if junos_module.conn_type == "local": for response_loc in responses: results_loc = response_loc.test_details - for cmd, data in results.items(): + for cmd, data in results_loc.items(): for data1 in data: if (('test_name' in data1.keys()) and ('result' in data1.keys())): if data1['result'] == False: From b69231d992d2fe3e4c15fde5502e4ca4d877f2b3 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:16:31 +0530 Subject: [PATCH 416/426] Fix for import error (#685) --- ansible_collections/juniper/device/docs/ansible2rst.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py index fd3ab65f..b09af444 100755 --- a/ansible_collections/juniper/device/docs/ansible2rst.py +++ b/ansible_collections/juniper/device/docs/ansible2rst.py @@ -31,7 +31,11 @@ import yaml from six import print_ -from collections import MutableMapping, MutableSet, MutableSequence +try: + from collections import MutableMapping, MutableSet, MutableSequence +except ImportError: + # Python-3.8 or later + from collections.abc import MutableMapping, MutableSet, MutableSequence from ansible.module_utils.six import iteritems, string_types from ansible.parsing.plugin_docs import read_docstring From fab695ddb16b266d79bc6659ca913dd9d52d3dd2 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:42:06 +0530 Subject: [PATCH 417/426] fix for ping packet loss in float (#686) --- ansible_collections/juniper/device/plugins/modules/pmtud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py index 5e9450cc..bbb6832d 100644 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -345,7 +345,7 @@ def main(): results_for_minimal = junos_module.ping(ping_params, acceptable_percent_loss=100, results=results_for_minimal) - if int(results_for_minimal.get('packet_loss', 100)) == 100: + if round(float(results_for_minimal.get('packet_loss', 100))) == 100: results['msg'] = "Basic connectivity to %s failed." % (results['host']) junos_module.exit_json(**results) @@ -368,7 +368,7 @@ def main(): current_results = junos_module.ping(ping_params, acceptable_percent_loss=100, results=current_results) - loss = int(current_results.get('packet_loss', 100)) + loss = round(float(current_results.get('packet_loss', 100))) if loss < 100 and test_size == params['max_size']: # ping success with max test_size, save and break results['failed'] = False From bc4ff8ea30def50e85cbec65e74aa1f005a82fc8 Mon Sep 17 00:00:00 2001 From: chidanandpujar <46497833+chidanandpujar@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:09:34 +0530 Subject: [PATCH 418/426] release v1.0.6 update (#689) --- ansible_collections/juniper/device/galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index 0a8e3e40..86ff1810 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -9,7 +9,7 @@ namespace: juniper name: device # The version of the collection. Must be compatible with semantic versioning -version: 1.0.5 +version: 1.0.6 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md From c0443f60e198133fdb0c593ae95a1a3b1f18bd66 Mon Sep 17 00:00:00 2001 From: Ryan Leonard Date: Tue, 1 Oct 2024 01:25:29 -0700 Subject: [PATCH 419/426] Fix galaxy.yml "documentation" link (#692) --- ansible_collections/juniper/device/galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index 86ff1810..c9e043a8 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -41,7 +41,7 @@ dependencies: {} repository: https://github.com/Juniper/ansible-junos-stdlib # The URL to any online docs -documentation: https://junos-ansible-modules.readthedocs.io +documentation: https://ansible-juniper-collection.readthedocs.io/ # The URL to the homepage of the collection/project homepage: https://github.com/Juniper/ansible-junos-stdlib From 594e805b52c2cc53a8c52095cc464f21a4fbd6ad Mon Sep 17 00:00:00 2001 From: Yannik Sembritzki Date: Mon, 11 Nov 2024 14:03:33 +0100 Subject: [PATCH 420/426] Fix version extraction from filename for ex2300 firmware (fixes #694) (#695) --- ansible_collections/juniper/device/plugins/modules/software.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 8a921e94..ddadae84 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -417,7 +417,7 @@ def parse_version_from_filename(filename): # Known prefixes for filenames which contain Junos software packages. JUNOS_PACKAGE_PREFIXES = ['jbundle', 'jinstall', 'junos-install', 'junos-srx', 'junos-vmhost-install', 'junos-vrr', - 'vmx-bundle'] + 'vmx-bundle', 'junos-arm'] for prefix in JUNOS_PACKAGE_PREFIXES: if filename.startswith(prefix): # Assumes the version string will be prefixed by -. From 576b32bbb2848777eb3d926b75db20a508eacded Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Tue, 12 Nov 2024 18:31:47 +0530 Subject: [PATCH 421/426] whitespace removal (#699) --- .dockerignore | 2 +- ISSUE_TEMPLATE.md | 2 +- LICENSE | 1 - README.md | 10 +- ansible-junos-awx/Makefile | 16 +- ansible-junos-awx/Makefile.variable | 6 +- ansible-junos-awx/README.md | 32 +- .../docs/_static/juniper-junos-modules.css | 2 +- .../juniper/device/docs/ansible2rst.py | 319 +++-- .../juniper/device/docs/command.rst | 4 +- .../juniper/device/docs/conf.py | 168 +-- .../juniper/device/docs/config.rst | 14 +- .../juniper/device/docs/docreq.txt | 2 +- .../juniper/device/docs/facts.rst | 4 +- .../juniper/device/docs/file_copy.rst | 1 - .../juniper/device/docs/jsnapy.rst | 4 +- .../juniper/device/docs/ping.rst | 4 +- .../juniper/device/docs/pmtud.rst | 4 +- .../juniper/device/docs/rpc.rst | 4 +- .../juniper/device/docs/software.rst | 4 +- .../juniper/device/docs/srx_cluster.rst | 4 +- .../juniper/device/docs/system.rst | 4 +- .../juniper/device/docs/table.rst | 88 +- .../juniper/device/plugins/README.md | 2 +- .../device/plugins/action/extract_data.py | 106 +- .../device/plugins/action/file_copy.py | 8 +- .../action/juniper_junos_common_action.py | 8 +- .../juniper/device/plugins/callback/jsnapy.py | 209 +-- .../juniper/device/plugins/connection/pyez.py | 476 +++---- .../plugins/module_utils/configuration.py | 148 ++- .../module_utils/juniper_junos_common.py | 1143 +++++++++-------- .../juniper/device/plugins/modules/command.py | 240 ++-- .../juniper/device/plugins/modules/config.py | 601 +++++---- .../juniper/device/plugins/modules/facts.py | 125 +- .../device/plugins/modules/file_copy.py | 81 +- .../juniper/device/plugins/modules/jsnapy.py | 318 ++--- .../juniper/device/plugins/modules/ping.py | 129 +- .../juniper/device/plugins/modules/pmtud.py | 194 +-- .../juniper/device/plugins/modules/rpc.py | 321 ++--- .../device/plugins/modules/software.py | 471 +++---- .../device/plugins/modules/srx_cluster.py | 172 +-- .../juniper/device/plugins/modules/system.py | 233 ++-- .../juniper/device/plugins/modules/table.py | 273 ++-- entrypoint.sh | 3 +- env-setup | 2 +- setup.py | 38 +- tests/README.md | 14 +- tests/ansible.cfg | 1 - tools/sw_upgrade | 5 +- 49 files changed, 3193 insertions(+), 2827 deletions(-) diff --git a/.dockerignore b/.dockerignore index 31441a7d..8af1a17a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,4 +5,4 @@ test/ tools/ COPYRIGHT env-setup -.vscode \ No newline at end of file +.vscode diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index f5f0b51d..35546af3 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -14,7 +14,7 @@ Module Name --> juniper.device collection and Python libraries version - ``` diff --git a/LICENSE b/LICENSE index e06d2081..5c304d1a 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,3 @@ Apache License WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/README.md b/README.md index ad3ae6be..88360e98 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ ## About -Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS and Junos Evolved). -This collection is hosted on the Ansible Galaxy website under the collection -[juniper.device](https://galaxy.ansible.com/ui/repo/published/juniper/device/). +Juniper Networks supports Ansible for managing devices running the Junos operating system (Junos OS and Junos Evolved). +This collection is hosted on the Ansible Galaxy website under the collection +[juniper.device](https://galaxy.ansible.com/ui/repo/published/juniper/device/). -The `juniper.device` collection includes a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. +The `juniper.device` collection includes a set of Ansible modules that perform specific operational and configuration tasks on devices running Junos OS. These tasks include: installing and upgrading Junos OS, provisioning new Junos devices in the network, loading configuration changes, retrieving information, and resetting, rebooting, or shutting down managed devices. Please refer to the [INSTALLATION](#installation) section for instructions on installing this collection. @@ -41,7 +41,7 @@ This `juniper.device` collection includes the following modules: ### PyEZ Version Requirement -For ansible collection `juniper.device` we will need to install [junos-eznc](https://github.com/Juniper/py-junos-eznc) version 2.6.0 or higher. +For ansible collection `juniper.device` we will need to install [junos-eznc](https://github.com/Juniper/py-junos-eznc) version 2.6.0 or higher. ### Overview of Plugins diff --git a/ansible-junos-awx/Makefile b/ansible-junos-awx/Makefile index cd273ff3..851b500a 100644 --- a/ansible-junos-awx/Makefile +++ b/ansible-junos-awx/Makefile @@ -11,24 +11,24 @@ include Makefile.variable all:prequisite virtual-env ansible-awx docker-start docker-exec ## install ansible-junos-awx - + .PHONY: prequisite prequisite: pip install virtualenv - rm -rf ./awx ./awx-env - + rm -rf ./awx ./awx-env + .PHONY: virtual-env virtual-env: virtualenv awx-env --no-site-packages . awx-env/bin/activate && \ - pip install ansible docker-py + pip install ansible docker-py .PHONY: ansible-awx ansible-awx: . awx-env/bin/activate && \ git clone https://github.com/ansible/awx.git --single-branch --depth 1 - -.PHONY: docker-start + +.PHONY: docker-start docker-start: ifneq '$(PROJECT_DATA_DIR)' '' mkdir -p $(PROJECT_DATA_DIR) @@ -60,14 +60,14 @@ docker-exec: docker exec -it awx_task pip install jsnapy jxmlease junos-eznc docker exec -it awx_task ansible-galaxy install juniper.junos,$(ANSIBLE_JUNOS_VERSION) -p /etc/ansible/roles docker exec -it awx_task /bin/bash -c 'sed -i '/roles_path/s/^#//g' /etc/ansible/ansible.cfg' -ifneq '$(HOST_FILE)' '' +ifneq '$(HOST_FILE)' '' curl -u admin:password --noproxy '*' http://localhost/api/v2/inventories/ --header "Content-Type: application/json" -x POST -d '{"name":"$(INVENTORY_NAME)" , "organization": 1}' docker exec -it awx_task /bin/bash -c 'awx-manage inventory_import --source=/var/lib/awx/projects/hosts --inventory-name=$(INVENTORY_NAME) --overwrite' endif .PHONY: docker-stop docker-stop: ## stop the docker - docker stop awx_task + docker stop awx_task docker stop awx_web docker stop memcached docker stop rabbitmq diff --git a/ansible-junos-awx/Makefile.variable b/ansible-junos-awx/Makefile.variable index 90ab278d..dfbebb71 100644 --- a/ansible-junos-awx/Makefile.variable +++ b/ansible-junos-awx/Makefile.variable @@ -1,6 +1,6 @@ -PROJECT_DATA_DIR = +PROJECT_DATA_DIR = AWX_TASK_TAG = POSTGRES_DATA_DIR = ANSIBLE_JUNOS_VERSION = -HOST_FILE = -INVENTORY_NAME = +HOST_FILE = +INVENTORY_NAME = diff --git a/ansible-junos-awx/README.md b/ansible-junos-awx/README.md index 70afd815..65515018 100644 --- a/ansible-junos-awx/README.md +++ b/ansible-junos-awx/README.md @@ -38,12 +38,12 @@ This will do the following operations: $ make pip install virtualenv Requirement already satisfied: virtualenv in /Library/Python/2.7/site-packages -rm -rf ./awx ./awx-env /private/tmp/pgdocker +rm -rf ./awx ./awx-env /private/tmp/pgdocker virtualenv awx-env --no-site-packages New python executable in /private/tmp/ansible-junos-awx/awx-env/bin/python Installing setuptools, pip, wheel...done. . awx-env/bin/activate && \ - pip install ansible docker-py + pip install ansible docker-py Collecting ansible Collecting docker-py Using cached docker_py-1.10.6-py2.py3-none-any.whl @@ -98,10 +98,10 @@ remote: Total 2596 (delta 603), reused 1034 (delta 237), pack-reused 0 Receiving objects: 100% (2596/2596), 7.48 MiB | 482.00 KiB/s, done. Resolving deltas: 100% (603/603), done. Checking out files: 100% (2317/2317), done. -mkdir -p /private/tmp/pgdocker +mkdir -p /private/tmp/pgdocker . awx-env/bin/activate && \ ansible-playbook -i /private/tmp/ansible-junos-awx/awx/installer/inventory /private/tmp/ansible-junos-awx/awx/installer/install.yml -[DEPRECATION WARNING]: DEFAULT_SUDO_USER option, In favor of become which is a generic framework . This feature will be removed in version 2.8. Deprecation +[DEPRECATION WARNING]: DEFAULT_SUDO_USER option, In favor of become which is a generic framework . This feature will be removed in version 2.8. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. PLAY [Build and deploy AWX] ************************************************************************************************************************************ @@ -114,13 +114,13 @@ included: /private/tmp/ansible-junos-awx/awx/installer/check_vars/tasks/check_do TASK [check_vars : postgres_data_dir should be defined] ******************************************************************************************************** ok: [localhost] => { - "changed": false, + "changed": false, "msg": "All assertions passed" } TASK [check_vars : host_port should be defined] **************************************************************************************************************** ok: [localhost] => { - "changed": false, + "changed": false, "msg": "All assertions passed" } @@ -448,33 +448,33 @@ TASK [local_docker : Start the containers] ************************************* skipping: [localhost] PLAY RECAP ***************************************************************************************************************************************************** -localhost : ok=12 changed=5 unreachable=0 failed=0 +localhost : ok=12 changed=5 unreachable=0 failed=0 sleep 120 docker exec -it awx_task pip install jsnapy jxmlease junos-eznc Collecting jsnapy Downloading jsnapy-1.3.1.tar.gz (50kB) - 100% |################################| 51kB 399kB/s + 100% |################################| 51kB 399kB/s Collecting jxmlease Downloading jxmlease-1.0.1-py2.py3-none-any.whl Collecting junos-eznc Downloading junos_eznc-2.1.7-py2.py3-none-any.whl (150kB) - 100% |################################| 153kB 1.6MB/s + 100% |################################| 153kB 1.6MB/s Collecting colorama (from jsnapy) Downloading colorama-0.3.9-py2.py3-none-any.whl Collecting configparser (from jsnapy) Downloading configparser-3.5.0.tar.gz Collecting pyparsing (from jsnapy) Downloading pyparsing-2.2.0-py2.py3-none-any.whl (56kB) - 100% |################################| 61kB 5.5MB/s + 100% |################################| 61kB 5.5MB/s Collecting icdiff (from jsnapy) Downloading icdiff-1.9.1.tar.gz Collecting future (from jsnapy) Downloading future-0.16.0.tar.gz (824kB) - 100% |################################| 829kB 937kB/s + 100% |################################| 829kB 937kB/s Collecting ncclient>=0.5.3 (from junos-eznc) Downloading ncclient-0.5.3.tar.gz (63kB) - 100% |################################| 71kB 6.5MB/s + 100% |################################| 71kB 6.5MB/s Requirement already satisfied (use --upgrade to upgrade): paramiko>=1.15.2 in /usr/lib/python2.7/site-packages (from junos-eznc) Requirement already satisfied (use --upgrade to upgrade): six in /usr/lib/python2.7/site-packages (from junos-eznc) Collecting scp>=0.7.0 (from junos-eznc) @@ -482,14 +482,14 @@ Collecting scp>=0.7.0 (from junos-eznc) Requirement already satisfied (use --upgrade to upgrade): jinja2>=2.7.1 in /usr/lib/python2.7/site-packages (from junos-eznc) Collecting lxml>=3.2.4 (from junos-eznc) Downloading lxml-4.1.1-cp27-cp27mu-manylinux1_x86_64.whl (5.6MB) - 100% |################################| 5.6MB 229kB/s + 100% |################################| 5.6MB 229kB/s Collecting pyserial (from junos-eznc) Downloading pyserial-3.4-py2.py3-none-any.whl (193kB) - 100% |################################| 194kB 4.3MB/s + 100% |################################| 194kB 4.3MB/s Requirement already satisfied (use --upgrade to upgrade): PyYAML>=3.10 in /usr/lib64/python2.7/site-packages (from junos-eznc) Collecting netaddr (from junos-eznc) Downloading netaddr-0.7.19-py2.py3-none-any.whl (1.6MB) - 100% |################################| 1.6MB 815kB/s + 100% |################################| 1.6MB 815kB/s Requirement already satisfied (use --upgrade to upgrade): setuptools>0.6 in /usr/lib/python2.7/site-packages (from ncclient>=0.5.3->junos-eznc) Requirement already satisfied (use --upgrade to upgrade): cryptography>=1.1 in /usr/lib64/python2.7/site-packages (from paramiko>=1.15.2->junos-eznc) Requirement already satisfied (use --upgrade to upgrade): pyasn1>=0.1.7 in /usr/lib/python2.7/site-packages (from paramiko>=1.15.2->junos-eznc) @@ -569,7 +569,7 @@ ansible-galaxy respectively. ``` Example: -PROJECT_DATA_DIR = +PROJECT_DATA_DIR = AWX_TASK_TAG = POSTGRES_DATA_DIR = ANSIBLE_JUNOS_VERSION = diff --git a/ansible_collections/juniper/device/docs/_static/juniper-junos-modules.css b/ansible_collections/juniper/device/docs/_static/juniper-junos-modules.css index 2723c307..29d867d0 100644 --- a/ansible_collections/juniper/device/docs/_static/juniper-junos-modules.css +++ b/ansible_collections/juniper/device/docs/_static/juniper-junos-modules.css @@ -4,4 +4,4 @@ td, th { code { color: #3a87ad; background-color: #d9edf7; -} \ No newline at end of file +} diff --git a/ansible_collections/juniper/device/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py index b09af444..5eef8faa 100755 --- a/ansible_collections/juniper/device/docs/ansible2rst.py +++ b/ansible_collections/juniper/device/docs/ansible2rst.py @@ -21,27 +21,28 @@ # along with Ansible. If not, see . # +import cgi +import datetime import os import re import sys -import datetime -import cgi from distutils.version import LooseVersion -from jinja2 import Environment, FileSystemLoader + import yaml +from jinja2 import Environment, FileSystemLoader from six import print_ try: - from collections import MutableMapping, MutableSet, MutableSequence + from collections import MutableMapping, MutableSequence, MutableSet except ImportError: # Python-3.8 or later from collections.abc import MutableMapping, MutableSet, MutableSequence +from ansible.module_utils._text import to_bytes from ansible.module_utils.six import iteritems, string_types from ansible.parsing.plugin_docs import read_docstring from ansible.parsing.yaml.loader import AnsibleLoader from ansible.plugins.loader import fragment_loader -from ansible.module_utils._text import to_bytes try: from html import escape as html_escape @@ -52,6 +53,7 @@ def html_escape(text, quote=True): return cgi.escape(text, quote) + from ansible import __version__ as ansible_version ##################################################################################### @@ -76,6 +78,7 @@ def html_escape(text, quote=True): ##################################################################################### + def too_old(added): if not added: return False @@ -88,35 +91,45 @@ def too_old(added): return False return added_float < TO_OLD_TO_BE_NOTABLE + ##################################################################################### + def rst_ify(text): - ''' convert symbols like I(this is in italics) to valid restructured text ''' + """convert symbols like I(this is in italics) to valid restructured text""" try: - t = _ITALIC.sub(r'*' + r"\1" + r"*", text) - t = _BOLD.sub(r'**' + r"\1" + r"**", t) - t = _MODULE.sub(r':ref:`' + r"\1 <\1>" + r"`", t) - t = _URL_W_TEXT.sub(r'`' + r"\1" + r" <" + r"\2" + r">`_", t) - t = _URL.sub(r'`' + r"\1" + r" <" + r"\1" + r">`_", t) - t = _CONST.sub(r'``' + r"\1" + r"``", t) + t = _ITALIC.sub(r"*" + r"\1" + r"*", text) + t = _BOLD.sub(r"**" + r"\1" + r"**", t) + t = _MODULE.sub(r":ref:`" + r"\1 <\1>" + r"`", t) + t = _URL_W_TEXT.sub(r"`" + r"\1" + r" <" + r"\2" + r">`_", t) + t = _URL.sub(r"`" + r"\1" + r" <" + r"\1" + r">`_", t) + t = _CONST.sub(r"``" + r"\1" + r"``", t) except Exception as e: raise AnsibleError("Could not process (%s) : %s" % (str(text), str(e))) return t + ##################################################################################### + def module_to_html(matchobj): if matchobj.group(1) is not None: module_name = matchobj.group(1) - module_href = _UNDERSCORE.sub('-', module_name) - return '' + \ - module_name + '' - return '' + module_href = _UNDERSCORE.sub("-", module_name) + return ( + '' + + module_name + + "" + ) + return "" + def html_ify(text): - ''' convert symbols like I(this is in italics) to valid HTML ''' + """convert symbols like I(this is in italics) to valid HTML""" t = html_escape(text) t = _ITALIC.sub("" + r"\1" + "", t) @@ -133,62 +146,68 @@ def html_ify(text): def rst_fmt(text, fmt): - ''' helper for Jinja2 to do format strings ''' + """helper for Jinja2 to do format strings""" return fmt % (text) + ##################################################################################### def rst_xline(width, char="="): - ''' return a restructured text line of a given length ''' + """return a restructured text line of a given length""" return char * width + ##################################################################################### def write_data(text, outputname, module, output_dir=None): - ''' dumps module output to a file or the screen, as requested ''' + """dumps module output to a file or the screen, as requested""" if output_dir is not None: if not os.path.exists(output_dir): os.makedirs(output_dir) fname = os.path.join(output_dir, outputname % (module)) - with open(fname, 'wb') as f: + with open(fname, "wb") as f: f.write(to_bytes(text)) else: print(text) + ##################################################################################### def jinja2_environment(template_dir, template_type): - env = Environment(loader=FileSystemLoader(template_dir), - variable_start_string="@{", - variable_end_string="}@", - trim_blocks=True, - ) - env.globals['xline'] = rst_xline - - if template_type == 'rst': - env.filters['convert_symbols_to_format'] = rst_ify - env.filters['html_ify'] = html_ify - env.filters['fmt'] = rst_fmt - env.filters['xline'] = rst_xline - template = env.get_template('rst.j2') + env = Environment( + loader=FileSystemLoader(template_dir), + variable_start_string="@{", + variable_end_string="}@", + trim_blocks=True, + ) + env.globals["xline"] = rst_xline + + if template_type == "rst": + env.filters["convert_symbols_to_format"] = rst_ify + env.filters["html_ify"] = html_ify + env.filters["fmt"] = rst_fmt + env.filters["xline"] = rst_xline + template = env.get_template("rst.j2") outputname = "%s.rst" else: raise Exception("unknown module format type: %s" % template_type) return env, template, outputname + ##################################################################################### + def add_fragments(doc, filename): - fragments = doc.get('extends_documentation_fragment', []) + fragments = doc.get("extends_documentation_fragment", []) if isinstance(fragments, string_types): fragments = [fragments] @@ -197,28 +216,35 @@ def add_fragments(doc, filename): # to pull the fragment from, using dot notation as a separator for fragment_slug in fragments: fragment_slug = fragment_slug.lower() - if '.' in fragment_slug: - fragment_name, fragment_var = fragment_slug.split('.', 1) + if "." in fragment_slug: + fragment_name, fragment_var = fragment_slug.split(".", 1) fragment_var = fragment_var.upper() else: - fragment_name, fragment_var = fragment_slug, 'DOCUMENTATION' + fragment_name, fragment_var = fragment_slug, "DOCUMENTATION" - fragment_loader.add_directory('../plugins/module_utils/') + fragment_loader.add_directory("../plugins/module_utils/") fragment_class = fragment_loader.get(fragment_name) assert fragment_class is not None - fragment_yaml = getattr(fragment_class, fragment_var, '{}') + fragment_yaml = getattr(fragment_class, fragment_var, "{}") fragment = AnsibleLoader(fragment_yaml, file_name=filename).get_single_data() - if 'notes' in fragment: - notes = fragment.pop('notes') + if "notes" in fragment: + notes = fragment.pop("notes") if notes: - if 'notes' not in doc: - doc['notes'] = [] - doc['notes'].extend(notes) - - if 'options' not in fragment and 'logging_options' not in fragment and 'connection_options' not in fragment: - raise Exception("missing options in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename)) + if "notes" not in doc: + doc["notes"] = [] + doc["notes"].extend(notes) + + if ( + "options" not in fragment + and "logging_options" not in fragment + and "connection_options" not in fragment + ): + raise Exception( + "missing options in fragment (%s), possibly misformatted?: %s" + % (fragment_name, filename) + ) for key, value in iteritems(fragment): if key in doc: @@ -230,7 +256,10 @@ def add_fragments(doc, filename): elif isinstance(doc[key], MutableSequence): value = sorted(frozenset(value + doc[key])) else: - raise Exception("Attempt to extend a documentation fragement (%s) of unknown type: %s" % (fragment_name, filename)) + raise Exception( + "Attempt to extend a documentation fragement (%s) of unknown type: %s" + % (fragment_name, filename) + ) doc[key] = value @@ -245,30 +274,33 @@ def get_docstring(filename, verbose=False): # if data.get('doc', False): # add_fragments(data['doc'], filename) - return data['doc'], data['plainexamples'], data['returndocs'], data['metadata'] + return data["doc"], data["plainexamples"], data["returndocs"], data["metadata"] + def process_module(fname, template, outputname, aliases=None): module_name = fname.replace(".py", "") print_("Processing module %s" % (MODULEDIR + fname)) - doc, examples, returndocs, metadata = get_docstring(MODULEDIR + fname, - verbose=True) + doc, examples, returndocs, metadata = get_docstring(MODULEDIR + fname, verbose=True) # add some defaults for plugins that dont have most of the info - doc['module'] = doc.get('module', module_name) - doc['version_added'] = doc.get('version_added', 'historical') - doc['plugin_type'] = 'module' + doc["module"] = doc.get("module", module_name) + doc["version_added"] = doc.get("version_added", "historical") + doc["plugin_type"] = "module" - required_fields = ('short_description',) + required_fields = ("short_description",) for field in required_fields: if field not in doc: print_("%s: WARNING: MODULE MISSING field '%s'" % (fname, field)) - not_nullable_fields = ('short_description',) + not_nullable_fields = ("short_description",) for field in not_nullable_fields: - if field in doc and doc[field] in (None, ''): - print_("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field])) + if field in doc and doc[field] in (None, ""): + print_( + "%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" + % (fname, field, doc[field]) + ) # # The present template gets everything from doc so we spend most of this @@ -276,134 +308,160 @@ def process_module(fname, template, outputname, aliases=None): # if aliases: - doc['aliases'] = aliases + doc["aliases"] = aliases # don't show version added information if it's too old to be called out added = 0 - if doc['version_added'] == 'historical': - del doc['version_added'] + if doc["version_added"] == "historical": + del doc["version_added"] else: - added = doc['version_added'] + added = doc["version_added"] # Strip old version_added for the module if too_old(added): - del doc['version_added'] + del doc["version_added"] option_names = [] - if 'options' in doc and doc['options']: - for (k, v) in iteritems(doc['options']): + if "options" in doc and doc["options"]: + for k, v in iteritems(doc["options"]): # Error out if there's no description - if 'description' not in doc['options'][k]: - raise AnsibleError("Missing required description for option %s in %s " % (k, module)) + if "description" not in doc["options"][k]: + raise AnsibleError( + "Missing required description for option %s in %s " % (k, module) + ) # Error out if required isn't a boolean (people have been putting # information on when something is required in here. Those need # to go in the description instead). - required_value = doc['options'][k].get('required', False) + required_value = doc["options"][k].get("required", False) if not isinstance(required_value, bool): - raise AnsibleError("Invalid required value '%s' for option '%s' in '%s' (must be truthy)" % ( - required_value, k, module)) + raise AnsibleError( + "Invalid required value '%s' for option '%s' in '%s' (must be truthy)" + % (required_value, k, module) + ) # Strip old version_added information for options - if 'version_added' in doc['options'][k] and too_old(doc['options'][k]['version_added']): - del doc['options'][k]['version_added'] + if "version_added" in doc["options"][k] and too_old( + doc["options"][k]["version_added"] + ): + del doc["options"][k]["version_added"] # Make sure description is a list of lines for later formatting - if not isinstance(doc['options'][k]['description'], list): - doc['options'][k]['description'] = [doc['options'][k]['description']] + if not isinstance(doc["options"][k]["description"], list): + doc["options"][k]["description"] = [doc["options"][k]["description"]] option_names.append(k) option_names.sort() - doc['option_keys'] = option_names + doc["option_keys"] = option_names connection_option_names = [] - if 'connection_options' in doc and doc['connection_options']: - for (k, v) in iteritems(doc['connection_options']): + if "connection_options" in doc and doc["connection_options"]: + for k, v in iteritems(doc["connection_options"]): # Error out if there's no description - if 'description' not in doc['connection_options'][k]: - raise AnsibleError("Missing required description for connection_option %s in %s " % (k, module)) + if "description" not in doc["connection_options"][k]: + raise AnsibleError( + "Missing required description for connection_option %s in %s " + % (k, module) + ) # Error out if required isn't a boolean (people have been putting # information on when something is required in here. Those need # to go in the description instead). - required_value = doc['connection_options'][k].get('required', False) + required_value = doc["connection_options"][k].get("required", False) if not isinstance(required_value, bool): - raise AnsibleError("Invalid required value '%s' for connection_option '%s' in '%s' (must be truthy)" % - (required_value, k, module)) + raise AnsibleError( + "Invalid required value '%s' for connection_option '%s' in '%s' (must be truthy)" + % (required_value, k, module) + ) # Strip old version_added information for options - if ('version_added' in doc['connection_options'][k] and - too_old(doc['connection_options'][k]['version_added'])): - del doc['connection_options'][k]['version_added'] + if "version_added" in doc["connection_options"][k] and too_old( + doc["connection_options"][k]["version_added"] + ): + del doc["connection_options"][k]["version_added"] # Make sure description is a list of lines for later formatting - if not isinstance(doc['connection_options'][k]['description'], list): - doc['connection_options'][k]['description'] = [doc['connection_options'][k]['description']] + if not isinstance(doc["connection_options"][k]["description"], list): + doc["connection_options"][k]["description"] = [ + doc["connection_options"][k]["description"] + ] connection_option_names.append(k) connection_option_names.sort() - doc['connection_option_keys'] = connection_option_names + doc["connection_option_keys"] = connection_option_names logging_option_names = [] - if 'logging_options' in doc and doc['logging_options']: - for (k, v) in iteritems(doc['logging_options']): + if "logging_options" in doc and doc["logging_options"]: + for k, v in iteritems(doc["logging_options"]): # Error out if there's no description - if 'description' not in doc['logging_options'][k]: - raise AnsibleError("Missing required description for logging_option %s in %s " % (k, module)) + if "description" not in doc["logging_options"][k]: + raise AnsibleError( + "Missing required description for logging_option %s in %s " + % (k, module) + ) # Error out if required isn't a boolean (people have been putting # information on when something is required in here. Those need # to go in the description instead). - required_value = doc['logging_options'][k].get('required', False) + required_value = doc["logging_options"][k].get("required", False) if not isinstance(required_value, bool): - raise AnsibleError("Invalid required value '%s' for logging_option '%s' in '%s' (must be truthy)" % - (required_value, k, module)) + raise AnsibleError( + "Invalid required value '%s' for logging_option '%s' in '%s' (must be truthy)" + % (required_value, k, module) + ) # Strip old version_added information for options - if ('version_added' in doc['logging_options'][k] and - too_old(doc['logging_options'][k]['version_added'])): - del doc['logging_options'][k]['version_added'] + if "version_added" in doc["logging_options"][k] and too_old( + doc["logging_options"][k]["version_added"] + ): + del doc["logging_options"][k]["version_added"] # Make sure description is a list of lines for later formatting - if not isinstance(doc['logging_options'][k]['description'], list): - doc['logging_options'][k]['description'] = [doc['logging_options'][k]['description']] + if not isinstance(doc["logging_options"][k]["description"], list): + doc["logging_options"][k]["description"] = [ + doc["logging_options"][k]["description"] + ] logging_option_names.append(k) logging_option_names.sort() - doc['logging_option_keys'] = logging_option_names + doc["logging_option_keys"] = logging_option_names - doc['filename'] = fname - doc['docuri'] = doc['module'].replace('_', '-') - doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') - doc['ansible_version'] = ansible_version - doc['plainexamples'] = examples # plain text - doc['metadata'] = metadata + doc["filename"] = fname + doc["docuri"] = doc["module"].replace("_", "-") + doc["now_date"] = datetime.date.today().strftime("%Y-%m-%d") + doc["ansible_version"] = ansible_version + doc["plainexamples"] = examples # plain text + doc["metadata"] = metadata if returndocs: try: - doc['returndocs'] = yaml.safe_load(returndocs) - returndocs_keys = list(doc['returndocs'].keys()) + doc["returndocs"] = yaml.safe_load(returndocs) + returndocs_keys = list(doc["returndocs"].keys()) returndocs_keys.sort() - doc['returndocs_keys'] = returndocs_keys + doc["returndocs_keys"] = returndocs_keys except Exception as e: - print_("%s:%s:yaml error:%s:returndocs=%s" % (fname, module_name, e, returndocs)) - doc['returndocs'] = None - doc['returndocs_keys'] = None + print_( + "%s:%s:yaml error:%s:returndocs=%s" + % (fname, module_name, e, returndocs) + ) + doc["returndocs"] = None + doc["returndocs_keys"] = None else: - doc['returndocs'] = None - doc['returndocs_keys'] = None + doc["returndocs"] = None + doc["returndocs_keys"] = None - doc['author'] = doc.get('author', ['UNKNOWN']) - if isinstance(doc['author'], string_types): - doc['author'] = [doc['author']] + doc["author"] = doc.get("author", ["UNKNOWN"]) + if isinstance(doc["author"], string_types): + doc["author"] = [doc["author"]] # here is where we build the table of contents... text = template.render(doc) # write_data(text, outputname, module_name, OUTPUTDIR) + ##################################################################################### def main(): - env, template, outputname = jinja2_environment('.', 'rst') + env, template, outputname = jinja2_environment(".", "rst") module_names = [] for module in os.listdir(MODULEDIR): @@ -413,17 +471,18 @@ def main(): index_file_path = os.path.join(OUTPUTDIR, "index.rst") index_file = open(index_file_path, "w") - index_file.write('juniper.device Ansible Modules\n') - index_file.write('==============================\n') - index_file.write('\n') - index_file.write('Contents:\n') - index_file.write('\n') - index_file.write('.. toctree::\n') - index_file.write(' :maxdepth: 1\n') - index_file.write('\n') + index_file.write("juniper.device Ansible Modules\n") + index_file.write("==============================\n") + index_file.write("\n") + index_file.write("Contents:\n") + index_file.write("\n") + index_file.write(".. toctree::\n") + index_file.write(" :maxdepth: 1\n") + index_file.write("\n") for module_name in module_names: - index_file.write(' %s\n' % module_name) + index_file.write(" %s\n" % module_name) + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/docs/command.rst b/ansible_collections/juniper/device/docs/command.rst index aa1ba5f4..daf7fe62 100644 --- a/ansible_collections/juniper/device/docs/command.rst +++ b/ansible_collections/juniper/device/docs/command.rst @@ -368,7 +368,7 @@ Examples :: - + - name: 'Explicit host argument' hosts: junos connection: local @@ -564,5 +564,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/conf.py b/ansible_collections/juniper/device/docs/conf.py index cb4397e1..906d8a5b 100644 --- a/ansible_collections/juniper/device/docs/conf.py +++ b/ansible_collections/juniper/device/docs/conf.py @@ -12,8 +12,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os +import sys + import sphinx_bootstrap_theme @@ -24,18 +25,19 @@ def setup(app): # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(1, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath(".")) +sys.path.insert(1, os.path.abspath("..")) # Import ansible2rst so that RST files can be generated. import ansible2rst + # Call ansible2rst.main() to generate RST files. ansible2rst.main() # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -43,26 +45,27 @@ def setup(app): extensions = [] # Add any paths that contain templates here, relative to this directory. -#templates_path = ['_templates'] +# templates_path = ['_templates'] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Junos Ansible Collection Modules' -copyright = u'2014-2017, Juniper Networks, Inc' +project = "Junos Ansible Collection Modules" +copyright = "2014-2017, Juniper Networks, Inc" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # from version import VERSION + # The short X.Y version. version = VERSION # The full version, including alpha/beta/rc tags. @@ -70,13 +73,13 @@ def setup(app): # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -84,167 +87,170 @@ def setup(app): # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'bootstrap' +html_theme = "bootstrap" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - 'bootswatch_theme': "spacelab", - 'navbar_sidebarrel': False, - 'navbar_site_name': "Modules", - 'source_link_position': "footer", - 'navbar_links': [ + "bootswatch_theme": "spacelab", + "navbar_sidebarrel": False, + "navbar_site_name": "Modules", + "source_link_position": "footer", + "navbar_links": [ ("Wiki", "https://techwiki.juniper.net/Automation_Scripting", True), ("Forum", "http://groups.google.com/group/junos-python-ez", True), - ], - } + ], +} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = '_static/juniper.png' +html_logo = "_static/juniper.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { - '**': [ - 'globaltoc.html', - ] + "**": [ + "globaltoc.html", + ] } # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'JunosAnsibleCollectionModulesdoc' +htmlhelp_basename = "JunosAnsibleCollectionModulesdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'JunosAnsibleCollectionModules.tex', u'Junos Ansible Collection Modules Documentation', - u'Juniper Networks, Inc.', 'manual'), + ( + "index", + "JunosAnsibleCollectionModules.tex", + "Junos Ansible Collection Modules Documentation", + "Juniper Networks, Inc.", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -252,12 +258,17 @@ def setup(app): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'junosansiblecollectionmodules', u'Junos Ansible Collection Modules Documentation', - [u'Juniper Networks, Inc.'], 1) + ( + "index", + "junosansiblecollectionmodules", + "Junos Ansible Collection Modules Documentation", + ["Juniper Networks, Inc."], + 1, + ) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -266,20 +277,25 @@ def setup(app): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'JunosAnsibleCollectionModules', u'Junos Ansible Collection Modules Documentation', - u'Juniper Networks, Inc.', 'JunosAnsibleCollectionModules', 'Ansible Modules for ' - 'Junos', - 'Miscellaneous'), + ( + "index", + "JunosAnsibleCollectionModules", + "Junos Ansible Collection Modules Documentation", + "Juniper Networks, Inc.", + "JunosAnsibleCollectionModules", + "Ansible Modules for " "Junos", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/ansible_collections/juniper/device/docs/config.rst b/ansible_collections/juniper/device/docs/config.rst index c31cb80d..8d1aac89 100644 --- a/ansible_collections/juniper/device/docs/config.rst +++ b/ansible_collections/juniper/device/docs/config.rst @@ -25,7 +25,7 @@ Synopsis candidate configuration database. If opening the private configuration database fails the module fails and reports an error. #. Load configuration data into the candidate configuration database. - + * Configuration data may be loaded using the *load* or *rollback* options. If either of these options are specified, new configuration data is loaded. If neither option is specified, this step is skipped. @@ -36,7 +36,7 @@ Synopsis * The value of the *load* option defines the type of load which is performed. * The source of the new configuration data is one of the following: - + * *src* - A file path on the local Ansible control machine. * *lines* - A list of strings containing the configuration data. * *template* - A file path to a Jinja2 template on the local @@ -58,7 +58,7 @@ Synopsis fails, and an error is reported. #. Determine differences between the candidate and committed configuration databases. - + * If step 2 was not skipped, and the *diff* option is ``true``, the default, perform a diff between the candidate and committed configuration databases. @@ -68,7 +68,7 @@ Synopsis generated configuration difference in the *diff* and *diff_lines* keys of the module's response. #. Retrieve the configuration database from the Junos device. - + * If the *retrieve* option is specified, retrieve the configuration database specified by the *retrieve* value from the target Junos device to the local Ansible control machine. @@ -98,7 +98,7 @@ Synopsis option is specified, wait *check_commit_wait* seconds before performing the commit. #. Close the candidate configuration database. - + * Close and discard the candidate configuration database. * If the *config_mode* option has a value of ``exclusive``, the default, unlock the candidate configuration database. @@ -708,7 +708,7 @@ Examples :: - + --- - name: 'Explicit host argument' hosts: junos @@ -1016,5 +1016,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/docreq.txt b/ansible_collections/juniper/device/docs/docreq.txt index cbd83c58..2a15208e 100644 --- a/ansible_collections/juniper/device/docs/docreq.txt +++ b/ansible_collections/juniper/device/docs/docreq.txt @@ -4,4 +4,4 @@ six jxmlease xmltodict junos-eznc >= 2.5.4 -jsnapy>=1.3.4 \ No newline at end of file +jsnapy>=1.3.4 diff --git a/ansible_collections/juniper/device/docs/facts.rst b/ansible_collections/juniper/device/docs/facts.rst index e02ab78d..1c5a272a 100644 --- a/ansible_collections/juniper/device/docs/facts.rst +++ b/ansible_collections/juniper/device/docs/facts.rst @@ -329,7 +329,7 @@ Examples :: - + --- - name: 'Explicit host argument' hosts: junos @@ -507,5 +507,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/file_copy.rst b/ansible_collections/juniper/device/docs/file_copy.rst index 38b9d12a..74913ac0 100644 --- a/ansible_collections/juniper/device/docs/file_copy.rst +++ b/ansible_collections/juniper/device/docs/file_copy.rst @@ -127,4 +127,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - diff --git a/ansible_collections/juniper/device/docs/jsnapy.rst b/ansible_collections/juniper/device/docs/jsnapy.rst index 63e1bc75..865fc6cb 100644 --- a/ansible_collections/juniper/device/docs/jsnapy.rst +++ b/ansible_collections/juniper/device/docs/jsnapy.rst @@ -349,7 +349,7 @@ Examples :: - + --- - name: Examples of jsnapy hosts: junos-all @@ -495,5 +495,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/ping.rst b/ansible_collections/juniper/device/docs/ping.rst index 72516d37..b647dccd 100644 --- a/ansible_collections/juniper/device/docs/ping.rst +++ b/ansible_collections/juniper/device/docs/ping.rst @@ -416,7 +416,7 @@ Examples :: - + --- - name: Examples of ping hosts: junos-all @@ -769,5 +769,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/pmtud.rst b/ansible_collections/juniper/device/docs/pmtud.rst index bedca7b4..8f165cc2 100644 --- a/ansible_collections/juniper/device/docs/pmtud.rst +++ b/ansible_collections/juniper/device/docs/pmtud.rst @@ -373,7 +373,7 @@ Examples :: - + --- - name: Examples of pmtud hosts: junos-all @@ -557,5 +557,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/rpc.rst b/ansible_collections/juniper/device/docs/rpc.rst index 6f85f873..94a79e14 100644 --- a/ansible_collections/juniper/device/docs/rpc.rst +++ b/ansible_collections/juniper/device/docs/rpc.rst @@ -418,7 +418,7 @@ Examples :: - + --- - name: 'Explicit host argument' hosts: junos @@ -635,5 +635,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/software.rst b/ansible_collections/juniper/device/docs/software.rst index 69730ccb..a25aacac 100644 --- a/ansible_collections/juniper/device/docs/software.rst +++ b/ansible_collections/juniper/device/docs/software.rst @@ -567,7 +567,7 @@ Examples :: - + --- - name: 'Explicit host argument' hosts: junos @@ -682,5 +682,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/srx_cluster.rst b/ansible_collections/juniper/device/docs/srx_cluster.rst index 207b1799..f5993882 100644 --- a/ansible_collections/juniper/device/docs/srx_cluster.rst +++ b/ansible_collections/juniper/device/docs/srx_cluster.rst @@ -341,7 +341,7 @@ Examples :: - + --- - name: Manipulate the SRX cluster configuration of Junos SRX devices hosts: junos-all @@ -450,5 +450,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/system.rst b/ansible_collections/juniper/device/docs/system.rst index 0a1a9d7d..661f3abb 100644 --- a/ansible_collections/juniper/device/docs/system.rst +++ b/ansible_collections/juniper/device/docs/system.rst @@ -397,7 +397,7 @@ Examples :: - + --- - name: 'Explicit host argument' hosts: junos @@ -561,5 +561,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/docs/table.rst b/ansible_collections/juniper/device/docs/table.rst index bfc323ed..fe2d5fb2 100644 --- a/ansible_collections/juniper/device/docs/table.rst +++ b/ansible_collections/juniper/device/docs/table.rst @@ -359,7 +359,7 @@ Examples :: - + --- - name: Retrieve data from a Junos device using a PyEZ table/view. hosts: junos-all @@ -456,88 +456,88 @@ Return Values # when response_type == 'list_of_dicts' [ { - "local_int": "ge-0/0/3", - "local_parent": "-", - "remote_chassis_id": "00:05:86:08:d4:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/0", - "remote_sysname": "r5", + "local_int": "ge-0/0/3", + "local_parent": "-", + "remote_chassis_id": "00:05:86:08:d4:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/0", + "remote_sysname": "r5", "remote_type": "Mac address" - }, + }, { - "local_int": "ge-0/0/0", - "local_parent": "-", - "remote_chassis_id": "00:05:86:18:f3:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/2", - "remote_sysname": "r4", + "local_int": "ge-0/0/0", + "local_parent": "-", + "remote_chassis_id": "00:05:86:18:f3:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/2", + "remote_sysname": "r4", "remote_type": "Mac address" } ] # when response_type == 'juniper_items' [ [ - "ge-0/0/3", + "ge-0/0/3", [ [ - "local_parent", + "local_parent", "-" - ], + ], [ - "remote_port_id", + "remote_port_id", "ge-0/0/0" - ], + ], [ - "remote_chassis_id", + "remote_chassis_id", "00:05:86:08:d4:c0" - ], + ], [ - "remote_port_desc", + "remote_port_desc", null - ], + ], [ - "remote_type", + "remote_type", "Mac address" - ], + ], [ - "local_int", + "local_int", "ge-0/0/3" - ], + ], [ - "remote_sysname", + "remote_sysname", "r5" ] ] - ], + ], [ - "ge-0/0/0", + "ge-0/0/0", [ [ - "local_parent", + "local_parent", "-" - ], + ], [ - "remote_port_id", + "remote_port_id", "ge-0/0/2" - ], + ], [ - "remote_chassis_id", + "remote_chassis_id", "00:05:86:18:f3:c0" - ], + ], [ - "remote_port_desc", + "remote_port_desc", null - ], + ], [ - "remote_type", + "remote_type", "Mac address" - ], + ], [ - "local_int", + "local_int", "ge-0/0/0" - ], + ], [ - "remote_sysname", + "remote_sysname", "r4" ] ] @@ -572,5 +572,3 @@ Status ~~~~~~ This module is flagged as **stableinterface** which means that the maintainers for this module guarantee that no backward incompatible interface changes will be made. - - diff --git a/ansible_collections/juniper/device/plugins/README.md b/ansible_collections/juniper/device/plugins/README.md index 6541cf7c..06857260 100644 --- a/ansible_collections/juniper/device/plugins/README.md +++ b/ansible_collections/juniper/device/plugins/README.md @@ -28,4 +28,4 @@ Here is an example directory of the majority of plugins currently supported by A └── vars ``` -A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/2.9/plugins/plugins.html). \ No newline at end of file +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible/2.9/plugins/plugins.html). diff --git a/ansible_collections/juniper/device/plugins/action/extract_data.py b/ansible_collections/juniper/device/plugins/action/extract_data.py index faff5a2b..bda01219 100644 --- a/ansible_collections/juniper/device/plugins/action/extract_data.py +++ b/ansible_collections/juniper/device/plugins/action/extract_data.py @@ -32,25 +32,37 @@ # from __future__ import absolute_import, division, print_function + import os connection_spec_fallbacks = { - 'host': ['host', 'hostname', 'ip', 'ansible_host', 'inventory_hostname'], - 'user': ['user', 'username', 'ansible_connection_user', 'ansible_ssh_user', 'ansible_user'], - 'passwd': ['passwd', 'password', 'ansible_ssh_pass', 'ansible_pass'], - 'port': ['port', 'ansible_ssh_port', 'ansible_port'], - 'ssh_private_key_file': ['ssh_private_key_file', 'ansible_ssh_private_key_file', - 'ansible_private_key_file', 'ssh_keyfile'], - 'ssh_config': ['ssh_config'], - 'cs_user': ['cs_user', 'console_username'], - 'cs_passwd': ['cs_passwd', 'console_password'], - 'attempts': ['attempts'], - 'baud': ['baud'], - 'console': ['console'], - 'mode': ['mode'], - 'timeout': ['timeout', 'ansible_timeout'] + "host": ["host", "hostname", "ip", "ansible_host", "inventory_hostname"], + "user": [ + "user", + "username", + "ansible_connection_user", + "ansible_ssh_user", + "ansible_user", + ], + "passwd": ["passwd", "password", "ansible_ssh_pass", "ansible_pass"], + "port": ["port", "ansible_ssh_port", "ansible_port"], + "ssh_private_key_file": [ + "ssh_private_key_file", + "ansible_ssh_private_key_file", + "ansible_private_key_file", + "ssh_keyfile", + ], + "ssh_config": ["ssh_config"], + "cs_user": ["cs_user", "console_username"], + "cs_passwd": ["cs_passwd", "console_password"], + "attempts": ["attempts"], + "baud": ["baud"], + "console": ["console"], + "mode": ["mode"], + "timeout": ["timeout", "ansible_timeout"], } + class ExtractData: def extract(self, tmp=None, task_vars=None): # The new connection arguments based on fallback/defaults. @@ -58,25 +70,25 @@ def extract(self, tmp=None, task_vars=None): connection_args = self._task.args - self._task.args['_connection'] = self._play_context.connection - new_connection_args['_connection'] = self._play_context.connection - + self._task.args["_connection"] = self._play_context.connection + new_connection_args["_connection"] = self._play_context.connection + # The environment variables used by Ansible Tower - if 'user' not in connection_args: - net_user = os.getenv('ANSIBLE_NET_USERNAME') + if "user" not in connection_args: + net_user = os.getenv("ANSIBLE_NET_USERNAME") if net_user is not None: - new_connection_args['user'] = net_user - connection_args['user'] = net_user - if 'passwd' not in connection_args: - net_passwd = os.getenv('ANSIBLE_NET_PASSWORD') + new_connection_args["user"] = net_user + connection_args["user"] = net_user + if "passwd" not in connection_args: + net_passwd = os.getenv("ANSIBLE_NET_PASSWORD") if net_passwd is not None: - new_connection_args['passwd'] = net_passwd - connection_args['passwd'] = net_passwd - if 'ssh_private_key_file' not in connection_args: - net_key = os.getenv('ANSIBLE_NET_SSH_KEYFILE') + new_connection_args["passwd"] = net_passwd + connection_args["passwd"] = net_passwd + if "ssh_private_key_file" not in connection_args: + net_key = os.getenv("ANSIBLE_NET_SSH_KEYFILE") if net_key is not None: - new_connection_args['ssh_private_key_file'] = net_key - connection_args['ssh_private_key_file'] = net_key + new_connection_args["ssh_private_key_file"] = net_key + connection_args["ssh_private_key_file"] = net_key # The values set by Ansible command line arguments, configuration # settings, or environment variables. @@ -87,38 +99,42 @@ def extract(self, tmp=None, task_vars=None): new_connection_args[key] = task_vars[task_var_key] # check if the value is in form of a variable {{var}} # In case of variables, resolve it to the value - index = str(new_connection_args[key]).find('{{') + index = str(new_connection_args[key]).find("{{") if index == 0: tempKey = new_connection_args[key][2:-2].strip() new_connection_args[key] = task_vars[tempKey] break # Fix for ansible-core>=2.13 when -u, -k or --private-key are command line arguments - if 'user' not in connection_args: + if "user" not in connection_args: if self._play_context.remote_user is not None: - new_connection_args['user'] = self._play_context.remote_user - connection_args['user'] = self._play_context.remote_user - if 'passwd' not in connection_args: + new_connection_args["user"] = self._play_context.remote_user + connection_args["user"] = self._play_context.remote_user + if "passwd" not in connection_args: if self._play_context.password is not None: - new_connection_args['passwd'] = self._play_context.password - connection_args['passwd'] = self._play_context.password - if 'ssh_private_key_file' not in connection_args: + new_connection_args["passwd"] = self._play_context.password + connection_args["passwd"] = self._play_context.password + if "ssh_private_key_file" not in connection_args: if self._play_context.private_key_file is not None: - new_connection_args['ssh_private_key_file'] = self._play_context.private_key_file - connection_args['ssh_private_key_file'] = self._play_context.private_key_file + new_connection_args["ssh_private_key_file"] = ( + self._play_context.private_key_file + ) + connection_args["ssh_private_key_file"] = ( + self._play_context.private_key_file + ) # Backwards compatible behavior to fallback to USER env. variable. - if 'user' not in connection_args and 'user' not in new_connection_args: - user = os.getenv('USER') + if "user" not in connection_args and "user" not in new_connection_args: + user = os.getenv("USER") if user is not None: - new_connection_args['user'] = user + new_connection_args["user"] = user self._task.args.update(new_connection_args) # Pass the hidden _module_utils_path option module_utils_path = os.path.normpath(os.path.dirname(__file__)) - self._task.args['_module_utils_path'] = module_utils_path + self._task.args["_module_utils_path"] = module_utils_path # Pass the hidden _module_name option - self._task.args['_module_name'] = self._task.action + self._task.args["_module_name"] = self._task.action # Pass the hidden _inventory_hostname option - self._task.args['_inventory_hostname'] = task_vars['inventory_hostname'] + self._task.args["_inventory_hostname"] = task_vars["inventory_hostname"] diff --git a/ansible_collections/juniper/device/plugins/action/file_copy.py b/ansible_collections/juniper/device/plugins/action/file_copy.py index 6031d6e5..684ef02c 100755 --- a/ansible_collections/juniper/device/plugins/action/file_copy.py +++ b/ansible_collections/juniper/device/plugins/action/file_copy.py @@ -31,9 +31,12 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +import os + from ansible.plugins.action.normal import ActionModule as ActionNormal + from ansible_collections.juniper.device.plugins.action.extract_data import ExtractData -import os + # The Ansible core engine will call ActionModule.run() class ActionModule(ExtractData, ActionNormal): @@ -44,7 +47,8 @@ class ActionModule(ExtractData, ActionNormal): passing the "hidden" _module_utils_path option to the module. """ + def run(self, tmp=None, task_vars=None): - super().extract(tmp,task_vars) + super().extract(tmp, task_vars) # Call the parent action module. return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py index 464fbda2..f1bb9d92 100755 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py +++ b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py @@ -31,9 +31,12 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +import os + from ansible.plugins.action.normal import ActionModule as ActionNormal + from ansible_collections.juniper.device.plugins.action.extract_data import ExtractData -import os + # The Ansible core engine will call ActionModule.run() class ActionModule(ExtractData, ActionNormal): @@ -44,7 +47,8 @@ class ActionModule(ExtractData, ActionNormal): passing the "hidden" _module_utils_path option to the module. """ + def run(self, tmp=None, task_vars=None): - super().extract(tmp,task_vars) + super().extract(tmp, task_vars) # Call the parent action module. return super(ActionModule, self).run(tmp, task_vars) diff --git a/ansible_collections/juniper/device/plugins/callback/jsnapy.py b/ansible_collections/juniper/device/plugins/callback/jsnapy.py index 668ca69f..c543efd7 100644 --- a/ansible_collections/juniper/device/plugins/callback/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/callback/jsnapy.py @@ -31,114 +31,125 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -from __future__ import (absolute_import, division, print_function) +from __future__ import absolute_import, division, print_function + __metaclass__ = type import collections +import json import os -import time import pprint -import json -from six import iteritems +import time -from ansible.plugins.callback import CallbackBase from ansible import constants as C +from ansible.plugins.callback import CallbackBase +from six import iteritems -class CallbackModule(CallbackBase): - """ - This callback add extra logging for the module junos_jsnapy . - """ - CALLBACK_VERSION = 2.0 - CALLBACK_TYPE = 'aggregate' - CALLBACK_NAME = 'jsnapy' - - # callback needs to be enabled with config-file to use jsnapy callback during execution - CALLBACK_NEEDS_WHITELIST = True - -## useful links regarding Callback -## https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py - - def __init__(self): - self._pp = pprint.PrettyPrinter(indent=4) - self._results = {} - - super(CallbackModule, self).__init__() - def v2_runner_on_ok(self, result): +class CallbackModule(CallbackBase): """ - Collect test results for all tests executed if action is snapcheck or check + This callback add extra logging for the module junos_jsnapy . """ - ## Extract module name - module_args = {} - if 'invocation' in result._result: - if 'module_args' in result._result['invocation']: - module_args = result._result['invocation']['module_args'] - - ## Check if dic return has all valid information - if 'action' not in module_args: - return None - - if module_args['action'] == 'snapcheck' or module_args['action'] == 'check': - - ## Check if dict entry already exist for this host - host = result._host.name - if not host in self._results.keys(): - self._results[host] = [] - - self._results[host].append(result) - - def v2_playbook_on_stats(self, stats): - - ## Go over all results for all hosts - for host, results in iteritems(self._results): - has_printed_banner = False - for result in results: - # self._pp.pprint(result.__dict__) - res = result._result - if res.get('final_result') == "Failed": - for test_name, test_results in iteritems(res['test_results']): - for testlet in test_results: - if ('count' in testlet) and testlet['count']['fail'] != 0: - if not has_printed_banner: - self._display.banner("JSNAPy Results for: " + str(host)) - has_printed_banner = True - - for test in testlet['failed']: - - # Check if POST exist in the response - data = '' - if 'post' in test: - data = test['post'] - else: - data = test - - self._display.display( - "Value of '{0}' not '{1}' at '{2}' with {3}".format( - str(testlet['node_name']), - str(testlet['testoperation']), - str(testlet['xpath']), - json.dumps(data)), - color=C.COLOR_ERROR) - - elif testlet['count']['pass'] != 0 : - if not has_printed_banner: - self._display.banner("JSNAPy Results for: " + str(host)) - has_printed_banner = True - - for test in testlet['passed']: - - # Check if POST exist in the response - data = '' - if 'post' in test: - data = test['post'] - else: - data = test - - self._display.display( - "Value of '{0}' '{1}' at '{2}' with {3}".format( - str(testlet['node_name']), - str(testlet['testoperation']), - str(testlet['xpath']), - json.dumps(data)), - color=C.COLOR_DEBUG) + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = "aggregate" + CALLBACK_NAME = "jsnapy" + + # callback needs to be enabled with config-file to use jsnapy callback during execution + CALLBACK_NEEDS_WHITELIST = True + + ## useful links regarding Callback + ## https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py + + def __init__(self): + self._pp = pprint.PrettyPrinter(indent=4) + self._results = {} + + super(CallbackModule, self).__init__() + + def v2_runner_on_ok(self, result): + """ + Collect test results for all tests executed if action is snapcheck or check + """ + + ## Extract module name + module_args = {} + if "invocation" in result._result: + if "module_args" in result._result["invocation"]: + module_args = result._result["invocation"]["module_args"] + + ## Check if dic return has all valid information + if "action" not in module_args: + return None + + if module_args["action"] == "snapcheck" or module_args["action"] == "check": + + ## Check if dict entry already exist for this host + host = result._host.name + if not host in self._results.keys(): + self._results[host] = [] + + self._results[host].append(result) + + def v2_playbook_on_stats(self, stats): + + ## Go over all results for all hosts + for host, results in iteritems(self._results): + has_printed_banner = False + for result in results: + # self._pp.pprint(result.__dict__) + res = result._result + if res.get("final_result") == "Failed": + for test_name, test_results in iteritems(res["test_results"]): + for testlet in test_results: + if ("count" in testlet) and testlet["count"]["fail"] != 0: + if not has_printed_banner: + self._display.banner( + "JSNAPy Results for: " + str(host) + ) + has_printed_banner = True + + for test in testlet["failed"]: + + # Check if POST exist in the response + data = "" + if "post" in test: + data = test["post"] + else: + data = test + + self._display.display( + "Value of '{0}' not '{1}' at '{2}' with {3}".format( + str(testlet["node_name"]), + str(testlet["testoperation"]), + str(testlet["xpath"]), + json.dumps(data), + ), + color=C.COLOR_ERROR, + ) + + elif testlet["count"]["pass"] != 0: + if not has_printed_banner: + self._display.banner( + "JSNAPy Results for: " + str(host) + ) + has_printed_banner = True + + for test in testlet["passed"]: + + # Check if POST exist in the response + data = "" + if "post" in test: + data = test["post"] + else: + data = test + + self._display.display( + "Value of '{0}' '{1}' at '{2}' with {3}".format( + str(testlet["node_name"]), + str(testlet["testoperation"]), + str(testlet["xpath"]), + json.dumps(data), + ), + color=C.COLOR_DEBUG, + ) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 071cc6f2..dc5012e7 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -32,6 +32,7 @@ # from __future__ import absolute_import, division, print_function + import xmltodict __metaclass__ = type @@ -204,18 +205,18 @@ - name: ssh_config """ +import json +import logging import pickle from ansible.errors import AnsibleConnectionFailure, AnsibleError from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.plugins.connection import NetworkConnectionBase, ensure_connect -import json -import logging - # Non-standard library imports and checks try: from jnpr.junos.version import VERSION + HAS_PYEZ_VERSION = VERSION except ImportError: HAS_PYEZ_VERSION = None @@ -249,9 +250,9 @@ HAS_PYEZ_CONFIG = False try: - import jnpr.junos.op import jnpr.junos.factory.factory_loader import jnpr.junos.factory.table + import jnpr.junos.op HAS_PYEZ_OP_TABLE = True except ImportError: @@ -266,6 +267,7 @@ try: from jnpr.jsnapy import SnapAdmin, __version__ + HAS_JSNAPY_VERSION = __version__ except ImportError: HAS_JSNAPY_VERSION = None @@ -301,7 +303,7 @@ # Python 3 basestring = str -#import q +# import q logging.getLogger("ncclient").setLevel(logging.INFO) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( @@ -309,7 +311,7 @@ ) # Supported configuration modes -CONFIG_MODE_CHOICES = ['exclusive', 'private', 'dynamic', 'batch', 'ephemeral'] +CONFIG_MODE_CHOICES = ["exclusive", "private", "dynamic", "batch", "ephemeral"] class Connection(NetworkConnectionBase): @@ -343,10 +345,7 @@ def _connect(self): super(Connection, self)._connect() self._sub_plugin = {"name": "pyez", "obj": self.dev} - self.queue_message( - "vvvv", - "created pyez connection type" - ) + self.queue_message("vvvv", "created pyez connection type") return ( 0, to_bytes(self.dev._conn.session_id, errors="surrogate_or_strict"), @@ -362,45 +361,48 @@ def open(self): connect_args = {} # check for mode - if self.get_option('port') is None: - if self.get_option('mode') == 'telnet': - connect_args['port'] = 23 - elif self.get_option('mode') == 'serial': - connect_args['port'] = '/dev/ttyUSB0' + if self.get_option("port") is None: + if self.get_option("mode") == "telnet": + connect_args["port"] = 23 + elif self.get_option("mode") == "serial": + connect_args["port"] = "/dev/ttyUSB0" else: - connect_args['port'] = 830 + connect_args["port"] = 830 else: - connect_args['port'] = self.get_option('port') + connect_args["port"] = self.get_option("port") - if (self.get_option('mode') == 'telnet' or - self.get_option('mode') == 'serial'): - if self.get_option('baud') is None: + if self.get_option("mode") == "telnet" or self.get_option("mode") == "serial": + if self.get_option("baud") is None: # Default baud if serial or telnet mode - connect_args['baud'] = 9600 - if self.get_option('attempts') is None: + connect_args["baud"] = 9600 + if self.get_option("attempts") is None: # Default attempts if serial or telnet mode - connect_args['attempts'] = 10 + connect_args["attempts"] = 10 - connect_args['host'] = self.get_option('host') + connect_args["host"] = self.get_option("host") # connect_args['port'] = self.get_option('port') - connect_args['user'] = self.get_option('remote_user') - connect_args['passwd'] = self.get_option('password') - connect_args['ssh_private_key_file'] = self.get_option('private_key_file') - connect_args['ssh_config'] = self.get_option('pyez_ssh_config') - connect_args['timeout'] = self.get_option('persistent_connect_timeout') + connect_args["user"] = self.get_option("remote_user") + connect_args["passwd"] = self.get_option("password") + connect_args["ssh_private_key_file"] = self.get_option("private_key_file") + connect_args["ssh_config"] = self.get_option("pyez_ssh_config") + connect_args["timeout"] = self.get_option("persistent_connect_timeout") try: log_connect_args = dict(connect_args) log_connect_args["passwd"] = "NOT_LOGGING_PARAMETER" - self.queue_message("vvvv", "Creating device parameters: %s" % log_connect_args) + self.queue_message( + "vvvv", "Creating device parameters: %s" % log_connect_args + ) timeout = connect_args.pop("timeout") self.dev = jnpr.junos.device.Device(**connect_args) self.queue_message("vvvv", "Opening device.") self.dev.open() self.queue_message("vvvv", "Device opened.") - self.dev.timeout = self.get_option('persistent_command_timeout') - self.queue_message("vvvv", "Setting default device timeout to %d." % timeout) + self.dev.timeout = self.get_option("persistent_command_timeout") + self.queue_message( + "vvvv", "Setting default device timeout to %d." % timeout + ) # Exceptions raised by close() or open() are all sub-classes of # ConnectError, so this should catch all connection-related exceptions # raised from PyEZ. @@ -408,8 +410,7 @@ def open(self): raise AnsibleError("Unable to make a PyEZ connection: %s" % (str(ex))) def close(self): - """Close the self.dev PyEZ Device instance. - """ + """Close the self.dev PyEZ Device instance.""" if self.dev is not None: try: # Because self.fail_json() calls self.close(), we must set @@ -431,12 +432,18 @@ def close(self): @ensure_connect def get_capabilities(self): - """Get the capabilities for network api.. - """ - return json.dumps({'network_api': 'pyez'}) - - def get_config(self, filter_xml=None, options=None, model=None, - namespace=None, remove_ns=True, **kwarg): + """Get the capabilities for network api..""" + return json.dumps({"network_api": "pyez"}) + + def get_config( + self, + filter_xml=None, + options=None, + model=None, + namespace=None, + remove_ns=True, + **kwarg + ): """Get Configuration. Args: @@ -458,10 +465,12 @@ def get_config(self, filter_xml=None, options=None, model=None, - Invalid filter. - Format not understood by device. """ - resp = self.dev.rpc.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) + resp = self.dev.rpc.get_config( + filter_xml, options, model, namespace, remove_ns, **kwarg + ) return etree.tostring(resp) - def get_rpc_resp(self,rpc, ignore_warning, format): + def get_rpc_resp(self, rpc, ignore_warning, format): """Execute rpc on the device and get response. Args: @@ -478,17 +487,18 @@ def get_rpc_resp(self,rpc, ignore_warning, format): """ # data comes in JSON format, needs to be converted rpc_val = xmltodict.unparse(rpc) - rpc_val = rpc_val.encode('utf-8') - parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8') + rpc_val = rpc_val.encode("utf-8") + parser = etree.XMLParser(ns_clean=True, recover=True, encoding="utf-8") rpc_etree = etree.fromstring(rpc_val, parser=parser) - resp = self.dev.rpc(rpc_etree, normalize=bool(format == 'xml'), ignore_warning=ignore_warning) - if(format == 'json'): + resp = self.dev.rpc( + rpc_etree, normalize=bool(format == "xml"), ignore_warning=ignore_warning + ) + if format == "json": return resp return etree.tostring(resp) def get_facts(self): - """Get device facts. - """ + """Get device facts.""" return dict(self.dev.facts) def ping_device(self, normalize, **params): @@ -509,8 +519,7 @@ def ping_device(self, normalize, **params): return rpc_str def get_chassis_inventory(self): - """Get chassis inventory details from the device. - """ + """Get chassis inventory details from the device.""" resp = self.dev.rpc.get_chassis_inventory() return etree.tostring(resp) @@ -521,85 +530,75 @@ def get_checksum_information(self, remote_file): return etree.tostring(resp) def get_re_name(self): - """Get re name from the device. - """ + """Get re name from the device.""" return self.dev.re_name def set_chassis_cluster_enable(self, cluster_id, node_id): - """send set chassis cluster enable rpc to the device. - """ + """send set chassis cluster enable rpc to the device.""" return self.dev.rpc.set_chassis_cluster_enable( - cluster_id=cluster_id, node=node_id, - reboot=True, normalize=True) + cluster_id=cluster_id, node=node_id, reboot=True, normalize=True + ) def set_chassis_cluster_disable(self): - """send set chassis cluster disable rpc to the device. - """ - return self.dev.rpc.set_chassis_cluster_disable( - reboot=True, normalize=True) + """send set chassis cluster disable rpc to the device.""" + return self.dev.rpc.set_chassis_cluster_disable(reboot=True, normalize=True) def invoke_jsnapy(self, data, action): - """invoke jsnapy for persistent connection. - """ + """invoke jsnapy for persistent connection.""" try: self.queue_message("vvvv", "Creating jnpr.jsnapy.SnapAdmin instance.") jsa = jnpr.jsnapy.SnapAdmin() - self.queue_message("vvvv", 'Executing %s action.' % action) - if action == 'check': - responses = jsa.check(data=data, - dev=self.dev, - pre_file='PRE', - post_file='POST') - elif action == 'snapcheck': - responses = jsa.snapcheck(data=data, - dev=self.dev, - pre_file='PRE') - elif action == 'snap_pre': - responses = jsa.snap(data=data, - dev=self.dev, - file_name='PRE') - elif action == 'snap_post': - responses = jsa.snap(data=data, - dev=self.dev, - file_name='POST') + self.queue_message("vvvv", "Executing %s action." % action) + if action == "check": + responses = jsa.check( + data=data, dev=self.dev, pre_file="PRE", post_file="POST" + ) + elif action == "snapcheck": + responses = jsa.snapcheck(data=data, dev=self.dev, pre_file="PRE") + elif action == "snap_pre": + responses = jsa.snap(data=data, dev=self.dev, file_name="PRE") + elif action == "snap_post": + responses = jsa.snap(data=data, dev=self.dev, file_name="POST") else: raise AnsibleError("Unexpected action: %s." % (action)) - self.queue_message("vvvv", 'The %s action executed successfully' % action) + self.queue_message("vvvv", "The %s action executed successfully" % action) except (pyez_exception.RpcError, pyez_exception.ConnectError) as ex: raise AnsibleError("Error communicating with the device: %s" % str(ex)) results = {} if isinstance(responses, list) and len(responses) == 1: - if action in ('snapcheck', 'check'): + if action in ("snapcheck", "check"): for response in responses: - results['device'] = response.device - results['router'] = response.device - results['final_result'] = response.result - results['total_passed'] = response.no_passed - results['total_failed'] = response.no_failed - results['test_results'] = response.test_results + results["device"] = response.device + results["router"] = response.device + results["final_result"] = response.result + results["total_passed"] = response.no_passed + results["total_failed"] = response.no_failed + results["test_results"] = response.test_results total_tests = int(response.no_passed) + int(response.no_failed) - results['total_tests'] = total_tests + results["total_tests"] = total_tests pass_percentage = 0 if total_tests > 0: - pass_percentage = ((int(response.no_passed) * 100) // - total_tests) - results['passPercentage'] = pass_percentage - results['pass_percentage'] = pass_percentage - if results['final_result'] == 'Failed': - results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) + pass_percentage = (int(response.no_passed) * 100) // total_tests + results["passPercentage"] = pass_percentage + results["pass_percentage"] = pass_percentage + if results["final_result"] == "Failed": + results["msg"] = "Test Failed: Passed %s, Failed %s" % ( + results["total_passed"], + results["total_failed"], + ) else: - results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) - elif action in ('snap_pre', 'snap_post'): - results['msg'] = "The %s action successfully executed." % (action) + results["msg"] = "Test Passed: Passed %s, Failed %s" % ( + results["total_passed"], + results["total_failed"], + ) + elif action in ("snap_pre", "snap_post"): + results["msg"] = "The %s action successfully executed." % (action) else: - raise AnsibleError("Unexpected JSNAPy responses. Type: %s." - "Responses: %s" % - (type(responses), str(responses))) + raise AnsibleError( + "Unexpected JSNAPy responses. Type: %s." + "Responses: %s" % (type(responses), str(responses)) + ) return results def open_configuration(self, mode, ignore_warn=None, ephemeral_instance=None): @@ -613,43 +612,47 @@ def open_configuration(self, mode, ignore_warn=None, ephemeral_instance=None): if self.config is None: if mode not in CONFIG_MODE_CHOICES: raise AnsibleError("Invalid configuration mode: %s" % mode) - if mode != 'ephemeral' and ephemeral_instance is not None: - self.fail_json(msg='Ephemeral instance is specified while the mode ' - 'is not ephemeral. Specify the mode as ephemeral ' - 'or do not specify the instance.') + if mode != "ephemeral" and ephemeral_instance is not None: + self.fail_json( + msg="Ephemeral instance is specified while the mode " + "is not ephemeral. Specify the mode as ephemeral " + "or do not specify the instance." + ) if self.dev is None: self.open() config = jnpr.junos.utils.config.Config(self.dev, mode=mode) try: - if config.mode == 'exclusive': + if config.mode == "exclusive": config.lock() - elif config.mode == 'private': + elif config.mode == "private": self.dev.rpc.open_configuration( - private=True, - ignore_warning=ignore_warn) - elif config.mode == 'dynamic': + private=True, ignore_warning=ignore_warn + ) + elif config.mode == "dynamic": self.dev.rpc.open_configuration( - dynamic=True, - ignore_warning=ignore_warn) - elif config.mode == 'batch': + dynamic=True, ignore_warning=ignore_warn + ) + elif config.mode == "batch": self.dev.rpc.open_configuration( - batch=True, - ignore_warning=ignore_warn) - elif config.mode == 'ephemeral': + batch=True, ignore_warning=ignore_warn + ) + elif config.mode == "ephemeral": if ephemeral_instance is None: self.dev.rpc.open_configuration( - ephemeral=True, - ignore_warning=ignore_warn) + ephemeral=True, ignore_warning=ignore_warn + ) else: self.dev.rpc.open_configuration( - ephemeral_instance = ephemeral_instance, - ignore_warning=ignore_warn) - except (pyez_exception.ConnectError, - pyez_exception.RpcError) as ex: - raise AnsibleError('Unable to open the configuration in %s ' - 'mode: %s' % (config.mode, str(ex))) + ephemeral_instance=ephemeral_instance, + ignore_warning=ignore_warn, + ) + except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: + raise AnsibleError( + "Unable to open the configuration in %s " + "mode: %s" % (config.mode, str(ex)) + ) self.config = config - self.queue_message("log", "Configuration opened in %s mode."% config.mode) + self.queue_message("log", "Configuration opened in %s mode." % config.mode) def close_configuration(self): """Close candidate configuration database. @@ -662,15 +665,13 @@ def close_configuration(self): config = self.config self.config = None try: - if config.mode == 'exclusive': + if config.mode == "exclusive": config.unlock() - elif config.mode in ['batch', 'dynamic', 'private', 'ephemeral']: + elif config.mode in ["batch", "dynamic", "private", "ephemeral"]: self.dev.rpc.close_configuration() self.queue_message("log", "Configuration closed.") - except (pyez_exception.ConnectError, - pyez_exception.RpcError) as ex: - raise AnsibleError('Unable to close the configuration: %s' % - (str(ex))) + except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: + raise AnsibleError("Unable to close the configuration: %s" % (str(ex))) def rollback_configuration(self, id): """Rollback the device configuration to the specified id. @@ -687,29 +688,36 @@ def rollback_configuration(self, id): - Unable to rollback the configuration due to an RpcError or ConnectError """ if self.dev is None or self.config is None: - raise AnsibleError('The device or configuration is not open.') + raise AnsibleError("The device or configuration is not open.") - if id == 'rescue': + if id == "rescue": self.queue_message("log", "Rolling back to the rescue configuration.") try: - self.config.rescue(action='reload') + self.config.rescue(action="reload") self.queue_message("log", "Rescue configuration loaded.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError('Unable to load the rescue configuraton: ' - '%s' % (str(ex))) + except ( + self.pyez_exception.RpcError, + self.pyez_exception.ConnectError, + ) as ex: + raise AnsibleError( + "Unable to load the rescue configuraton: " "%s" % (str(ex)) + ) elif id >= 0 and id <= 49: self.queue_message("log", "Loading rollback {} configuration.".format(id)) try: self.config.rollback(rb_id=id) - self.queue_message("log", "Rollback {} configuration loaded.".format(id)) - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError('Unable to load the rollback %d ' - 'configuraton: %s' % (id, str(ex))) + self.queue_message( + "log", "Rollback {} configuration loaded.".format(id) + ) + except ( + self.pyez_exception.RpcError, + self.pyez_exception.ConnectError, + ) as ex: + raise AnsibleError( + "Unable to load the rollback %d " "configuraton: %s" % (id, str(ex)) + ) else: - raise AnsibleError('Unrecognized rollback configuraton value: %s' - % (id)) + raise AnsibleError("Unrecognized rollback configuraton value: %s" % (id)) def check_configuration(self): """Check the candidate configuration. Assumes the configuration is already opened. @@ -722,10 +730,8 @@ def check_configuration(self): try: self.config.commit_check() self.queue_message("log", "Configuration checked.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError('Failure checking the configuraton: %s' % - (str(ex))) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError("Failure checking the configuraton: %s" % (str(ex))) def diff_configuration(self, ignore_warning=False): """Diff the candidate and committed configurations. @@ -740,10 +746,8 @@ def diff_configuration(self, ignore_warning=False): diff = self.config.diff(rb_id=0, ignore_warning=ignore_warning) self.queue_message("log", "Configuration diff completed.") return diff - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError('Failure diffing the configuraton: %s' % - (str(ex))) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError("Failure diffing the configuraton: %s" % (str(ex))) def load_configuration(self, config, load_args): """Load the candidate configuration from the specified src file using the @@ -756,17 +760,22 @@ def load_configuration(self, config, load_args): if config is not None: self.config.load(config, **load_args) else: - self.queue_message("log", "Load args %s." %str(load_args)) + self.queue_message("log", "Load args %s." % str(load_args)) self.config.load(**load_args) self.queue_message("log", "Configuration loaded.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError('Failure loading the configuraton: %s' % - (str(ex))) - - def commit_configuration(self, ignore_warning=None, comment=None, - confirmed=None, timeout=30, full=False, - force_sync=False, sync=False): + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError("Failure loading the configuraton: %s" % (str(ex))) + + def commit_configuration( + self, + ignore_warning=None, + comment=None, + confirmed=None, + timeout=30, + full=False, + force_sync=False, + sync=False, + ): """Commit the candidate configuration. Assumes the configuration is already opened. @@ -781,87 +790,88 @@ def commit_configuration(self, ignore_warning=None, comment=None, if self.dev.timeout: timeout = self.dev.timeout try: - self.config.commit(ignore_warning=ignore_warning, - comment=comment, - confirm=confirmed, - timeout=timeout, - full=full, - force_sync=force_sync, - sync=sync) + self.config.commit( + ignore_warning=ignore_warning, + comment=comment, + confirm=confirmed, + timeout=timeout, + full=full, + force_sync=force_sync, + sync=sync, + ) self.queue_message("log", "Configuration committed.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError('Failure committing the configuraton: %s' % - (str(ex))) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError("Failure committing the configuraton: %s" % (str(ex))) - def system_api(self, action, in_min, at, all_re, vmhost, other_re, media, member_id=None): - """Triggers the system calls like reboot, shutdown, halt and zeroize to device. - """ + def system_api( + self, action, in_min, at, all_re, vmhost, other_re, media, member_id=None + ): + """Triggers the system calls like reboot, shutdown, halt and zeroize to device.""" msg = None - if action != 'zeroize': - if (at == 'now' or (in_min == 0 and at is None)): + if action != "zeroize": + if at == "now" or (in_min == 0 and at is None): if self.dev.timeout > 5: - self.queue_message("log", "Decreasing device RPC timeout to 5 seconds.") + self.queue_message( + "log", "Decreasing device RPC timeout to 5 seconds." + ) self.dev.timeout = 5 try: self.sw = jnpr.junos.utils.sw.SW(self.dev) - if action == 'reboot': + if action == "reboot": if member_id is not None: for m_id in member_id: - got = self.sw.reboot(in_min, at, all_re, None, vmhost, other_re, member_id=m_id) + got = self.sw.reboot( + in_min, at, all_re, None, vmhost, other_re, member_id=m_id + ) else: got = self.sw.reboot(in_min, at, all_re, None, vmhost, other_re) - elif action == 'shutdown': + elif action == "shutdown": got = self.sw.poweroff(in_min, at, None, all_re, other_re, vmhost) - elif action == 'halt': + elif action == "halt": got = self.sw.halt(in_min, at, all_re, other_re) - elif action == 'zeroize': + elif action == "zeroize": got = self.sw.zeroize(all_re, media) else: - raise AnsibleError('Relevant action not found') + raise AnsibleError("Relevant action not found") self.queue_message("log", "RPC executed") if got is None: - msg = 'Did not find expected RPC response.' + msg = "Did not find expected RPC response." else: - msg = '%s successfully initiated. Response got %s' % (action, got) - except (self.pyez_exception.RpcTimeoutError) as ex: + msg = "%s successfully initiated. Response got %s" % (action, got) + except self.pyez_exception.RpcTimeoutError as ex: try: self.close(raise_exceptions=True) # This means the device wasn't already disconnected. - raise AnsibleError('%s failed. %s may not have been ' \ - 'initiated.' % (action, action)) - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError): + raise AnsibleError( + "%s failed. %s may not have been " "initiated." % (action, action) + ) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError): # This is expected. The device has already disconnected. - msg = '%s succeeded.' % (action) - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError('%s failed. Error: %s' % (action, str(ex))) + msg = "%s succeeded." % (action) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError("%s failed. Error: %s" % (action, str(ex))) return msg def software_api(self, install_params): - """Installs package to device. - """ + """Installs package to device.""" try: self.sw = jnpr.junos.utils.sw.SW(self.dev) ok, msg_ret = self.sw.install(**install_params) if ok is not True: - raise AnsibleError('Unable to install the software %s' % msg_ret) - msg = 'Package %s successfully installed. Response from device is: %s' % ( - install_params.get('package') or - install_params.get('pkg_set'), - msg_ret) - self.queue_message("log",str(msg)) + raise AnsibleError("Unable to install the software %s" % msg_ret) + msg = "Package %s successfully installed. Response from device is: %s" % ( + install_params.get("package") or install_params.get("pkg_set"), + msg_ret, + ) + self.queue_message("log", str(msg)) return msg - except (self.pyez_exception.ConnectError, - self.pyez_exception.RpcError) as ex: - raise AnsibleError('Installation failed. Error: %s' % str(ex)) + except (self.pyez_exception.ConnectError, self.pyez_exception.RpcError) as ex: + raise AnsibleError("Installation failed. Error: %s" % str(ex)) def reboot_api(self, all_re, vmhost, member_id=None): - """reboots the device. - """ + """reboots the device.""" msg = None try: restore_timeout = self.dev.timeout @@ -870,7 +880,9 @@ def reboot_api(self, all_re, vmhost, member_id=None): try: if member_id is not None: for m_id in member_id: - got = self.sw.reboot(0, None, all_re, None, vmhost, member_id=m_id) + got = self.sw.reboot( + 0, None, all_re, None, vmhost, member_id=m_id + ) else: got = self.sw.reboot(0, None, all_re, None, vmhost) self.dev.timeout = restore_timeout @@ -880,33 +892,33 @@ def reboot_api(self, all_re, vmhost, member_id=None): self.queue_message("log", "Reboot RPC executed.") if got is not None: - msg += ' Reboot successfully initiated. ' \ - 'Reboot message: %s' % got + msg += " Reboot successfully initiated. " "Reboot message: %s" % got else: - raise AnsibleError(' Did not find expected response ' \ - 'from reboot RPC. ') - except (self.pyez_exception.RpcTimeoutError) as ex: + raise AnsibleError( + " Did not find expected response " "from reboot RPC. " + ) + except self.pyez_exception.RpcTimeoutError as ex: try: self.close(raise_exceptions=True) # This means the device wasn't already disconnected. - raise AnsibleError(' Reboot failed. It may not have been ' \ - 'initiated.') - except (self.pyez_exception.RpcError, - self.pyez_exception.RpcTimeoutError, - self.pyez_exception.ConnectError): + raise AnsibleError(" Reboot failed. It may not have been " "initiated.") + except ( + self.pyez_exception.RpcError, + self.pyez_exception.RpcTimeoutError, + self.pyez_exception.ConnectError, + ): # This is expected. The device has already disconnected. - msg += ' Reboot succeeded.' - except (self.ncclient_exception.TimeoutExpiredError): + msg += " Reboot succeeded." + except self.ncclient_exception.TimeoutExpiredError: # This is not really expected. Still consider reboot success as # Looks like rpc was consumed but no response as its rebooting. - msg += ' Reboot succeeded. Ignoring close error.' - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - raise AnsibleError(' Reboot failed. Error: %s' % (str(ex))) + msg += " Reboot succeeded. Ignoring close error." + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + raise AnsibleError(" Reboot failed. Error: %s" % (str(ex))) else: try: self.close() - except (self.ncclient_exception.TimeoutExpiredError): + except self.ncclient_exception.TimeoutExpiredError: self.queue_message("log", "Ignoring TimeoutError for close call") self.queue_message("log", "Reboot RPC successfully initiated.") @@ -941,4 +953,4 @@ def scp_file_copy_get(self, remote_file, local_file): except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: raise AnsibleError( "Failure checking the configuraton: {0}".format(str(ex)) - ) from ex + ) from ex diff --git a/ansible_collections/juniper/device/plugins/module_utils/configuration.py b/ansible_collections/juniper/device/plugins/module_utils/configuration.py index d37a820a..6431a481 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/configuration.py +++ b/ansible_collections/juniper/device/plugins/module_utils/configuration.py @@ -30,53 +30,60 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -from distutils.version import LooseVersion import os +from distutils.version import LooseVersion # Non-standard library imports and checks try: from jnpr.junos.version import VERSION + HAS_PYEZ_VERSION = VERSION except ImportError: HAS_PYEZ_VERSION = None try: - import jnpr.junos.op import jnpr.junos.factory.factory_loader import jnpr.junos.factory.table + import jnpr.junos.op + HAS_PYEZ_OP_TABLE = True except ImportError: HAS_PYEZ_OP_TABLE = False try: import ncclient.operations.errors as ncclient_exception + HAS_NCCLIENT_EXCEPTIONS = True except ImportError: HAS_NCCLIENT_EXCEPTIONS = False try: import jnpr.jsnapy + HAS_JSNAPY_VERSION = jnpr.jsnapy.__version__ except ImportError: HAS_JSNAPY_VERSION = None # Most likely JSNAPy 1.2.0 with https://github.com/Juniper/jsnapy/issues/263 except TypeError: - HAS_JSNAPY_VERSION = 'possibly 1.2.0' + HAS_JSNAPY_VERSION = "possibly 1.2.0" try: from lxml import etree - HAS_LXML_ETREE_VERSION = '.'.join(map(str, etree.LXML_VERSION)) + + HAS_LXML_ETREE_VERSION = ".".join(map(str, etree.LXML_VERSION)) except ImportError: HAS_LXML_ETREE_VERSION = None try: import jxmlease + HAS_JXMLEASE_VERSION = jxmlease.__version__ except ImportError: HAS_JXMLEASE_VERSION = None try: import yaml + HAS_YAML_VERSION = yaml.__version__ except ImportError: HAS_YAML_VERSION = None @@ -104,19 +111,19 @@ # Minimum jxmlease version required by shared code. MIN_JXMLEASE_VERSION = "1.0.1" # Installation URL for jxmlease. -JXMLEASE_INSTALLATION_URL = \ - "http://jxmlease.readthedocs.io/en/stable/install.html" +JXMLEASE_INSTALLATION_URL = "http://jxmlease.readthedocs.io/en/stable/install.html" # Minimum yaml version required by shared code. MIN_YAML_VERSION = "3.08" YAML_INSTALLATION_URL = "http://pyyaml.org/wiki/PyYAMLDocumentation" def _check_library( - library_name, - installed_version, - installation_url, - minimum=None, - library_nickname=None): + library_name, + installed_version, + installation_url, + minimum=None, + library_nickname=None, +): """Check if library_name is installed and version is >= minimum. Args: @@ -137,29 +144,45 @@ def _check_library( library_nickname = library_name if installed_version is None: if minimum is not None: - return('%s >= %s is required for this module. ' - 'However, %s does not appear to be ' - 'currently installed. See %s for ' - 'details on installing %s.' % - (library_nickname, minimum, library_name, - installation_url, library_name)) + return ( + "%s >= %s is required for this module. " + "However, %s does not appear to be " + "currently installed. See %s for " + "details on installing %s." + % ( + library_nickname, + minimum, + library_name, + installation_url, + library_name, + ) + ) else: - return('%s is required for this module. However, ' - '%s does not appear to be currently ' - 'installed. See %s for details on ' - 'installing %s.' % - (library_nickname, library_name, - installation_url, library_name)) + return ( + "%s is required for this module. However, " + "%s does not appear to be currently " + "installed. See %s for details on " + "installing %s." + % (library_nickname, library_name, installation_url, library_name) + ) elif installed_version is not None and minimum is not None: if not LooseVersion(installed_version) >= LooseVersion(minimum): - return( - '%s >= %s is required for this module. Version %s of ' - '%s is currently installed. See %s for details on ' - 'upgrading %s.' % - (library_nickname, minimum, installed_version, - library_name, installation_url, library_name)) + return ( + "%s >= %s is required for this module. Version %s of " + "%s is currently installed. See %s for details on " + "upgrading %s." + % ( + library_nickname, + minimum, + installed_version, + library_name, + installation_url, + library_name, + ) + ) return "success" + def check_pyez(minimum=None): """Check PyEZ is available and version is >= minimum. @@ -174,11 +197,15 @@ def check_pyez(minimum=None): - PyEZ version < minimum. """ if HAS_NCCLIENT_EXCEPTIONS is False: - return('ncclient.operations.errors module could not ' - 'be imported.') - return _check_library('junos-eznc', HAS_PYEZ_VERSION, - PYEZ_INSTALLATION_URL, minimum=minimum, - library_nickname='junos-eznc (aka PyEZ)') + return "ncclient.operations.errors module could not " "be imported." + return _check_library( + "junos-eznc", + HAS_PYEZ_VERSION, + PYEZ_INSTALLATION_URL, + minimum=minimum, + library_nickname="junos-eznc (aka PyEZ)", + ) + def check_jsnapy(minimum=None): """Check jsnapy is available and version is >= minimum. @@ -191,8 +218,10 @@ def check_jsnapy(minimum=None): - jsnapy not installed. - jsnapy version < minimum. """ - return _check_library('jsnapy', HAS_JSNAPY_VERSION, - JSNAPY_INSTALLATION_URL, minimum=minimum) + return _check_library( + "jsnapy", HAS_JSNAPY_VERSION, JSNAPY_INSTALLATION_URL, minimum=minimum + ) + def check_jxmlease(minimum=None): """Check jxmlease is available and version is >= minimum. @@ -205,8 +234,10 @@ def check_jxmlease(minimum=None): - jxmlease not installed. - jxmlease version < minimum. """ - return _check_library('jxmlease', HAS_JXMLEASE_VERSION, - JXMLEASE_INSTALLATION_URL, minimum=minimum) + return _check_library( + "jxmlease", HAS_JXMLEASE_VERSION, JXMLEASE_INSTALLATION_URL, minimum=minimum + ) + def check_lxml_etree(minimum=None): """Check lxml etree is available and version is >= minimum. @@ -219,8 +250,13 @@ def check_lxml_etree(minimum=None): - lxml not installed. - lxml version < minimum. """ - return _check_library('lxml Etree', HAS_LXML_ETREE_VERSION, - LXML_ETREE_INSTALLATION_URL, minimum=minimum) + return _check_library( + "lxml Etree", + HAS_LXML_ETREE_VERSION, + LXML_ETREE_INSTALLATION_URL, + minimum=minimum, + ) + def check_yaml(minimum=None): """Check yaml is available and version is >= minimum. @@ -233,21 +269,25 @@ def check_yaml(minimum=None): - yaml not installed. - yaml version < minimum. """ - return _check_library('yaml', HAS_YAML_VERSION, - YAML_INSTALLATION_URL, minimum=minimum) - -def check_sw_compatibility(min_pyez_version, - min_lxml_etree_version, - min_jsnapy_version=None, - min_jxmlease_version=None, - min_yaml_version=None): + return _check_library( + "yaml", HAS_YAML_VERSION, YAML_INSTALLATION_URL, minimum=minimum + ) + + +def check_sw_compatibility( + min_pyez_version, + min_lxml_etree_version, + min_jsnapy_version=None, + min_jxmlease_version=None, + min_yaml_version=None, +): """Check yaml is available and version is >= minimum. - Args: - minimum: The minimum PyYAML version required. - Default = None which means no version check. - Returns: - string as success or the error + Args: + minimum: The minimum PyYAML version required. + Default = None which means no version check. + Returns: + string as success or the error """ ret_output = check_pyez(min_pyez_version) if ret_output != "success": @@ -271,5 +311,3 @@ def check_sw_compatibility(min_pyez_version, ret_output = check_yaml(min_yaml_version) return ret_output - - diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index faf4a8b2..0ce4b24c 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -33,28 +33,29 @@ from __future__ import absolute_import, division, print_function -# Ansible imports -from ansible.module_utils.connection import Connection -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.basic import boolean -from ansible.module_utils._text import to_bytes, to_text -from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg -import jnpr -from jnpr.junos.utils.sw import SW -from jnpr.junos.utils.scp import SCP - -from jnpr.junos import exception as pyez_exception -from ncclient.operations.errors import TimeoutExpiredError -from ansible.module_utils.common.validation import check_type_dict +import hashlib +import json +import logging +import os # Standard library imports from argparse import ArgumentParser from distutils.version import LooseVersion -import json + +import jnpr import xmltodict -import logging -import os -import hashlib +from ansible.module_utils._text import to_bytes, to_text +from ansible.module_utils.basic import AnsibleModule, boolean +from ansible.module_utils.common.validation import check_type_dict + +# Ansible imports +from ansible.module_utils.connection import Connection +from jnpr.junos import exception as pyez_exception +from jnpr.junos.utils.scp import SCP +from jnpr.junos.utils.sw import SW +from ncclient.operations.errors import TimeoutExpiredError + +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg try: # Python 2 @@ -63,6 +64,7 @@ # Python 3 basestring = str + class ModuleDocFragment(object): """Documentation fragment for connection-related parameters. @@ -80,7 +82,7 @@ class ModuleDocFragment(object): # The connection-specific options. Defined here so it can be re-used as # suboptions in provider. - _CONNECT_DOCUMENTATION = ''' + _CONNECT_DOCUMENTATION = """ attempts: description: - The number of times to try connecting and logging in to the Junos @@ -164,7 +166,7 @@ class ModuleDocFragment(object): - password port: description: - - The TCP port number or serial device port used to establish the + - The TCP port number or serial device port used to establish the connection. Mutually exclusive with the I(console) option. required: false default: C(830) if C(mode = none), C(23) if C(mode = 'telnet'), @@ -234,7 +236,7 @@ class ModuleDocFragment(object): - username cs_user: description: - - The username used to authenticate with the console server over SSH. + - The username used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the I(console) option. required: false @@ -243,7 +245,7 @@ class ModuleDocFragment(object): - console_username cs_passwd: description: - - The password used to authenticate with the console server over SSH. + - The password used to authenticate with the console server over SSH. This option is only required if you want to connect to a device over console using SSH as transport. Mutually exclusive with the I(console) option. required: false @@ -256,9 +258,9 @@ class ModuleDocFragment(object): required: false type: bool default: false -''' +""" - LOGGING_DOCUMENTATION = ''' + LOGGING_DOCUMENTATION = """ logging_options: logdir: description: @@ -342,117 +344,131 @@ class ModuleDocFragment(object): choices: - INFO - DEBUG - -''' + +""" # _SUB_CONNECT_DOCUMENTATION is just _CONNECT_DOCUMENTATION with each # line indented. - _SUB_CONNECT_DOCUMENTATION = '' + _SUB_CONNECT_DOCUMENTATION = "" for line in _CONNECT_DOCUMENTATION.splitlines(True): - _SUB_CONNECT_DOCUMENTATION += ' ' + line + _SUB_CONNECT_DOCUMENTATION += " " + line # Build actual DOCUMENTATION string by putting the pieces together. - CONNECTION_DOCUMENTATION = ''' - connection_options:''' + _CONNECT_DOCUMENTATION + ''' + CONNECTION_DOCUMENTATION = ( + """ + connection_options:""" + + _CONNECT_DOCUMENTATION + + """ requirements: - - U(junos-eznc|https://github.com/Juniper/py-junos-eznc) >= ''' + cfg.MIN_PYEZ_VERSION + ''' + - U(junos-eznc|https://github.com/Juniper/py-junos-eznc) >= """ + + cfg.MIN_PYEZ_VERSION + + """ - Python >= 3.5 notes: - The NETCONF system service must be enabled on the target Junos device. -''' +""" + ) # The common argument specification for connecting to Junos devices. connection_spec = { - 'host': dict(type='str', - # Required at top-level. - required=False, - aliases=['hostname', 'ip'], - # See documentation for real default behavior. - # Default behavior coded in JuniperJunosActionModule.run() - default=None), - 'user': dict(type='str', - # Required at top-level. - required=False, - aliases=['username'], - # See documentation for real default behavior. - # Default behavior coded in JuniperJunosActionModule.run() - default=None), - 'passwd': dict(type='str', - required=False, - aliases=['password'], - # See documentation for real default behavior. - # Default behavior coded in JuniperJunosActionModule.run() - default=None, - no_log=True), - 'cs_user': dict(type='str', - aliases=['console_username'], - required=False, - default=None), - 'cs_passwd': dict(type='str', - aliases=['console_password'], - required=False, - default=None, - no_log=True), - 'ssh_private_key_file': dict(type='path', - required=False, - aliases=['ssh_keyfile'], - # See documentation for real default behavior. - # Default behavior coded in - # JuniperJunosActionModule.run() - default=None), - 'ssh_config': dict(type='path', - required=False, - default=None), - 'mode': dict(choices=[None, 'telnet', 'serial'], - default=None), - 'console': dict(type='str', - required=False, - default=None), - 'port': dict(type='str', - required=False, - # See documentation for real default behavior. - # Default behavior coded in JuniperJunosModule.__init__() - default=None), - 'baud': dict(type='int', - required=False, - # See documentation for real default behavior. - # Default behavior coded in JuniperJunosModule.__init__() - default=None), - 'attempts': dict(type='int', - required=False, - # See documentation for real default behavior. - # Default behavior coded in JuniperJunosModule.__init__() - default=None), - 'timeout': dict(type='int', - required=False, - default=30), - 'huge_tree': dict(type='bool', - required=False, - default=False), + "host": dict( + type="str", + # Required at top-level. + required=False, + aliases=["hostname", "ip"], + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosActionModule.run() + default=None, + ), + "user": dict( + type="str", + # Required at top-level. + required=False, + aliases=["username"], + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosActionModule.run() + default=None, + ), + "passwd": dict( + type="str", + required=False, + aliases=["password"], + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosActionModule.run() + default=None, + no_log=True, + ), + "cs_user": dict( + type="str", aliases=["console_username"], required=False, default=None + ), + "cs_passwd": dict( + type="str", + aliases=["console_password"], + required=False, + default=None, + no_log=True, + ), + "ssh_private_key_file": dict( + type="path", + required=False, + aliases=["ssh_keyfile"], + # See documentation for real default behavior. + # Default behavior coded in + # JuniperJunosActionModule.run() + default=None, + ), + "ssh_config": dict(type="path", required=False, default=None), + "mode": dict(choices=[None, "telnet", "serial"], default=None), + "console": dict(type="str", required=False, default=None), + "port": dict( + type="str", + required=False, + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosModule.__init__() + default=None, + ), + "baud": dict( + type="int", + required=False, + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosModule.__init__() + default=None, + ), + "attempts": dict( + type="int", + required=False, + # See documentation for real default behavior. + # Default behavior coded in JuniperJunosModule.__init__() + default=None, + ), + "timeout": dict(type="int", required=False, default=30), + "huge_tree": dict(type="bool", required=False, default=False), } # Connection arguments which are mutually exclusive. -connection_spec_mutually_exclusive = [['mode', 'console'], - ['port', 'console'], - ['baud', 'console'], - ['attempts', 'console'], - ['cs_user', 'console'], - ['cs_passwd', 'console']] +connection_spec_mutually_exclusive = [ + ["mode", "console"], + ["port", "console"], + ["baud", "console"], + ["attempts", "console"], + ["cs_user", "console"], + ["cs_passwd", "console"], +] # Specify the logging spec. logging_spec = { - 'logfile': dict(type='path', required=False, default=None), - 'logdir': dict(type='path', required=False, default=None), - 'level': dict(choices=[None, 'INFO', 'DEBUG'], required=False, default=None) + "logfile": dict(type="path", required=False, default=None), + "logdir": dict(type="path", required=False, default=None), + "level": dict(choices=[None, "INFO", "DEBUG"], required=False, default=None), } # The logdir and logfile options are mutually exclusive. -logging_spec_mutually_exclusive = ['logfile', 'logdir'] +logging_spec_mutually_exclusive = ["logfile", "logdir"] # Other logging names which should be logged to the logfile -additional_logger_names = ['ncclient', 'paramiko'] +additional_logger_names = ["ncclient", "paramiko"] # top_spec is connection_spec + logging_spec top_spec = connection_spec @@ -464,30 +480,34 @@ class ModuleDocFragment(object): # "Hidden" arguments which are passed between the action plugin and the # Junos module, but which should not be visible to users. internal_spec = { - '_module_utils_path': dict(type='path', - default=None), - '_module_name': dict(type='str', - default=None), - '_inventory_hostname': dict(type='str', - default=None), - '_connection': dict(type='str', - default=None), + "_module_utils_path": dict(type="path", default=None), + "_module_name": dict(type="str", default=None), + "_inventory_hostname": dict(type="str", default=None), + "_connection": dict(type="str", default=None), } # Known RPC output formats -RPC_OUTPUT_FORMAT_CHOICES = ['text', 'xml', 'json'] +RPC_OUTPUT_FORMAT_CHOICES = ["text", "xml", "json"] # Known configuration formats -CONFIG_FORMAT_CHOICES = ['xml', 'set', 'text', 'json'] +CONFIG_FORMAT_CHOICES = ["xml", "set", "text", "json"] # Known configuration databases -CONFIG_DATABASE_CHOICES = ['candidate', 'committed'] +CONFIG_DATABASE_CHOICES = ["candidate", "committed"] # Known configuration actions -CONFIG_ACTION_CHOICES = ['set', 'merge', 'update', - 'replace', 'override', 'overwrite', 'patch'] +CONFIG_ACTION_CHOICES = [ + "set", + "merge", + "update", + "replace", + "override", + "overwrite", + "patch", +] # Supported configuration modes -CONFIG_MODE_CHOICES = ['exclusive', 'private', 'dynamic', 'batch', 'ephemeral'] +CONFIG_MODE_CHOICES = ["exclusive", "private", "dynamic", "batch", "ephemeral"] # Supported configuration models -CONFIG_MODEL_CHOICES = ['openconfig', 'custom', 'ietf', 'True'] +CONFIG_MODEL_CHOICES = ["openconfig", "custom", "ietf", "True"] + class JuniperJunosModule(AnsibleModule): """A subclass of AnsibleModule used by all modules. @@ -526,15 +546,17 @@ class JuniperJunosModule(AnsibleModule): """ # Method overrides - def __init__(self, - argument_spec={}, - mutually_exclusive=[], - min_pyez_version=cfg.MIN_PYEZ_VERSION, - min_lxml_etree_version=cfg.MIN_LXML_ETREE_VERSION, - min_jsnapy_version=None, - min_jxmlease_version=None, - min_yaml_version=None, - **kwargs): + def __init__( + self, + argument_spec={}, + mutually_exclusive=[], + min_pyez_version=cfg.MIN_PYEZ_VERSION, + min_lxml_etree_version=cfg.MIN_LXML_ETREE_VERSION, + min_jsnapy_version=None, + min_jxmlease_version=None, + min_yaml_version=None, + **kwargs + ): """Initialize a new JuniperJunosModule instance. Combines module-specific parameters with the common parameters shared @@ -571,7 +593,7 @@ def __init__(self, A JuniperJunosModule instance object. """ - #initialize default values here for error scenario while super is called + # initialize default values here for error scenario while super is called # by default local self.conn_type = "local" @@ -588,9 +610,8 @@ def __init__(self, mutually_exclusive += top_spec_mutually_exclusive # Call parent's __init__() super(JuniperJunosModule, self).__init__( - argument_spec=argument_spec, - mutually_exclusive=mutually_exclusive, - **kwargs) + argument_spec=argument_spec, mutually_exclusive=mutually_exclusive, **kwargs + ) # initialize the parameters self.initialize_params() @@ -600,13 +621,15 @@ def __init__(self, self.params.pop(arg_name) # check software compatibility for various 3rd party tools used - ret_output = cfg.check_sw_compatibility(min_pyez_version, - min_lxml_etree_version, - min_jsnapy_version, - min_jxmlease_version, - min_yaml_version) - - if ret_output != 'success': + ret_output = cfg.check_sw_compatibility( + min_pyez_version, + min_lxml_etree_version, + min_jsnapy_version, + min_jxmlease_version, + min_yaml_version, + ) + + if ret_output != "success": self.fail_json(msg="%s" % ret_output) self.pyez_factory_loader = jnpr.junos.factory.factory_loader @@ -636,54 +659,57 @@ def initialize_params(self): """ # priority for arguments is inventory < tasks < console - self.module_name = self.params.get('_module_name') - self.inventory_hostname = self.params.get('_inventory_hostname') - self.conn_type = self.params.get('_connection') + self.module_name = self.params.get("_module_name") + self.inventory_hostname = self.params.get("_inventory_hostname") + self.conn_type = self.params.get("_connection") # Parse the console option self._parse_console_options() # Check that we have a user and host - if not self.params.get('host'): + if not self.params.get("host"): self.fail_json(msg="missing required arguments: host") - if not self.params.get('user'): + if not self.params.get("user"): self.fail_json(msg="missing required arguments: user") - # Default port based on mode. - if self.params.get('port') is None: - if self.params.get('mode') == 'telnet': - self.params['port'] = 23 - elif self.params.get('mode') == 'serial': - self.params['port'] = '/dev/ttyUSB0' + if self.params.get("port") is None: + if self.params.get("mode") == "telnet": + self.params["port"] = 23 + elif self.params.get("mode") == "serial": + self.params["port"] = "/dev/ttyUSB0" else: - self.params['port'] = 830 + self.params["port"] = 830 else: - if self.params.get('mode') != 'serial': + if self.params.get("mode") != "serial": try: - self.params['port'] = int(self.params['port']) + self.params["port"] = int(self.params["port"]) except ValueError: - self.fail_json(msg="The port option (%s) must be an " - "integer value." % - (self.params['port'])) + self.fail_json( + msg="The port option (%s) must be an " + "integer value." % (self.params["port"]) + ) else: - self.params['port'] = self.params['port'] + self.params["port"] = self.params["port"] - if (self.params.get('mode') == 'telnet' or - self.params.get('mode') == 'serial'): - if self.params.get('baud') is None: + if self.params.get("mode") == "telnet" or self.params.get("mode") == "serial": + if self.params.get("baud") is None: # Default baud if serial or telnet mode - self.params['baud'] = 9600 - if self.params.get('attempts') is None: + self.params["baud"] = 9600 + if self.params.get("attempts") is None: # Default attempts if serial or telnet mode - self.params['attempts'] = 10 + self.params["attempts"] = 10 else: - if self.params.get('baud') is not None: - self.fail_json(msg="The baud option (%s) is not valid when " - "mode == none." % (self.params.get('baud'))) - if self.params.get('attempts') is not None: - self.fail_json(msg="The attempts option (%s) is not valid when " - "mode == none." % (self.params.get('attempts'))) + if self.params.get("baud") is not None: + self.fail_json( + msg="The baud option (%s) is not valid when " + "mode == none." % (self.params.get("baud")) + ) + if self.params.get("attempts") is not None: + self.fail_json( + msg="The attempts option (%s) is not valid when " + "mode == none." % (self.params.get("attempts")) + ) def get_connection(self): if hasattr(self, "_pyez_connection"): @@ -724,9 +750,9 @@ def exit_json(self, **kwargs): try: self.close() except TimeoutExpiredError: - if hasattr(self, 'logger'): + if hasattr(self, "logger"): self.logger.debug("Ignoring dev.close() timeout error") - if hasattr(self, 'logger'): + if hasattr(self, "logger"): self.logger.debug("Exit JSON: %s", kwargs) # Call the parent's exit_json() super(JuniperJunosModule, self).exit_json(**kwargs) @@ -745,9 +771,9 @@ def fail_json(self, **kwargs): try: self.close() except TimeoutExpiredError: - if hasattr(self, 'logger'): + if hasattr(self, "logger"): self.logger.debug("Ignoring dev.close() timeout error") - if hasattr(self, 'logger'): + if hasattr(self, "logger"): self.logger.debug("Fail JSON: %s", kwargs) # Call the parent's fail_json() super(JuniperJunosModule, self).fail_json(**kwargs) @@ -760,9 +786,9 @@ def _parse_console_options(self): Parse the console option value and turn it into the equivalent: host, mode, baud, attempts, and port options. """ - if self.params.get('console') is not None: + if self.params.get("console") is not None: try: - console_string = self.params.get('console') + console_string = self.params.get("console") # Subclass ArgumentParser to simply raise a ValueError # rather than printing to stderr and calling sys.exit() @@ -772,53 +798,56 @@ def error(self, message): # Parse the console_string. parser = QuiteArgumentParser(add_help=False) - parser.add_argument('-t', '--telnet', default=None) - parser.add_argument('-p', '--port', default=None) - parser.add_argument('-b', '--baud', default=None) - parser.add_argument('-a', '--attempts', default=None) - parser.add_argument('--timeout', default=None) + parser.add_argument("-t", "--telnet", default=None) + parser.add_argument("-p", "--port", default=None) + parser.add_argument("-b", "--baud", default=None) + parser.add_argument("-a", "--attempts", default=None) + parser.add_argument("--timeout", default=None) con_params = vars(parser.parse_args(console_string.split())) - telnet_params = con_params.get('telnet', None) + telnet_params = con_params.get("telnet", None) # mode == 'telnet' if telnet_params is not None: # Split on , - host_port = telnet_params.split(',', 1) + host_port = telnet_params.split(",", 1) # Strip any leading/trailing whitespace or equal sign # from host - host = host_port[0].strip(' ') + host = host_port[0].strip(" ") # Try to convert port to an int. port = int(host_port[1]) # Successfully parsed. Set params values - self.params['mode'] = 'telnet' - self.params['host'] = host - self.params['port'] = port + self.params["mode"] = "telnet" + self.params["host"] = host + self.params["port"] = port # mode == serial else: - port = con_params.get('port', None) - baud = con_params.get('baud', None) - attempts = con_params.get('attempts', None) - timeout = con_params.get('timeout', None) # not used - self.params['mode'] = 'serial' + port = con_params.get("port", None) + baud = con_params.get("baud", None) + attempts = con_params.get("attempts", None) + timeout = con_params.get("timeout", None) # not used + self.params["mode"] = "serial" if port is not None: - self.params['port'] = port + self.params["port"] = port if baud is not None: - self.params['baud'] = baud + self.params["baud"] = baud if attempts is not None: - self.params['attempts'] = attempts + self.params["attempts"] = attempts # Remove the console option. - self.params.pop('console') + self.params.pop("console") except ValueError as ex: - self.fail_json(msg="Unable to parse the console value (%s). " - "Error: %s" % (console_string, str(ex))) + self.fail_json( + msg="Unable to parse the console value (%s). " + "Error: %s" % (console_string, str(ex)) + ) except Exception as ex: - self.fail_json(msg="Unable to parse the console value (%s). " - "The value of the console argument is " - "typically in the format '--telnet " - ",'." - % (console_string)) + self.fail_json( + msg="Unable to parse the console value (%s). " + "The value of the console argument is " + "typically in the format '--telnet " + ",'." % (console_string) + ) def _setup_logging(self): """Setup logging for the module. @@ -845,7 +874,7 @@ class CustomAdapter(logging.LoggerAdapter): """ def process(self, msg, kwargs): - return '[%s] %s' % (self.extra['host'], msg), kwargs + return "[%s] %s" % (self.extra["host"], msg), kwargs # Default level to log. level = logging.WARNING @@ -857,10 +886,10 @@ def process(self, msg, kwargs): elif self._verbosity > 1: level = logging.DEBUG # Set level as mentioned in task - elif self.params.get('level') is not None: - level = self.params.get('level') + elif self.params.get("level") is not None: + level = self.params.get("level") # Get the logger object to be used for our logging. - logger = logging.getLogger('jnpr.ansible_module.' + self.module_name) + logger = logging.getLogger("jnpr.ansible_module." + self.module_name) # Attach the NullHandler to avoid any errors if no logging is needed. logger.addHandler(logging.NullHandler()) # Set the logging level for the modules logging. This will also control @@ -872,19 +901,21 @@ def process(self, msg, kwargs): logging.getLogger(name).setLevel(level) # Get the name of the logfile based on logfile or logdir options. logfile = None - if self.params.get('logfile') is not None: - logfile = self.params.get('logfile') - elif self.params.get('logdir') is not None: - logfile = os.path.normpath(self.params.get('logdir') + '/' + - self.params.get('host') + '.log') + if self.params.get("logfile") is not None: + logfile = self.params.get("logfile") + elif self.params.get("logdir") is not None: + logfile = os.path.normpath( + self.params.get("logdir") + "/" + self.params.get("host") + ".log" + ) # Create the FileHandler and attach it. if logfile is not None: try: - handler = logging.FileHandler(logfile, mode='a') + handler = logging.FileHandler(logfile, mode="a") handler.setLevel(level) # Create a custom formatter. formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) # add formatter to handler handler.setFormatter(formatter) # Handler should log anything from the 'jnpr.ansible_module.' namespace to @@ -893,15 +924,15 @@ def process(self, msg, kwargs): for name in additional_logger_names: logging.getLogger(name).addHandler(handler) except IOError as ex: - self.fail_json(msg="Unable to open the log file %s. %s" % - (logfile, str(ex))) + self.fail_json( + msg="Unable to open the log file %s. %s" % (logfile, str(ex)) + ) # Use the CustomAdapter to add host information. - return CustomAdapter(logger, {'host': self.params.get('host')}) + return CustomAdapter(logger, {"host": self.params.get("host")}) - def parse_arg_to_list_of_dicts(self, - option_name, - string_val, - allow_bool_values=False): + def parse_arg_to_list_of_dicts( + self, option_name, string_val, allow_bool_values=False + ): """Parses string_val into a list of dicts with bool and/or str values. In order to handle all of the different ways that list of dict type @@ -935,19 +966,21 @@ def parse_arg_to_list_of_dicts(self, try: kwargs = check_type_dict(kwargs) except TypeError as exc: - self.fail_json(msg="The value of the %s option (%s) is " - "invalid. Unable to translate into " - "a list of dicts." % - (option_name, string_val, str(exc))) + self.fail_json( + msg="The value of the %s option (%s) is " + "invalid. Unable to translate into " + "a list of dicts." % (option_name, string_val, str(exc)) + ) # Now, if it's a dict, let's make it a list of one dict if isinstance(kwargs, dict): kwargs = [kwargs] # Now, if it's not a list, we've got a problem. if not isinstance(kwargs, list): - self.fail_json(msg="The value of the %s option (%s) is invalid. " - "Unable to translate into a list of dicts." % - (option_name, string_val)) + self.fail_json( + msg="The value of the %s option (%s) is invalid. " + "Unable to translate into a list of dicts." % (option_name, string_val) + ) # We've got a list, traverse each element to make sure it's a dict. return_val = [] for kwarg in kwargs: @@ -958,16 +991,18 @@ def parse_arg_to_list_of_dicts(self, try: kwarg = check_type_dict(kwarg) except TypeError as exc: - self.fail_json(msg="The value of the %s option (%s) is " - "invalid. Unable to translate into a " - "list of dicts." % - (option_name, string_val, str(exc))) + self.fail_json( + msg="The value of the %s option (%s) is " + "invalid. Unable to translate into a " + "list of dicts." % (option_name, string_val, str(exc)) + ) # Now if it's not a dict, there's a problem. if not isinstance(kwarg, dict): - self.fail_json(msg="The value of the kwargs option (%s) is " - "invalid. Unable to translate into a list " - "of dicts." % - (option_name, string_val)) + self.fail_json( + msg="The value of the kwargs option (%s) is " + "invalid. Unable to translate into a list " + "of dicts." % (option_name, string_val) + ) # check if allow_bool_values passed in kwargs if "allow_bool_values" in kwarg: allow_bool_values = kwarg.pop("allow_bool_values") @@ -975,12 +1010,13 @@ def parse_arg_to_list_of_dicts(self, # Now we just need to make sure the key is a string and the value # is a string or bool. return_item = {} - for (k, v) in kwarg.items(): + for k, v in kwarg.items(): if not isinstance(k, basestring): - self.fail_json(msg="The value of the %s option (%s) " - "is invalid. Unable to translate into " - "a list of dicts." % - (option_name, string_val)) + self.fail_json( + msg="The value of the %s option (%s) " + "is invalid. Unable to translate into " + "a list of dicts." % (option_name, string_val) + ) if allow_bool_values is True: # Try to convert it to a boolean value. Will be None if it # can't be converted. @@ -1008,7 +1044,7 @@ def parse_ignore_warning_option(self): If there is an error parsing ignore_warning. """ # Nothing to do if ignore_warning wasn't specified. - ignore_warn_list = self.params.get('ignore_warning') + ignore_warn_list = self.params.get("ignore_warning") if ignore_warn_list is None: return ignore_warn_list if len(ignore_warn_list) == 1: @@ -1019,25 +1055,27 @@ def parse_ignore_warning_option(self): except TypeError: if isinstance(ignore_warn_list[0], basestring): return ignore_warn_list[0] - self.fail_json(msg="The value of the ignore_warning option " - "(%s) is invalid. Unexpected type (%s)." % - (ignore_warn_list[0], - type(ignore_warn_list[0]))) + self.fail_json( + msg="The value of the ignore_warning option " + "(%s) is invalid. Unexpected type (%s)." + % (ignore_warn_list[0], type(ignore_warn_list[0])) + ) elif len(ignore_warn_list) > 1: for ignore_warn in ignore_warn_list: if not isinstance(ignore_warn, basestring): - self.fail_json(msg="The value of the ignore_warning " - "option (%s) is invalid. " - "Element (%s) has unexpected " - "type (%s)." % - (str(ignore_warn_list), - ignore_warn, - type(ignore_warn))) + self.fail_json( + msg="The value of the ignore_warning " + "option (%s) is invalid. " + "Element (%s) has unexpected " + "type (%s)." + % (str(ignore_warn_list), ignore_warn, type(ignore_warn)) + ) return ignore_warn_list else: - self.fail_json(msg="The value of the ignore_warning option " - "(%s) is invalid." % - (ignore_warn_list)) + self.fail_json( + msg="The value of the ignore_warning option " + "(%s) is invalid." % (ignore_warn_list) + ) def parse_rollback_option(self): """Parses the rollback option. @@ -1054,8 +1092,8 @@ def parse_rollback_option(self): If there is an error parsing rollback. """ # Nothing to do if rollback wasn't specified or is 'rescue'. - rollback = self.params.get('rollback') - if rollback is None or rollback == 'rescue': + rollback = self.params.get("rollback") + if rollback is None or rollback == "rescue": return rollback if isinstance(rollback, basestring): try: @@ -1066,9 +1104,11 @@ def parse_rollback_option(self): except ValueError: # Fall through to fail_json() pass - self.fail_json(msg="The value of the rollback option (%s) is invalid. " - "Must be the string 'rescue' or an int between " - "0 and 49." % (str(rollback))) + self.fail_json( + msg="The value of the rollback option (%s) is invalid. " + "Must be the string 'rescue' or an int between " + "0 and 49." % (str(rollback)) + ) def open(self): """Open the self.dev PyEZ Device instance. @@ -1083,15 +1123,14 @@ def open(self): connect_args[key] = self.params.get(key) try: - #self.close() + # self.close() log_connect_args = dict(connect_args) - log_connect_args['passwd'] = 'NOT_LOGGING_PARAMETER' - if 'cs_passwd' in log_connect_args: - log_connect_args['cs_passwd'] = 'NOT_LOGGING_PARAMETER' + log_connect_args["passwd"] = "NOT_LOGGING_PARAMETER" + if "cs_passwd" in log_connect_args: + log_connect_args["cs_passwd"] = "NOT_LOGGING_PARAMETER" - self.logger.debug("Creating device parameters: %s", - log_connect_args) - timeout = connect_args.pop('timeout') + self.logger.debug("Creating device parameters: %s", log_connect_args) + timeout = connect_args.pop("timeout") self.dev = jnpr.junos.device.Device(**connect_args) self.logger.debug("Opening device.") self.dev.open() @@ -1103,12 +1142,10 @@ def open(self): # ConnectError, so this should catch all connection-related exceptions # raised from PyEZ. except pyez_exception.ConnectError as ex: - self.fail_json(msg='Unable to make a PyEZ connection: %s' % - (str(ex))) + self.fail_json(msg="Unable to make a PyEZ connection: %s" % (str(ex))) def close(self, raise_exceptions=False): - """Close the self.dev PyEZ Device instance. - """ + """Close the self.dev PyEZ Device instance.""" if self.dev is not None: try: # Because self.fail_json() calls self.close(), we must set @@ -1122,8 +1159,7 @@ def close(self, raise_exceptions=False): # Exceptions raised by close() are all sub-classes of # ConnectError or RpcError, so this should catch all # exceptions raised from PyEZ. - except (pyez_exception.ConnectError, - pyez_exception.RpcError) as ex: + except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: if raise_exceptions is True: raise ex else: @@ -1133,8 +1169,7 @@ def close(self, raise_exceptions=False): pass def add_sw(self): - """Add an instance of jnpr.junos.utils.sw.SW() to self. - """ + """Add an instance of jnpr.junos.utils.sw.SW() to self.""" self.sw = SW(self.dev) def open_configuration(self, mode, ignore_warning=None, ephemeral_instance=None): @@ -1146,7 +1181,7 @@ def open_configuration(self, mode, ignore_warning=None, ephemeral_instance=None) config or an already opened private config. """ - ignore_warn = ['uncommitted changes will be discarded on exit'] + ignore_warn = ["uncommitted changes will be discarded on exit"] # if ignore_warning is a bool, pass the bool # if ignore_warning is a string add to the list # if ignore_warning is a list, merge them @@ -1158,47 +1193,51 @@ def open_configuration(self, mode, ignore_warning=None, ephemeral_instance=None) ignore_warn = ignore_warn + ignore_warning if self.conn_type != "local": - self._pyez_conn.open_configuration(mode,ignore_warn) + self._pyez_conn.open_configuration(mode, ignore_warn) return if self.config is None: if mode not in CONFIG_MODE_CHOICES: - self.fail_json(msg='Invalid configuration mode: %s' % (mode)) - if mode != 'ephemeral' and ephemeral_instance is not None: - self.fail_json(msg='Ephemeral instance is specified while the mode ' - 'is not ephemeral.Specify the mode as ephemeral or ' - 'do not specify the instance.') + self.fail_json(msg="Invalid configuration mode: %s" % (mode)) + if mode != "ephemeral" and ephemeral_instance is not None: + self.fail_json( + msg="Ephemeral instance is specified while the mode " + "is not ephemeral.Specify the mode as ephemeral or " + "do not specify the instance." + ) if self.dev is None: self.open() config = jnpr.junos.utils.config.Config(self.dev, mode=mode) try: - if config.mode == 'exclusive': + if config.mode == "exclusive": config.lock() - elif config.mode == 'private': + elif config.mode == "private": self.dev.rpc.open_configuration( - private=True, - ignore_warning=ignore_warn) - elif config.mode == 'dynamic': + private=True, ignore_warning=ignore_warn + ) + elif config.mode == "dynamic": self.dev.rpc.open_configuration( - dynamic=True, - ignore_warning=ignore_warn) - elif config.mode == 'batch': + dynamic=True, ignore_warning=ignore_warn + ) + elif config.mode == "batch": self.dev.rpc.open_configuration( - batch=True, - ignore_warning=ignore_warn) - elif config.mode == 'ephemeral': + batch=True, ignore_warning=ignore_warn + ) + elif config.mode == "ephemeral": if ephemeral_instance is None: self.dev.rpc.open_configuration( - ephemeral=True, - ignore_warning=ignore_warn) + ephemeral=True, ignore_warning=ignore_warn + ) else: self.dev.rpc.open_configuration( - ephemeral_instance = ephemeral_instance, - ignore_warning=ignore_warn) - except (pyez_exception.ConnectError, - pyez_exception.RpcError) as ex: - self.fail_json(msg='Unable to open the configuration in %s ' - 'mode: %s' % (config.mode, str(ex))) + ephemeral_instance=ephemeral_instance, + ignore_warning=ignore_warn, + ) + except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: + self.fail_json( + msg="Unable to open the configuration in %s " + "mode: %s" % (config.mode, str(ex)) + ) self.config = config self.logger.debug("Configuration opened in %s mode.", config.mode) @@ -1221,19 +1260,24 @@ def close_configuration(self): config = self.config self.config = None try: - if config.mode == 'exclusive': + if config.mode == "exclusive": config.unlock() - elif config.mode in ['batch', 'dynamic', 'private', 'ephemeral']: + elif config.mode in ["batch", "dynamic", "private", "ephemeral"]: self.dev.rpc.close_configuration() self.logger.debug("Configuration closed.") - except (pyez_exception.ConnectError, - pyez_exception.RpcError) as ex: - self.fail_json(msg='Unable to close the configuration: %s' % - (str(ex))) - - def get_configuration(self, database='committed', format='text', - options={}, filter=None, model=None, - namespace=None, remove_ns=True): + except (pyez_exception.ConnectError, pyez_exception.RpcError) as ex: + self.fail_json(msg="Unable to close the configuration: %s" % (str(ex))) + + def get_configuration( + self, + database="committed", + format="text", + options={}, + filter=None, + model=None, + namespace=None, + remove_ns=True, + ): """Return the device configuration in the specified format. Return the database device configuration database in the format format. @@ -1271,79 +1315,101 @@ def get_configuration(self, database='committed', format='text', - Format not understood by device. """ if database not in CONFIG_DATABASE_CHOICES: - self.fail_json(msg='The configuration database % is not in the ' - 'list of recognized configuration databases: ' - '%s.' % - (database, str(CONFIG_DATABASE_CHOICES))) + self.fail_json( + msg="The configuration database % is not in the " + "list of recognized configuration databases: " + "%s." % (database, str(CONFIG_DATABASE_CHOICES)) + ) if format not in CONFIG_FORMAT_CHOICES: - self.fail_json(msg='The configuration format % is not in the list ' - 'of recognized configuration formats: %s.' % - (format, str(CONFIG_FORMAT_CHOICES))) + self.fail_json( + msg="The configuration format % is not in the list " + "of recognized configuration formats: %s." + % (format, str(CONFIG_FORMAT_CHOICES)) + ) - options.update({'database': database, - 'format': format}) + options.update({"database": database, "format": format}) if self.conn_type == "local": if self.dev is None: self.open() - self.logger.debug("Retrieving device configuration. Options: %s " - "Filter %s", str(options), str(filter)) + self.logger.debug( + "Retrieving device configuration. Options: %s " "Filter %s", + str(options), + str(filter), + ) config = None try: if self.conn_type == "local": - config = self.dev.rpc.get_config(options=options, - filter_xml=filter, - model=model, - remove_ns=remove_ns, - namespace=namespace) + config = self.dev.rpc.get_config( + options=options, + filter_xml=filter, + model=model, + remove_ns=remove_ns, + namespace=namespace, + ) else: - config = self.get_config(options=options, - filter_xml=filter, - model=model, - remove_ns=remove_ns, - namespace=namespace) + config = self.get_config( + options=options, + filter_xml=filter, + model=model, + remove_ns=remove_ns, + namespace=namespace, + ) self.logger.debug("Configuration retrieved.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Unable to retrieve the configuration: %s' % - (str(ex))) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + self.fail_json(msg="Unable to retrieve the configuration: %s" % (str(ex))) return_val = (None, None) - if format == 'text': + if format == "text": if not isinstance(config, self.etree._Element): - self.fail_json(msg='Unexpected configuration type returned. ' - 'Configuration is: %s' % (str(config))) - if model is None and config.tag != 'configuration-text': - self.fail_json(msg='Unexpected XML tag returned. ' - 'Configuration is: %s' % - (self.etree.tostring(config, pretty_print=True))) + self.fail_json( + msg="Unexpected configuration type returned. " + "Configuration is: %s" % (str(config)) + ) + if model is None and config.tag != "configuration-text": + self.fail_json( + msg="Unexpected XML tag returned. " + "Configuration is: %s" + % (self.etree.tostring(config, pretty_print=True)) + ) return_val = (config.text, None) - elif format == 'set': + elif format == "set": if not isinstance(config, self.etree._Element): - self.fail_json(msg='Unexpected configuration type returned. ' - 'Configuration is: %s' % (str(config))) - if model is None and config.tag != 'configuration-set': - self.fail_json(msg='Unexpected XML tag returned. ' - 'Configuration is: %s' % - (self.etree.tostring(config, pretty_print=True))) + self.fail_json( + msg="Unexpected configuration type returned. " + "Configuration is: %s" % (str(config)) + ) + if model is None and config.tag != "configuration-set": + self.fail_json( + msg="Unexpected XML tag returned. " + "Configuration is: %s" + % (self.etree.tostring(config, pretty_print=True)) + ) return_val = (config.text, config.text.splitlines()) - elif format == 'xml': + elif format == "xml": if not isinstance(config, self.etree._Element): - self.fail_json(msg='Unexpected configuration type returned. ' - 'Configuration is: %s' % (str(config))) - if model is None and config.tag != 'configuration': - self.fail_json(msg='Unexpected XML tag returned. ' - 'Configuration is: %s' % - (self.etree.tostring(config, pretty_print=True))) - return_val = (self.etree.tostring(config, pretty_print=True), - self.jxmlease.parse_etree(config)) - elif format == 'json': + self.fail_json( + msg="Unexpected configuration type returned. " + "Configuration is: %s" % (str(config)) + ) + if model is None and config.tag != "configuration": + self.fail_json( + msg="Unexpected XML tag returned. " + "Configuration is: %s" + % (self.etree.tostring(config, pretty_print=True)) + ) + return_val = ( + self.etree.tostring(config, pretty_print=True), + self.jxmlease.parse_etree(config), + ) + elif format == "json": return_val = (json.dumps(config), config) else: - self.fail_json(msg='Unable to return configuration in %s format.' % - (format)) + self.fail_json( + msg="Unable to return configuration in %s format." % (format) + ) return return_val def rollback_configuration(self, id): @@ -1366,29 +1432,35 @@ def rollback_configuration(self, id): return if self.dev is None or self.config is None: - self.fail_json(msg='The device or configuration is not open.') + self.fail_json(msg="The device or configuration is not open.") - if id == 'rescue': + if id == "rescue": self.logger.debug("Rolling back to the rescue configuration.") try: - self.config.rescue(action='reload') + self.config.rescue(action="reload") self.logger.debug("Rescue configuration loaded.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Unable to load the rescue configuraton: ' - '%s' % (str(ex))) + except ( + self.pyez_exception.RpcError, + self.pyez_exception.ConnectError, + ) as ex: + self.fail_json( + msg="Unable to load the rescue configuraton: " "%s" % (str(ex)) + ) elif id >= 0 and id <= 49: self.logger.debug("Loading rollback %d configuration.", id) try: self.config.rollback(rb_id=id) self.logger.debug("Rollback %d configuration loaded.", id) - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Unable to load the rollback %d ' - 'configuraton: %s' % (id, str(ex))) + except ( + self.pyez_exception.RpcError, + self.pyez_exception.ConnectError, + ) as ex: + self.fail_json( + msg="Unable to load the rollback %d " + "configuraton: %s" % (id, str(ex)) + ) else: - self.fail_json(msg='Unrecognized rollback configuraton value: %s' - % (id)) + self.fail_json(msg="Unrecognized rollback configuraton value: %s" % (id)) def check_configuration(self): """Check the candidate configuration. @@ -1406,15 +1478,13 @@ def check_configuration(self): return if self.dev is None or self.config is None: - self.fail_json(msg='The device or configuration is not open.') + self.fail_json(msg="The device or configuration is not open.") try: self.config.commit_check() self.logger.debug("Configuration checked.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Failure checking the configuraton: %s' % - (str(ex))) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + self.fail_json(msg="Failure checking the configuraton: %s" % (str(ex))) def diff_configuration(self, ignore_warning=False): """Diff the candidate and committed configurations. @@ -1432,26 +1502,26 @@ def diff_configuration(self, ignore_warning=False): diff = self._pyez_conn.diff_configuration(ignore_warning) return diff if self.dev is None or self.config is None: - self.fail_json(msg='The device or configuration is not open.') + self.fail_json(msg="The device or configuration is not open.") try: diff = self.config.diff(rb_id=0, ignore_warning=ignore_warning) self.logger.debug("Configuration diff completed.") return diff - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Failure diffing the configuraton: %s' % - (str(ex))) - - def load_configuration(self, - action, - lines=None, - src=None, - template=None, - vars=None, - url=None, - ignore_warning=None, - format=None): + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + self.fail_json(msg="Failure diffing the configuraton: %s" % (str(ex))) + + def load_configuration( + self, + action, + lines=None, + src=None, + template=None, + vars=None, + url=None, + ignore_warning=None, + format=None, + ): """Load the candidate configuration. Load the candidate configuration from the specified src file using the @@ -1475,38 +1545,39 @@ def load_configuration(self, """ if self.conn_type == "local": if self.dev is None or self.config is None: - self.fail_json(msg='The device or configuration is not open.') + self.fail_json(msg="The device or configuration is not open.") load_args = {} config = None if ignore_warning is not None: - load_args['ignore_warning'] = ignore_warning - if action == 'set': - format = 'set' + load_args["ignore_warning"] = ignore_warning + if action == "set": + format = "set" if format is not None: - load_args['format'] = format - if action == 'merge': - load_args['merge'] = True - if action == 'override' or action == 'overwrite': - load_args['overwrite'] = True - if action == 'update': - load_args['update'] = True - if action == 'patch': - load_args['patch'] = True + load_args["format"] = format + if action == "merge": + load_args["merge"] = True + if action == "override" or action == "overwrite": + load_args["overwrite"] = True + if action == "update": + load_args["update"] = True + if action == "patch": + load_args["patch"] = True if lines is not None: - config = '\n'.join(map(lambda line: line.rstrip('\n'), lines)) + config = "\n".join(map(lambda line: line.rstrip("\n"), lines)) self.logger.debug("Loading the supplied configuration.") if src is not None: abs_path_src = os.path.abspath(src) # For PyEZ persistent - load_args['path'] = abs_path_src + load_args["path"] = abs_path_src self.logger.debug("Loading the configuration from: %s.", src) if template is not None: - load_args['template_path'] = template - load_args['template_vars'] = vars - self.logger.debug("Loading the configuration from the %s " - "template.", template) + load_args["template_path"] = template + load_args["template_vars"] = vars + self.logger.debug( + "Loading the configuration from the %s " "template.", template + ) if url is not None: - load_args['url'] = url + load_args["url"] = url self.logger.debug("Loading the configuration from %s.", url) if self.conn_type != "local": @@ -1520,14 +1591,19 @@ def load_configuration(self, self.logger.debug("Load args %s.", str(load_args)) self.config.load(**load_args) self.logger.debug("Configuration loaded.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Failure loading the configuraton: %s' % - (str(ex))) - - def commit_configuration(self, ignore_warning=None, comment=None, - confirmed=None, timeout=30, full=False, - sync=False, force_sync=False): + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + self.fail_json(msg="Failure loading the configuraton: %s" % (str(ex))) + + def commit_configuration( + self, + ignore_warning=None, + comment=None, + confirmed=None, + timeout=30, + full=False, + sync=False, + force_sync=False, + ): """Commit the candidate configuration. Commit the configuration. Assumes the configuration is already opened. @@ -1549,32 +1625,34 @@ def commit_configuration(self, ignore_warning=None, comment=None, timeout = self.dev.timeout if self.conn_type != "local": - self._pyez_conn.commit_configuration(ignore_warning=ignore_warning, - comment=comment, - confirmed=confirmed, - timeout=timeout, - full=full, - force_sync=force_sync, - sync=sync) + self._pyez_conn.commit_configuration( + ignore_warning=ignore_warning, + comment=comment, + confirmed=confirmed, + timeout=timeout, + full=full, + force_sync=force_sync, + sync=sync, + ) return if self.dev is None or self.config is None: - self.fail_json(msg='The device or configuration is not open.') + self.fail_json(msg="The device or configuration is not open.") self.logger.debug("Committing the configuration.") try: - self.config.commit(ignore_warning=ignore_warning, - comment=comment, - confirm=confirmed, - timeout=timeout, - full=full, - force_sync=force_sync, - sync=sync) + self.config.commit( + ignore_warning=ignore_warning, + comment=comment, + confirm=confirmed, + timeout=timeout, + full=full, + force_sync=force_sync, + sync=sync, + ) self.logger.debug("Configuration committed.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Failure committing the configuraton: %s' % - (str(ex))) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + self.fail_json(msg="Failure committing the configuraton: %s" % (str(ex))) def ping(self, params, acceptable_percent_loss=0, results={}): """Execute a ping command with the parameters specified in params. @@ -1615,103 +1693,105 @@ def ping(self, params, acceptable_percent_loss=0, results={}): - If there are errors present in the results. """ # Assume failure until we know success. - results['failed'] = True + results["failed"] = True # Execute the ping. try: - self.logger.debug("Executing ping with parameters: %s", - str(params)) + self.logger.debug("Executing ping with parameters: %s", str(params)) if self.conn_type == "local": resp = self.dev.rpc.ping(normalize=True, **params) else: response = self._pyez_conn.ping_device(normalize=True, **params) resp = self.etree.fromstring(response) self.logger.debug("Ping executed.") - except (self.pyez_exception.RpcError, - self.pyez_exception.ConnectError) as ex: - self.fail_json(msg='Unable to execute ping: %s' % (str(ex))) + except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: + self.fail_json(msg="Unable to execute ping: %s" % (str(ex))) if not isinstance(resp, self.etree._Element): - self.fail_json(msg='Unexpected ping response: %s' % (str(resp))) + self.fail_json(msg="Unexpected ping response: %s" % (str(resp))) resp_xml = self.etree.tostring(resp, pretty_print=True) # Fail if any errors in the results - errors = resp.findall( - "rpc-error[error-severity='error']/error-message") + errors = resp.findall("rpc-error[error-severity='error']/error-message") if len(errors) != 0: # Create a comma-plus-space-seperated string of the errors. # Calls the text attribute of each element in the errors list. - err_msg = ', '.join(map(lambda err: err.text, errors)) - results['msg'] = "Ping returned errors: %s" % (err_msg) + err_msg = ", ".join(map(lambda err: err.text, errors)) + results["msg"] = "Ping returned errors: %s" % (err_msg) self.exit_json(**results) # Add any warnings into the results - warnings = resp.findall( - "rpc-error[error-severity='warning']/error-message") + warnings = resp.findall("rpc-error[error-severity='warning']/error-message") if len(warnings) != 0: # Create list of the text attributes of each element in the # warnings list. - results['warnings'] = list(map(lambda warn: warn.text, warnings)) + results["warnings"] = list(map(lambda warn: warn.text, warnings)) # Try to find probe summary - probe_summary = resp.find('probe-results-summary') + probe_summary = resp.find("probe-results-summary") if probe_summary is None: - results['msg'] = "Probe-results-summary not found in response: " \ - "%s" % (resp_xml) + results["msg"] = "Probe-results-summary not found in response: " "%s" % ( + resp_xml + ) self.exit_json(**results) # Extract some required fields and some optional fields r_fields = {} - r_fields['packet_loss'] = probe_summary.findtext('packet-loss') - r_fields['packets_sent'] = probe_summary.findtext('probes-sent') - r_fields['packets_received'] = probe_summary.findtext( - 'responses-received') + r_fields["packet_loss"] = probe_summary.findtext("packet-loss") + r_fields["packets_sent"] = probe_summary.findtext("probes-sent") + r_fields["packets_received"] = probe_summary.findtext("responses-received") o_fields = {} - o_fields['rtt_minimum'] = probe_summary.findtext('rtt-minimum') - o_fields['rtt_maximum'] = probe_summary.findtext('rtt-maximum') - o_fields['rtt_average'] = probe_summary.findtext('rtt-average') - o_fields['rtt_stddev'] = probe_summary.findtext('rtt-stddev') + o_fields["rtt_minimum"] = probe_summary.findtext("rtt-minimum") + o_fields["rtt_maximum"] = probe_summary.findtext("rtt-maximum") + o_fields["rtt_average"] = probe_summary.findtext("rtt-average") + o_fields["rtt_stddev"] = probe_summary.findtext("rtt-stddev") # Make sure we got values for required fields. for key in r_fields: if r_fields[key] is None: - results['msg'] = 'Expected field %s not found in ' \ - 'response: %s' % (key, resp_xml) + results["msg"] = "Expected field %s not found in " "response: %s" % ( + key, + resp_xml, + ) self.exit_json(**results) # Add the required fields to the result. results.update(r_fields) # Extract integer packet loss packet_loss = 100 - if results['packet_loss'] is not None: + if results["packet_loss"] is not None: try: - packet_loss = round(float(results['packet_loss'])) + packet_loss = round(float(results["packet_loss"])) except ValueError: - results['msg'] = 'Packet loss %s not an integer. ' \ - 'Response: %s' % \ - (results['packet_loss'], resp_xml) + results["msg"] = "Packet loss %s not an integer. " "Response: %s" % ( + results["packet_loss"], + resp_xml, + ) self.exit_json(**results) if packet_loss < 100: # Optional fields are present if packet_loss < 100 for key in o_fields: if o_fields[key] is None: - results['msg'] = 'Expected field %s not found in ' \ - 'response: %s' % (key, resp_xml) + results["msg"] = ( + "Expected field %s not found in " + "response: %s" % (key, resp_xml) + ) self.exit_json(**results) # Add the o_fields to the result (even if they're None) results.update(o_fields) # Set the result message. - results['msg'] = 'Loss %s%%, (Sent %s | Received %s)' % \ - (results['packet_loss'], - results['packets_sent'], - results['packets_received']) + results["msg"] = "Loss %s%%, (Sent %s | Received %s)" % ( + results["packet_loss"], + results["packets_sent"], + results["packets_received"], + ) # Was packet loss within limits? If so, we didn't fail. if packet_loss <= acceptable_percent_loss: - results['failed'] = False + results["failed"] = False return results @@ -1744,52 +1824,65 @@ def save_text_output(self, name, format, text): - If the destination file is not writable. """ file_path = None - mode = 'wb' - if name == 'diff': - if self.params.get('diffs_file') is not None: - file_path = os.path.normpath(self.params.get('diffs_file')) - elif self.params.get('dest_dir') is not None: - dest_dir = self.params.get('dest_dir') - file_name = '%s.diff' % (self.inventory_hostname,) + mode = "wb" + if name == "diff": + if self.params.get("diffs_file") is not None: + file_path = os.path.normpath(self.params.get("diffs_file")) + elif self.params.get("dest_dir") is not None: + dest_dir = self.params.get("dest_dir") + file_name = "%s.diff" % (self.inventory_hostname,) file_path = os.path.normpath(os.path.join(dest_dir, file_name)) else: - if self.params.get('dest') is not None: - file_path = os.path.normpath(self.params.get('dest')) - if getattr(self, 'destfile', None) is None: - self.destfile = self.params.get('dest') + if self.params.get("dest") is not None: + file_path = os.path.normpath(self.params.get("dest")) + if getattr(self, "destfile", None) is None: + self.destfile = self.params.get("dest") else: - mode = 'ab' - elif self.params.get('dest_dir') is not None: - dest_dir = self.params.get('dest_dir') + mode = "ab" + elif self.params.get("dest_dir") is not None: + dest_dir = self.params.get("dest_dir") # Substitute underscore for spaces. - name = name.replace(' ', '_') + name = name.replace(" ", "_") # Substitute underscore for pipe - name = name.replace('|', '_') - name = '' if name == 'config' else '_' + name - file_name = '%s%s.%s' % (self.inventory_hostname, name, format) + name = name.replace("|", "_") + name = "" if name == "config" else "_" + name + file_name = "%s%s.%s" % (self.inventory_hostname, name, format) file_path = os.path.normpath(os.path.join(dest_dir, file_name)) if file_path is not None: try: # Use ansible utility to convert objects to bytes # to achieve Python2/3 compatibility with open(file_path, mode) as save_file: - save_file.write(to_bytes(text, encoding='utf-8')) + save_file.write(to_bytes(text, encoding="utf-8")) self.logger.debug("Output saved to: %s.", file_path) except IOError: - self.fail_json(msg="Unable to save output. Failed to " - "open the %s file." % (file_path)) + self.fail_json( + msg="Unable to save output. Failed to " + "open the %s file." % (file_path) + ) - def get_config(self, filter_xml=None, options=None, model=None, - namespace=None, remove_ns=True, **kwarg): - response = self._pyez_conn.get_config(filter_xml, options, model, namespace, remove_ns, **kwarg) + def get_config( + self, + filter_xml=None, + options=None, + model=None, + namespace=None, + remove_ns=True, + **kwarg + ): + response = self._pyez_conn.get_config( + filter_xml, options, model, namespace, remove_ns, **kwarg + ) return self.etree.fromstring(response) def get_rpc(self, rpc, ignore_warning=None, format=None): rpc_1 = self.etree.tostring(rpc) rpc_str = xmltodict.parse(rpc_1) - #json.dumps(rpc_str) - response = self._pyez_conn.get_rpc_resp(rpc_str, ignore_warning=ignore_warning, format=format) - if format == 'json': + # json.dumps(rpc_str) + response = self._pyez_conn.get_rpc_resp( + rpc_str, ignore_warning=ignore_warning, format=format + ) + if format == "json": return response return self.etree.fromstring(response) @@ -1817,11 +1910,11 @@ def local_md5(self, package, action): :raises IOError: when **package** file does not exist """ try: - checksum= self._hashfile(open(package, 'rb'), hashlib.md5()) + checksum = self._hashfile(open(package, "rb"), hashlib.md5()) except Exception as err: self.logger.error("unable to get the hash due to:{0}".format(err)) - if (("No such file" in format(err)) and action == "get"): - checksum="no_file" + if ("No such file" in format(err)) and action == "get": + checksum = "no_file" else: raise err return checksum @@ -1833,15 +1926,15 @@ def remote_md5(self, remote_file, action): try: if self.conn_type == "local": rpc_reply = self.dev.rpc.get_checksum_information(path=remote_file) - checksum = rpc_reply.findtext('.//checksum').strip() + checksum = rpc_reply.findtext(".//checksum").strip() else: rpc_reply = self._pyez_conn.get_checksum_information(remote_file) resp = self.etree.fromstring(rpc_reply) - checksum = resp.findtext('.//checksum').strip() + checksum = resp.findtext(".//checksum").strip() except Exception as err: self.logger.error("unable to get rpc due to:{0}".format(str(err))) - if (("No such file or directory" in format(err)) and (action=="put")): - checksum="no_file" + if ("No such file or directory" in format(err)) and (action == "put"): + checksum = "no_file" else: raise err return checksum @@ -1861,13 +1954,14 @@ def scp_file_copy_put(self, local_file, remote_file): scp1.put(local_file, remote_file) else: self._pyez_conn.scp_file_copy_put(local_file, remote_file) - self.logger.info("computing remote MD5 checksum on: {0}".format(remote_file)) + self.logger.info( + "computing remote MD5 checksum on: {0}".format(remote_file) + ) remote_checksum = self.remote_md5(remote_file, "put") self.logger.info("Remote checksum: {0}".format(remote_checksum)) if remote_checksum != local_checksum: status = "Transfer failed (different MD5 between local and remote) {0} | {1}".format( - local_checksum, - remote_checksum + local_checksum, remote_checksum ) self.logger.error(status) self.fail_json(msg=status) @@ -1887,7 +1981,7 @@ def scp_file_copy_get(self, remote_file, local_file): remote_checksum = self.remote_md5(remote_file, "get") self.logger.info("Remote checksum: {0}".format(remote_checksum)) local_checksum = self.local_md5(local_file, "get") - if (local_checksum == "no_file" or local_checksum != remote_checksum): + if local_checksum == "no_file" or local_checksum != remote_checksum: status = "File not present, need to transfer" self.logger.info(status) if self.conn_type == "local": @@ -1900,8 +1994,7 @@ def scp_file_copy_get(self, remote_file, local_file): self.logger.info("Local checksum: {0}".format(local_checksum)) if remote_checksum != local_checksum: status = "Transfer failed (different MD5 between local and remote) {0} | {1}".format( - local_checksum, - remote_checksum + local_checksum, remote_checksum ) self.logger.error(status) self.fail_json(msg=status) diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index 8927e852..2932bbff 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: command @@ -141,9 +143,9 @@ required: false default: true type: bool -''' +""" -EXAMPLES = ''' +EXAMPLES = """ - name: 'Explicit host argument' hosts: junos connection: local @@ -202,9 +204,9 @@ - "show route" - "show lldp neighbors" dest: "/tmp/{{ inventory_hostname }}.commands.output" -''' +""" -RETURN = ''' +RETURN = """ changed: description: - Indicates if the device's state has changed. Since this module does not @@ -277,46 +279,48 @@ - The command reply from the Junos device as a list of single-line strings. returned: when command executed successfully and I(return_output) is C(true). type: list of str -''' +""" import sys - """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules But custom module_utils directory is supported from Ansible 2.3 Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - commands=dict(required=True, - type='list', - aliases=['cli', 'command', 'cmd', 'cmds'], - default=None), - formats=dict(required=False, - type='list', - aliases=['format', 'display', 'output'], - default=None), - dest=dict(required=False, - type='path', - aliases=['destination'], - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir'], - default=None), - ignore_warning=dict(required=False, - type='list', - default=None), - return_output=dict(required=False, - type='bool', - default=True) + commands=dict( + required=True, + type="list", + aliases=["cli", "command", "cmd", "cmds"], + default=None, + ), + formats=dict( + required=False, + type="list", + aliases=["format", "display", "output"], + default=None, + ), + dest=dict( + required=False, type="path", aliases=["destination"], default=None + ), + dest_dir=dict( + required=False, + type="path", + aliases=["destination_dir", "destdir"], + default=None, + ), + ignore_warning=dict(required=False, type="list", default=None), + return_output=dict(required=False, type="bool", default=True), ), # Since this module doesn't change the device's configuration, there is # no additional work required to support check mode. It's inherently @@ -331,144 +335,154 @@ def main(): ignore_warning = junos_module.parse_ignore_warning_option() # Check over commands - commands = junos_module.params.get('commands') + commands = junos_module.params.get("commands") # Ansible allows users to specify a commands argument with no value. if commands is None: junos_module.fail_json(msg="The commands option must have a value.") # Make sure the commands don't include any pipe modifiers. for command in commands: - pipe_index = command.find('|') - if (pipe_index != -1 and - command[pipe_index:].strip() != 'display xml rpc'): + pipe_index = command.find("|") + if pipe_index != -1 and command[pipe_index:].strip() != "display xml rpc": # Allow "show configuration | display set" - if ('show configuration' in command and - 'display set' in command[pipe_index:] and - '|' not in command[pipe_index+1:]): + if ( + "show configuration" in command + and "display set" in command[pipe_index:] + and "|" not in command[pipe_index + 1 :] + ): continue # Any other "| display " should use the format option instead. for valid_format in juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES: - if 'display ' + valid_format in command[pipe_index:]: + if "display " + valid_format in command[pipe_index:]: junos_module.fail_json( - msg='The pipe modifier (%s) in the command ' - '(%s) is not supported. Use format: "%s" ' - 'instead.' % - (command[pipe_index:], command, valid_format)) + msg="The pipe modifier (%s) in the command " + '(%s) is not supported. Use format: "%s" ' + "instead." % (command[pipe_index:], command, valid_format) + ) # Any other "| " is going to produce an error anyway, so fail # with a meaningful message. - junos_module.fail_json(msg='The pipe modifier (%s) in the command ' - '(%s) is not supported.' % - (command[pipe_index:], command)) + junos_module.fail_json( + msg="The pipe modifier (%s) in the command " + "(%s) is not supported." % (command[pipe_index:], command) + ) # Check over formats - formats = junos_module.params.get('formats') + formats = junos_module.params.get("formats") if formats is None: # Default to text format - formats = ['text'] + formats = ["text"] valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES # Check format values for format in formats: # Is it a valid format? if format not in valid_formats: - junos_module.fail_json(msg="The value %s in formats is invalid. " - "Must be one of: %s" % - (format, ', '.join(map(str, - valid_formats)))) + junos_module.fail_json( + msg="The value %s in formats is invalid. " + "Must be one of: %s" % (format, ", ".join(map(str, valid_formats))) + ) # Correct number of format values? if len(formats) != 1 and len(formats) != len(commands): - junos_module.fail_json(msg="The formats option must have a single " - "value, or one value per command. There " - "are %d commands and %d formats." % - (len(commands), len(formats))) + junos_module.fail_json( + msg="The formats option must have a single " + "value, or one value per command. There " + "are %d commands and %d formats." % (len(commands), len(formats)) + ) # Same format for all commands elif len(formats) == 1 and len(commands) > 1: formats = formats * len(commands) results = list() - for (command, format) in zip(commands, formats): + for command, format in zip(commands, formats): # Set initial result values. Assume failure until we know it's success. - result = {'msg': '', - 'command': command, - 'format': format, - 'changed': False, - 'failed': True} + result = { + "msg": "", + "command": command, + "format": format, + "changed": False, + "failed": True, + } # Execute the CLI command try: - junos_module.logger.debug('Executing command "%s".', - command) - rpc = junos_module.etree.Element('command', format=format) + junos_module.logger.debug('Executing command "%s".', command) + rpc = junos_module.etree.Element("command", format=format) rpc.text = command if junos_module.conn_type == "local": - resp = junos_module.dev.rpc(rpc, ignore_warning=ignore_warning, normalize=bool(format == 'xml')) + resp = junos_module.dev.rpc( + rpc, ignore_warning=ignore_warning, normalize=bool(format == "xml") + ) else: - resp = junos_module.get_rpc(rpc, - ignore_warning=ignore_warning, format=format) - result['msg'] = 'The command executed successfully.' - junos_module.logger.debug('Command "%s" executed successfully.', - command) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Unable to execute "%s". Error: %s', - command, str(ex)) - result['msg'] = 'Unable to execute the command: %s. Error: %s' % \ - (command, str(ex)) + resp = junos_module.get_rpc( + rpc, ignore_warning=ignore_warning, format=format + ) + result["msg"] = "The command executed successfully." + junos_module.logger.debug('Command "%s" executed successfully.', command) + except ( + junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError, + ) as ex: + junos_module.logger.debug( + 'Unable to execute "%s". Error: %s', command, str(ex) + ) + result["msg"] = "Unable to execute the command: %s. Error: %s" % ( + command, + str(ex), + ) results.append(result) continue text_output = None parsed_output = None if resp is True: - text_output = '' + text_output = "" elif (resp, junos_module.etree._Element): # Handle the output based on format - if format == 'text': - if resp.tag in ['output', 'rpc-reply']: + if format == "text": + if resp.tag in ["output", "rpc-reply"]: text_output = resp.text - junos_module.logger.debug('Text output set.') - elif resp.tag == 'configuration-information': - text_output = resp.findtext('configuration-output') - junos_module.logger.debug('Text configuration output set.') + junos_module.logger.debug("Text output set.") + elif resp.tag == "configuration-information": + text_output = resp.findtext("configuration-output") + junos_module.logger.debug("Text configuration output set.") else: - result['msg'] = 'Unexpected text response tag: %s.' % ( - (resp.tag)) + result["msg"] = "Unexpected text response tag: %s." % ((resp.tag)) results.append(result) - junos_module.logger.debug('Unexpected text response tag ' - '%s.', resp.tag) + junos_module.logger.debug( + "Unexpected text response tag " "%s.", resp.tag + ) continue - elif format == 'xml': - encode = None if sys.version < '3' else 'unicode' - text_output = junos_module.etree.tostring(resp, - pretty_print=True, - encoding=encode) + elif format == "xml": + encode = None if sys.version < "3" else "unicode" + text_output = junos_module.etree.tostring( + resp, pretty_print=True, encoding=encode + ) parsed_output = junos_module.jxmlease.parse_etree(resp) - junos_module.logger.debug('XML output set.') - elif format == 'json': + junos_module.logger.debug("XML output set.") + elif format == "json": text_output = str(resp) parsed_output = resp - junos_module.logger.debug('JSON output set.') + junos_module.logger.debug("JSON output set.") else: - result['msg'] = 'Unexpected format %s.' % (format) + result["msg"] = "Unexpected format %s." % (format) results.append(result) - junos_module.logger.debug('Unexpected format %s.', format) + junos_module.logger.debug("Unexpected format %s.", format) continue else: - result['msg'] = 'Unexpected response type %s.' % (type(resp)) + result["msg"] = "Unexpected response type %s." % (type(resp)) results.append(result) - junos_module.logger.debug('Unexpected response type %s.', - type(resp)) + junos_module.logger.debug("Unexpected response type %s.", type(resp)) continue # Set the output keys - if junos_module.params['return_output'] is True: + if junos_module.params["return_output"] is True: if text_output is not None: - result['stdout'] = text_output - result['stdout_lines'] = text_output.splitlines() + result["stdout"] = text_output + result["stdout_lines"] = text_output.splitlines() if parsed_output is not None: - result['parsed_output'] = parsed_output + result["parsed_output"] = parsed_output # Save the output junos_module.save_text_output(command, format, text_output) # This command succeeded. - result['failed'] = False + result["failed"] = False # Append to the list of results results.append(result) @@ -479,13 +493,11 @@ def main(): # Calculate the overall failed. Only failed if all commands failed. failed = True for result in results: - if result.get('failed') is False: + if result.get("failed") is False: failed = False break - junos_module.exit_json(results=results, - changed=False, - failed=failed) + junos_module.exit_json(results=results, changed=False, failed=failed) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index 4ea55c23..bdb9306b 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: config @@ -53,7 +55,7 @@ steps in order: #. Open a candidate configuration database. - + * If the I(config_mode) option has a value of C(exclusive), the default, take a lock on the candidate configuration database. If the lock fails the module fails and reports an error. @@ -75,7 +77,7 @@ opening the ephemeral onfiguration database fails the module fails and reports an error. #. Load configuration data into the candidate configuration database. - + * Configuration data may be loaded using the I(load) or I(rollback) options. If either of these options are specified, new configuration data is loaded. If neither option is specified, this step is skipped. @@ -86,7 +88,7 @@ * The value of the I(load) option defines the type of load which is performed. * The source of the new configuration data is one of the following: - + * I(src) - A file path on the local Ansible control machine. * I(lines) - A list of strings containing the configuration data. * I(template) - A file path to a Jinja2 template on the local @@ -98,7 +100,7 @@ loaded is in the specified format, rather than the format determined from the file name. #. Check the validity of the candidate configuration database. - + * If the I(check) option is C(true), the default, check the validity of the configuration by performing a "commit check" operation. * This option may be specified with I(diff) C(false) and I(commit) @@ -108,7 +110,7 @@ fails, and an error is reported. #. Determine differences between the candidate and committed configuration databases. - + * If step 2 was not skipped, and the I(diff) option is C(true), the default, perform a diff between the candidate and committed configuration databases. @@ -118,7 +120,7 @@ generated configuration difference in the I(diff) and I(diff_lines) keys of the module's response. #. Retrieve the configuration database from the Junos device. - + * If the I(retrieve) option is specified, retrieve the configuration database specified by the I(retrieve) value from the target Junos device to the local Ansible control machine. @@ -135,7 +137,7 @@ retrieved configuration in the I(config), I(config_lines), and I(config_parsed) keys of the module's response. #. Commit the configuration changes. - + * If the I(commit) option is C(true), the default, commit the configuration changes. * This option may be specified with I(diff) C(false) and I(check) @@ -148,7 +150,7 @@ option is specified, wait I(check_commit_wait) seconds before performing the commit. #. Close the candidate configuration database. - + * Close and discard the candidate configuration database. * If the I(config_mode) option has a value of C(exclusive), the default, unlock the candidate configuration database. @@ -379,7 +381,7 @@ type: bool, str, or list of str model: description: - - Specifies yang model openconfig/custom/ietf to fetch. + - Specifies yang model openconfig/custom/ietf to fetch. - When model is True and filter_xml is None, xml is enclosed under so that we get junos as well as other model configurations. - In case of custom, user will have to provide the namespace to be fetched @@ -452,7 +454,7 @@ format (a series of configuration mode commands). The new configuration data is loaded line by line and may contain any configuration mode commands, such as set, delete, edit, or deactivate. This value must be - specified if the new configuration is in set format. + specified if the new configuration is in set format. required: false default: none choices: @@ -478,7 +480,7 @@ - The configuration database to be retrieved. required: false default: none - choices: + choices: - none - candidate - committed @@ -553,7 +555,7 @@ - The Junos device uses this URL to load the configuration, therefore this URL must be reachable by the target Junos device. - The possible formats of this value are documented in the 'url' section - of the + of the U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). - The I(src), I(lines), I(template), and I(url) options are mutually exclusive. @@ -571,9 +573,9 @@ type: dict aliases: - template_vars -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: 'Explicit host argument' hosts: junos @@ -745,9 +747,9 @@ filter: re0 return_output: True register: config_output -''' +""" -RETURN = ''' +RETURN = """ changed: description: - Indicates if the device's configuration has changed, or would have @@ -779,7 +781,7 @@ C(json) and I(return_output) is C(true). type: dict diff: - description: + description: - The configuration differences between the previous and new configurations. The value is a dict that contains a single key named "prepared". Value associated with that key is a single multi-line string @@ -796,7 +798,7 @@ I(return_output) is C(true). type: list failed: - description: + description: - Indicates if the task failed. returned: always type: bool @@ -810,27 +812,27 @@ - A human-readable message indicating the result. returned: always type: str -''' +""" # Standard library imports import time - """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules But custom module_utils directory is supported from Ansible 2.3 Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # Choices which are defined in the common module. config_format_choices = juniper_junos_common.CONFIG_FORMAT_CHOICES - config_database_choices = [None] + \ - juniper_junos_common.CONFIG_DATABASE_CHOICES + config_database_choices = [None] + juniper_junos_common.CONFIG_DATABASE_CHOICES config_action_choices = [None] + juniper_junos_common.CONFIG_ACTION_CHOICES config_mode_choices = juniper_junos_common.CONFIG_MODE_CHOICES config_model_choices = juniper_junos_common.CONFIG_MODEL_CHOICES @@ -838,127 +840,89 @@ def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - ignore_warning=dict(required=False, - type='list', - default=None), - config_mode=dict(choices=config_mode_choices, - type='str', - required=False, - aliases=['config_access', 'edit_mode', - 'edit_access'], - default='exclusive'), - ephemeral_instance=dict(type='str', - required=False, - default=None), - rollback=dict(type='str', - required=False, - default=None), - load=dict(choices=config_action_choices, - type='str', - required=False, - default=None), - src=dict(type='path', - required=False, - aliases=['source', 'file'], - default=None), - lines=dict(type='list', - required=False, - default=None), - template=dict(type='path', - required=False, - aliases=['template_path'], - default=None), - vars=dict(type='dict', - required=False, - aliases=['template_vars'], - default=None), - url=dict(type='str', - required=False, - default=None), - format=dict(choices=config_format_choices, - type='str', - required=False, - default=None), - model=dict(required=False, - choices=config_model_choices, - type='str', - default=None), - remove_ns=dict(required=False, - type='bool', - default=None), - namespace=dict(required=False, - type='str', - default=None), - check=dict(required=False, - type='bool', - aliases=['check_commit', 'commit_check'], - default=None), - diff=dict(required=False, - type='bool', - aliases=['compare', 'diffs'], - default=None), - diffs_file=dict(type='path', - required=False, - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir', 'savedir', - 'save_dir'], - default=None), - return_output=dict(required=False, - type='bool', - default=True), - retrieve=dict(choices=config_database_choices, - type='str', - required=False, - default=None), - options=dict(type='dict', - required=False, - default={}), - filter=dict(required=False, - type='str', - aliases=['filter_xml'], - default=None), - dest=dict(type='path', - required=False, - aliases=['destination'], - default=None), - commit=dict(required=False, - type='bool', - default=None), - commit_empty_changes=dict(required=False, - type='bool', - default=False), - commit_full=dict(required=False, - type='bool', - default=False), - commit_sync=dict(required=False, - type='bool', - default=False), - commit_force_sync=dict(required=False, - type='bool', - default=False), - confirmed=dict(required=False, - type='int', - aliases=['confirm'], - default=None), - timeout=dict(required=False, - type='int', - default=30), - comment=dict(required=False, - type='str', - default=None), - check_commit_wait=dict(required=False, - type='int', - default=None) + ignore_warning=dict(required=False, type="list", default=None), + config_mode=dict( + choices=config_mode_choices, + type="str", + required=False, + aliases=["config_access", "edit_mode", "edit_access"], + default="exclusive", + ), + ephemeral_instance=dict(type="str", required=False, default=None), + rollback=dict(type="str", required=False, default=None), + load=dict( + choices=config_action_choices, type="str", required=False, default=None + ), + src=dict( + type="path", required=False, aliases=["source", "file"], default=None + ), + lines=dict(type="list", required=False, default=None), + template=dict( + type="path", required=False, aliases=["template_path"], default=None + ), + vars=dict( + type="dict", required=False, aliases=["template_vars"], default=None + ), + url=dict(type="str", required=False, default=None), + format=dict( + choices=config_format_choices, type="str", required=False, default=None + ), + model=dict( + required=False, choices=config_model_choices, type="str", default=None + ), + remove_ns=dict(required=False, type="bool", default=None), + namespace=dict(required=False, type="str", default=None), + check=dict( + required=False, + type="bool", + aliases=["check_commit", "commit_check"], + default=None, + ), + diff=dict( + required=False, type="bool", aliases=["compare", "diffs"], default=None + ), + diffs_file=dict(type="path", required=False, default=None), + dest_dir=dict( + required=False, + type="path", + aliases=["destination_dir", "destdir", "savedir", "save_dir"], + default=None, + ), + return_output=dict(required=False, type="bool", default=True), + retrieve=dict( + choices=config_database_choices, + type="str", + required=False, + default=None, + ), + options=dict(type="dict", required=False, default={}), + filter=dict( + required=False, type="str", aliases=["filter_xml"], default=None + ), + dest=dict( + type="path", required=False, aliases=["destination"], default=None + ), + commit=dict(required=False, type="bool", default=None), + commit_empty_changes=dict(required=False, type="bool", default=False), + commit_full=dict(required=False, type="bool", default=False), + commit_sync=dict(required=False, type="bool", default=False), + commit_force_sync=dict(required=False, type="bool", default=False), + confirmed=dict( + required=False, type="int", aliases=["confirm"], default=None + ), + timeout=dict(required=False, type="int", default=30), + comment=dict(required=False, type="str", default=None), + check_commit_wait=dict(required=False, type="int", default=None), ), # Mutually exclusive options. - mutually_exclusive=[['load', 'rollback'], - ['src', 'lines', 'template', 'url'], - ['diffs_file', 'dest_dir'], - ['dest', 'dest_dir']], + mutually_exclusive=[ + ["load", "rollback"], + ["src", "lines", "template", "url"], + ["diffs_file", "dest_dir"], + ["dest", "dest_dir"], + ], # Required together options. - required_together=[['template', 'vars']], + required_together=[["template", "vars"]], # Check mode is implemented. supports_check_mode=True, min_jxmlease_version=cfg.MIN_JXMLEASE_VERSION, @@ -969,45 +933,45 @@ def main(): ignore_warning = junos_module.parse_ignore_warning_option() # Straight from params - config_mode = junos_module.params.get('config_mode') - ephemeral_instance = junos_module.params.get('ephemeral_instance') + config_mode = junos_module.params.get("config_mode") + ephemeral_instance = junos_module.params.get("ephemeral_instance") # Parse rollback value rollback = junos_module.parse_rollback_option() # Straight from params - load = junos_module.params.get('load') - src = junos_module.params.get('src') - lines = junos_module.params.get('lines') - template = junos_module.params.get('template') - vars = junos_module.params.get('vars') - url = junos_module.params.get('url') - format = junos_module.params.get('format') - check = junos_module.params.get('check') - diff = junos_module.params.get('diff') - diffs_file = junos_module.params.get('diffs_file') - dest_dir = junos_module.params.get('dest_dir') - return_output = junos_module.params.get('return_output') - retrieve = junos_module.params.get('retrieve') - options = junos_module.params.get('options') - filter = junos_module.params.get('filter') - dest = junos_module.params.get('dest') - commit = junos_module.params.get('commit') - commit_empty_changes = junos_module.params.get('commit_empty_changes') - commit_full = junos_module.params.get('commit_full') - commit_sync = junos_module.params.get('commit_sync') - commit_force_sync = junos_module.params.get('commit_force_sync') - confirmed = junos_module.params.get('confirmed') - timeout = junos_module.params.get('timeout') - comment = junos_module.params.get('comment') - check_commit_wait = junos_module.params.get('check_commit_wait') - model = junos_module.params.get('model') - remove_ns = junos_module.params.get('remove_ns') - namespace = junos_module.params.get('namespace') + load = junos_module.params.get("load") + src = junos_module.params.get("src") + lines = junos_module.params.get("lines") + template = junos_module.params.get("template") + vars = junos_module.params.get("vars") + url = junos_module.params.get("url") + format = junos_module.params.get("format") + check = junos_module.params.get("check") + diff = junos_module.params.get("diff") + diffs_file = junos_module.params.get("diffs_file") + dest_dir = junos_module.params.get("dest_dir") + return_output = junos_module.params.get("return_output") + retrieve = junos_module.params.get("retrieve") + options = junos_module.params.get("options") + filter = junos_module.params.get("filter") + dest = junos_module.params.get("dest") + commit = junos_module.params.get("commit") + commit_empty_changes = junos_module.params.get("commit_empty_changes") + commit_full = junos_module.params.get("commit_full") + commit_sync = junos_module.params.get("commit_sync") + commit_force_sync = junos_module.params.get("commit_force_sync") + confirmed = junos_module.params.get("confirmed") + timeout = junos_module.params.get("timeout") + comment = junos_module.params.get("comment") + check_commit_wait = junos_module.params.get("check_commit_wait") + model = junos_module.params.get("model") + remove_ns = junos_module.params.get("remove_ns") + namespace = junos_module.params.get("namespace") # Ephemeral database doesn't support "show | compare", # so setting diff to False. - if config_mode == 'ephemeral': + if config_mode == "ephemeral": diff = False # If retrieve is set and load and rollback are not set, then @@ -1030,191 +994,211 @@ def main(): # If load is not None, must have one of src, template, url, lines if load is not None: - for option in ['src', 'lines', 'template', 'url']: + for option in ["src", "lines", "template", "url"]: if junos_module.params.get(option) is not None: break # for/else only executed if we didn't break out of the loop. else: - junos_module.fail_json(msg="The load option (%s) is specified, " - "but none of 'src', 'lines', " - "'template', or 'url' are specified. " - "Must specify one of the 'src', " - "'lines', 'template', or 'url' options." - % (load)) + junos_module.fail_json( + msg="The load option (%s) is specified, " + "but none of 'src', 'lines', " + "'template', or 'url' are specified. " + "Must specify one of the 'src', " + "'lines', 'template', or 'url' options." % (load) + ) # format is valid if retrieve is not None or load is not None. if format is not None: if load is None and retrieve is None: - junos_module.fail_json(msg="The format option (%s) is specified, " - "but neither 'load' or 'retrieve' are " - "specified. Must specify one of " - "'load' or 'retrieve' options." - % (format)) + junos_module.fail_json( + msg="The format option (%s) is specified, " + "but neither 'load' or 'retrieve' are " + "specified. Must specify one of " + "'load' or 'retrieve' options." % (format) + ) # dest_dir is valid if retrieve is not None or diff is True. if dest_dir is not None: if retrieve is None and diff is False: - junos_module.fail_json(msg="The dest_dir option (%s) is specified," - " but neither 'retrieve' or 'diff' " - "are specified. Must specify one of " - "'retrieve' or 'diff' options." - % (dest_dir)) + junos_module.fail_json( + msg="The dest_dir option (%s) is specified," + " but neither 'retrieve' or 'diff' " + "are specified. Must specify one of " + "'retrieve' or 'diff' options." % (dest_dir) + ) # dest is valid if retrieve is not None if dest is not None: if retrieve is None: - junos_module.fail_json(msg="The dest option (%s) is specified," - " but 'retrieve' is not specified. " - "Must specify the 'retrieve' option." - % (dest)) + junos_module.fail_json( + msg="The dest option (%s) is specified," + " but 'retrieve' is not specified. " + "Must specify the 'retrieve' option." % (dest) + ) # diffs_file is valid if diff is True if diffs_file is not None: if diff is False: - junos_module.fail_json(msg="The diffs_file option (%s) is " - "specified, but 'diff' is false." - % (diffs_file)) + junos_module.fail_json( + msg="The diffs_file option (%s) is " + "specified, but 'diff' is false." % (diffs_file) + ) # commit_empty_changes is valid if commit is True if commit_empty_changes is True: if commit is False: - junos_module.fail_json(msg="The commit_empty_changes option " - "is true, but 'commit' is false. " - "The commit_empty_changes option " - "may only be specified when " - "'commit' is true.") + junos_module.fail_json( + msg="The commit_empty_changes option " + "is true, but 'commit' is false. " + "The commit_empty_changes option " + "may only be specified when " + "'commit' is true." + ) # comment is valid if commit is True if comment is not None: if commit is False: - junos_module.fail_json(msg="The comment option (%s) is " - "specified, but 'commit' is false." - % (comment)) + junos_module.fail_json( + msg="The comment option (%s) is " + "specified, but 'commit' is false." % (comment) + ) # confirmed is valid if commit is True if confirmed is not None: if commit is False: - junos_module.fail_json(msg="The confirmed option (%s) is " - "specified, but 'commit' is false." - % (confirmed)) + junos_module.fail_json( + msg="The confirmed option (%s) is " + "specified, but 'commit' is false." % (confirmed) + ) # Must be greater >= 1. if confirmed < 1: - junos_module.fail_json(msg="The confirmed option (%s) must have a " - "positive integer value." % (confirmed)) + junos_module.fail_json( + msg="The confirmed option (%s) must have a " + "positive integer value." % (confirmed) + ) # check_commit_wait is valid if check is True and commit is True if check_commit_wait is not None: if commit is False: - junos_module.fail_json(msg="The check_commit_wait option (%s) is " - "specified, but 'commit' is false." - % (check_commit_wait)) + junos_module.fail_json( + msg="The check_commit_wait option (%s) is " + "specified, but 'commit' is false." % (check_commit_wait) + ) if check is False: - junos_module.fail_json(msg="The check_commit_wait option (%s) is " - "specified, but 'check' is false." - % (check_commit_wait)) + junos_module.fail_json( + msg="The check_commit_wait option (%s) is " + "specified, but 'check' is false." % (check_commit_wait) + ) # Must be greater >= 1. if check_commit_wait < 1: - junos_module.fail_json(msg="The check_commit_wait option (%s) " - "must have a positive integer value." % - (check_commit_wait)) + junos_module.fail_json( + msg="The check_commit_wait option (%s) " + "must have a positive integer value." % (check_commit_wait) + ) # Initialize the results. Assume failure until we know it's success. - results = {'msg': 'Configuration has been: ', - 'changed': False, - 'failed': True} - - junos_module.logger.debug("Step 1 - Open a candidate configuration " - "database.") - junos_module.open_configuration(mode=config_mode, ignore_warning=ignore_warning, - ephemeral_instance=ephemeral_instance) - results['msg'] += 'opened' - - junos_module.logger.debug("Step 2 - Load configuration data into the " - "candidate configuration database.") + results = {"msg": "Configuration has been: ", "changed": False, "failed": True} + + junos_module.logger.debug("Step 1 - Open a candidate configuration " "database.") + junos_module.open_configuration( + mode=config_mode, + ignore_warning=ignore_warning, + ephemeral_instance=ephemeral_instance, + ) + results["msg"] += "opened" + + junos_module.logger.debug( + "Step 2 - Load configuration data into the " "candidate configuration database." + ) if rollback is not None: junos_module.rollback_configuration(id=rollback) # Assume configuration changed in case we don't perform a diff later. # If diff is set, we'll check for actual differences later. - results['changed'] = True - results['msg'] += ', rolled back' + results["changed"] = True + results["msg"] += ", rolled back" elif load is not None: if src is not None: - junos_module.load_configuration(action=load, - src=src, - ignore_warning=ignore_warning, - format=format) - results['file'] = src + junos_module.load_configuration( + action=load, src=src, ignore_warning=ignore_warning, format=format + ) + results["file"] = src elif lines is not None: - junos_module.load_configuration(action=load, - lines=lines, - ignore_warning=ignore_warning, - format=format) + junos_module.load_configuration( + action=load, lines=lines, ignore_warning=ignore_warning, format=format + ) elif template is not None: - junos_module.load_configuration(action=load, - template=template, - vars=vars, - ignore_warning=ignore_warning, - format=format) + junos_module.load_configuration( + action=load, + template=template, + vars=vars, + ignore_warning=ignore_warning, + format=format, + ) elif url is not None: - junos_module.load_configuration(action=load, - url=url, - ignore_warning=ignore_warning, - format=format) + junos_module.load_configuration( + action=load, url=url, ignore_warning=ignore_warning, format=format + ) else: - junos_module.fail_json(msg="The load option was set to: %s, but " - "no 'src', 'lines', 'template', or " - "'url' option was set." % - (load)) + junos_module.fail_json( + msg="The load option was set to: %s, but " + "no 'src', 'lines', 'template', or " + "'url' option was set." % (load) + ) # Assume configuration changed in case we don't perform a diff later. # If diff is set, we'll check for actual differences later. - results['changed'] = True - results['msg'] += ', loaded' + results["changed"] = True + results["msg"] += ", loaded" - junos_module.logger.debug("Step 3 - Check the validity of the candidate " - "configuration database.") + junos_module.logger.debug( + "Step 3 - Check the validity of the candidate " "configuration database." + ) if check is True: junos_module.check_configuration() - results['msg'] += ', checked' + results["msg"] += ", checked" - junos_module.logger.debug("Step 4 - Determine differences between the " - "candidate and committed configuration " - "databases.") + junos_module.logger.debug( + "Step 4 - Determine differences between the " + "candidate and committed configuration " + "databases." + ) if diff is True or junos_module._diff: diff = junos_module.diff_configuration(ignore_warning) if diff is not None: - results['changed'] = True + results["changed"] = True if return_output is True or junos_module._diff: - results['diff'] = {'prepared': diff} - results['diff_lines'] = diff.splitlines() + results["diff"] = {"prepared": diff} + results["diff_lines"] = diff.splitlines() # Save the diff output - junos_module.save_text_output('diff', 'diff', diff) + junos_module.save_text_output("diff", "diff", diff) else: - results['changed'] = False - results['msg'] += ', diffed' + results["changed"] = False + results["msg"] += ", diffed" - junos_module.logger.debug("Step 5 - Retrieve the configuration database " - "from the Junos device.") + junos_module.logger.debug( + "Step 5 - Retrieve the configuration database " "from the Junos device." + ) if retrieve is not None: if format is None: - format = 'text' + format = "text" (config, config_parsed) = junos_module.get_configuration( - database=retrieve, - format=format, - options=options, - filter=filter, - model=model, - namespace=namespace, - remove_ns=remove_ns) + database=retrieve, + format=format, + options=options, + filter=filter, + model=model, + namespace=namespace, + remove_ns=remove_ns, + ) if return_output is True: if config is not None: - results['config'] = config - results['config_lines'] = config.splitlines() + results["config"] = config + results["config_lines"] = config.splitlines() if config_parsed is not None: - results['config_parsed'] = config_parsed + results["config_parsed"] = config_parsed # Save the output - format_extension = 'config' if format == 'text' else format - junos_module.save_text_output('config', format_extension, config) - results['msg'] += ', retrieved' + format_extension = "config" if format == "text" else format + junos_module.save_text_output("config", format_extension, config) + results["msg"] += ", retrieved" junos_module.logger.debug("Step 6 - Commit the configuration changes.") if commit is True and not junos_module.check_mode: @@ -1222,34 +1206,39 @@ def main(): # 1) commit_empty_changes is True # 2) Neither rollback or load is set. i.e. confirming a previous commit # 3) rollback or load is set, and there were actual changes. - if (commit_empty_changes is True or - (rollback is None and load is None) or - ((rollback is not None or load is not None) and - results['changed'] is True)): + if ( + commit_empty_changes is True + or (rollback is None and load is None) + or ( + (rollback is not None or load is not None) + and results["changed"] is True + ) + ): if check_commit_wait is not None: time.sleep(check_commit_wait) - junos_module.commit_configuration(ignore_warning=ignore_warning, - comment=comment, - confirmed=confirmed, - timeout=timeout, - full=commit_full, - sync=commit_sync, - force_sync=commit_force_sync) - results['msg'] += ', committed' + junos_module.commit_configuration( + ignore_warning=ignore_warning, + comment=comment, + confirmed=confirmed, + timeout=timeout, + full=commit_full, + sync=commit_sync, + force_sync=commit_force_sync, + ) + results["msg"] += ", committed" else: junos_module.logger.debug("Skipping commit. Nothing changed.") - junos_module.logger.debug("Step 7 - Close the candidate configuration " - "database.") + junos_module.logger.debug("Step 7 - Close the candidate configuration " "database.") junos_module.close_configuration() - results['msg'] += ', closed.' + results["msg"] += ", closed." # If we made it this far, everything was successful. - results['failed'] = False + results["failed"] = False # Return response. junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py index 8073c9b5..bc750b5a 100644 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: facts @@ -70,7 +72,7 @@ - The resulting JSON file is saved in I(savedir)C(/)I(hostname)C(-facts.json). - The I(savedir) directory is the value of the I(savedir) option. - - The I(hostname)C(-facts.json) filename begins with the value of the + - The I(hostname)C(-facts.json) filename begins with the value of the C(hostname) fact returned from the Junos device, which might be different than the value of the I(host) option passed to the module. - If the value of the I(savedir) option is C(none), the default, then @@ -78,9 +80,9 @@ required: false default: none type: path -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: 'Explicit host argument' hosts: junos @@ -116,9 +118,9 @@ # Using savedir option # Print the saved JSON file -''' +""" -RETURN = ''' +RETURN = """ ansible_facts.junos: description: - Facts collected from the Junos device. This dictionary contains the @@ -174,23 +176,24 @@ returned: always type: bool sample: false -''' +""" # Standard library imports import json import os.path - - """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules But custom module_utils directory is supported from Ansible 2.3 Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ +from ansible.module_utils._text import to_bytes + # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common -from ansible.module_utils._text import to_bytes + from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def get_facts_dict(junos_module): """Retreive PyEZ facts and convert to a standard dict w/o custom types. @@ -213,25 +216,26 @@ def get_facts_dict(junos_module): # Retrieve all PyEZ-supported facts and copy to a standard dict. facts = dict(dev.facts) # Add two useful facts that are implement as PyEZ Device attributes. - facts['re_name'] = dev.re_name - facts['master_state'] = dev.master + facts["re_name"] = dev.re_name + facts["master_state"] = dev.master else: facts = junos_module.get_facts() # Ansible doesn't allow keys starting with numbers. # Replace the '2RE' key with the 'has_2RE' key. - if '2RE' in facts: - facts['has_2RE'] = facts['2RE'] - del facts['2RE'] + if "2RE" in facts: + facts["has_2RE"] = facts["2RE"] + del facts["2RE"] # The value of the 'version_info' key is a custom junos.version_info # object. Convert this value to a dict. - if 'version_info' in facts and facts['version_info'] is not None: - facts['version_info'] = dict(facts['version_info']) + if "version_info" in facts and facts["version_info"] is not None: + facts["version_info"] = dict(facts["version_info"]) # The values of the ['junos_info'][re_name]['object'] keys are # custom junos.version_info objects. Convert all of these to dicts. - if 'junos_info' in facts and facts['junos_info'] is not None: - for key in facts['junos_info']: - facts['junos_info'][key]['object'] = dict( - facts['junos_info'][key]['object']) + if "junos_info" in facts and facts["junos_info"] is not None: + for key in facts["junos_info"]: + facts["junos_info"][key]["object"] = dict( + facts["junos_info"][key]["object"] + ) return facts @@ -251,19 +255,20 @@ def save_facts(junos_module, facts): IOError: Calls junos_module.fail_json if unable to open the facts file for writing. """ - if junos_module.params.get('savedir') is not None: - save_dir = junos_module.params.get('savedir') - file_name = '%s-facts.json' % (facts['hostname']) + if junos_module.params.get("savedir") is not None: + save_dir = junos_module.params.get("savedir") + file_name = "%s-facts.json" % (facts["hostname"]) file_path = os.path.normpath(os.path.join(save_dir, file_name)) junos_module.logger.debug("Saving facts to: %s.", file_path) try: # TODO: Verify does this work with Python3 - with open(file_path, 'w') as fact_file: + with open(file_path, "w") as fact_file: json.dump(facts, fact_file) junos_module.logger.debug("Facts saved to: %s.", file_path) except IOError: - junos_module.fail_json(msg="Unable to save facts. Failed to open " - "the %s file." % (file_path)) + junos_module.fail_json( + msg="Unable to save facts. Failed to open " "the %s file." % (file_path) + ) def save_inventory(junos_module, inventory): @@ -282,23 +287,25 @@ def save_inventory(junos_module, inventory): IOError: Calls junos_module.fail_json if unable to open the inventory file for writing. """ - if junos_module.conn_type == "local" : + if junos_module.conn_type == "local": dev = junos_module.dev - file_name = '%s-inventory.xml' % (dev.facts['hostname']) + file_name = "%s-inventory.xml" % (dev.facts["hostname"]) else: facts = junos_module._pyez_conn.get_facts() - file_name = '%s-inventory.xml' % (facts['hostname']) - if junos_module.params.get('savedir') is not None: - save_dir = junos_module.params.get('savedir') + file_name = "%s-inventory.xml" % (facts["hostname"]) + if junos_module.params.get("savedir") is not None: + save_dir = junos_module.params.get("savedir") file_path = os.path.normpath(os.path.join(save_dir, file_name)) junos_module.logger.debug("Saving inventory to: %s.", file_path) try: - with open(file_path, 'wb') as fact_file: - fact_file.write(to_bytes(inventory, encoding='utf-8')) + with open(file_path, "wb") as fact_file: + fact_file.write(to_bytes(inventory, encoding="utf-8")) junos_module.logger.debug("Inventory saved to: %s.", file_path) except IOError: - junos_module.fail_json(msg="Unable to save inventory. Failed to " - "open the %s file." % (file_path)) + junos_module.fail_json( + msg="Unable to save inventory. Failed to " + "open the %s file." % (file_path) + ) def main(): @@ -308,10 +315,10 @@ def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - config_format=dict(choices=config_format_choices, - required=False, - default=None), - savedir=dict(type='path', required=False, default=None), + config_format=dict( + choices=config_format_choices, required=False, default=None + ), + savedir=dict(type="path", required=False, default=None), ), # Since this module doesn't change the device's configuration, there is # no additional work required to support check mode. It's inherently @@ -325,7 +332,7 @@ def main(): facts = get_facts_dict(junos_module) junos_module.logger.debug("Facts gathered.") - if junos_module.params.get('savedir') is not None: + if junos_module.params.get("savedir") is not None: # Save the facts. save_facts(junos_module, facts) @@ -337,19 +344,19 @@ def main(): else: inventory = junos_module.get_chassis_inventory() junos_module.logger.debug("Inventory gathered.") - save_inventory(junos_module, - junos_module.etree.tostring(inventory, - pretty_print=True)) + save_inventory( + junos_module, junos_module.etree.tostring(inventory, pretty_print=True) + ) except junos_module.pyez_exception.RpcError as ex: - junos_module.fail_json(msg='Unable to retrieve hardware ' - 'inventory: %s' % (str(ex))) + junos_module.fail_json( + msg="Unable to retrieve hardware " "inventory: %s" % (str(ex)) + ) - config_format = junos_module.params.get('config_format') + config_format = junos_module.params.get("config_format") if config_format is not None: - (config, config_parsed) = junos_module.get_configuration( - format=config_format) + (config, config_parsed) = junos_module.get_configuration(format=config_format) if config is not None: - facts.update({'config': config}) + facts.update({"config": config}) # Need to wait until the ordering issues are figured out before # using config_parsed. # if config_parsed is not None: @@ -357,11 +364,9 @@ def main(): # Return response. junos_module.exit_json( - changed=False, - failed=False, - ansible_facts={'junos': facts}, - facts=facts) + changed=False, failed=False, ansible_facts={"junos": facts}, facts=facts + ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/file_copy.py b/ansible_collections/juniper/device/plugins/modules/file_copy.py index 9b020cb8..a0884ad2 100644 --- a/ansible_collections/juniper/device/plugins/modules/file_copy.py +++ b/ansible_collections/juniper/device/plugins/modules/file_copy.py @@ -33,20 +33,22 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation -module: file_copy +module: file_copy author: "Juniper Networks - Dinesh Babu (@dineshbaburam91)" -short_description: File put and get over SCP module +short_description: File put and get over SCP module description: - - Copy file over SCP to and from a Juniper device + - Copy file over SCP to and from a Juniper device options: local_dir: description: @@ -56,7 +58,7 @@ type: str remote_dir: description: - - path of the directory on the remote device where the file is located + - path of the directory on the remote device where the file is located or needs to be copied to required: true type: str @@ -70,9 +72,9 @@ - Type of operation to execute, currently only support get and put required: true type: str -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: Examples of juniper_device_file_copy hosts: all @@ -91,15 +93,15 @@ local_dir: /tmp action: put file: license.txt -''' +""" -RETURN = ''' +RETURN = """ changed: description: - Indicates if the device's state has changed. returned: when the file has been successfully copied. type: bool -''' +""" """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules But custom module_utils directory is supported from Ansible 2.3 @@ -107,54 +109,49 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # The argument spec for the module. junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec = dict( - local_dir=dict(type='str', - required=True, - default=None), - remote_dir=dict(type='str', - required=True, - default=None), - file=dict(type='str', - required=True, - default=None), - action=dict(type='str', - choices=['put', 'get'], - required=True, - default=None) - ), + argument_spec=dict( + local_dir=dict(type="str", required=True, default=None), + remote_dir=dict(type="str", required=True, default=None), + file=dict(type="str", required=True, default=None), + action=dict( + type="str", choices=["put", "get"], required=True, default=None + ), + ), supports_check_mode=True, min_jxmlease_version=cfg.MIN_JXMLEASE_VERSION, ) # Set initial results values. Assume failure until we know it's success. - results = {'msg': '', 'changed': False, 'failed': False} + results = {"msg": "", "changed": False, "failed": False} output = [] # We're going to be using params a lot params = junos_module.params - remote_path = params['remote_dir'] - local_file=params['local_dir']+"/"+params['file'] - remote_file=params['remote_dir']+"/"+params['file'] + remote_path = params["remote_dir"] + local_file = params["local_dir"] + "/" + params["file"] + remote_file = params["remote_dir"] + "/" + params["file"] - if (params['action'] == "put"): + if params["action"] == "put": output = junos_module.scp_file_copy_put(local_file, remote_file) - results['msg'] = output[0] - results['changed'] = output[1] - elif (params['action'] == "get"): - output = junos_module.scp_file_copy_get(remote_file, local_file) - results['msg'] = output[0] - results['changed'] = output[1] + results["msg"] = output[0] + results["changed"] = output[1] + elif params["action"] == "get": + output = junos_module.scp_file_copy_get(remote_file, local_file) + results["msg"] = output[0] + results["changed"] = output[1] # Return results. junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 11efcc7c..78f440b0 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: jsnapy @@ -102,10 +104,10 @@ required: false type: list of path default: none -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: Examples of jsnapy hosts: junos-all @@ -163,9 +165,9 @@ - name: Print the full test response ansible.builtin.debug: var: test3 -''' +""" -RETURN = ''' +RETURN = """ action: description: - The JSNAPy action performed as specified by the I(action) option. @@ -191,68 +193,66 @@ type: str # total_passed: # total_failed: -''' +""" # Standard Library imports import os.path - """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules But custom module_utils directory is supported from Ansible 2.3 Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): - JSNAPY_ACTION_CHOICES = ['check', 'snapcheck', 'snap_pre', 'snap_post'] + JSNAPY_ACTION_CHOICES = ["check", "snapcheck", "snap_pre", "snap_post"] # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - action=dict(required=True, - choices=JSNAPY_ACTION_CHOICES, - type='str', - default=None), - test_files=dict(required=False, - type='list', - default=None), - config_file=dict(required=False, - type='path', - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir'], - default=None), - dir=dict(required=False, - type='path', - aliases=['directory'], - default='/etc/jsnapy/testfiles')), + action=dict( + required=True, choices=JSNAPY_ACTION_CHOICES, type="str", default=None + ), + test_files=dict(required=False, type="list", default=None), + config_file=dict(required=False, type="path", default=None), + dest_dir=dict( + required=False, + type="path", + aliases=["destination_dir", "destdir"], + default=None, + ), + dir=dict( + required=False, + type="path", + aliases=["directory"], + default="/etc/jsnapy/testfiles", + ), + ), # Mutually exclusive options. - mutually_exclusive=[['test_files', 'config_file']], + mutually_exclusive=[["test_files", "config_file"]], # One of test_files or config_file is required. - required_one_of=[['test_files', 'config_file']], + required_one_of=[["test_files", "config_file"]], supports_check_mode=True, min_jsnapy_version=cfg.MIN_JSNAPY_VERSION, ) # Straight from params - action = junos_module.params.get('action') - test_files = junos_module.params.get('test_files') - config_file = junos_module.params.get('config_file') - dir = junos_module.params.get('dir') - dest_dir = junos_module.params.get('dest_dir') + action = junos_module.params.get("action") + test_files = junos_module.params.get("test_files") + config_file = junos_module.params.get("config_file") + dir = junos_module.params.get("dir") + dest_dir = junos_module.params.get("dest_dir") # Initialize the results. Assume failure until we know otherwise. - results = {'msg': '', - 'action': action, - 'changed': False, - 'failed': True} + results = {"msg": "", "action": action, "changed": False, "failed": True} if config_file is not None: - junos_module.logger.debug('Checking config file: %s.', config_file) + junos_module.logger.debug("Checking config file: %s.", config_file) config_file_path = os.path.abspath(config_file) config_dir_file_path = os.path.abspath(os.path.join(dir, config_file)) if os.path.isfile(config_file_path): @@ -260,166 +260,186 @@ def main(): elif os.path.isfile(config_dir_file_path): data = config_dir_file_path else: - junos_module.fail_json(msg="Unable to locate the %s config file " - "at %s or %s." % (config_file, - config_file_path, - config_dir_file_path)) + junos_module.fail_json( + msg="Unable to locate the %s config file " + "at %s or %s." % (config_file, config_file_path, config_dir_file_path) + ) elif test_files is not None and len(test_files) > 0: - data = {'tests': []} + data = {"tests": []} for test_file in test_files: - junos_module.logger.debug('Checking test file: %s.', test_file) + junos_module.logger.debug("Checking test file: %s.", test_file) test_file_path = os.path.abspath(test_file) test_dir_file_path = os.path.abspath(os.path.join(dir, test_file)) if os.path.isfile(test_file_path): - data['tests'].append(test_file_path) + data["tests"].append(test_file_path) elif os.path.isfile(test_dir_file_path): - data['tests'].append(test_dir_file_path) + data["tests"].append(test_dir_file_path) else: - junos_module.fail_json(msg="Unable to locate the %s test file " - "at %s or %s." % - (test_file, - test_file_path, - test_dir_file_path)) + junos_module.fail_json( + msg="Unable to locate the %s test file " + "at %s or %s." % (test_file, test_file_path, test_dir_file_path) + ) else: junos_module.fail_json(msg="No config_file or test_files specified.") try: - junos_module.logger.debug('Creating jnpr.jsnapy.SnapAdmin instance.') + junos_module.logger.debug("Creating jnpr.jsnapy.SnapAdmin instance.") jsa = junos_module.jsnapy.SnapAdmin() - junos_module.logger.debug('Executing %s action.', action) - if action == 'check': + junos_module.logger.debug("Executing %s action.", action) + if action == "check": if junos_module.conn_type != "local": - responses = junos_module._pyez_conn.invoke_jsnapy(data=data, - action='check') + responses = junos_module._pyez_conn.invoke_jsnapy( + data=data, action="check" + ) else: - responses = jsa.check(data=data, - dev=junos_module.dev, - pre_file='PRE', - post_file='POST') - elif action == 'snapcheck': + responses = jsa.check( + data=data, dev=junos_module.dev, pre_file="PRE", post_file="POST" + ) + elif action == "snapcheck": if junos_module.conn_type != "local": - responses = junos_module._pyez_conn.invoke_jsnapy(data=data, - action='snapcheck') + responses = junos_module._pyez_conn.invoke_jsnapy( + data=data, action="snapcheck" + ) else: - responses = jsa.snapcheck(data=data, - dev=junos_module.dev, - pre_file='PRE') - elif action == 'snap_pre': + responses = jsa.snapcheck( + data=data, dev=junos_module.dev, pre_file="PRE" + ) + elif action == "snap_pre": if junos_module.conn_type != "local": - responses = junos_module._pyez_conn.invoke_jsnapy(data=data, - action='snap_pre') + responses = junos_module._pyez_conn.invoke_jsnapy( + data=data, action="snap_pre" + ) else: - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='PRE') - elif action == 'snap_post': + responses = jsa.snap(data=data, dev=junos_module.dev, file_name="PRE") + elif action == "snap_post": if junos_module.conn_type != "local": - responses = junos_module._pyez_conn.invoke_jsnapy(data=data, - action='snap_post') + responses = junos_module._pyez_conn.invoke_jsnapy( + data=data, action="snap_post" + ) else: - responses = jsa.snap(data=data, - dev=junos_module.dev, - file_name='POST') + responses = jsa.snap(data=data, dev=junos_module.dev, file_name="POST") else: junos_module.fail_json(msg="Unexpected action: %s." % (action)) - junos_module.logger.debug('The %s action executed successfully.', - action) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - junos_module.fail_json(msg="Error communicating with the device: %s" % - (str(ex))) + junos_module.logger.debug("The %s action executed successfully.", action) + except ( + junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError, + ) as ex: + junos_module.fail_json( + msg="Error communicating with the device: %s" % (str(ex)) + ) except Exception as ex: - junos_module.fail_json(msg="Uncaught exception - please report: %s" % - (str(ex))) + junos_module.fail_json(msg="Uncaught exception - please report: %s" % (str(ex))) if isinstance(responses, list) and len(responses) == 1: - if action in ('snapcheck', 'check'): + if action in ("snapcheck", "check"): for response in responses: - results['device'] = response.device - results['router'] = response.device - results['final_result'] = response.result - results['total_passed'] = response.no_passed - results['total_failed'] = response.no_failed - results['test_results'] = response.test_results + results["device"] = response.device + results["router"] = response.device + results["final_result"] = response.result + results["total_passed"] = response.no_passed + results["total_failed"] = response.no_failed + results["test_results"] = response.test_results total_tests = int(response.no_passed) + int(response.no_failed) - results['total_tests'] = total_tests + results["total_tests"] = total_tests pass_percentage = 0 if total_tests > 0: - pass_percentage = ((int(response.no_passed) * 100) // - total_tests) - results['passPercentage'] = pass_percentage - results['pass_percentage'] = pass_percentage - if results['final_result'] == 'Failed': - results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) + pass_percentage = (int(response.no_passed) * 100) // total_tests + results["passPercentage"] = pass_percentage + results["pass_percentage"] = pass_percentage + if results["final_result"] == "Failed": + results["msg"] = "Test Failed: Passed %s, Failed %s" % ( + results["total_passed"], + results["total_failed"], + ) else: - results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) - elif action in ('snap_pre', 'snap_post'): - results['msg'] = "The %s action successfully executed." % (action) + results["msg"] = "Test Passed: Passed %s, Failed %s" % ( + results["total_passed"], + results["total_failed"], + ) + elif action in ("snap_pre", "snap_post"): + results["msg"] = "The %s action successfully executed." % (action) elif isinstance(responses, dict) and len(responses) >= 1: - if action in ('snapcheck', 'check'): - results['device'] = responses["device"] - results['router'] = responses["router"] - results['final_result'] = responses["final_result"] - results['total_passed'] = responses["total_passed"] - results['total_failed'] = responses["total_failed"] - results['test_results'] = responses["test_results"] - total_tests = int(responses["total_passed"]) + int(responses["total_failed"]) - results['total_tests'] = total_tests + if action in ("snapcheck", "check"): + results["device"] = responses["device"] + results["router"] = responses["router"] + results["final_result"] = responses["final_result"] + results["total_passed"] = responses["total_passed"] + results["total_failed"] = responses["total_failed"] + results["test_results"] = responses["test_results"] + total_tests = int(responses["total_passed"]) + int( + responses["total_failed"] + ) + results["total_tests"] = total_tests pass_percentage = 0 if total_tests > 0: - pass_percentage = ((int(responses["total_passed"]) * 100) // - total_tests) - results['passPercentage'] = pass_percentage - results['pass_percentage'] = pass_percentage - if results['final_result'] == 'Failed': - results['msg'] = 'Test Failed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) + pass_percentage = (int(responses["total_passed"]) * 100) // total_tests + results["passPercentage"] = pass_percentage + results["pass_percentage"] = pass_percentage + if results["final_result"] == "Failed": + results["msg"] = "Test Failed: Passed %s, Failed %s" % ( + results["total_passed"], + results["total_failed"], + ) else: - results['msg'] = 'Test Passed: Passed %s, Failed %s' % \ - (results['total_passed'], - results['total_failed']) - elif action in ('snap_pre', 'snap_post'): - results['msg'] = "The %s action successfully executed." % (action) + results["msg"] = "Test Passed: Passed %s, Failed %s" % ( + results["total_passed"], + results["total_failed"], + ) + elif action in ("snap_pre", "snap_post"): + results["msg"] = "The %s action successfully executed." % (action) else: - junos_module.fail_json(msg="Unexpected JSNAPy responses. Type: %s." - "Responses: %s" % - (type(responses), str(responses))) + junos_module.fail_json( + msg="Unexpected JSNAPy responses. Type: %s." + "Responses: %s" % (type(responses), str(responses)) + ) # If we made it this far, it's success. - results['failed'] = False + results["failed"] = False # To save the failed test_name details to dest_dir # for connection local if dest_dir is not None: - if action in ('snapcheck', 'check'): + if action in ("snapcheck", "check"): if junos_module.conn_type == "local": for response_loc in responses: results_loc = response_loc.test_details for cmd, data in results_loc.items(): for data1 in data: - if (('test_name' in data1.keys()) and ('result' in data1.keys())): - if data1['result'] == False: - test_name = str(data1['test_name']) + "_" + str(data1['result']) + if ("test_name" in data1.keys()) and ( + "result" in data1.keys() + ): + if data1["result"] == False: + test_name = ( + str(data1["test_name"]) + + "_" + + str(data1["result"]) + ) text_msg = str(data) - junos_module.save_text_output(test_name, "text", text_msg) + junos_module.save_text_output( + test_name, "text", text_msg + ) else: # For connection pyez for res in results["test_results"]: - for data in results['test_results'][res]: + for data in results["test_results"][res]: for key, value in data.items(): - if (('test_name' in data.keys()) and ('result' in data.keys())): - if data['result'] == False: - test_name = str(data['test_name']) + "_" + str(data['result']) + if ("test_name" in data.keys()) and ( + "result" in data.keys() + ): + if data["result"] == False: + test_name = ( + str(data["test_name"]) + + "_" + + str(data["result"]) + ) text_msg = str(data) - junos_module.save_text_output(test_name, "text", text_msg) + junos_module.save_text_output( + test_name, "text", text_msg + ) junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py index fa68861a..b155e727 100644 --- a/ansible_collections/juniper/device/plugins/modules/ping.py +++ b/ansible_collections/juniper/device/plugins/modules/ping.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: ping @@ -134,9 +136,9 @@ required: false default: none (default ttl for device) type: int -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: Examples of ping hosts: junos-all @@ -221,9 +223,9 @@ juniper.device.ping: dest: "224.0.0.1" interface: "ge-0/0/0.0" -''' +""" -RETURN = ''' +RETURN = """ acceptable_percent_loss: description: - The acceptable packet loss (as a percentage) for this task as specified @@ -371,7 +373,7 @@ - A list of warning strings, if any, produced from the ping. returned: when warnings are present type: list -''' +""" """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules @@ -380,51 +382,46 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule + from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # The argument spec for the module. argument_spec = dict( - dest=dict(type='str', - required=True, - aliases=['dest_ip', 'dest_host', 'destination', - 'destination_ip', 'destination_host'], - default=None), - acceptable_percent_loss=dict(type='int', - required=False, - aliases=['acceptable_packet_loss'], - default=0), + dest=dict( + type="str", + required=True, + aliases=[ + "dest_ip", + "dest_host", + "destination", + "destination_ip", + "destination_host", + ], + default=None, + ), + acceptable_percent_loss=dict( + type="int", required=False, aliases=["acceptable_packet_loss"], default=0 + ), ) # The portion of the argument spec that's specifically a parameter # to the ping RPC. ping_argument_spec = dict( - count=dict(type='int', - required=False, - default=5), - rapid=dict(type='bool', - required=False, - default=True), - ttl=dict(type='int', - required=False, - default=None), - size=dict(type='int', - required=False, - default=None), - do_not_fragment=dict(type='bool', - required=False, - default=False), - source=dict(type='str', - required=False, - aliases=['source_ip', 'source_host', 'src', - 'src_ip', 'src_host'], - default=None), - interface=dict(type='str', - required=False, - default=None), - routing_instance=dict(type='str', - required=False, - default=None), + count=dict(type="int", required=False, default=5), + rapid=dict(type="bool", required=False, default=True), + ttl=dict(type="int", required=False, default=None), + size=dict(type="int", required=False, default=None), + do_not_fragment=dict(type="bool", required=False, default=False), + source=dict( + type="str", + required=False, + aliases=["source_ip", "source_host", "src", "src_ip", "src_host"], + default=None, + ), + interface=dict(type="str", required=False, default=None), + routing_instance=dict(type="str", required=False, default=None), ) # Add the ping RPC parameter argument spec fo the full argument_spec. @@ -438,7 +435,7 @@ def main(): # Since this module doesn't change the device's configuration, there is # no additional work required to support check mode. It's inherently # supported. - supports_check_mode=True + supports_check_mode=True, ) # We're going to be using params a lot @@ -446,17 +443,17 @@ def main(): # acceptable packet loss is a percentage. Check to make sure it's between # 0 and 100 inclusive - if (params['acceptable_percent_loss'] > 100 or - params['acceptable_percent_loss'] < 0): - junos_module.fail_json(msg='The value of the acceptable_percent_loss' - 'option (%d) is a percentage and must have ' - 'a value between 0 and 100.' % - (params['acceptable_percent_loss'])) + if params["acceptable_percent_loss"] > 100 or params["acceptable_percent_loss"] < 0: + junos_module.fail_json( + msg="The value of the acceptable_percent_loss" + "option (%d) is a percentage and must have " + "a value between 0 and 100." % (params["acceptable_percent_loss"]) + ) # All of the params keys which are also keys in ping_argument_spec are the # ping_params. Omit None and False values because they don't need to be # passed to the RPC. - ping_params = {'host': params.get('dest')} + ping_params = {"host": params.get("dest")} for key in ping_argument_spec: value = params.get(key) # Convert int (but not bool) to str @@ -469,29 +466,33 @@ def main(): ping_params.update({key: value}) # Set initial results values. Assume failure until we know it's success. - results = {'msg': '', 'changed': False, 'failed': True} + results = {"msg": "", "changed": False, "failed": True} # Results should include all the ping params in argument_spec_keys. for key in argument_spec_keys: results[key] = params.get(key) # Overwrite to be a string in the results - results['acceptable_percent_loss'] = str( - params.get('acceptable_percent_loss')) + results["acceptable_percent_loss"] = str(params.get("acceptable_percent_loss")) # Add timeout to the response even though it's a connect parameter. - results['timeout'] = str(params.get('timeout')) + results["timeout"] = str(params.get("timeout")) # Add aliases for backwards compatibility - results.update({'host': params.get('dest'), - 'dest_ip': params.get('dest'), - 'source_ip': params.get('source')}) + results.update( + { + "host": params.get("dest"), + "dest_ip": params.get("dest"), + "source_ip": params.get("source"), + } + ) # Execute the ping. results = junos_module.ping( - ping_params, - acceptable_percent_loss=params['acceptable_percent_loss'], - results=results) + ping_params, + acceptable_percent_loss=params["acceptable_percent_loss"], + results=results, + ) # Return results. junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py index bbb6832d..9a000522 100644 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: pmtud @@ -120,9 +122,9 @@ - src - src_ip - src_host -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: Examples of pmtud hosts: junos-all @@ -178,9 +180,9 @@ - name: Print the discovered MTU. ansible.builtin.debug: var: response.inet_mtu -''' +""" -RETURN = ''' +RETURN = """ changed: description: - Indicates if the device's state has changed. Since this module @@ -233,7 +235,7 @@ - A list of warning strings, if any, produced from the ping. returned: when warnings are present type: list -''' +""" """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules @@ -242,8 +244,10 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule + from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # Constants for MTU size INET_MIN_MTU_SIZE = 68 # As prescribed by RFC 791, Section 3.2 - @@ -261,123 +265,131 @@ def main(): INET_AND_ICMP_HEADER_SIZE = INET_HEADER_SIZE + ICMP_HEADER_SIZE # Choices for max_size - MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2 ** x, range(1, 17))) + MAX_SIZE_CHOICES = [0] + list(map(lambda x: 2**x, range(1, 17))) # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - dest=dict(type='str', - required=True, - aliases=['dest_ip', 'dest_host', 'destination', - 'destination_ip', 'destination_host'], - default=None), - max_size=dict(type='int', - required=False, - default=1500), - max_range=dict(type='int', - required=False, - choices=MAX_SIZE_CHOICES, - default=512), - source=dict(type='str', - required=False, - aliases=['source_ip', 'source_host', 'src', - 'src_ip', 'src_host'], - default=None), - interface=dict(type='str', - required=False, - default=None), - routing_instance=dict(type='str', - required=False, - default=None), + dest=dict( + type="str", + required=True, + aliases=[ + "dest_ip", + "dest_host", + "destination", + "destination_ip", + "destination_host", + ], + default=None, + ), + max_size=dict(type="int", required=False, default=1500), + max_range=dict( + type="int", required=False, choices=MAX_SIZE_CHOICES, default=512 + ), + source=dict( + type="str", + required=False, + aliases=["source_ip", "source_host", "src", "src_ip", "src_host"], + default=None, + ), + interface=dict(type="str", required=False, default=None), + routing_instance=dict(type="str", required=False, default=None), ), # Since this module doesn't change the device's configuration, there is # no additional work required to support check mode. It's inherently # supported. - supports_check_mode=True + supports_check_mode=True, ) # We're going to be using params a lot params = junos_module.params # max_size must be between INET_MIN_MTU_SIZE and INET_MAX_MTU_SIZE - if (params['max_size'] < INET_MIN_MTU_SIZE or - params['max_size'] > INET_MAX_MTU_SIZE): - junos_module.fail_json(msg='The value of the max_size option(%d) ' - 'must be between %d and %d.' % - (params['max_size'], INET_MIN_MTU_SIZE, - INET_MAX_MTU_SIZE)) + if params["max_size"] < INET_MIN_MTU_SIZE or params["max_size"] > INET_MAX_MTU_SIZE: + junos_module.fail_json( + msg="The value of the max_size option(%d) " + "must be between %d and %d." + % (params["max_size"], INET_MIN_MTU_SIZE, INET_MAX_MTU_SIZE) + ) # Initialize ping parameters. - ping_params = {'host': params.get('dest'), - 'count': '3', - 'rapid': True, - 'inet': True, - 'do_not_fragment': True} + ping_params = { + "host": params.get("dest"), + "count": "3", + "rapid": True, + "inet": True, + "do_not_fragment": True, + } # Add optional ping parameters o_ping_params = {} - if params['source'] is not None: - o_ping_params['source'] = params['source'] - if params['interface'] is not None: - o_ping_params['interface'] = params['interface'] - if params['routing_instance'] is not None: - o_ping_params['routing_instance'] = params['routing_instance'] + if params["source"] is not None: + o_ping_params["source"] = params["source"] + if params["interface"] is not None: + o_ping_params["interface"] = params["interface"] + if params["routing_instance"] is not None: + o_ping_params["routing_instance"] = params["routing_instance"] ping_params.update(o_ping_params) # Set initial results values. Assume failure until we know it's success. - results = {'changed': False, - 'failed': True, - 'inet_mtu': 0, - 'host': params.get('dest')} + results = { + "changed": False, + "failed": True, + "inet_mtu": 0, + "host": params.get("dest"), + } # Results should include all the o_ping_params. for key in o_ping_params: results[key] = ping_params.get(key) # Add aliases for backwards compatibility - results.update({'dest': ping_params.get('host'), - 'dest_ip': ping_params.get('host'), - 'source_ip': ping_params.get('source')}) + results.update( + { + "dest": ping_params.get("host"), + "dest_ip": ping_params.get("host"), + "source_ip": ping_params.get("source"), + } + ) # Execute a minimally-sized ping just to verify basic connectivity. junos_module.logger.debug("Verifying basic connectivity.") - ping_params['size'] = str(INET_MIN_MTU_SIZE - - INET_AND_ICMP_HEADER_SIZE) + ping_params["size"] = str(INET_MIN_MTU_SIZE - INET_AND_ICMP_HEADER_SIZE) results_for_minimal = dict(results) - results_for_minimal = junos_module.ping(ping_params, - acceptable_percent_loss=100, - results=results_for_minimal) - if round(float(results_for_minimal.get('packet_loss', 100))) == 100: - results['msg'] = "Basic connectivity to %s failed." % (results['host']) + results_for_minimal = junos_module.ping( + ping_params, acceptable_percent_loss=100, results=results_for_minimal + ) + if round(float(results_for_minimal.get("packet_loss", 100))) == 100: + results["msg"] = "Basic connectivity to %s failed." % (results["host"]) junos_module.exit_json(**results) # Initialize test_size and step - test_size = params['max_size'] - step = params['max_range'] - min_test_size = test_size - (params['max_range'] - 1) + test_size = params["max_size"] + step = params["max_range"] + min_test_size = test_size - (params["max_range"] - 1) if min_test_size < INET_MIN_MTU_SIZE: min_test_size = INET_MIN_MTU_SIZE while True: if test_size < INET_MIN_MTU_SIZE: test_size = INET_MIN_MTU_SIZE - if test_size > params['max_size']: - test_size = params['max_size'] + if test_size > params["max_size"]: + test_size = params["max_size"] junos_module.logger.debug("Probing with size: %d", test_size) step = step // 2 if step >= 2 else 0 - ping_params['size'] = str(test_size - INET_AND_ICMP_HEADER_SIZE) + ping_params["size"] = str(test_size - INET_AND_ICMP_HEADER_SIZE) current_results = dict(results) - current_results = junos_module.ping(ping_params, - acceptable_percent_loss=100, - results=current_results) - loss = round(float(current_results.get('packet_loss', 100))) - if loss < 100 and test_size == params['max_size']: + current_results = junos_module.ping( + ping_params, acceptable_percent_loss=100, results=current_results + ) + loss = round(float(current_results.get("packet_loss", 100))) + if loss < 100 and test_size == params["max_size"]: # ping success with max test_size, save and break - results['failed'] = False - results['inet_mtu'] = test_size + results["failed"] = False + results["inet_mtu"] = test_size break elif loss < 100: # ping success, increase test_size - results['failed'] = False - results['inet_mtu'] = test_size + results["failed"] = False + results["inet_mtu"] = test_size test_size += step else: # ping fail, lower size @@ -385,19 +397,19 @@ def main(): if step < 1: break - if results.get('inet_mtu', 0) == 0: - junos_module.fail_json(msg='The MTU of the path to %s is less than ' - 'the minimum tested size(%d). Try ' - 'decreasing max_size(%d) or increasing ' - 'max_range(%d).' % (results['host'], - min_test_size, - params['max_size'], - params['max_range']), - **results) + if results.get("inet_mtu", 0) == 0: + junos_module.fail_json( + msg="The MTU of the path to %s is less than " + "the minimum tested size(%d). Try " + "decreasing max_size(%d) or increasing " + "max_range(%d)." + % (results["host"], min_test_size, params["max_size"], params["max_range"]), + **results + ) # Return results. junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index ef15549d..d5750d4e 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -31,15 +31,18 @@ # from __future__ import absolute_import, division, print_function + from six import iteritems -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: rpc @@ -164,8 +167,8 @@ two lists must always contain the same number of elements. For RPC arguments which do not require a value, specify the value of True as shown in the :ref:`rpc-examples-label`. - - By default "0" and "1" will be converted to boolean values. In case - it doesn't need to be transformed to boolean pass first kwargs as + - By default "0" and "1" will be converted to boolean values. In case + it doesn't need to be transformed to boolean pass first kwargs as required: false default: none type: dict or list of dict @@ -191,9 +194,9 @@ type: list aliases: - rpc -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: 'Explicit host argument' hosts: junos @@ -253,9 +256,9 @@ media: True format: json dest: get_interface_information.conf - register: junos''' + register: junos""" -RETURN = ''' +RETURN = """ attrs: description: - The RPC attributes and values from the list of dictionaries in the @@ -345,11 +348,10 @@ - The RPC reply from the Junos device as a list of single-line strings. returned: when RPC executed successfully and I(return_output) is C(true). type: list of str -''' +""" import os.path - try: # Python 2 basestring @@ -364,47 +366,43 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - rpcs=dict(required=True, - type='list', - aliases=['rpc'], - default=None), - formats=dict(required=False, - type='list', - aliases=['format', 'display', 'output'], - default=None), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='str', - default=None), - attrs=dict(required=False, - type='str', - aliases=['attr'], - default=None), - filter=dict(required=False, - type='str', - aliases=['filter_xml'], - default=None), - dest=dict(required=False, - type='path', - aliases=['destination'], - default=None), - dest_dir=dict(required=False, - type='path', - aliases=['destination_dir', 'destdir'], - default=None), - ignore_warning=dict(required=False, - type='list', - default=None), - return_output=dict(required=False, - type='bool', - default=True) + rpcs=dict(required=True, type="list", aliases=["rpc"], default=None), + formats=dict( + required=False, + type="list", + aliases=["format", "display", "output"], + default=None, + ), + kwargs=dict( + required=False, + aliases=["kwarg", "args", "arg"], + type="str", + default=None, + ), + attrs=dict(required=False, type="str", aliases=["attr"], default=None), + filter=dict( + required=False, type="str", aliases=["filter_xml"], default=None + ), + dest=dict( + required=False, type="path", aliases=["destination"], default=None + ), + dest_dir=dict( + required=False, + type="path", + aliases=["destination_dir", "destdir"], + default=None, + ), + ignore_warning=dict(required=False, type="list", default=None), + return_output=dict(required=False, type="bool", default=True), ), # Since this module doesn't change the device's configuration, there is # no additional work required to support check mode. It's inherently @@ -416,7 +414,7 @@ def main(): ) # Check over rpcs - rpcs = junos_module.params.get('rpcs') + rpcs = junos_module.params.get("rpcs") # Ansible allows users to specify a rpcs argument with no value. if rpcs is None: junos_module.fail_json(msg="The rpcs option must have a value.") @@ -424,195 +422,208 @@ def main(): # Parse ignore_warning value ignore_warning = junos_module.parse_ignore_warning_option() # Check over formats - formats = junos_module.params.get('formats') + formats = junos_module.params.get("formats") if formats is None: # Default to xml format - formats = ['xml'] + formats = ["xml"] valid_formats = juniper_junos_common.RPC_OUTPUT_FORMAT_CHOICES # Check format values for format in formats: # Is it a valid format? if format not in valid_formats: - junos_module.fail_json(msg="The value %s in formats is invalid. " - "Must be one of: %s" % - (format, ', '.join(map(str, - valid_formats)))) + junos_module.fail_json( + msg="The value %s in formats is invalid. " + "Must be one of: %s" % (format, ", ".join(map(str, valid_formats))) + ) # Correct number of format values? if len(formats) != 1 and len(formats) != len(rpcs): - junos_module.fail_json(msg="The formats option must have a single " - "value, or one value per rpc. There " - "are %d rpcs and %d formats." % - (len(rpcs), len(formats))) + junos_module.fail_json( + msg="The formats option must have a single " + "value, or one value per rpc. There " + "are %d rpcs and %d formats." % (len(rpcs), len(formats)) + ) # Same format for all rpcs elif len(formats) == 1 and len(rpcs) > 1: formats = formats * len(rpcs) # Check over kwargs - kwstring = junos_module.params.get('kwargs') - kwargs = junos_module.parse_arg_to_list_of_dicts('kwargs', - kwstring, - allow_bool_values=True) + kwstring = junos_module.params.get("kwargs") + kwargs = junos_module.parse_arg_to_list_of_dicts( + "kwargs", kwstring, allow_bool_values=True + ) if kwargs is not None: if len(kwargs) != len(rpcs): - junos_module.fail_json(msg="The kwargs option must have one value " - "per rpc. There are %d rpcs and %d " - "kwargs." % - (len(rpcs), len(kwargs))) + junos_module.fail_json( + msg="The kwargs option must have one value " + "per rpc. There are %d rpcs and %d " + "kwargs." % (len(rpcs), len(kwargs)) + ) else: kwargs = [None] * len(rpcs) # Check over attrs - attrstring = junos_module.params.get('attrs') - attrs = junos_module.parse_arg_to_list_of_dicts('attrs', - attrstring) + attrstring = junos_module.params.get("attrs") + attrs = junos_module.parse_arg_to_list_of_dicts("attrs", attrstring) if attrs is not None: if len(attrs) != len(rpcs): - junos_module.fail_json(msg="The attrs option must have one value" - "per rpc. There are %d rpcs and %d " - "attrs." % - (len(rpcs), len(attrs))) + junos_module.fail_json( + msg="The attrs option must have one value" + "per rpc. There are %d rpcs and %d " + "attrs." % (len(rpcs), len(attrs)) + ) else: attrs = [None] * len(rpcs) # Check filter - if junos_module.params.get('filter') is not None: - if (len(rpcs) != 1 or (rpcs[0] != 'get-config' and - rpcs[0] != 'get_config')): - junos_module.fail_json(msg="The filter option is only valid " - "when the rpcs option value is a " - "single 'get-config' RPC.") + if junos_module.params.get("filter") is not None: + if len(rpcs) != 1 or (rpcs[0] != "get-config" and rpcs[0] != "get_config"): + junos_module.fail_json( + msg="The filter option is only valid " + "when the rpcs option value is a " + "single 'get-config' RPC." + ) results = list() - for (rpc_string, format, kwarg, attr) in zip(rpcs, formats, kwargs, attrs): + for rpc_string, format, kwarg, attr in zip(rpcs, formats, kwargs, attrs): # Replace underscores with dashes in RPC name. - rpc_string = rpc_string.replace('_', '-') + rpc_string = rpc_string.replace("_", "-") # Set initial result values. Assume failure until we know it's success. - result = {'msg': '', - 'rpc': rpc_string, - 'format': format, - 'kwargs': kwarg, - 'attrs': attr, - 'changed': False, - 'failed': True} + result = { + "msg": "", + "rpc": rpc_string, + "format": format, + "kwargs": kwarg, + "attrs": attr, + "changed": False, + "failed": True, + } # Execute the RPC try: - #for get-config in case of exception handling it will not display - #filters and arguments. To be added in future. + # for get-config in case of exception handling it will not display + # filters and arguments. To be added in future. rpc = junos_module.etree.Element(rpc_string, format=format) - if rpc_string == 'get-config': - filter = junos_module.params.get('filter') + if rpc_string == "get-config": + filter = junos_module.params.get("filter") if attr is None: attr = {} if kwarg is None: kwarg = {} if format is not None: - attr['format'] = format - junos_module.logger.debug('Executing "get-config" RPC. ' - 'filter_xml=%s, options=%s, ' - 'kwargs=%s', - filter, str(attr), str(kwarg)) + attr["format"] = format + junos_module.logger.debug( + 'Executing "get-config" RPC. ' + "filter_xml=%s, options=%s, " + "kwargs=%s", + filter, + str(attr), + str(kwarg), + ) # not adding ignore_warning as we don't expect to get rpc-error # with severity warning during get_config if junos_module.conn_type == "local": - resp = junos_module.dev.rpc.get_config(filter_xml=filter, - options=attr, **kwarg) + resp = junos_module.dev.rpc.get_config( + filter_xml=filter, options=attr, **kwarg + ) else: - resp = junos_module.get_config(filter_xml=filter, - options=attr, **kwarg) - result['msg'] = 'The "get-config" RPC executed successfully.' - junos_module.logger.debug('The "get-config" RPC executed ' - 'successfully.') + resp = junos_module.get_config( + filter_xml=filter, options=attr, **kwarg + ) + result["msg"] = 'The "get-config" RPC executed successfully.' + junos_module.logger.debug( + 'The "get-config" RPC executed ' "successfully." + ) else: if kwarg is not None: # Add kwarg - for (key, value) in iteritems(kwarg): + for key, value in iteritems(kwarg): # Replace underscores with dashes in key name. - key = key.replace('_', '-') + key = key.replace("_", "-") sub_element = junos_module.etree.SubElement(rpc, key) if not isinstance(value, bool): sub_element.text = value if attr is not None: # Add attr - for (key, value) in iteritems(attr): + for key, value in iteritems(attr): # Replace underscores with dashes in key name. - key = key.replace('_', '-') + key = key.replace("_", "-") rpc.set(key, value) - junos_module.logger.debug('Executing RPC "%s".', - junos_module.etree.tostring( - rpc, - pretty_print=True)) + junos_module.logger.debug( + 'Executing RPC "%s".', + junos_module.etree.tostring(rpc, pretty_print=True), + ) if junos_module.conn_type == "local": - resp = junos_module.dev.rpc(rpc, - normalize=bool(format == 'xml')) + resp = junos_module.dev.rpc(rpc, normalize=bool(format == "xml")) else: try: - resp = junos_module.get_rpc(rpc, - ignore_warning=ignore_warning, format=format) + resp = junos_module.get_rpc( + rpc, ignore_warning=ignore_warning, format=format + ) except Exception as ex: if "RpcError" in (str(ex)): raise junos_module.pyez_exception.RpcError if "ConnectError" in (str(ex)): raise junos_module.pyez_exception.ConnectError - result['msg'] = 'The RPC executed successfully.' - junos_module.logger.debug('RPC "%s" executed successfully.', - junos_module.etree.tostring( - rpc, - pretty_print=True)) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Unable to execute RPC "%s". Error: %s', - junos_module.etree.tostring( - rpc, - pretty_print=True), str(ex)) - result['msg'] = 'Unable to execute the RPC: %s. Error: %s' % \ - (junos_module.etree.tostring(rpc, - pretty_print=True), - str(ex)) + result["msg"] = "The RPC executed successfully." + junos_module.logger.debug( + 'RPC "%s" executed successfully.', + junos_module.etree.tostring(rpc, pretty_print=True), + ) + except ( + junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError, + ) as ex: + junos_module.logger.debug( + 'Unable to execute RPC "%s". Error: %s', + junos_module.etree.tostring(rpc, pretty_print=True), + str(ex), + ) + result["msg"] = "Unable to execute the RPC: %s. Error: %s" % ( + junos_module.etree.tostring(rpc, pretty_print=True), + str(ex), + ) results.append(result) continue text_output = None parsed_output = None if resp is True: - text_output = '' + text_output = "" elif (resp, junos_module.etree._Element): # Handle the output based on format - if format == 'text': + if format == "text": text_output = resp.text - junos_module.logger.debug('Text output set.') - elif format == 'xml': - text_output = junos_module.etree.tostring(resp, - pretty_print=True) + junos_module.logger.debug("Text output set.") + elif format == "xml": + text_output = junos_module.etree.tostring(resp, pretty_print=True) parsed_output = junos_module.jxmlease.parse_etree(resp) - junos_module.logger.debug('XML output set.') - elif format == 'json': + junos_module.logger.debug("XML output set.") + elif format == "json": text_output = str(resp) parsed_output = resp - junos_module.logger.debug('JSON output set.') + junos_module.logger.debug("JSON output set.") else: - result['msg'] = 'Unexpected format %s.' % (format) + result["msg"] = "Unexpected format %s." % (format) results.append(result) - junos_module.logger.debug('Unexpected format %s.', format) + junos_module.logger.debug("Unexpected format %s.", format) continue else: - result['msg'] = 'Unexpected response type %s.' % (type(resp)) + result["msg"] = "Unexpected response type %s." % (type(resp)) results.append(result) - junos_module.logger.debug('Unexpected response type %s.', - type(resp)) + junos_module.logger.debug("Unexpected response type %s.", type(resp)) continue # Set the output keys - if junos_module.params['return_output'] is True: + if junos_module.params["return_output"] is True: if text_output is not None: - result['stdout'] = text_output - result['stdout_lines'] = text_output.splitlines() + result["stdout"] = text_output + result["stdout_lines"] = text_output.splitlines() if parsed_output is not None: - result['parsed_output'] = parsed_output + result["parsed_output"] = parsed_output # Save the output junos_module.save_text_output(rpc_string, format, text_output) # This command succeeded. - result['failed'] = False + result["failed"] = False # Append to the list of results results.append(result) @@ -623,13 +634,11 @@ def main(): # Calculate the overall failed. Only failed if all commands failed. failed = True for result in results: - if result.get('failed') is False: + if result.get("failed") is False: failed = False break - junos_module.exit_json(results=results, - changed=False, - failed=failed) + junos_module.exit_json(results=results, changed=False, failed=failed) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index ddadae84..0b62057b 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: software @@ -57,7 +59,7 @@ #. Compare the currently installed Junos version to the desired version specified by the I(version) option. - + * If the current and desired versions are the same, stop and return I(changed) with a value of C(false). * If running in check mode, and the current and desired versions differ, @@ -92,8 +94,8 @@ description: - install software on the specified members ids of VC. required: false - default: none - type: list + default: none + type: list checksum: description: - The pre-calculated checksum, using the I(checksum_algorithm) of the @@ -316,9 +318,9 @@ software specified by the I(version) option is actually activated on the target Junos device. It is the user's responsibility to confirm the software installation using additional follow on tasks in their playbook. -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: 'Explicit host argument' hosts: junos @@ -345,10 +347,10 @@ cleanfs: false validate: true register: response -''' +""" -RETURN = ''' +RETURN = """ changed: description: - Indicates if the device's state has changed, or if the state would have @@ -375,12 +377,13 @@ installation. returned: always type: str -''' +""" # Standard Library imports import os.path import re import time + try: # Python 3.x from urllib.parse import urlparse @@ -395,8 +398,10 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule + from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def parse_version_from_filename(filename): """Attempts to parse a version string from the filename of a Junos package. @@ -415,15 +420,22 @@ def parse_version_from_filename(filename): The version string, or None if unable to parse. """ # Known prefixes for filenames which contain Junos software packages. - JUNOS_PACKAGE_PREFIXES = ['jbundle', 'jinstall', 'junos-install', - 'junos-srx', 'junos-vmhost-install', 'junos-vrr', - 'vmx-bundle', 'junos-arm'] + JUNOS_PACKAGE_PREFIXES = [ + "jbundle", + "jinstall", + "junos-install", + "junos-srx", + "junos-vmhost-install", + "junos-vrr", + "vmx-bundle", + "junos-arm", + ] for prefix in JUNOS_PACKAGE_PREFIXES: if filename.startswith(prefix): # Assumes the version string will be prefixed by -. # Assume major version will begin with two digits followed by dot. # Assume the version string ends with the last digit in filename. - match = re.search(r'-(\d{2}\..*\d).*', filename) + match = re.search(r"-(\d{2}\..*\d).*", filename) if match is not None: return match.group(1) # Not a known Junos package name. @@ -432,8 +444,8 @@ def parse_version_from_filename(filename): def define_progress_callback(junos_module): - """Create callback which can be passed to SW.install(progress=progress) - """ + """Create callback which can be passed to SW.install(progress=progress)""" + def myprogress(_, report): """A progress function which logs report at level INFO. @@ -442,86 +454,58 @@ def myprogress(_, report): report: The string to be logged. """ junos_module.logger.info(report) + return myprogress def main(): - CHECKSUM_ALGORITHM_CHOICES = ['md5', 'sha1', 'sha256'] - - #Define the argument spec. - software_argument_spec=dict( - local_package=dict(required=False, - aliases=['package'], - type='path', - default=None), - remote_package=dict(required=False, - type='path', - # Default is '/var/tmp/' + filename from the - # local_package option, if set. - default=None), - pkg_set=dict(required=False, - type='list', - default=None), - version=dict(required=False, - aliases=['target_version', 'new_version', - 'desired_version'], - type='str', - # Default is determined from filename portion of - # remote_package option. - default=None), - no_copy=dict(required=False, - type='bool', - default=False), - reboot=dict(required=False, - type='bool', - default=True), - reboot_pause=dict(required=False, - type='int', - default=10), - issu=dict(required=False, - type='bool', - default=False), - nssu=dict(required=False, - type='bool', - default=False), - force_host=dict(required=False, - type='bool', - default=False), - validate=dict(required=False, - type='bool', - default=False), - cleanfs=dict(required=False, - type='bool', - default=True), - all_re=dict(required=False, - type='bool', - default=True), - member_id=dict(required=False, - type='list', - default=None), - vmhost=dict(required=False, - type='bool', - default=False), - checksum=dict(required=False, - type='str', - default=None), - checksum_algorithm=dict(required=False, - choices=CHECKSUM_ALGORITHM_CHOICES, - type='str', - default='md5'), - checksum_timeout=dict(required=False, - type='int', - default=300), - cleanfs_timeout=dict(required=False, - type='int', - default=300), - install_timeout=dict(required=False, - type='int', - default=1800), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='dict', - default=None), + CHECKSUM_ALGORITHM_CHOICES = ["md5", "sha1", "sha256"] + + # Define the argument spec. + software_argument_spec = dict( + local_package=dict( + required=False, aliases=["package"], type="path", default=None + ), + remote_package=dict( + required=False, + type="path", + # Default is '/var/tmp/' + filename from the + # local_package option, if set. + default=None, + ), + pkg_set=dict(required=False, type="list", default=None), + version=dict( + required=False, + aliases=["target_version", "new_version", "desired_version"], + type="str", + # Default is determined from filename portion of + # remote_package option. + default=None, + ), + no_copy=dict(required=False, type="bool", default=False), + reboot=dict(required=False, type="bool", default=True), + reboot_pause=dict(required=False, type="int", default=10), + issu=dict(required=False, type="bool", default=False), + nssu=dict(required=False, type="bool", default=False), + force_host=dict(required=False, type="bool", default=False), + validate=dict(required=False, type="bool", default=False), + cleanfs=dict(required=False, type="bool", default=True), + all_re=dict(required=False, type="bool", default=True), + member_id=dict(required=False, type="list", default=None), + vmhost=dict(required=False, type="bool", default=False), + checksum=dict(required=False, type="str", default=None), + checksum_algorithm=dict( + required=False, + choices=CHECKSUM_ALGORITHM_CHOICES, + type="str", + default="md5", + ), + checksum_timeout=dict(required=False, type="int", default=300), + cleanfs_timeout=dict(required=False, type="int", default=300), + install_timeout=dict(required=False, type="int", default=1800), + kwargs=dict( + required=False, aliases=["kwarg", "args", "arg"], type="dict", default=None + ), ) # Save keys for later. Must do because software_argument_spec gets # modified. @@ -529,34 +513,34 @@ def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( - argument_spec = software_argument_spec, + argument_spec=software_argument_spec, # Mutually exclusive options. - mutually_exclusive=[['issu', 'nssu']], + mutually_exclusive=[["issu", "nssu"]], # One of local_package and remote_package is required. - required_one_of=[['local_package', 'remote_package', 'pkg_set']], - supports_check_mode=True + required_one_of=[["local_package", "remote_package", "pkg_set"]], + supports_check_mode=True, ) # Straight from params - local_package = junos_module.params.pop('local_package') - remote_package = junos_module.params.pop('remote_package') - pkg_set = junos_module.params.pop('pkg_set') - target_version = junos_module.params.pop('version') - no_copy = junos_module.params.pop('no_copy') - reboot = junos_module.params.pop('reboot') - reboot_pause = junos_module.params.pop('reboot_pause') - install_timeout = junos_module.params.pop('install_timeout') - cleanfs = junos_module.params.pop('cleanfs') - all_re = junos_module.params.pop('all_re') - member_id = junos_module.params.pop('member_id') - kwargs = junos_module.params.pop('kwargs') + local_package = junos_module.params.pop("local_package") + remote_package = junos_module.params.pop("remote_package") + pkg_set = junos_module.params.pop("pkg_set") + target_version = junos_module.params.pop("version") + no_copy = junos_module.params.pop("no_copy") + reboot = junos_module.params.pop("reboot") + reboot_pause = junos_module.params.pop("reboot_pause") + install_timeout = junos_module.params.pop("install_timeout") + cleanfs = junos_module.params.pop("cleanfs") + all_re = junos_module.params.pop("all_re") + member_id = junos_module.params.pop("member_id") + kwargs = junos_module.params.pop("kwargs") url = None remote_dir = None if remote_package is not None: # Is the remote package a URL? parsed_url = urlparse(remote_package) - if parsed_url.scheme == '': + if parsed_url.scheme == "": # A file on the remote host. (remote_dir, remote_filename) = os.path.split(remote_package) else: @@ -564,18 +548,20 @@ def main(): (_, remote_filename) = os.path.split(parsed_url.path) else: # Default remote_dir value - remote_dir = '/var/tmp' - remote_filename = '' + remote_dir = "/var/tmp" + remote_filename = "" if url is not None and local_package is not None: - junos_module.fail_json(msg='There remote_package (%s) is a URL. ' - 'The local_package option is not allowed.' % - remote_package) + junos_module.fail_json( + msg="There remote_package (%s) is a URL. " + "The local_package option is not allowed." % remote_package + ) if url is not None and no_copy is True: - junos_module.fail_json(msg='There remote_package (%s) is a URL. ' - 'The no_copy option is not allowed.' % - remote_package) + junos_module.fail_json( + msg="There remote_package (%s) is a URL. " + "The no_copy option is not allowed." % remote_package + ) if url is None: local_filename = None @@ -583,36 +569,41 @@ def main(): # Expand out the path. local_package = os.path.abspath(local_package) (local_dir, local_filename) = os.path.split(local_package) - if local_filename == '': - junos_module.fail_json(msg='There is no filename component to ' - 'the local_package (%s).' % - local_package) + if local_filename == "": + junos_module.fail_json( + msg="There is no filename component to " + "the local_package (%s)." % local_package + ) elif remote_package is not None: # remote package was, so we must assume no_copy. no_copy = True if no_copy is False: if local_package is not None and not os.path.isfile(local_package): - junos_module.fail_json(msg='The local_package (%s) is not a ' - 'valid file on the local Ansible ' - 'control machine.' % local_package) + junos_module.fail_json( + msg="The local_package (%s) is not a " + "valid file on the local Ansible " + "control machine." % local_package + ) elif pkg_set is not None: pkg_set = [os.path.abspath(item) for item in pkg_set] for pkg_set_item in pkg_set: if not os.path.isfile(pkg_set_item): junos_module.fail_json( - msg='The pkg (%s) is not a valid file on the local' - ' Ansible control machine.' % pkg_set_item) + msg="The pkg (%s) is not a valid file on the local" + " Ansible control machine." % pkg_set_item + ) - if remote_filename == '': + if remote_filename == "": # Use the same name as local_filename remote_filename = local_filename if local_filename is not None and remote_filename != local_filename: - junos_module.fail_json(msg='The filename of the remote_package ' - '(%s) must be the same as the filename ' - 'of the local_package (%s).' % - (remote_filename, local_filename)) + junos_module.fail_json( + msg="The filename of the remote_package " + "(%s) must be the same as the filename " + "of the local_package (%s)." % (remote_filename, local_filename) + ) # If no_copy is True, then we need to turn off cleanfs to keep from # deleting the software package which is already present on the device. @@ -624,12 +615,14 @@ def main(): junos_module.logger.debug("New target version is: %s.", target_version) # Initialize the results. Assume not changed and failure until we know. - results = {'msg': '', - 'changed': False, - 'check_mode': junos_module.check_mode, - 'failed': True} - - if junos_module.conn_type == "local" : + results = { + "msg": "", + "changed": False, + "check_mode": junos_module.check_mode, + "failed": True, + } + + if junos_module.conn_type == "local": facts = dict(junos_module.dev.facts) else: facts = junos_module.get_facts() @@ -638,64 +631,72 @@ def main(): # Check version info to see if we need to do the install. if target_version is not None: if all_re is True: - junos_info = facts['junos_info'] + junos_info = facts["junos_info"] for current_re in junos_info: - current_version = junos_info[current_re]['text'] + current_version = junos_info[current_re]["text"] if target_version != current_version: - junos_module.logger.debug("Current version on %s: %s. " - "Target version: %s.", - current_version, current_re, - target_version) - results['changed'] = True + junos_module.logger.debug( + "Current version on %s: %s. " "Target version: %s.", + current_version, + current_re, + target_version, + ) + results["changed"] = True else: - results['msg'] += "Current version on %s: %s same as Targeted " \ - "version: %s.\n" % (current_version, current_re, - target_version) + results["msg"] += ( + "Current version on %s: %s same as Targeted " + "version: %s.\n" % (current_version, current_re, target_version) + ) else: - current_version = facts['version'] + current_version = facts["version"] if junos_module.conn_type == "local": re_name = junos_module.dev.re_name else: re_name = junos_module._pyez_conn.get_re_name() if target_version != current_version: - junos_module.logger.debug("Current version on %s: %s. " - "Target version: %s.", - current_version, re_name, - target_version) - results['changed'] = True + junos_module.logger.debug( + "Current version on %s: %s. " "Target version: %s.", + current_version, + re_name, + target_version, + ) + results["changed"] = True else: - results['msg'] += "Current version on %s: %s same as Targeted " \ - "version: %s.\n" % (current_version, re_name, - target_version) + results[ + "msg" + ] += "Current version on %s: %s same as Targeted " "version: %s.\n" % ( + current_version, + re_name, + target_version, + ) else: # A non-Junos install. Always attempt to install. - results['changed'] = True + results["changed"] = True # Do the install if necessary - if results['changed'] is True and not junos_module.check_mode: - junos_module.logger.debug("Beginning installation of %s.", - remote_filename) + if results["changed"] is True and not junos_module.check_mode: + junos_module.logger.debug("Beginning installation of %s.", remote_filename) # Calculate the install parameters install_params = {} if url is not None: - install_params['package'] = url + install_params["package"] = url elif local_package is not None: - install_params['package'] = local_package + install_params["package"] = local_package elif pkg_set is not None: - install_params['pkg_set'] = pkg_set + install_params["pkg_set"] = pkg_set else: - install_params['package'] = remote_filename + install_params["package"] = remote_filename if remote_dir is not None: - install_params['remote_path'] = remote_dir + install_params["remote_path"] = remote_dir if junos_module.conn_type != "local": - install_params['progress'] = True + install_params["progress"] = True else: - install_params['progress'] = define_progress_callback(junos_module) - install_params['cleanfs'] = cleanfs - install_params['no_copy'] = no_copy - install_params['timeout'] = install_timeout - install_params['all_re'] = all_re - install_params['member_id'] = member_id + install_params["progress"] = define_progress_callback(junos_module) + install_params["cleanfs"] = cleanfs + install_params["no_copy"] = no_copy + install_params["timeout"] = install_timeout + install_params["all_re"] = all_re + install_params["member_id"] = member_id for key in option_keys: value = junos_module.params.get(key) if value is not None: @@ -703,17 +704,16 @@ def main(): if kwargs is not None: install_params.update(kwargs) - junos_module.logger.debug("Install parameters are: %s", - str(install_params)) + junos_module.logger.debug("Install parameters are: %s", str(install_params)) if junos_module.conn_type != "local": try: - results['msg'] = junos_module._pyez_conn.software_api(install_params) + results["msg"] = junos_module._pyez_conn.software_api(install_params) except Exception as err: # pylint: disable=broad-except if "ConnectionError" in str(type(err)): # If Exception is ConnectionError, it is excpected # Device installation inititated succesfully junos_module.logger.debug("Package successfully installed.") - results['msg'] += 'Package successfully installed.' + results["msg"] += "Package successfully installed." else: # If exception is not ConnectionError # we will raise the exception @@ -724,33 +724,42 @@ def main(): junos_module.add_sw() ok, msg_ret = junos_module.sw.install(**install_params) if ok is not True: - results['msg'] = 'Unable to install the software %s', msg_ret + results["msg"] = "Unable to install the software %s", msg_ret junos_module.fail_json(**results) - msg = 'Package %s successfully installed. Response from device is: %s' % ( - install_params.get('package') or - install_params.get('pkg_set'), - msg_ret) - results['msg'] = msg + msg = ( + "Package %s successfully installed. Response from device is: %s" + % ( + install_params.get("package") or install_params.get("pkg_set"), + msg_ret, + ) + ) + results["msg"] = msg junos_module.logger.debug(msg) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - results['msg'] = 'Installation failed. Error: %s' % str(ex) + except ( + junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError, + ) as ex: + results["msg"] = "Installation failed. Error: %s" % str(ex) junos_module.fail_json(**results) if reboot is True: - junos_module.logger.debug('Initiating reboot.') + junos_module.logger.debug("Initiating reboot.") if junos_module.conn_type != "local": try: - #Handling reboot of specific VC members + # Handling reboot of specific VC members if member_id is not None: - results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost'), member_id=member_id) + results["msg"] += junos_module._pyez_conn.reboot_api( + all_re, install_params.get("vmhost"), member_id=member_id + ) else: - results['msg'] += junos_module._pyez_conn.reboot_api(all_re, install_params.get('vmhost')) + results["msg"] += junos_module._pyez_conn.reboot_api( + all_re, install_params.get("vmhost") + ) except Exception as err: # pylint: disable=broad-except if "ConnectionError" in str(type(err)): # If Exception is ConnectionError, it is excpected # Device reboot inititated succesfully junos_module.logger.debug("Reboot RPC executed.") - results['msg'] += ' Reboot succeeded.' + results["msg"] += " Reboot succeeded." else: # If exception is not ConnectionError # we will raise the exception @@ -764,19 +773,24 @@ def main(): # happens relatively quickly. restore_timeout = junos_module.dev.timeout if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") + junos_module.logger.debug( + "Decreasing device RPC timeout " "to 5 seconds." + ) junos_module.dev.timeout = 5 try: if member_id is not None: - got = junos_module.sw.reboot(0, - None, - all_re, - None, - install_params.get('vmhost'), - member_id=member_id) + got = junos_module.sw.reboot( + 0, + None, + all_re, + None, + install_params.get("vmhost"), + member_id=member_id, + ) else: - got = junos_module.sw.reboot(0, None, all_re, None, install_params.get('vmhost')) + got = junos_module.sw.reboot( + 0, None, all_re, None, install_params.get("vmhost") + ) junos_module.dev.timeout = restore_timeout except Exception: # pylint: disable=broad-except junos_module.dev.timeout = restore_timeout @@ -784,16 +798,19 @@ def main(): junos_module.logger.debug("Reboot RPC executed.") if got is not None: - results['msg'] += ' Reboot successfully initiated. ' \ - 'Reboot message: %s' % got + results["msg"] += ( + " Reboot successfully initiated. " + "Reboot message: %s" % got + ) else: # This is the else clause of the for loop. # It only gets executed if the loop finished without # hitting the break. - results['msg'] += ' Did not find expected response ' \ - 'from reboot RPC. ' + results["msg"] += ( + " Did not find expected response " "from reboot RPC. " + ) junos_module.fail_json(**results) - except (junos_module.pyez_exception.RpcTimeoutError) as ex: + except junos_module.pyez_exception.RpcTimeoutError as ex: # This might be OK. It might just indicate the device didn't # send the closing (known Junos bug). # Try to close the device. If it closes cleanly, then it was @@ -801,39 +818,45 @@ def main(): try: junos_module.close(raise_exceptions=True) # This means the device wasn't already disconnected. - results['msg'] += ' Reboot failed. It may not have been ' \ - 'initiated.' + results["msg"] += ( + " Reboot failed. It may not have been " "initiated." + ) junos_module.fail_json(**results) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.RpcTimeoutError, - junos_module.pyez_exception.ConnectError): + except ( + junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.RpcTimeoutError, + junos_module.pyez_exception.ConnectError, + ): # This is expected. The device has already disconnected. - results['msg'] += ' Reboot succeeded.' - except (junos_module.ncclient_exception.TimeoutExpiredError): + results["msg"] += " Reboot succeeded." + except junos_module.ncclient_exception.TimeoutExpiredError: # This is not really expected. Still consider reboot success as # Looks like rpc was consumed but no response as its rebooting. - results['msg'] += ' Reboot succeeded. Ignoring close error.' - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['msg'] += ' Reboot failed. Error: %s' % (str(ex)) + results["msg"] += " Reboot succeeded. Ignoring close error." + except ( + junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError, + ) as ex: + results["msg"] += " Reboot failed. Error: %s" % (str(ex)) junos_module.fail_json(**results) else: try: junos_module.close() - except (junos_module.ncclient_exception.TimeoutExpiredError): - junos_module.logger.debug("Ignoring TimeoutError for close call") + except junos_module.ncclient_exception.TimeoutExpiredError: + junos_module.logger.debug( + "Ignoring TimeoutError for close call" + ) junos_module.logger.debug("Reboot RPC successfully initiated.") if reboot_pause > 0: - junos_module.logger.debug("Sleeping for %d seconds", - reboot_pause) + junos_module.logger.debug("Sleeping for %d seconds", reboot_pause) time.sleep(reboot_pause) # If we made it this far, it's success. - results['failed'] = False + results["failed"] = False junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index c5baf36a..a0759c2e 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -33,13 +33,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: srx_cluster @@ -82,9 +84,9 @@ type: int aliases: - node -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: Manipulate the SRX cluster configuration of Junos SRX devices hosts: junos-all @@ -108,9 +110,9 @@ - name: Print the response. ansible.builtin.debug: var: response.config_lines -''' +""" -RETURN = ''' +RETURN = """ changed: description: - Indicates if the device's configuration has changed, or would have @@ -132,7 +134,7 @@ - Indicates if a reboot of the device has been initiated. returned: success type: bool -''' +""" # Standard library imports @@ -143,56 +145,53 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule + from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - enable=dict(type='bool', - required=True, - aliases=['cluster_enable'], - default=None), - cluster_id=dict(type='int', - required=False, - aliases=['cluster'], - default=None), - node_id=dict(type='int', - required=False, - aliases=['node'], - default=None) + enable=dict( + type="bool", required=True, aliases=["cluster_enable"], default=None + ), + cluster_id=dict( + type="int", required=False, aliases=["cluster"], default=None + ), + node_id=dict(type="int", required=False, aliases=["node"], default=None), ), # Required if options # If enable is True, then cluster_id and node_id must be set. - required_if=[['enable', True, ['cluster_id', 'node_id']]], + required_if=[["enable", True, ["cluster_id", "node_id"]]], # Check mode is implemented. - supports_check_mode=True + supports_check_mode=True, ) # Do additional argument verification. # Straight from params - enable = junos_module.params.get('enable') - cluster_id = junos_module.params.get('cluster_id') - node_id = junos_module.params.get('node_id') + enable = junos_module.params.get("enable") + cluster_id = junos_module.params.get("cluster_id") + node_id = junos_module.params.get("node_id") # cluster_id must be between 0 and 255 if cluster_id is not None: if cluster_id < 0 or cluster_id > 255: - junos_module.fail_json(msg="The cluster_id option (%s) must have " - "an integer value between 0 and 255." % - (cluster_id)) + junos_module.fail_json( + msg="The cluster_id option (%s) must have " + "an integer value between 0 and 255." % (cluster_id) + ) # node_id must be between 0 and 1 if node_id is not None: if node_id < 0 or node_id > 1: - junos_module.fail_json(msg="The node_id option (%s) must have a " - "value of 0 or 1." % (node_id)) + junos_module.fail_json( + msg="The node_id option (%s) must have a " + "value of 0 or 1." % (node_id) + ) # Initialize the results. Assume failure until we know it's success. - results = {'msg': '', - 'changed': False, - 'reboot': False, - 'failed': True} + results = {"msg": "", "changed": False, "reboot": False, "failed": True} if junos_module.conn_type == "local": facts = dict(junos_module.dev.facts) @@ -200,8 +199,8 @@ def main(): facts = junos_module.get_facts() # facts checking has been done as part of persitent connection itself. junos_module.logger.debug("Check current SRX cluster operational state.") - current_cluster_state = facts['srx_cluster'] - current_cluster_id = facts['srx_cluster_id'] + current_cluster_state = facts["srx_cluster"] + current_cluster_id = facts["srx_cluster_id"] if current_cluster_id is not None: current_cluster_id = int(current_cluster_id) if junos_module.conn_type == "local": @@ -210,68 +209,78 @@ def main(): current_node_name = junos_module._pyez_conn.get_re_name() current_node_id = None if current_node_name is not None: - (_, _, current_node_id) = current_node_name.partition('node') + (_, _, current_node_id) = current_node_name.partition("node") if current_node_id: current_node_id = int(current_node_id) junos_module.logger.debug( - "Current SRX cluster operational state: %s, cluster_id: %s, " - "node_id: %s", - 'enabled' if current_cluster_state else 'disabled', + "Current SRX cluster operational state: %s, cluster_id: %s, " "node_id: %s", + "enabled" if current_cluster_state else "disabled", str(current_cluster_id), - str(current_node_id)) + str(current_node_id), + ) # Is a state change needed? if current_cluster_state != enable: junos_module.logger.debug( "SRX cluster configuration change needed. Current state: %s. " "Desired state: %s", - 'enabled' if current_cluster_state else 'disabled', - 'enabled' if enable else 'disabled') - results['changed'] = True + "enabled" if current_cluster_state else "disabled", + "enabled" if enable else "disabled", + ) + results["changed"] = True # Is a cluster ID change needed? - if (enable is True and current_cluster_id is not None and - current_cluster_id != cluster_id): + if ( + enable is True + and current_cluster_id is not None + and current_cluster_id != cluster_id + ): junos_module.logger.debug( "SRX cluster ID change needed. Current cluster ID: %d. " "Desired cluster ID: %d", - current_cluster_id, cluster_id) - results['changed'] = True + current_cluster_id, + cluster_id, + ) + results["changed"] = True # Is a node ID change needed? - if (enable is True and current_node_id is not None and - current_node_id != node_id): + if enable is True and current_node_id is not None and current_node_id != node_id: junos_module.logger.debug( - "SRX node ID change needed. Current node ID: %d. " - "Desired cluster ID: %d", - current_node_id, node_id) - results['changed'] = True + "SRX node ID change needed. Current node ID: %d. " "Desired cluster ID: %d", + current_node_id, + node_id, + ) + results["changed"] = True - results['msg'] = 'Current state: %s, cluster_id: %s, node_id: %s' % \ - ('enabled' if current_cluster_state else 'disabled', - str(current_cluster_id), - str(current_node_id)) + results["msg"] = "Current state: %s, cluster_id: %s, node_id: %s" % ( + "enabled" if current_cluster_state else "disabled", + str(current_cluster_id), + str(current_node_id), + ) - if results['changed'] is True: - results['msg'] += ' Desired state: %s, cluster_id: %s, ' \ - 'node_id: %s' % \ - ('enabled' if enable else 'disabled', - str(cluster_id), - str(node_id)) + if results["changed"] is True: + results["msg"] += " Desired state: %s, cluster_id: %s, " "node_id: %s" % ( + "enabled" if enable else "disabled", + str(cluster_id), + str(node_id), + ) if not junos_module.check_mode: - results['msg'] += ' Initiating change.' + results["msg"] += " Initiating change." try: output = None if enable is True: if junos_module.conn_type == "local": resp = junos_module.dev.rpc.set_chassis_cluster_enable( - cluster_id=str(cluster_id), node=str(node_id), - reboot=True, normalize=True + cluster_id=str(cluster_id), + node=str(node_id), + reboot=True, + normalize=True, ) else: resp = junos_module._pyez_conn.set_chassis_cluster_enable( - cluster_id=str(cluster_id), node=str(node_id)) + cluster_id=str(cluster_id), node=str(node_id) + ) else: if junos_module.conn_type == "local": resp = junos_module.dev.rpc.set_chassis_cluster_disable( @@ -287,22 +296,25 @@ def main(): resp = None output = None if resp is not None: - output = resp.getparent().findtext('.//output') + output = resp.getparent().findtext(".//output") if output is None: - output = resp.getparent().findtext('.//message') - results['msg'] += ' Reboot initiated. Response: %s' % (output) - results['reboot'] = True - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.logger.debug('Error: %s', str(ex)) - results['msg'] += ' Error: %s' % (str(ex)) + output = resp.getparent().findtext(".//message") + results["msg"] += " Reboot initiated. Response: %s" % (output) + results["reboot"] = True + except ( + junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError, + ) as ex: + junos_module.logger.debug("Error: %s", str(ex)) + results["msg"] += " Error: %s" % (str(ex)) junos_module.fail_json(**results) # If we made it this far, everything was successful. - results['failed'] = False + results["failed"] = False # Return response. junos_module.exit_json(**results) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index cc891ffd..af338cf0 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -33,11 +33,13 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- extends_documentation_fragment: - juniper_junos_common.connection_documentation @@ -151,9 +153,9 @@ connection. However, this module does still permit connecting to Junos devices via the console port and this functionality may still be used for Junos devices running Junos versions less than 15.1. -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: 'Explicit host argument' hosts: junos @@ -200,9 +202,9 @@ juniper.device.system: action: "zeroize" media: True -''' +""" -RETURN = ''' +RETURN = """ action: description: - The value of the I(action) option. @@ -241,7 +243,7 @@ - The value of the I(other_re) option. returned: always type: str -''' +""" """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules @@ -250,63 +252,61 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule + from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - action=dict(type='str', - required=True, - choices=['shutdown', 'off', 'power-off', 'power_off', - 'halt', 'reboot', 'zeroize'], - default=None), - at=dict(type='str', - required=False, - default=None), - in_min=dict(type='int', - required=False, - aliases=['in'], - default=0), - all_re=dict(type='bool', - required=False, - default=True), - member_id=dict(type='list', - required=False, - default=None), - other_re=dict(type='bool', - required=False, - default=False), - vmhost=dict(required=False, - type='bool', - default=False), - media=dict(type='bool', - required=False, - default=False), + action=dict( + type="str", + required=True, + choices=[ + "shutdown", + "off", + "power-off", + "power_off", + "halt", + "reboot", + "zeroize", + ], + default=None, + ), + at=dict(type="str", required=False, default=None), + in_min=dict(type="int", required=False, aliases=["in"], default=0), + all_re=dict(type="bool", required=False, default=True), + member_id=dict(type="list", required=False, default=None), + other_re=dict(type="bool", required=False, default=False), + vmhost=dict(required=False, type="bool", default=False), + media=dict(type="bool", required=False, default=False), ), - mutually_exclusive=[['at', 'in_min'], ['all_re', 'other_re']], - supports_check_mode=True + mutually_exclusive=[["at", "in_min"], ["all_re", "other_re"]], + supports_check_mode=True, ) # We're going to be using params a lot params = junos_module.params - action = params['action'] - at = params.get('at') - in_min = params.get('in_min') - all_re = params.get('all_re') - other_re = params.get('other_re') - media = params.get('media') - vmhost = params.get('vmhost') - member_id = params.get('member_id') + action = params["action"] + at = params.get("at") + in_min = params.get("in_min") + all_re = params.get("all_re") + other_re = params.get("other_re") + media = params.get("media") + vmhost = params.get("vmhost") + member_id = params.get("member_id") # Synonymn for shutdown - if action == 'off' or action == 'power_off' or action == 'power-off': - action = 'shutdown' + if action == "off" or action == "power_off" or action == "power-off": + action = "shutdown" - if action != 'reboot' and vmhost is True: - junos_module.fail_json(msg='The vmhost option can only be used when ' - 'the action option has the value "reboot".') + if action != "reboot" and vmhost is True: + junos_module.fail_json( + msg="The vmhost option can only be used when " + 'the action option has the value "reboot".' + ) # Four actions are expected - reboot, shutdown, halt and zeroize @@ -314,71 +314,104 @@ def main(): if action == "zeroize": # at, in_min and other_re option only applies to reboot, shutdown, or halt action. if (at != None) or (in_min != 0) or (other_re == True): - junos_module.fail_json(msg='The options at, in_min and other_re can only be used when ' - 'the action option has the value "zeroize"') - elif media is True: # media option only applies to zeroize action. - junos_module.fail_json(msg='The media option can only be used when ' - 'the action option has the value "zeroize".') + junos_module.fail_json( + msg="The options at, in_min and other_re can only be used when " + 'the action option has the value "zeroize"' + ) + elif media is True: # media option only applies to zeroize action. + junos_module.fail_json( + msg="The media option can only be used when " + 'the action option has the value "zeroize".' + ) # Set initial results values. Assume failure until we know it's success. - results = {'changed': True, - 'msg': '', - 'reboot': bool(action == 'reboot'), - 'action': action, - 'all_re': all_re, - 'other_re': other_re, - 'media': media, - 'vmhost': vmhost, - 'failed': True} + results = { + "changed": True, + "msg": "", + "reboot": bool(action == "reboot"), + "action": action, + "all_re": all_re, + "other_re": other_re, + "media": media, + "vmhost": vmhost, + "failed": True, + } if not junos_module.check_mode: if junos_module.conn_type != "local": if member_id is not None: for m_id in member_id: - results['msg'] = junos_module._pyez_conn.system_api(action, in_min, at, all_re, vmhost, other_re, media, member_id=m_id) + results["msg"] = junos_module._pyez_conn.system_api( + action, + in_min, + at, + all_re, + vmhost, + other_re, + media, + member_id=m_id, + ) else: - results['msg'] = junos_module._pyez_conn.system_api(action, in_min, at, all_re, vmhost, other_re, media) - results['failed'] = False + results["msg"] = junos_module._pyez_conn.system_api( + action, in_min, at, all_re, vmhost, other_re, media + ) + results["failed"] = False else: - if action != 'zeroize': + if action != "zeroize": # If we're going to do a shutdown, reboot, or halt right away then # try to deal with the fact that we might not get the closing # and therefore might get an RpcTimeout. # (This is a known Junos bug.) Set the timeout low so this happens # relatively quickly. - if (at == 'now' or (in_min == 0 and at is None)): + if at == "now" or (in_min == 0 and at is None): if junos_module.dev.timeout > 5: - junos_module.logger.debug("Decreasing device RPC timeout " - "to 5 seconds.") + junos_module.logger.debug( + "Decreasing device RPC timeout " "to 5 seconds." + ) junos_module.dev.timeout = 5 # Execute the RPC. try: junos_module.logger.debug("Executing RPC") junos_module.add_sw() - if action == 'reboot': + if action == "reboot": if member_id is not None: for m_id in member_id: - got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re, member_id=m_id) + got = junos_module.sw.reboot( + in_min, + at, + all_re, + None, + vmhost, + other_re, + member_id=m_id, + ) else: - got = junos_module.sw.reboot(in_min, at, all_re, None, vmhost, other_re) - elif action == 'shutdown': - got = junos_module.sw.poweroff(in_min, at, None, all_re, other_re, vmhost) - elif action == 'halt': + got = junos_module.sw.reboot( + in_min, at, all_re, None, vmhost, other_re + ) + elif action == "shutdown": + got = junos_module.sw.poweroff( + in_min, at, None, all_re, other_re, vmhost + ) + elif action == "halt": got = junos_module.sw.halt(in_min, at, all_re, other_re) - elif action == 'zeroize': + elif action == "zeroize": got = junos_module.sw.zeroize(all_re, media) else: - junos_module.fail_json(msg='Relevant action not found') + junos_module.fail_json(msg="Relevant action not found") junos_module.logger.debug("RPC executed") if got is None: - results['msg'] = 'Did not find expected RPC response.' - results['changed'] = False + results["msg"] = "Did not find expected RPC response." + results["changed"] = False else: - results['msg'] = '%s successfully initiated. Response got %s' % (action, got) - results['failed'] = False - except (junos_module.pyez_exception.RpcTimeoutError) as ex: + results["msg"] = "%s successfully initiated. Response got %s" % ( + action, + got, + ) + results["failed"] = False + except junos_module.pyez_exception.RpcTimeoutError as ex: # This might be OK. It might just indicate the device didn't # send the closing (known Junos bug). # Try to close the device. If it closes cleanly, then it was @@ -386,22 +419,28 @@ def main(): try: junos_module.close(raise_exceptions=True) # This means the device wasn't already disconnected. - results['changed'] = False - results['msg'] = '%s failed. %s may not have been ' \ - 'initiated.' % (action, action) - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError): + results["changed"] = False + results["msg"] = "%s failed. %s may not have been " "initiated." % ( + action, + action, + ) + except ( + junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError, + ): # This is expected. The device has already disconnected. - results['msg'] = '%s succeeded.' % (action) - results['failed'] = False - except (junos_module.pyez_exception.RpcError, - junos_module.pyez_exception.ConnectError) as ex: - results['changed'] = False - results['msg'] = '%s failed. Error: %s' % (action, str(ex)) + results["msg"] = "%s succeeded." % (action) + results["failed"] = False + except ( + junos_module.pyez_exception.RpcError, + junos_module.pyez_exception.ConnectError, + ) as ex: + results["changed"] = False + results["msg"] = "%s failed. Error: %s" % (action, str(ex)) # Return results. junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py index 38c1541f..e3988e87 100644 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -36,13 +36,15 @@ from __future__ import absolute_import, division, print_function -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'supported_by': 'community', - 'status': ['stableinterface']} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "supported_by": "community", + "status": ["stableinterface"], +} -DOCUMENTATION = ''' +DOCUMENTATION = """ --- -extends_documentation_fragment: +extends_documentation_fragment: - juniper_junos_common.connection_documentation - juniper_junos_common.logging_documentation module: table @@ -117,9 +119,9 @@ notes: - This module only works with operational tables/views; it does not work with configuration tables/views. -''' +""" -EXAMPLES = ''' +EXAMPLES = """ --- - name: Retrieve data from a Junos device using a PyEZ table/view. hosts: junos-all @@ -155,11 +157,11 @@ - name: Print response ansible.builtin.debug: var: response -''' +""" -RETURN = ''' +RETURN = """ changed: - description: + description: - Indicates if the device's configuration has changed. Since this module does not change the operational or configuration state of the device, the value is always set to C(false). @@ -185,100 +187,100 @@ # when response_type == 'list_of_dicts' [ { - "local_int": "ge-0/0/3", - "local_parent": "-", - "remote_chassis_id": "00:05:86:08:d4:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/0", - "remote_sysname": "r5", + "local_int": "ge-0/0/3", + "local_parent": "-", + "remote_chassis_id": "00:05:86:08:d4:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/0", + "remote_sysname": "r5", "remote_type": "Mac address" - }, + }, { - "local_int": "ge-0/0/0", - "local_parent": "-", - "remote_chassis_id": "00:05:86:18:f3:c0", - "remote_port_desc": null, - "remote_port_id": "ge-0/0/2", - "remote_sysname": "r4", + "local_int": "ge-0/0/0", + "local_parent": "-", + "remote_chassis_id": "00:05:86:18:f3:c0", + "remote_port_desc": null, + "remote_port_id": "ge-0/0/2", + "remote_sysname": "r4", "remote_type": "Mac address" } ] # when response_type == 'juniper_items' [ [ - "ge-0/0/3", + "ge-0/0/3", [ [ - "local_parent", + "local_parent", "-" - ], + ], [ - "remote_port_id", + "remote_port_id", "ge-0/0/0" - ], + ], [ - "remote_chassis_id", + "remote_chassis_id", "00:05:86:08:d4:c0" - ], + ], [ - "remote_port_desc", + "remote_port_desc", null - ], + ], [ - "remote_type", + "remote_type", "Mac address" - ], + ], [ - "local_int", + "local_int", "ge-0/0/3" - ], + ], [ - "remote_sysname", + "remote_sysname", "r5" ] ] - ], + ], [ - "ge-0/0/0", + "ge-0/0/0", [ [ - "local_parent", + "local_parent", "-" - ], + ], [ - "remote_port_id", + "remote_port_id", "ge-0/0/2" - ], + ], [ - "remote_chassis_id", + "remote_chassis_id", "00:05:86:18:f3:c0" - ], + ], [ - "remote_port_desc", + "remote_port_desc", null - ], + ], [ - "remote_type", + "remote_type", "Mac address" - ], + ], [ - "local_int", + "local_int", "ge-0/0/0" - ], + ], [ - "remote_sysname", + "remote_sysname", "r4" ] ] ] ] -''' +""" # Standard library imports import os.path # Constants -RESPONSE_CHOICES = ['list_of_dicts', 'juniper_items'] +RESPONSE_CHOICES = ["list_of_dicts", "juniper_items"] """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules @@ -287,12 +289,13 @@ # Ansiballz packages module_utils into ansible.module_utils from ansible.module_utils.basic import AnsibleModule -from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg +from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common + def expand_items(module, data): - """Recursively expand any table items - """ + """Recursively expand any table items""" resources = [] # data.items() is a list of tuples for table_key, table_fields in data.items(): @@ -312,8 +315,7 @@ def expand_items(module, data): def juniper_items_to_list_of_dicts(module, data): - """Recursively convert Juniper PyEZ Table/View items to list of dicts. - """ + """Recursively convert Juniper PyEZ Table/View items to list of dicts.""" resources = [] # data.items() is a list of tuples for table_key, table_fields in data.items(): @@ -335,24 +337,23 @@ def main(): # Create the module instance. junos_module = juniper_junos_common.JuniperJunosModule( argument_spec=dict( - file=dict(type='path', - required=True, - default=None), - table=dict(type='str', - required=False, - default=None), - path=dict(type='path', - required=False, - aliases=['directory', 'dir'], - default=None), - kwargs=dict(required=False, - aliases=['kwarg', 'args', 'arg'], - type='dict', - default=None), - response_type=dict(choices=RESPONSE_CHOICES, - type='str', - required=False, - default='list_of_dicts'), + file=dict(type="path", required=True, default=None), + table=dict(type="str", required=False, default=None), + path=dict( + type="path", required=False, aliases=["directory", "dir"], default=None + ), + kwargs=dict( + required=False, + aliases=["kwarg", "args", "arg"], + type="dict", + default=None, + ), + response_type=dict( + choices=RESPONSE_CHOICES, + type="str", + required=False, + default="list_of_dicts", + ), ), # Check mode is implemented. supports_check_mode=True, @@ -360,72 +361,79 @@ def main(): ) # Straight from params - file = junos_module.params.get('file') - table = junos_module.params.get('table') - path = junos_module.params.get('path') - kwargs = junos_module.params.get('kwargs') - response_type = junos_module.params.get('response_type') + file = junos_module.params.get("file") + table = junos_module.params.get("table") + path = junos_module.params.get("path") + kwargs = junos_module.params.get("kwargs") + response_type = junos_module.params.get("response_type") - if not file.endswith('.yml') and not file.endswith('.yaml'): - junos_module.fail_json(msg='The value of the file option must end ' - 'with the .yml or .yaml extension') + if not file.endswith(".yml") and not file.endswith(".yaml"): + junos_module.fail_json( + msg="The value of the file option must end " + "with the .yml or .yaml extension" + ) # If needed, get the default path if path is None: - path = os.path.dirname( - os.path.abspath(junos_module.pyez_op_table.__file__)) + path = os.path.dirname(os.path.abspath(junos_module.pyez_op_table.__file__)) # file_name is path + file file_name = os.path.join(path, file) junos_module.logger.debug("Attempting to open: %s.", file_name) try: - with open(file_name, 'r') as fp: + with open(file_name, "r") as fp: try: - junos_module.logger.debug("Attempting to parse YAML from : " - "%s.", file_name) + junos_module.logger.debug( + "Attempting to parse YAML from : " "%s.", file_name + ) table_view = junos_module.yaml.safe_load(fp) - junos_module.logger.debug("YAML from %s successfully parsed.", - file_name) + junos_module.logger.debug( + "YAML from %s successfully parsed.", file_name + ) except junos_module.yaml.YAMLError as ex: - junos_module.fail_json(msg='Failed parsing YAML file %s. ' - 'Error: %s' % (file_name, str(ex))) + junos_module.fail_json( + msg="Failed parsing YAML file %s. " + "Error: %s" % (file_name, str(ex)) + ) except IOError: - junos_module.fail_json(msg='The file name %s could not be opened for' - 'reading.' % (file_name)) + junos_module.fail_json( + msg="The file name %s could not be opened for" "reading." % (file_name) + ) junos_module.logger.debug("%s successfully read.", file_name) # Initialize the results. Assume failure until we know it's success. - results = {'msg': '', - 'changed': False, - 'failed': True} + results = {"msg": "", "changed": False, "failed": True} # Default to the table defined in file_name. # Ignore table names which begin with an underscore. if table is None: for key in table_view: - if not key.startswith('_') and 'Table' in key: + if not key.startswith("_") and "Table" in key: if table is not None: junos_module.fail_json( - msg='The file name %s contains multiple table ' - 'definitions. Specify the desired table with the ' - 'table option.' % (file_name)) + msg="The file name %s contains multiple table " + "definitions. Specify the desired table with the " + "table option." % (file_name) + ) table = key if table is None: junos_module.fail_json( - msg='No table definition was found in the %s file. Specify a ' - 'value for the file option which contains a valid table/view ' - 'definition.' % (file_name)) + msg="No table definition was found in the %s file. Specify a " + "value for the file option which contains a valid table/view " + "definition." % (file_name) + ) junos_module.logger.debug("Table: %s", table) try: - loader = \ - junos_module.pyez_factory_loader.FactoryLoader().load(table_view) + loader = junos_module.pyez_factory_loader.FactoryLoader().load(table_view) junos_module.logger.debug("Loader created successfully.") except Exception as ex: - junos_module.fail_json(msg='Unable to create a table loader from the ' - '%s file. Error: %s' % (file_name, str(ex))) + junos_module.fail_json( + msg="Unable to create a table loader from the " + "%s file. Error: %s" % (file_name, str(ex)) + ) try: # there is a limitation of JSON for persistent connection. # the connection passes information as JSON and can't use device object here. @@ -440,40 +448,45 @@ def main(): data.get() else: data.get(**kwargs) - junos_module.logger.debug("Data retrieved from %s successfully.", - table) + junos_module.logger.debug("Data retrieved from %s successfully.", table) except KeyError: - junos_module.fail_json(msg='Unable to find table %s in the ' - '%s file.' % (table, file_name)) - except (junos_module.pyez_exception.ConnectError, - junos_module.pyez_exception.RpcError) as ex: - junos_module.fail_json(msg='Unable to retrieve data from table %s. ' - 'Error: %s' % (table, str(ex))) + junos_module.fail_json( + msg="Unable to find table %s in the " "%s file." % (table, file_name) + ) + except ( + junos_module.pyez_exception.ConnectError, + junos_module.pyez_exception.RpcError, + ) as ex: + junos_module.fail_json( + msg="Unable to retrieve data from table %s. " "Error: %s" % (table, str(ex)) + ) if data is not None: try: len_data = len(data) except Exception as ex: - junos_module.fail_json(msg='Unable to parse table %s data into ' - 'items. Error: %s' % (table, str(ex))) - junos_module.logger.debug('Successfully retrieved %d items from %s.', - len_data, table) - results['msg'] = 'Successfully retrieved %d items from %s.' % \ - (len_data, table) - - if response_type == 'list_of_dicts': - junos_module.logger.debug('Converting data to list of dicts.') + junos_module.fail_json( + msg="Unable to parse table %s data into " + "items. Error: %s" % (table, str(ex)) + ) + junos_module.logger.debug( + "Successfully retrieved %d items from %s.", len_data, table + ) + results["msg"] = "Successfully retrieved %d items from %s." % (len_data, table) + + if response_type == "list_of_dicts": + junos_module.logger.debug("Converting data to list of dicts.") resource = juniper_items_to_list_of_dicts(junos_module, data) else: resource = expand_items(junos_module, data) # If we made it this far, everything was successful. - results['failed'] = False - results['resource'] = resource + results["failed"] = False + results["resource"] = resource # Return response. junos_module.exit_json(**results) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/entrypoint.sh b/entrypoint.sh index 868563bb..1fc1d0c4 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -51,9 +51,8 @@ fi [[ -z "$ROLES" ]] || galaxy_install "$ROLES" if [ -z "$1" ] -then +then echo "Starting an interactive Bash session" /bin/bash else run_command "$*" fi - diff --git a/env-setup b/env-setup index e87c55fd..67c4772a 100755 --- a/env-setup +++ b/env-setup @@ -1,5 +1,5 @@ #!/bin/bash -# usage: source env-setup +# usage: source env-setup # When run using source as directed, $0 gets set to bash, so we must use $BASH_SOURCE if [ -n "$BASH_SOURCE" ] ; then diff --git a/setup.py b/setup.py index 13db04b5..a0cc7e6f 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python from setuptools import setup + from version import VERSION setup( @@ -11,23 +12,26 @@ license="Apache 2.0", keywords="Ansible Junos NETCONF networking automation", url="http://www.github.com/Juniper/ansible-junos-stdlib", - packages=['ansible_collections/juniper/device/plugins/modules', 'ansible_collections/juniper/device/plugins/module_utils' ], + packages=[ + "ansible_collections/juniper/device/plugins/modules", + "ansible_collections/juniper/device/plugins/module_utils", + ], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Telecommunications Industry', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Application Frameworks', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: System :: Networking', - 'Topic :: Text Processing :: Markup :: XML' + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "Intended Audience :: Telecommunications Industry", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Networking", + "Topic :: Text Processing :: Markup :: XML", ], ) diff --git a/tests/README.md b/tests/README.md index 0bd2424f..babb0b13 100644 --- a/tests/README.md +++ b/tests/README.md @@ -4,7 +4,7 @@ Following are the steps to execute Ansible Functional test playbooks ## Steps to execute Ansible Functional test playbooks -1. Git clone the functional tests +1. Git clone the functional tests ``` git clone https://github.com/Juniper/ansible-junos-stdlib.git ``` @@ -12,7 +12,7 @@ git clone https://github.com/Juniper/ansible-junos-stdlib.git ``` cd ansible-junos-stdlib/tests/ ``` -3. Update the ansible.cfg under tests directory +3. Update the ansible.cfg under tests directory ``` [defaults] hash_behaviour=merge @@ -32,7 +32,7 @@ local_connection_testcases ansible_host=xx.xx.xx.xx ansible_user=xyz ansible_ ansible_python_interpreter= ``` 5. To execute test playbook with PyEZ persistent connection, you need to update the inventory file for persistent connection test cases by setting ansible_connection=juniper.device.pyez -``` +``` [junos] pyez_connection_testcases ansible_host=xx.xx.xx.xx ansible_user=xyz ansible_ssh_pass=xyz ansible_port=22 ansible_connection=juniper.device.pyez ansible_command_timeout=300 @@ -44,18 +44,18 @@ ansible-playbook ``` ansible-playbook pb.juniper_junos_system.yml ``` -### NOTE: +### NOTE: To execute pb.juniper_junos_software_member.yml playbook, you have to use the following devices -Virtual Chassis - EX-VC, MX-VC +Virtual Chassis - EX-VC, MX-VC To execute pb.juniper_junos_srx_cluster.yml playbook, you have to use the following devices SRX HA Cluster enabled devices ### Ansible Fixes : -For the PyEZ persistent connection support, you need to apply the following fix for Ansible issue +For the PyEZ persistent connection support, you need to apply the following fix for Ansible issue ``` https://github.com/Juniper/ansible-junos-stdlib/issues/606 -``` \ No newline at end of file +``` diff --git a/tests/ansible.cfg b/tests/ansible.cfg index 56cad009..c2c908e2 100644 --- a/tests/ansible.cfg +++ b/tests/ansible.cfg @@ -5,4 +5,3 @@ roles_path = ~/.ansible/roles [persistent_connection] command_timeout = 300 - diff --git a/tools/sw_upgrade b/tools/sw_upgrade index ec5cb8b4..bdb813ee 100755 --- a/tools/sw_upgrade +++ b/tools/sw_upgrade @@ -1,9 +1,12 @@ #!/usr/bin/env python2.7 import argparse -import os, sys, re import logging +import os +import re +import sys from getpass import getpass + from jnpr.junos import Device JUNOSDIR = '/usr/local/junos' From 430ac074f84630b282e8bfc78170396911906065 Mon Sep 17 00:00:00 2001 From: Dinesh babu Date: Tue, 26 Nov 2024 20:52:26 +0530 Subject: [PATCH 422/426] Fixed Sanity test issues (#701) * Fixed Sanity test issues * Fixed Sanity test issues * Sanity_fix * Fix sanity test * Fixed unused import * Fixed import issue * Fixed import issue --- .../juniper/device/docs/ansible2rst.py | 25 ++-- .../juniper/device/docs/conf.py | 8 +- .../device/plugins/action/file_copy.py | 2 +- .../action/juniper_junos_common_action.py | 3 +- .../juniper/device/plugins/callback/jsnapy.py | 19 ++- .../juniper/device/plugins/connection/pyez.py | 28 ++--- .../plugins/module_utils/configuration.py | 27 +++-- .../module_utils/juniper_junos_common.py | 109 ++++++++++++------ .../juniper/device/plugins/modules/command.py | 9 +- .../juniper/device/plugins/modules/config.py | 19 +-- .../juniper/device/plugins/modules/facts.py | 3 +- .../device/plugins/modules/file_copy.py | 5 +- .../juniper/device/plugins/modules/jsnapy.py | 7 +- .../juniper/device/plugins/modules/ping.py | 3 +- .../juniper/device/plugins/modules/pmtud.py | 3 +- .../juniper/device/plugins/modules/rpc.py | 18 +-- .../device/plugins/modules/software.py | 19 ++- .../device/plugins/modules/srx_cluster.py | 13 ++- .../juniper/device/plugins/modules/system.py | 7 +- .../juniper/device/plugins/modules/table.py | 5 +- ansible_collections/juniper/device/version.py | 3 + 21 files changed, 182 insertions(+), 153 deletions(-) diff --git a/ansible_collections/juniper/device/docs/ansible2rst.py b/ansible_collections/juniper/device/docs/ansible2rst.py index 5eef8faa..73971a03 100755 --- a/ansible_collections/juniper/device/docs/ansible2rst.py +++ b/ansible_collections/juniper/device/docs/ansible2rst.py @@ -25,24 +25,19 @@ import datetime import os import re -import sys -from distutils.version import LooseVersion - +import warnings import yaml from jinja2 import Environment, FileSystemLoader from six import print_ -try: - from collections import MutableMapping, MutableSequence, MutableSet -except ImportError: - # Python-3.8 or later - from collections.abc import MutableMapping, MutableSet, MutableSequence +from collections.abc import MutableMapping, MutableSet, MutableSequence from ansible.module_utils._text import to_bytes from ansible.module_utils.six import iteritems, string_types from ansible.parsing.plugin_docs import read_docstring from ansible.parsing.yaml.loader import AnsibleLoader from ansible.plugins.loader import fragment_loader +from ansible.errors import AnsibleError try: from html import escape as html_escape @@ -327,7 +322,7 @@ def process_module(fname, template, outputname, aliases=None): # Error out if there's no description if "description" not in doc["options"][k]: raise AnsibleError( - "Missing required description for option %s in %s " % (k, module) + "Missing required description for option %s in %s " % (k, module_name) ) # Error out if required isn't a boolean (people have been putting @@ -337,7 +332,7 @@ def process_module(fname, template, outputname, aliases=None): if not isinstance(required_value, bool): raise AnsibleError( "Invalid required value '%s' for option '%s' in '%s' (must be truthy)" - % (required_value, k, module) + % (required_value, k, module_name) ) # Strip old version_added information for options @@ -360,7 +355,7 @@ def process_module(fname, template, outputname, aliases=None): if "description" not in doc["connection_options"][k]: raise AnsibleError( "Missing required description for connection_option %s in %s " - % (k, module) + % (k, module_name) ) # Error out if required isn't a boolean (people have been putting @@ -370,7 +365,7 @@ def process_module(fname, template, outputname, aliases=None): if not isinstance(required_value, bool): raise AnsibleError( "Invalid required value '%s' for connection_option '%s' in '%s' (must be truthy)" - % (required_value, k, module) + % (required_value, k, module_name) ) # Strip old version_added information for options @@ -395,7 +390,7 @@ def process_module(fname, template, outputname, aliases=None): if "description" not in doc["logging_options"][k]: raise AnsibleError( "Missing required description for logging_option %s in %s " - % (k, module) + % (k, module_name) ) # Error out if required isn't a boolean (people have been putting @@ -405,7 +400,7 @@ def process_module(fname, template, outputname, aliases=None): if not isinstance(required_value, bool): raise AnsibleError( "Invalid required value '%s' for logging_option '%s' in '%s' (must be truthy)" - % (required_value, k, module) + % (required_value, k, module_name) ) # Strip old version_added information for options @@ -452,7 +447,7 @@ def process_module(fname, template, outputname, aliases=None): doc["author"] = [doc["author"]] # here is where we build the table of contents... - text = template.render(doc) + template.render(doc) # write_data(text, outputname, module_name, OUTPUTDIR) diff --git a/ansible_collections/juniper/device/docs/conf.py b/ansible_collections/juniper/device/docs/conf.py index 906d8a5b..b2155e6f 100644 --- a/ansible_collections/juniper/device/docs/conf.py +++ b/ansible_collections/juniper/device/docs/conf.py @@ -212,11 +212,11 @@ def setup(app): latex_elements = { # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', + # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', + # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. - #'preamble': '', + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples @@ -283,7 +283,7 @@ def setup(app): "Junos Ansible Collection Modules Documentation", "Juniper Networks, Inc.", "JunosAnsibleCollectionModules", - "Ansible Modules for " "Junos", + "Ansible Modules for Junos", "Miscellaneous", ), ] diff --git a/ansible_collections/juniper/device/plugins/action/file_copy.py b/ansible_collections/juniper/device/plugins/action/file_copy.py index 684ef02c..912bdca7 100755 --- a/ansible_collections/juniper/device/plugins/action/file_copy.py +++ b/ansible_collections/juniper/device/plugins/action/file_copy.py @@ -1,3 +1,4 @@ +#!/usr/bin/python # -*- coding: utf-8 -*- # @@ -31,7 +32,6 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -import os from ansible.plugins.action.normal import ActionModule as ActionNormal diff --git a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py index f1bb9d92..7f0e5a0f 100755 --- a/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py +++ b/ansible_collections/juniper/device/plugins/action/juniper_junos_common_action.py @@ -1,3 +1,4 @@ +#!/usr/bin/python # -*- coding: utf-8 -*- # @@ -31,8 +32,6 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -import os - from ansible.plugins.action.normal import ActionModule as ActionNormal from ansible_collections.juniper.device.plugins.action.extract_data import ExtractData diff --git a/ansible_collections/juniper/device/plugins/callback/jsnapy.py b/ansible_collections/juniper/device/plugins/callback/jsnapy.py index c543efd7..b7be564c 100644 --- a/ansible_collections/juniper/device/plugins/callback/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/callback/jsnapy.py @@ -35,15 +35,12 @@ __metaclass__ = type -import collections import json -import os import pprint -import time +from ansible.module_utils.six import iteritems from ansible import constants as C from ansible.plugins.callback import CallbackBase -from six import iteritems class CallbackModule(CallbackBase): @@ -58,8 +55,8 @@ class CallbackModule(CallbackBase): # callback needs to be enabled with config-file to use jsnapy callback during execution CALLBACK_NEEDS_WHITELIST = True - ## useful links regarding Callback - ## https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py + # useful links regarding Callback + # https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/__init__.py def __init__(self): self._pp = pprint.PrettyPrinter(indent=4) @@ -72,28 +69,28 @@ def v2_runner_on_ok(self, result): Collect test results for all tests executed if action is snapcheck or check """ - ## Extract module name + # Extract module name module_args = {} if "invocation" in result._result: if "module_args" in result._result["invocation"]: module_args = result._result["invocation"]["module_args"] - ## Check if dic return has all valid information + # Check if dic return has all valid information if "action" not in module_args: return None if module_args["action"] == "snapcheck" or module_args["action"] == "check": - ## Check if dict entry already exist for this host + # Check if dict entry already exist for this host host = result._host.name - if not host in self._results.keys(): + if host not in self._results.keys(): self._results[host] = [] self._results[host].append(result) def v2_playbook_on_stats(self, stats): - ## Go over all results for all hosts + # Go over all results for all hosts for host, results in iteritems(self._results): has_printed_banner = False for result in results: diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index dc5012e7..73ae716c 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -33,7 +33,12 @@ from __future__ import absolute_import, division, print_function -import xmltodict +try: + import xmltodict + + HAS_XMLTODICT = True +except ImportError: + HAS_XMLTODICT = False __metaclass__ = type @@ -207,10 +212,9 @@ """ import json import logging -import pickle -from ansible.errors import AnsibleConnectionFailure, AnsibleError -from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.errors import AnsibleError +from ansible.module_utils._text import to_bytes from ansible.plugins.connection import NetworkConnectionBase, ensure_connect # Non-standard library imports and checks @@ -266,7 +270,7 @@ HAS_PYEZ_EXCEPTIONS = False try: - from jnpr.jsnapy import SnapAdmin, __version__ + from jnpr.jsnapy import __version__ HAS_JSNAPY_VERSION = __version__ except ImportError: @@ -296,19 +300,9 @@ except ImportError: HAS_YAML_VERSION = None -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str - # import q logging.getLogger("ncclient").setLevel(logging.INFO) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( - to_list, -) # Supported configuration modes CONFIG_MODE_CHOICES = ["exclusive", "private", "dynamic", "batch", "ephemeral"] @@ -895,13 +889,13 @@ def reboot_api(self, all_re, vmhost, member_id=None): msg += " Reboot successfully initiated. " "Reboot message: %s" % got else: raise AnsibleError( - " Did not find expected response " "from reboot RPC. " + " Did not find expected response from reboot RPC. " ) except self.pyez_exception.RpcTimeoutError as ex: try: self.close(raise_exceptions=True) # This means the device wasn't already disconnected. - raise AnsibleError(" Reboot failed. It may not have been " "initiated.") + raise AnsibleError(" Reboot failed. It may not have been initiated.") except ( self.pyez_exception.RpcError, self.pyez_exception.RpcTimeoutError, diff --git a/ansible_collections/juniper/device/plugins/module_utils/configuration.py b/ansible_collections/juniper/device/plugins/module_utils/configuration.py index 6431a481..5bf98e87 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/configuration.py +++ b/ansible_collections/juniper/device/plugins/module_utils/configuration.py @@ -29,10 +29,16 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +from __future__ import absolute_import, division, print_function -import os -from distutils.version import LooseVersion +__metaclass__ = type +try: + from looseversion import LooseVersion + + HAS_LOOSE_VERSION = True +except ImportError: + HAS_LOOSE_VERSION = False # Non-standard library imports and checks try: from jnpr.junos.version import VERSION @@ -51,11 +57,11 @@ HAS_PYEZ_OP_TABLE = False try: - import ncclient.operations.errors as ncclient_exception + import ncclient - HAS_NCCLIENT_EXCEPTIONS = True + HAS_NCCLIENT_VERSION = ncclient.__version__ except ImportError: - HAS_NCCLIENT_EXCEPTIONS = False + HAS_NCCLIENT_VERSION = None try: import jnpr.jsnapy @@ -88,13 +94,6 @@ except ImportError: HAS_YAML_VERSION = None -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str - # Constants # Minimum PyEZ version required by shared code. MIN_PYEZ_VERSION = "2.5.2" @@ -196,8 +195,8 @@ def check_pyez(minimum=None): - PyEZ not installed (unable to import). - PyEZ version < minimum. """ - if HAS_NCCLIENT_EXCEPTIONS is False: - return "ncclient.operations.errors module could not " "be imported." + if HAS_NCCLIENT_VERSION is None: + return "ncclient module could not " "be imported." return _check_library( "junos-eznc", HAS_PYEZ_VERSION, diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index 0ce4b24c..d4651e9b 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + import hashlib import json import logging @@ -40,29 +42,58 @@ # Standard library imports from argparse import ArgumentParser -from distutils.version import LooseVersion -import jnpr -import xmltodict -from ansible.module_utils._text import to_bytes, to_text +try: + import jnpr + + HAS_PYEZ_JNPR = True +except ImportError: + HAS_PYEZ_JNPR = False +try: + import xmltodict + + HAS_XMLTODICT = True +except ImportError: + HAS_XMLTODICT = False +from ansible.module_utils._text import to_bytes from ansible.module_utils.basic import AnsibleModule, boolean from ansible.module_utils.common.validation import check_type_dict +from ansible.module_utils.six import string_types # Ansible imports from ansible.module_utils.connection import Connection -from jnpr.junos import exception as pyez_exception -from jnpr.junos.utils.scp import SCP -from jnpr.junos.utils.sw import SW -from ncclient.operations.errors import TimeoutExpiredError +try: + from jnpr.junos.utils.sw import SW -from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg + HAS_PYEZ_SW = True +except ImportError: + HAS_PYEZ_SW = False +try: + from jnpr.junos.utils.scp import SCP + HAS_PYEZ_SW = True +except ImportError: + HAS_PYEZ_SW = False try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str + from jnpr.junos import exception as pyez_exception + + HAS_PYEZ_EXCEPTION = True +except ImportError: + HAS_PYEZ_EXCEPTION = False +try: + from ncclient.operations.errors import TimeoutExpiredError + + HAS_NCCLIENT = True +except ImportError: + HAS_NCCLIENT = False +try: + import ncclient.operations.errors as ncclient_exception + + HAS_NCCLIENT_EXCEPTIONS = True +except ImportError: + HAS_NCCLIENT_EXCEPTIONS = False + +from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg class ModuleDocFragment(object): @@ -548,8 +579,8 @@ class JuniperJunosModule(AnsibleModule): # Method overrides def __init__( self, - argument_spec={}, - mutually_exclusive=[], + argument_spec=None, + mutually_exclusive=None, min_pyez_version=cfg.MIN_PYEZ_VERSION, min_lxml_etree_version=cfg.MIN_LXML_ETREE_VERSION, min_jsnapy_version=None, @@ -593,6 +624,10 @@ def __init__( A JuniperJunosModule instance object. """ + if mutually_exclusive is None: + mutually_exclusive = [] + if argument_spec is None: + argument_spec = {} # initialize default values here for error scenario while super is called # by default local @@ -636,7 +671,7 @@ def __init__( self.pyez_factory_table = jnpr.junos.factory.table self.pyez_op_table = jnpr.junos.op self.pyez_exception = pyez_exception - self.ncclient_exception = cfg.ncclient_exception + self.ncclient_exception = ncclient_exception self.etree = cfg.etree self.jxmlease = cfg.jxmlease self.yaml = cfg.yaml @@ -716,7 +751,7 @@ def get_connection(self): return self._pyez_connection try: capabilities = self.get_capabilities() - except ConnectionError as exc: + except ConnectionError: self.logger.debug("Connection might be local") return # module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) @@ -841,7 +876,7 @@ def error(self, message): msg="Unable to parse the console value (%s). " "Error: %s" % (console_string, str(ex)) ) - except Exception as ex: + except Exception: self.fail_json( msg="Unable to parse the console value (%s). " "The value of the console argument is " @@ -960,7 +995,7 @@ def parse_arg_to_list_of_dicts( # Evaluate the string kwargs = self.safe_eval(string_val) - if isinstance(kwargs, basestring): + if isinstance(kwargs, string_types): # This might be a keyword1=value1 keyword2=value2 type string. # The _check_type_dict method will parse this into a dict for us. try: @@ -969,7 +1004,7 @@ def parse_arg_to_list_of_dicts( self.fail_json( msg="The value of the %s option (%s) is " "invalid. Unable to translate into " - "a list of dicts." % (option_name, string_val, str(exc)) + "a list of dicts. %s" % (option_name, string_val, str(exc)) ) # Now, if it's a dict, let's make it a list of one dict @@ -985,7 +1020,7 @@ def parse_arg_to_list_of_dicts( return_val = [] for kwarg in kwargs: # If it's now a string, see if it can be parsed into a dictionary. - if isinstance(kwarg, basestring): + if isinstance(kwarg, string_types): # This might be a keyword1=value1 keyword2=value2 type string. # The _check_type_dict method will parse this into a dict. try: @@ -994,13 +1029,13 @@ def parse_arg_to_list_of_dicts( self.fail_json( msg="The value of the %s option (%s) is " "invalid. Unable to translate into a " - "list of dicts." % (option_name, string_val, str(exc)) + "list of dicts %s." % (option_name, string_val, str(exc)) ) # Now if it's not a dict, there's a problem. if not isinstance(kwarg, dict): self.fail_json( msg="The value of the kwargs option (%s) is " - "invalid. Unable to translate into a list " + "%s. Unable to translate into a list " "of dicts." % (option_name, string_val) ) # check if allow_bool_values passed in kwargs @@ -1011,7 +1046,7 @@ def parse_arg_to_list_of_dicts( # is a string or bool. return_item = {} for k, v in kwarg.items(): - if not isinstance(k, basestring): + if not isinstance(k, string_types): self.fail_json( msg="The value of the %s option (%s) " "is invalid. Unable to translate into " @@ -1053,7 +1088,7 @@ def parse_ignore_warning_option(self): if bool_val is not None: return bool_val except TypeError: - if isinstance(ignore_warn_list[0], basestring): + if isinstance(ignore_warn_list[0], string_types): return ignore_warn_list[0] self.fail_json( msg="The value of the ignore_warning option " @@ -1062,7 +1097,7 @@ def parse_ignore_warning_option(self): ) elif len(ignore_warn_list) > 1: for ignore_warn in ignore_warn_list: - if not isinstance(ignore_warn, basestring): + if not isinstance(ignore_warn, string_types): self.fail_json( msg="The value of the ignore_warning " "option (%s) is invalid. " @@ -1095,7 +1130,7 @@ def parse_rollback_option(self): rollback = self.params.get("rollback") if rollback is None or rollback == "rescue": return rollback - if isinstance(rollback, basestring): + if isinstance(rollback, string_types): try: # Is it an int between 0 and 49? int_val = int(rollback) @@ -1185,11 +1220,11 @@ def open_configuration(self, mode, ignore_warning=None, ephemeral_instance=None) # if ignore_warning is a bool, pass the bool # if ignore_warning is a string add to the list # if ignore_warning is a list, merge them - if ignore_warning != None and isinstance(ignore_warning, bool): + if ignore_warning is not None and isinstance(ignore_warning, bool): ignore_warn = ignore_warning - elif ignore_warning != None and isinstance(ignore_warning, str): + elif ignore_warning is not None and isinstance(ignore_warning, str): ignore_warn.append(ignore_warning) - elif ignore_warning != None and isinstance(ignore_warning, list): + elif ignore_warning is not None and isinstance(ignore_warning, list): ignore_warn = ignore_warn + ignore_warning if self.conn_type != "local": @@ -1272,7 +1307,7 @@ def get_configuration( self, database="committed", format="text", - options={}, + options=None, filter=None, model=None, namespace=None, @@ -1314,6 +1349,8 @@ def get_configuration( - Invalid filter. - Format not understood by device. """ + if options is None: + options = {} if database not in CONFIG_DATABASE_CHOICES: self.fail_json( msg="The configuration database % is not in the " @@ -1335,7 +1372,7 @@ def get_configuration( self.open() self.logger.debug( - "Retrieving device configuration. Options: %s " "Filter %s", + "Retrieving device configuration. Options: %s Filter %s", str(options), str(filter), ) @@ -1574,7 +1611,7 @@ def load_configuration( load_args["template_path"] = template load_args["template_vars"] = vars self.logger.debug( - "Loading the configuration from the %s " "template.", template + "Loading the configuration from the %s template.", template ) if url is not None: load_args["url"] = url @@ -1654,7 +1691,7 @@ def commit_configuration( except (self.pyez_exception.RpcError, self.pyez_exception.ConnectError) as ex: self.fail_json(msg="Failure committing the configuraton: %s" % (str(ex))) - def ping(self, params, acceptable_percent_loss=0, results={}): + def ping(self, params, acceptable_percent_loss=0, results=None): """Execute a ping command with the parameters specified in params. Args: @@ -1692,6 +1729,8 @@ def ping(self, params, acceptable_percent_loss=0, results={}): - If the ping RPC produces an exception. - If there are errors present in the results. """ + if results is None: + results = {} # Assume failure until we know success. results["failed"] = True diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index 2932bbff..d376e151 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -288,7 +290,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -415,7 +416,7 @@ def main(): rpc, ignore_warning=ignore_warning, format=format ) result["msg"] = "The command executed successfully." - junos_module.logger.debug('Command "%s" executed successfully.', command) + junos_module.logger.debug("Command %s executed successfully.", command) except ( junos_module.pyez_exception.ConnectError, junos_module.pyez_exception.RpcError, @@ -434,7 +435,7 @@ def main(): parsed_output = None if resp is True: text_output = "" - elif (resp, junos_module.etree._Element): + elif isinstance(resp, junos_module.etree._Element): # Handle the output based on format if format == "text": if resp.tag in ["output", "rpc-reply"]: @@ -447,7 +448,7 @@ def main(): result["msg"] = "Unexpected text response tag: %s." % ((resp.tag)) results.append(result) junos_module.logger.debug( - "Unexpected text response tag " "%s.", resp.tag + "Unexpected text response tag %s.", resp.tag ) continue elif format == "xml": diff --git a/ansible_collections/juniper/device/plugins/modules/config.py b/ansible_collections/juniper/device/plugins/modules/config.py index bdb9306b..8e74a09d 100644 --- a/ansible_collections/juniper/device/plugins/modules/config.py +++ b/ansible_collections/juniper/device/plugins/modules/config.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -470,7 +472,8 @@ description: - Additional options, specified as a dictionary of key/value pairs, used when retrieving the configuration. See the - U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) + U( RPC documentation| + https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-get-configuration.html) for information on available options. required: false default: None @@ -556,7 +559,8 @@ this URL must be reachable by the target Junos device. - The possible formats of this value are documented in the 'url' section of the - U( RPC documentation|https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). + U( RPC documentation| + https://www.juniper.net/documentation/en_US/junos/topics/reference/tag-summary/junos-xml-protocol-load-configuration.html). - The I(src), I(lines), I(template), and I(url) options are mutually exclusive. required: false @@ -823,7 +827,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -1099,7 +1102,7 @@ def main(): # Initialize the results. Assume failure until we know it's success. results = {"msg": "Configuration has been: ", "changed": False, "failed": True} - junos_module.logger.debug("Step 1 - Open a candidate configuration " "database.") + junos_module.logger.debug("Step 1 - Open a candidate configuration database.") junos_module.open_configuration( mode=config_mode, ignore_warning=ignore_warning, @@ -1108,7 +1111,7 @@ def main(): results["msg"] += "opened" junos_module.logger.debug( - "Step 2 - Load configuration data into the " "candidate configuration database." + "Step 2 - Load configuration data into the candidate configuration database." ) if rollback is not None: junos_module.rollback_configuration(id=rollback) @@ -1150,7 +1153,7 @@ def main(): results["msg"] += ", loaded" junos_module.logger.debug( - "Step 3 - Check the validity of the candidate " "configuration database." + "Step 3 - Check the validity of the candidate configuration database." ) if check is True: junos_module.check_configuration() @@ -1175,7 +1178,7 @@ def main(): results["msg"] += ", diffed" junos_module.logger.debug( - "Step 5 - Retrieve the configuration database " "from the Junos device." + "Step 5 - Retrieve the configuration database from the Junos device." ) if retrieve is not None: if format is None: @@ -1229,7 +1232,7 @@ def main(): else: junos_module.logger.debug("Skipping commit. Nothing changed.") - junos_module.logger.debug("Step 7 - Close the candidate configuration " "database.") + junos_module.logger.debug("Step 7 - Close the candidate configuration database.") junos_module.close_configuration() results["msg"] += ", closed." diff --git a/ansible_collections/juniper/device/plugins/modules/facts.py b/ansible_collections/juniper/device/plugins/modules/facts.py index bc750b5a..fec4df04 100644 --- a/ansible_collections/juniper/device/plugins/modules/facts.py +++ b/ansible_collections/juniper/device/plugins/modules/facts.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -189,7 +191,6 @@ from ansible.module_utils._text import to_bytes # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common diff --git a/ansible_collections/juniper/device/plugins/modules/file_copy.py b/ansible_collections/juniper/device/plugins/modules/file_copy.py index a0884ad2..7cdc6872 100644 --- a/ansible_collections/juniper/device/plugins/modules/file_copy.py +++ b/ansible_collections/juniper/device/plugins/modules/file_copy.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -108,7 +110,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -136,7 +137,7 @@ def main(): # We're going to be using params a lot params = junos_module.params - remote_path = params["remote_dir"] + params["remote_dir"] local_file = params["local_dir"] + "/" + params["file"] remote_file = params["remote_dir"] + "/" + params["file"] diff --git a/ansible_collections/juniper/device/plugins/modules/jsnapy.py b/ansible_collections/juniper/device/plugins/modules/jsnapy.py index 78f440b0..1a66ba20 100644 --- a/ansible_collections/juniper/device/plugins/modules/jsnapy.py +++ b/ansible_collections/juniper/device/plugins/modules/jsnapy.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -203,7 +205,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -409,7 +410,7 @@ def main(): if ("test_name" in data1.keys()) and ( "result" in data1.keys() ): - if data1["result"] == False: + if data1["result"] is False: test_name = ( str(data1["test_name"]) + "_" @@ -427,7 +428,7 @@ def main(): if ("test_name" in data.keys()) and ( "result" in data.keys() ): - if data["result"] == False: + if data["result"] is False: test_name = ( str(data["test_name"]) + "_" diff --git a/ansible_collections/juniper/device/plugins/modules/ping.py b/ansible_collections/juniper/device/plugins/modules/ping.py index b155e727..da4688e0 100644 --- a/ansible_collections/juniper/device/plugins/modules/ping.py +++ b/ansible_collections/juniper/device/plugins/modules/ping.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -381,7 +383,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common diff --git a/ansible_collections/juniper/device/plugins/modules/pmtud.py b/ansible_collections/juniper/device/plugins/modules/pmtud.py index 9a000522..c4110b25 100644 --- a/ansible_collections/juniper/device/plugins/modules/pmtud.py +++ b/ansible_collections/juniper/device/plugins/modules/pmtud.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -243,7 +245,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common diff --git a/ansible_collections/juniper/device/plugins/modules/rpc.py b/ansible_collections/juniper/device/plugins/modules/rpc.py index d5750d4e..dd32f828 100644 --- a/ansible_collections/juniper/device/plugins/modules/rpc.py +++ b/ansible_collections/juniper/device/plugins/modules/rpc.py @@ -32,7 +32,9 @@ from __future__ import absolute_import, division, print_function -from six import iteritems +__metaclass__ = type + +from ansible.module_utils.six import iteritems ANSIBLE_METADATA = { "metadata_version": "1.1", @@ -350,22 +352,12 @@ type: list of str """ -import os.path - -try: - # Python 2 - basestring -except NameError: - # Python 3 - basestring = str - """From Ansible 2.1, Ansible uses Ansiballz framework for assembling modules But custom module_utils directory is supported from Ansible 2.3 Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -531,7 +523,7 @@ def main(): ) result["msg"] = 'The "get-config" RPC executed successfully.' junos_module.logger.debug( - 'The "get-config" RPC executed ' "successfully." + "The 'get-config' RPC executed successfully." ) else: if kwarg is not None: @@ -589,7 +581,7 @@ def main(): parsed_output = None if resp is True: text_output = "" - elif (resp, junos_module.etree._Element): + elif isinstance(resp, junos_module.etree._Element): # Handle the output based on format if format == "text": text_output = resp.text diff --git a/ansible_collections/juniper/device/plugins/modules/software.py b/ansible_collections/juniper/device/plugins/modules/software.py index 0b62057b..679adbc0 100644 --- a/ansible_collections/juniper/device/plugins/modules/software.py +++ b/ansible_collections/juniper/device/plugins/modules/software.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -397,7 +399,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -438,19 +439,17 @@ def parse_version_from_filename(filename): match = re.search(r"-(\d{2}\..*\d).*", filename) if match is not None: return match.group(1) - # Not a known Junos package name. - else: - return None + return None def define_progress_callback(junos_module): """Create callback which can be passed to SW.install(progress=progress)""" - def myprogress(_, report): + def myprogress(_0, report): """A progress function which logs report at level INFO. Args: - _: The PyEZ device object. Unused because the logger already knows. + _0: The PyEZ device object. Unused because the logger already knows. report: The string to be logged. """ junos_module.logger.info(report) @@ -545,7 +544,7 @@ def main(): (remote_dir, remote_filename) = os.path.split(remote_package) else: url = remote_package - (_, remote_filename) = os.path.split(parsed_url.path) + (_0, remote_filename) = os.path.split(parsed_url.path) else: # Default remote_dir value remote_dir = "/var/tmp" @@ -636,7 +635,7 @@ def main(): current_version = junos_info[current_re]["text"] if target_version != current_version: junos_module.logger.debug( - "Current version on %s: %s. " "Target version: %s.", + "Current version on %s: %s. Target version: %s.", current_version, current_re, target_version, @@ -655,7 +654,7 @@ def main(): re_name = junos_module._pyez_conn.get_re_name() if target_version != current_version: junos_module.logger.debug( - "Current version on %s: %s. " "Target version: %s.", + "Current version on %s: %s. Target version: %s.", current_version, re_name, target_version, @@ -774,7 +773,7 @@ def main(): restore_timeout = junos_module.dev.timeout if junos_module.dev.timeout > 5: junos_module.logger.debug( - "Decreasing device RPC timeout " "to 5 seconds." + "Decreasing device RPC timeout to 5 seconds." ) junos_module.dev.timeout = 5 try: diff --git a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py index a0759c2e..d66e31ea 100644 --- a/ansible_collections/juniper/device/plugins/modules/srx_cluster.py +++ b/ansible_collections/juniper/device/plugins/modules/srx_cluster.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -144,7 +146,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -209,12 +210,12 @@ def main(): current_node_name = junos_module._pyez_conn.get_re_name() current_node_id = None if current_node_name is not None: - (_, _, current_node_id) = current_node_name.partition("node") + (_0, _1, current_node_id) = current_node_name.partition("node") if current_node_id: current_node_id = int(current_node_id) junos_module.logger.debug( - "Current SRX cluster operational state: %s, cluster_id: %s, " "node_id: %s", - "enabled" if current_cluster_state else "disabled", + "Current SRX cluster operational state: %s, cluster_id: %s, node_id: %s", + "enabled if current_cluster_state else disabled", str(current_cluster_id), str(current_node_id), ) @@ -246,7 +247,7 @@ def main(): # Is a node ID change needed? if enable is True and current_node_id is not None and current_node_id != node_id: junos_module.logger.debug( - "SRX node ID change needed. Current node ID: %d. " "Desired cluster ID: %d", + "SRX node ID change needed. Current node ID: %d. Desired cluster ID: %d", current_node_id, node_id, ) @@ -289,7 +290,7 @@ def main(): else: try: resp = junos_module._pyez_conn.set_chassis_cluster_disable() - except Exception as err: + except Exception: # Reboot initiated # We got Exception ConnectionError # so handling the exception diff --git a/ansible_collections/juniper/device/plugins/modules/system.py b/ansible_collections/juniper/device/plugins/modules/system.py index af338cf0..32bba3b6 100644 --- a/ansible_collections/juniper/device/plugins/modules/system.py +++ b/ansible_collections/juniper/device/plugins/modules/system.py @@ -33,6 +33,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -251,7 +253,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -313,7 +314,7 @@ def main(): # if action is zeroize, at, in_min, other_re shouldn't be set if action == "zeroize": # at, in_min and other_re option only applies to reboot, shutdown, or halt action. - if (at != None) or (in_min != 0) or (other_re == True): + if (at is not None) or (in_min != 0) or (other_re is True): junos_module.fail_json( msg="The options at, in_min and other_re can only be used when " 'the action option has the value "zeroize"' @@ -366,7 +367,7 @@ def main(): if at == "now" or (in_min == 0 and at is None): if junos_module.dev.timeout > 5: junos_module.logger.debug( - "Decreasing device RPC timeout " "to 5 seconds." + "Decreasing device RPC timeout to 5 seconds." ) junos_module.dev.timeout = 5 diff --git a/ansible_collections/juniper/device/plugins/modules/table.py b/ansible_collections/juniper/device/plugins/modules/table.py index e3988e87..52b82169 100644 --- a/ansible_collections/juniper/device/plugins/modules/table.py +++ b/ansible_collections/juniper/device/plugins/modules/table.py @@ -36,6 +36,8 @@ from __future__ import absolute_import, division, print_function +__metaclass__ = type + ANSIBLE_METADATA = { "metadata_version": "1.1", "supported_by": "community", @@ -288,7 +290,6 @@ Reference for the issue: https://groups.google.com/forum/#!topic/ansible-project/J8FL7Z1J1Mw """ # Ansiballz packages module_utils into ansible.module_utils -from ansible.module_utils.basic import AnsibleModule from ansible_collections.juniper.device.plugins.module_utils import configuration as cfg from ansible_collections.juniper.device.plugins.module_utils import juniper_junos_common @@ -385,7 +386,7 @@ def main(): with open(file_name, "r") as fp: try: junos_module.logger.debug( - "Attempting to parse YAML from : " "%s.", file_name + "Attempting to parse YAML from : %s.", file_name ) table_view = junos_module.yaml.safe_load(fp) junos_module.logger.debug( diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index bf9f997b..baae81f0 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,2 +1,5 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + VERSION = "v1.0.6" DATE = "2024-Aug-5" From 5693e84cea3d1dd2972656996fd29c463fa9fc54 Mon Sep 17 00:00:00 2001 From: CHIDANAND PUJAR <46497833+chidanandpujar@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:19:17 +0530 Subject: [PATCH 423/426] ansible v1.0.7 release update (#702) * ansible v1.0.7 release update * ansible v1.0.7 release update --- ansible_collections/juniper/device/galaxy.yml | 2 +- ansible_collections/juniper/device/version.py | 4 ++-- requirements.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ansible_collections/juniper/device/galaxy.yml b/ansible_collections/juniper/device/galaxy.yml index c9e043a8..12004f75 100644 --- a/ansible_collections/juniper/device/galaxy.yml +++ b/ansible_collections/juniper/device/galaxy.yml @@ -9,7 +9,7 @@ namespace: juniper name: device # The version of the collection. Must be compatible with semantic versioning -version: 1.0.6 +version: 1.0.7 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/ansible_collections/juniper/device/version.py b/ansible_collections/juniper/device/version.py index baae81f0..4791496c 100755 --- a/ansible_collections/juniper/device/version.py +++ b/ansible_collections/juniper/device/version.py @@ -1,5 +1,5 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -VERSION = "v1.0.6" -DATE = "2024-Aug-5" +VERSION = "v1.0.7" +DATE = "2024-Dec-5" diff --git a/requirements.txt b/requirements.txt index 0052a0d7..314f7eb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ junos-eznc >= 2.6.0 jsnapy>=1.3.6 jxmlease xmltodict +looseversion From f290e11323258080c13aedb524af559399a315f6 Mon Sep 17 00:00:00 2001 From: CHIDANAND PUJAR <46497833+chidanandpujar@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:39:06 +0530 Subject: [PATCH 424/426] Fix for JSON response handle (#703) --- .../juniper/device/plugins/connection/pyez.py | 5 ++++- .../device/plugins/module_utils/juniper_junos_common.py | 5 ++++- .../juniper/device/plugins/modules/command.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ansible_collections/juniper/device/plugins/connection/pyez.py b/ansible_collections/juniper/device/plugins/connection/pyez.py index 73ae716c..3680c6a0 100644 --- a/ansible_collections/juniper/device/plugins/connection/pyez.py +++ b/ansible_collections/juniper/device/plugins/connection/pyez.py @@ -462,7 +462,10 @@ def get_config( resp = self.dev.rpc.get_config( filter_xml, options, model, namespace, remove_ns, **kwarg ) - return etree.tostring(resp) + if options['format'] == 'json': + return rsp + else: + return etree.tostring(resp) def get_rpc_resp(self, rpc, ignore_warning, format): """Execute rpc on the device and get response. diff --git a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py index d4651e9b..ca3282bb 100644 --- a/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py +++ b/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py @@ -1912,7 +1912,10 @@ def get_config( response = self._pyez_conn.get_config( filter_xml, options, model, namespace, remove_ns, **kwarg ) - return self.etree.fromstring(response) + if options['format'] == 'json': + return response + else: + return self.etree.fromstring(response) def get_rpc(self, rpc, ignore_warning=None, format=None): rpc_1 = self.etree.tostring(rpc) diff --git a/ansible_collections/juniper/device/plugins/modules/command.py b/ansible_collections/juniper/device/plugins/modules/command.py index d376e151..454eec2c 100644 --- a/ansible_collections/juniper/device/plugins/modules/command.py +++ b/ansible_collections/juniper/device/plugins/modules/command.py @@ -435,7 +435,7 @@ def main(): parsed_output = None if resp is True: text_output = "" - elif isinstance(resp, junos_module.etree._Element): + elif (isinstance(resp, junos_module.etree._Element)) or (isinstance(resp, dict)): # Handle the output based on format if format == "text": if resp.tag in ["output", "rpc-reply"]: From 454f137681df7a5960cc4ef5c71b455077b29005 Mon Sep 17 00:00:00 2001 From: CHIDANAND PUJAR <46497833+chidanandpujar@users.noreply.github.com> Date: Wed, 18 Dec 2024 20:13:36 +0530 Subject: [PATCH 425/426] Dockerfile update (#707) * Dockerfile update * Dockerfile update * Dockerfile update * Dockerfile update * Dockerfile update --- DOCKER-EXAMPLES.md | 78 ++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 39 +++++++++++----------- README.md | 80 ---------------------------------------------- entrypoint.sh | 10 +++--- 4 files changed, 104 insertions(+), 103 deletions(-) create mode 100644 DOCKER-EXAMPLES.md diff --git a/DOCKER-EXAMPLES.md b/DOCKER-EXAMPLES.md new file mode 100644 index 00000000..ea927c87 --- /dev/null +++ b/DOCKER-EXAMPLES.md @@ -0,0 +1,78 @@ +# Examples of using the Docker image + +To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. + + docker run -it --rm juniper/pyez-ansible + +Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. + + docker run -it --rm -v $PWD:/project juniper/pyez-ansible + +You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: + + example + |playbook.yml + |hosts + |-vars + |-templates + |-scripts + +We can move to the example directory and run the playbook with the following command: + + cd example/ + docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml + +You can pass any valid command string after the container name and it will be passed to Bash for execution. + +You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. + + alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" + pb-ansible -i hosts playbook.yml + +### Extending the container with additional packages + +It's possible to install additional OS (Alpine) packages, Python packages (via pip), and Ansible collections at container instantiation. This can be done by passing in environment variables or binding mount files. + +#### OS Packages + +Environment Variable: `$APK` +Bind Mount: `/extras/apk.txt` +File Format: list of valid Alpine packages, one per line +Examples: + +As an environment variable, where the file containing a list of packages is in the current directory. + + docker run -it --rm -v $PWD:/project -e APK="apk.txt" juniper/pyez-ansible + +As a bind mount. + + docker run -it --rm -v $PWD/apk.txt:/extras/apk.txt juniper/pyez-ansible + +#### Python Packages + +Environment Variable: `$REQ` +Bind Mount: `/extras/requirements.txt` +File Format: pip [requirements](https://pip.pypa.io/en/stable/reference/requirements-file-format/) file + +Examples: + + docker run -it --rm -v $PWD:/project -e REQ="requirements.txt" juniper/pyez-ansible + +As a bind mount. + + docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.txt juniper/pyez-ansible + +#### Ansible Packages + +Environment Variable: `$COLLECTIONS` +Bind Mount: `/extras/requirements.yml` +File Format: Ansible [requirements](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html#install-multiple-collections-with-a-requirements-file) file + + +Examples: + + docker run -it --rm -v $PWD:/project -e REQ="requirements.yml" juniper/pyez-ansible + +As a bind mount. + + docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.yml juniper/pyez-ansible diff --git a/Dockerfile b/Dockerfile index e78a4023..cce6b48d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,35 @@ -FROM juniper/pyez:2.5.3 +FROM python:3.12-alpine LABEL net.juniper.image.maintainer="Juniper Networks " \ net.juniper.image.description="Lightweight image with Ansible and the Junos roles" -RUN apk add --no-cache build-base python3-dev py3-pip \ - openssl-dev curl ca-certificates bash openssh-client - WORKDIR /tmp -COPY requirements.txt . -RUN pip3 install -r requirements.txt -RUN apk del -r --purge gcc make g++ &&\ - rm -rf /source/* &&\ - rm -rf /var/cache/apk/* &&\ - rm -rf /tmp/* +## Copy project inside the containers +ADD requirements.txt . +ADD entrypoint.sh /usr/local/bin/. + +## Install dependencies and PyEZ +RUN apk add --no-cache build-base python3-dev py3-pip \ + libxslt-dev libxml2-dev libffi-dev openssl-dev curl \ + ca-certificates py3-pip bash openssh-client -WORKDIR /usr/share/ansible/collections/ansible_collections/ -COPY ansible_collections/ . +RUN pip install --upgrade pip \ + && python3 -m pip install -r requirements.txt -WORKDIR /usr/bin -COPY entrypoint.sh . -RUN chmod +x entrypoint.sh +# Also install the collections juniper.device +# Install Ansible modules in one layer +RUN ansible-galaxy collection install juniper.device -# Also install the roles, until collections is ready for prime-time -RUN ansible-galaxy role install Juniper.junos,2.4.3 +## Clean up and start init +RUN apk del -r --purge gcc make g++ \ + && rm -rf /var/cache/apk/* \ + && rm -rf /tmp/* \ + && rm -rf /root/.cache \ + && chmod +x /usr/local/bin/entrypoint.sh WORKDIR /project VOLUME /project -ENTRYPOINT ["/usr/bin/entrypoint.sh"] +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/README.md b/README.md index 88360e98..55c1479f 100644 --- a/README.md +++ b/README.md @@ -120,86 +120,6 @@ This will set your `$ANSIBLE_LIBRARY` variable to the repo location and the inst $ echo $ANSIBLE_LIBRARY /home/jeremy/Ansible/ansible-junos-stdlib/library:/usr/share/ansible -### Docker - -To run this as a Docker container, which includes JSNAPy and PyEZ, simply pull it from the Docker hub and run it. The following will pull the latest image and run it in an interactive ash shell. - - docker run -it --rm juniper/pyez-ansible - -Although, you'll probably want to bind mount a host directory (perhaps the directory containing your playbooks and associated files). The following will bind mount the current working directory and start the ash shell. - - docker run -it --rm -v $PWD:/project juniper/pyez-ansible - -You can also use the container as an executable to run your playbooks. Let's assume we have a typical playbook structure as below: - - example - |playbook.yml - |hosts - |-vars - |-templates - |-scripts - -We can move to the example directory and run the playbook with the following command: - - cd example/ - docker run -it --rm -v $PWD:/playbooks juniper/pyez-ansible ansible-playbook -i hosts playbook.yml - -You can pass any valid command string after the container name and it will be passed to Bash for execution. - -You may have noticed that the base command is almost always the same. We can also use an alias to save some keystrokes. - - alias pb-ansible="docker run -it --rm -v $PWD:/project juniper/pyez-ansible ansible-playbook" - pb-ansible -i hosts playbook.yml - -### Extending the container with additional packages - -It's possible to install additional OS (Alpine) packages, Python packages (via pip), and Ansible collections at container instantiation. This can be done by passing in environment variables or binding mount files. - -#### OS Packages - -Environment Variable: `$APK` -Bind Mount: `/extras/apk.txt` -File Format: list of valid Alpine packages, one per line -Examples: - -As an environment variable, where the file containing a list of packages is in the current directory. - - docker run -it --rm -v $PWD:/project -e APK="apk.txt" juniper/pyez-ansible - -As a bind mount. - - docker run -it --rm -v $PWD/apk.txt:/extras/apk.txt juniper/pyez-ansible - -#### Python Packages - -Environment Variable: `$REQ` -Bind Mount: `/extras/requirements.txt` -File Format: pip [requirements](https://pip.pypa.io/en/stable/reference/requirements-file-format/) file - -Examples: - - docker run -it --rm -v $PWD:/project -e REQ="requirements.txt" juniper/pyez-ansible - -As a bind mount. - - docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.txt juniper/pyez-ansible - -#### Ansible Packages - -Environment Variable: `$ROLES` -Bind Mount: `/extras/requirements.yml` -File Format: Ansible [requirements](https://docs.ansible.com/ansible/devel/user_guide/collections_using.html#install-multiple-collections-with-a-requirements-file) file - -_NOTE:_ This works for collections as well as roles. - -Examples: - - docker run -it --rm -v $PWD:/project -e REQ="requirements.yml" juniper/pyez-ansible - -As a bind mount. - - docker run -it --rm -v $PWD/requirements.txt:/extras/requirements.yml juniper/pyez-ansible - ## Example Playbook This example outlines how to use Ansible to install or upgrade the software image on a device running Junos OS. diff --git a/entrypoint.sh b/entrypoint.sh index 1fc1d0c4..87be8428 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -18,7 +18,7 @@ function pip_install { } function galaxy_install { - echo "Install Ansible roles" + echo "Install Ansible Collections" ansible-galaxy install -r "$1" } @@ -38,9 +38,9 @@ elif [ -f "/extras/requirements.txt" ];then REQ="/extras/requirements.txt" else REQ='' fi -if [ "$ROLES" ]; then ROLES=$ROLES -elif [ -f "/extras/requirements.yml" ]; then ROLES="/extras/requirements.yml" -else ROLES='' +if [ "$COLLECTIONS" ]; then COLLECTIONS=$COLLECTIONS +elif [ -f "/extras/requirements.yml" ]; then COLLECTIONS="/extras/requirements.yml" +else COLLECTIONS='' fi @@ -48,7 +48,7 @@ fi [[ -z "$REQ" ]] || pip_install "$REQ" -[[ -z "$ROLES" ]] || galaxy_install "$ROLES" +[[ -z "$COLLECTIONS" ]] || galaxy_install "$COLLECTIONS" if [ -z "$1" ] then From dc5e659942a03039d52162d3a95608d1ac032cc9 Mon Sep 17 00:00:00 2001 From: CHIDANAND PUJAR <46497833+chidanandpujar@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:32:55 +0530 Subject: [PATCH 426/426] release v1.0.7 update (#708) --- tests/pb.juniper_junos_config.yml | 3 ++- version.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/pb.juniper_junos_config.yml b/tests/pb.juniper_junos_config.yml index ed80da34..a7021f54 100644 --- a/tests/pb.juniper_junos_config.yml +++ b/tests/pb.juniper_junos_config.yml @@ -52,7 +52,8 @@ - name: Save rescue configuration juniper.device.command: commands: "request system configuration rescue save" - + formats: + - "xml" - name: Configure syslog configuration juniper.device.config: diff --git a/version.py b/version.py index bf9f997b..71d704f6 100755 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = "v1.0.6" -DATE = "2024-Aug-5" +VERSION = "v1.0.7" +DATE = "2024-Dec-18"