diff options
Diffstat (limited to 'lib/ansible/modules/network/edgeos')
-rw-r--r-- | lib/ansible/modules/network/edgeos/edgeos_command.py | 177 | ||||
-rw-r--r-- | lib/ansible/modules/network/edgeos/edgeos_config.py | 319 | ||||
-rw-r--r-- | lib/ansible/modules/network/edgeos/edgeos_facts.py | 311 |
3 files changed, 0 insertions, 807 deletions
diff --git a/lib/ansible/modules/network/edgeos/edgeos_command.py b/lib/ansible/modules/network/edgeos/edgeos_command.py deleted file mode 100644 index a6c9256d75..0000000000 --- a/lib/ansible/modules/network/edgeos/edgeos_command.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/python - -# Copyright: (c) 2018, 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) -__metaclass__ = type - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = """ -module: edgeos_command -version_added: "2.5" -author: - - Chad Norgan (@beardymcbeards) - - Sam Doran (@samdoran) -short_description: Run one or more commands on EdgeOS devices -description: - - This command module allows running one or more commands on a remote - device running EdgeOS, such as the Ubiquiti EdgeRouter. - - This module does not support running commands in configuration mode. - - Certain C(show) commands in EdgeOS produce many lines of output and - use a custom pager that can cause this module to hang. If the - value of the environment variable C(ANSIBLE_EDGEOS_TERMINAL_LENGTH) - is not set, the default number of 10000 is used. - - "This is a network module and requires C(connection: network_cli) - in order to work properly." - - For more information please see the L(Network Guide,../network/getting_started/index.html). -options: - commands: - description: - - The commands or ordered set of commands that should be run against the - remote device. The output of the command is returned to the playbook. - If the C(wait_for) argument is provided, the module is not returned - until the condition is met or the number of retries is exceeded. - required: True - wait_for: - description: - - Causes the task to wait for a specific condition to be met before - moving forward. If the condition is not met before the specified - number of retries is exceeded, the task will fail. - required: False - match: - description: - - Used in conjunction with C(wait_for) to create match policy. If set to - C(all), then all conditions in C(wait_for) must be met. If set to - C(any), then only one condition must match. - required: False - default: 'all' - choices: ['any', 'all'] - retries: - description: - - Number of times a command should be tried before it is considered failed. - The command is run on the target device and evaluated against the - C(wait_for) conditionals. - required: False - default: 10 - interval: - description: - - The number of seconds to wait between C(retries) of the command. - required: False - default: 1 - -notes: - - Tested against EdgeOS 1.9.7 - - Running C(show system boot-messages all) will cause the module to hang since - EdgeOS is using a custom pager setting to display the output of that command. -""" - -EXAMPLES = """ -tasks: - - name: Reboot the device - edgeos_command: - commands: reboot now - - - name: Show the configuration for eth0 and eth1 - edgeos_command: - commands: show interfaces ethernet {{ item }} - loop: - - eth0 - - eth1 -""" - -RETURN = """ -stdout: - description: The set of responses from the commands - returned: always apart from low level errors (such as action plugin) - type: list - sample: ['...', '...'] -stdout_lines: - description: The value of stdout split into a list - returned: always - type: list - sample: [['...', '...'], ['...'], ['...']] -""" -import time - -from ansible.module_utils._text import to_text -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.common.parsing import Conditional -from ansible.module_utils.network.common.utils import transform_commands, to_lines -from ansible.module_utils.network.edgeos.edgeos import run_commands - - -def parse_commands(module, warnings): - commands = transform_commands(module) - - if module.check_mode: - for item in list(commands): - if not item['command'].startswith('show'): - warnings.append( - 'Only show commands are supported when using check mode, not ' - 'executing %s' % item['command'] - ) - commands.remove(item) - - return commands - - -def main(): - spec = dict( - commands=dict(type='list', required=True), - wait_for=dict(type='list'), - match=dict(default='all', choices=['all', 'any']), - retries=dict(default=10, type='int'), - interval=dict(default=1, type='int') - ) - - module = AnsibleModule(argument_spec=spec, supports_check_mode=True) - - warnings = list() - result = {'changed': False, 'warnings': warnings} - commands = parse_commands(module, warnings) - wait_for = module.params['wait_for'] or list() - - try: - conditionals = [Conditional(c) for c in wait_for] - except AttributeError as exc: - module.fail_json(msg=to_text(exc)) - - retries = module.params['retries'] - interval = module.params['interval'] - match = module.params['match'] - - while retries > 0: - responses = run_commands(module, commands) - - for item in list(conditionals): - if item(responses): - if match == 'any': - conditionals = list() - break - conditionals.remove(item) - - if not conditionals: - break - - time.sleep(interval) - retries -= 1 - - if conditionals: - failed_conditions = [item.raw for item in conditionals] - msg = 'One or more conditional statements have not been satisfied' - module.fail_json(msg=msg, failed_conditions=failed_conditions) - - result.update({ - 'stdout': responses, - 'stdout_lines': list(to_lines(responses)), - }) - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/edgeos/edgeos_config.py b/lib/ansible/modules/network/edgeos/edgeos_config.py deleted file mode 100644 index 84f016a728..0000000000 --- a/lib/ansible/modules/network/edgeos/edgeos_config.py +++ /dev/null @@ -1,319 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright (c) 2018 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 -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = """ ---- -module: edgeos_config -version_added: "2.5" -author: - - "Nathaniel Case (@Qalthos)" - - "Sam Doran (@samdoran)" -short_description: Manage EdgeOS configuration on remote device -description: - - This module provides configuration file management of EdgeOS - devices. It provides arguments for managing both the - configuration file and state of the active configuration. All - configuration statements are based on `set` and `delete` commands - in the device configuration. - - "This is a network module and requires the C(connection: network_cli) in order - to work properly." - - For more information please see the L(Network Guide,../network/getting_started/index.html). -notes: - - Tested against EdgeOS 1.9.7 - - Setting C(ANSIBLE_PERSISTENT_COMMAND_TIMEOUT) to 30 is recommended since - the save command can take longer than the default of 10 seconds on - some EdgeOS hardware. -options: - lines: - description: - - The ordered set of configuration lines to be managed and - compared with the existing configuration on the remote - device. - src: - description: - - The C(src) argument specifies the path to the source config - file to load. The source config file can either be in - bracket format or set format. The source file can include - Jinja2 template variables. - match: - description: - - The C(match) argument controls the method used to match - against the current active configuration. By default, the - desired config is matched against the active config and the - deltas are loaded. If the C(match) argument is set to C(none) - the active configuration is ignored and the configuration is - always loaded. - default: line - choices: ['line', 'none'] - backup: - description: - - The C(backup) argument will backup the current device's active - configuration to the Ansible control host prior to making any - changes. If the C(backup_options) value is not given, the backup - file will be located in the backup folder in the playbook root - directory or role root directory if the playbook is part of an - ansible role. If the directory does not exist, it is created. - type: bool - default: 'no' - comment: - description: - - Allows a commit description to be specified to be included - when the configuration is committed. If the configuration is - not changed or committed, this argument is ignored. - default: 'configured by edgeos_config' - config: - description: - - The C(config) argument specifies the base configuration to use - to compare against the desired configuration. If this value - is not specified, the module will automatically retrieve the - current active configuration from the remote device. - save: - description: - - The C(save) argument controls whether or not changes made - to the active configuration are saved to disk. This is - independent of committing the config. When set to C(True), the - active configuration is saved. - type: bool - default: 'no' - backup_options: - description: - - This is a dict object containing configurable options related to backup file path. - The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set - to I(no) this option will be silently ignored. - suboptions: - filename: - description: - - The filename to be used to store the backup configuration. If the filename - is not given it will be generated based on the hostname, current time and date - in format defined by <hostname>_config.<current-date>@<current-time> - dir_path: - description: - - This option provides the path ending with directory name in which the backup - configuration file will be stored. If the directory does not exist it will be first - created and the filename is either the value of C(filename) or default filename - as described in C(filename) options description. If the path value is not given - in that case a I(backup) directory will be created in the current working directory - and backup configuration will be copied in C(filename) within I(backup) directory. - type: path - type: dict - version_added: "2.8" -""" - -EXAMPLES = """ -- name: configure the remote device - edgeos_config: - lines: - - set system host-name {{ inventory_hostname }} - - set service lldp - - delete service dhcp-server - -- name: backup and load from file - edgeos_config: - src: edgeos.cfg - backup: yes - -- name: configurable backup path - edgeos_config: - src: edgeos.cfg - backup: yes - backup_options: - filename: backup.cfg - dir_path: /home/user -""" - -RETURN = """ -commands: - description: The list of configuration commands sent to the device - returned: always - type: list - sample: ['...', '...'] -backup_path: - description: The full path to the backup file - returned: when backup is yes - type: str - sample: /playbooks/ansible/backup/edgeos_config.2016-07-16@22:28:34 -""" - -import re - -from ansible.module_utils._text import to_native -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.network.common.config import NetworkConfig -from ansible.module_utils.network.edgeos.edgeos import load_config, get_config, run_commands - - -DEFAULT_COMMENT = 'configured by edgeos_config' - - -def config_to_commands(config): - set_format = config.startswith('set') or config.startswith('delete') - candidate = NetworkConfig(indent=4, contents=config) - if not set_format: - candidate = [c.line for c in candidate.items] - commands = list() - # this filters out less specific lines - for item in candidate: - for index, entry in enumerate(commands): - if item.startswith(entry): - del commands[index] - break - commands.append(item) - - commands = ['set %s' % cmd.replace(' {', '') for cmd in commands] - - else: - commands = to_native(candidate).split('\n') - - return commands - - -def get_candidate(module): - contents = module.params['src'] or module.params['lines'] - - if module.params['lines']: - contents = '\n'.join(contents) - - return config_to_commands(contents) - - -def check_command(module, command): - """Tests against a command line to be valid otherwise raise errors - - Error on uneven single quote which breaks ansible waiting for further input. Ansible - will handle even single quote failures correctly. - - :param command: the command line from current or new config - :type command: string - :raises ValueError: - * if contains odd number of single quotes - :return: command string unchanged - :rtype: string - """ - if command.count("'") % 2 != 0: - module.fail_json(msg="Unmatched single (') quote found in command: " + command) - - return command - - -def diff_config(module, commands, config): - config = [to_native(check_command(module, c)) for c in config.splitlines()] - - updates = list() - visited = set() - delete_commands = [line for line in commands if line.startswith('delete')] - - for line in commands: - item = to_native(check_command(module, line)) - - if not item.startswith('set') and not item.startswith('delete'): - raise ValueError('line must start with either `set` or `delete`') - - elif item.startswith('set'): - - if item not in config: - updates.append(line) - - # If there is a corresponding delete command in the desired config, make sure to append - # the set command even though it already exists in the running config - else: - ditem = re.sub('set', 'delete', item) - for line in delete_commands: - if ditem.startswith(line): - updates.append(item) - - elif item.startswith('delete'): - if not config: - updates.append(line) - else: - item = re.sub(r'delete', 'set', item) - for entry in config: - if entry.startswith(item) and line not in visited: - updates.append(line) - visited.add(line) - - return list(updates) - - -def run(module, result): - # get the current active config from the node or passed in via - # the config param - config = module.params['config'] or get_config(module) - - # create the candidate config object from the arguments - candidate = get_candidate(module) - - # create loadable config that includes only the configuration updates - commands = diff_config(module, candidate, config) - - result['commands'] = commands - - commit = not module.check_mode - comment = module.params['comment'] - - if commands: - load_config(module, commands, commit=commit, comment=comment) - - result['changed'] = True - - -def main(): - - backup_spec = dict( - filename=dict(), - dir_path=dict(type='path') - ) - spec = dict( - src=dict(type='path'), - lines=dict(type='list'), - - match=dict(default='line', choices=['line', 'none']), - - comment=dict(default=DEFAULT_COMMENT), - - config=dict(), - - backup=dict(type='bool', default=False), - backup_options=dict(type='dict', options=backup_spec), - save=dict(type='bool', default=False), - ) - - mutually_exclusive = [('lines', 'src')] - - module = AnsibleModule( - argument_spec=spec, - mutually_exclusive=mutually_exclusive, - supports_check_mode=True - ) - - warnings = list() - - result = dict(changed=False, warnings=warnings) - - if module.params['backup']: - result['__backup__'] = get_config(module=module) - - if any((module.params['src'], module.params['lines'])): - run(module, result) - - if module.params['save']: - diff = run_commands(module, commands=['configure', 'compare saved'])[1] - if diff != '[edit]': - run_commands(module, commands=['save']) - result['changed'] = True - run_commands(module, commands=['exit']) - - module.exit_json(**result) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/network/edgeos/edgeos_facts.py b/lib/ansible/modules/network/edgeos/edgeos_facts.py deleted file mode 100644 index 9b872cdeea..0000000000 --- a/lib/ansible/modules/network/edgeos/edgeos_facts.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# Copyright (c) 2018 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 -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = """ ---- -module: edgeos_facts -version_added: "2.5" -author: - - Nathaniel Case (@Qalthos) - - Sam Doran (@samdoran) -short_description: Collect facts from remote devices running EdgeOS -description: - - Collects a base set of device facts from a remote device that - is running EdgeOS. This module prepends all of the - base network fact keys with U(ansible_net_<fact>). The facts - module will always collect a base set of facts from the device - and can enable or disable collection of additional facts. -notes: - - Tested against EdgeOS 1.9.7 -options: - gather_subset: - description: - - When supplied, this argument will restrict the facts collected - to a given subset. Possible values for this argument include - all, default, config, and neighbors. Can specify a list of - values to include a larger subset. Values can also be used - with an initial C(M(!)) to specify that a specific subset should - not be collected. - required: false - default: "!config" -""" - -EXAMPLES = """ -- name: collect all facts from the device - edgeos_facts: - gather_subset: all - -- name: collect only the config and default facts - edgeos_facts: - gather_subset: config - -- name: collect everything exception the config - edgeos_facts: - gather_subset: "!config" -""" - -RETURN = """ -ansible_net_config: - description: The running-config from the device - returned: when config is configured - type: str -ansible_net_commits: - description: The set of available configuration revisions - returned: when present - type: list -ansible_net_hostname: - description: The configured system hostname - returned: always - type: str -ansible_net_model: - description: The device model string - returned: always - type: str -ansible_net_serialnum: - description: The serial number of the device - returned: always - type: str -ansible_net_version: - description: The version of the software running - returned: always - type: str -ansible_net_neighbors: - description: The set of LLDP neighbors - returned: when interface is configured - type: list -ansible_net_gather_subset: - description: The list of subsets gathered by the module - returned: always - type: list -""" - -import re - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six import iteritems -from ansible.module_utils.network.edgeos.edgeos import run_commands - - -class FactsBase(object): - - COMMANDS = frozenset() - - def __init__(self, module): - self.module = module - self.facts = dict() - self.responses = None - - def populate(self): - self.responses = run_commands(self.module, list(self.COMMANDS)) - - -class Default(FactsBase): - - COMMANDS = [ - 'show version', - 'show host name', - ] - - def populate(self): - super(Default, self).populate() - data = self.responses[0] - - self.facts['version'] = self.parse_version(data) - self.facts['serialnum'] = self.parse_serialnum(data) - self.facts['model'] = self.parse_model(data) - - self.facts['hostname'] = self.responses[1] - - def parse_version(self, data): - match = re.search(r'Version:\s*v(\S+)', data) - if match: - return match.group(1) - - def parse_model(self, data): - match = re.search(r'HW model:\s*([A-Za-z0-9- ]+)', data) - if match: - return match.group(1) - - def parse_serialnum(self, data): - match = re.search(r'HW S/N:\s+(\S+)', data) - if match: - return match.group(1) - - -class Config(FactsBase): - - COMMANDS = [ - 'show configuration commands', - 'show system commit', - ] - - def populate(self): - super(Config, self).populate() - - self.facts['config'] = self.responses - - commits = self.responses[1] - entries = list() - entry = None - - for line in commits.split('\n'): - match = re.match(r'(\d+)\s+(.+)by(.+)via(.+)', line) - if match: - if entry: - entries.append(entry) - - entry = dict(revision=match.group(1), - datetime=match.group(2), - by=str(match.group(3)).strip(), - via=str(match.group(4)).strip(), - comment=None) - elif entry: - entry['comment'] = line.strip() - - self.facts['commits'] = entries - - -class Neighbors(FactsBase): - - COMMANDS = [ - 'show lldp neighbors', - 'show lldp neighbors detail', - ] - - def populate(self): - super(Neighbors, self).populate() - - all_neighbors = self.responses[0] - if 'LLDP not configured' not in all_neighbors: - neighbors = self.parse( - self.responses[1] - ) - self.facts['neighbors'] = self.parse_neighbors(neighbors) - - def parse(self, data): - parsed = list() - values = None - for line in data.split('\n'): - if not line: - continue - elif line[0] == ' ': - values += '\n%s' % line - elif line.startswith('Interface'): - if values: - parsed.append(values) - values = line - if values: - parsed.append(values) - return parsed - - def parse_neighbors(self, data): - facts = dict() - for item in data: - interface = self.parse_interface(item) - host = self.parse_host(item) - port = self.parse_port(item) - if interface not in facts: - facts[interface] = list() - facts[interface].append(dict(host=host, port=port)) - return facts - - def parse_interface(self, data): - match = re.search(r'^Interface:\s+(\S+),', data) - return match.group(1) - - def parse_host(self, data): - match = re.search(r'SysName:\s+(.+)$', data, re.M) - if match: - return match.group(1) - - def parse_port(self, data): - match = re.search(r'PortDescr:\s+(.+)$', data, re.M) - if match: - return match.group(1) - - -FACT_SUBSETS = dict( - default=Default, - neighbors=Neighbors, - config=Config -) - -VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) - - -def main(): - spec = dict( - gather_subset=dict(default=['!config'], type='list') - ) - - module = AnsibleModule(argument_spec=spec, - supports_check_mode=True) - - warnings = list() - - gather_subset = module.params['gather_subset'] - - runable_subsets = set() - exclude_subsets = set() - - for subset in gather_subset: - if subset == 'all': - runable_subsets.update(VALID_SUBSETS) - continue - - if subset.startswith('!'): - subset = subset[1:] - if subset == 'all': - exclude_subsets.update(VALID_SUBSETS) - continue - exclude = True - else: - exclude = False - - if subset not in VALID_SUBSETS: - module.fail_json(msg='Subset must be one of [%s], got %s' % - (', '.join(VALID_SUBSETS), subset)) - - if exclude: - exclude_subsets.add(subset) - else: - runable_subsets.add(subset) - - if not runable_subsets: - runable_subsets.update(VALID_SUBSETS) - - runable_subsets.difference_update(exclude_subsets) - runable_subsets.add('default') - - facts = dict() - facts['gather_subset'] = list(runable_subsets) - - instances = list() - for key in runable_subsets: - instances.append(FACT_SUBSETS[key](module)) - - for inst in instances: - inst.populate() - facts.update(inst.facts) - - ansible_facts = dict() - for key, value in iteritems(facts): - key = 'ansible_net_%s' % key - ansible_facts[key] = value - - module.exit_json(ansible_facts=ansible_facts, warnings=warnings) - - -if __name__ == '__main__': - main() |