Removing deprecated openswitch modules (#28397)

pull/28404/head
Chris Alfonso 7 years ago committed by GitHub
parent 2941e86671
commit bd56e6c9ce

@ -470,7 +470,6 @@ files:
$modules/network/netvisor/: $team_netvisor
$modules/network/nuage/: pdellaert
$modules/network/nxos/: $team_nxos
$modules/network/openswitch/: $team_openswitch
$modules/network/openvswitch/:
ignored: stygstra
maintainers: $team_networking
@ -482,7 +481,7 @@ files:
$modules/network/panos/panos_address.py: itdependsnetworks ivanbojer jtschichold
$modules/network/protocol/: $team_networking
$modules/network/routing/: $team_networking
$modules/network/sros/: $team_openswitch
$modules/network/sros/: privateip
$modules/network/system/: $team_networking
$modules/network/vyos/: Qalthos
$modules/notification/bearychat.py: tonyseek
@ -1071,7 +1070,6 @@ macros:
team_networking: Qalthos ganeshrn gundalow privateip rcarrillocruz trishnaguha
team_nxos: GGabriele jedelman8 mikewiebe privateip rahushen rcarrillocruz trishnaguha
team_openstack: emonty j2sol juliakreger rcarrillocruz shrews thingee
team_openswitch: Qalthos gundalow privateip
team_rabbitmq: chrishoffman manuel-sousa romanek-adam
team_rhn: alikins barnabycourt flossware vritant
team_tower: ghjm jlaska matburt wwitzel3

@ -63,6 +63,7 @@ Ansible Changes By Release
* junos_template (use junos_config instead)
* nxos_template (use nxos_config instead)
* ops_template (use ops_config instead)
* openswitch
* Modules (scheduled for removal in 2.6)

@ -1,260 +0,0 @@
#
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import re
try:
import ovs.poller
import ops.dc
from ops.settings import settings
from opslib import restparser
HAS_OPS = True
except ImportError:
HAS_OPS = False
from ansible.module_utils.basic import json, json_dict_bytes_to_unicode
from ansible.module_utils.network import ModuleStub, NetworkError, NetworkModule
from ansible.module_utils.network import add_argument, register_transport, to_list
from ansible.module_utils.shell import CliBase
from ansible.module_utils.urls import fetch_url, url_argument_spec
add_argument('use_ssl', dict(default=True, type='bool'))
add_argument('validate_certs', dict(default=True, type='bool'))
def get_opsidl():
extschema = restparser.parseSchema(settings.get('ext_schema'))
ovsschema = settings.get('ovs_schema')
ovsremote = settings.get('ovs_remote')
opsidl = ops.dc.register(extschema, ovsschema, ovsremote)
init_seqno = opsidl.change_seqno
while True:
opsidl.run()
if init_seqno != opsidl.change_seqno:
break
poller = ovs.poller.Poller()
opsidl.wait(poller)
poller.block()
return (extschema, opsidl)
class Response(object):
def __init__(self, resp, hdrs):
self.body = None
self.headers = hdrs
if resp:
self.body = resp.read()
@property
def json(self):
if not self.body:
return None
try:
return json.loads(self.body)
except ValueError:
return None
class Rest(object):
DEFAULT_HEADERS = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
def __init__(self):
self.url = None
self.url_args = ModuleStub(url_argument_spec(), self._error)
self.headers = self.DEFAULT_HEADERS
self._connected = False
self.default_output = 'json'
def _error(self, msg):
raise NetworkError(msg, url=self.url)
def connect(self, params, **kwargs):
host = params['host']
port = params['port']
# sets the module_utils/urls.py req parameters
self.url_args.params['url_username'] = params['username']
self.url_args.params['url_password'] = params['password']
self.url_args.params['validate_certs'] = params['validate_certs']
if params['use_ssl']:
proto = 'https'
if not port:
port = 443
else:
proto = 'http'
if not port:
port = 80
baseurl = '%s://%s:%s' % (proto, host, port)
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
# Get a cookie and save it the rest of the operations.
url = '%s/%s' % (baseurl, 'login')
data = 'username=%s&password=%s' % (params['username'], params['password'])
resp, hdrs = fetch_url(self.url_args, url, data=data, headers=headers, method='POST')
# Update the base url for the rest of the operations.
self.url = '%s/rest/v1' % (baseurl)
self.headers['Cookie'] = hdrs['set-cookie']
def disconnect(self, **kwargs):
self.url = None
self._connected = False
def authorize(self, params, **kwargs):
raise NotImplementedError
# REST methods
def _url_builder(self, path):
if path[0] == '/':
path = path[1:]
return '%s/%s' % (self.url, path)
def request(self, method, path, data=None, headers=None):
url = self._url_builder(path)
data = self._jsonify(data)
if headers is None:
headers = dict()
headers.update(self.headers)
resp, hdrs = fetch_url(self.url_args, url, data=data, headers=headers, method=method)
return Response(resp, hdrs)
def get(self, path, data=None, headers=None):
return self.request('GET', path, data, headers)
def put(self, path, data=None, headers=None):
return self.request('PUT', path, data, headers)
def post(self, path, data=None, headers=None):
return self.request('POST', path, data, headers)
def delete(self, path, data=None, headers=None):
return self.request('DELETE', path, data, headers)
# Command methods
def run_commands(self, commands):
raise NotImplementedError
# Config methods
def configure(self, commands):
path = '/system/full-configuration'
return self.put(path, data=commands)
def load_config(self, commands, **kwargs):
return self.configure(commands)
def get_config(self, **kwargs):
resp = self.get('/system/full-configuration')
return resp.json
def save_config(self):
raise NotImplementedError
def _jsonify(self, data):
for encoding in ("utf-8", "latin-1"):
try:
return json.dumps(data, encoding=encoding)
# Old systems using old simplejson module does not support encoding keyword.
except TypeError:
try:
new_data = json_dict_bytes_to_unicode(data, encoding=encoding)
except UnicodeDecodeError:
continue
return json.dumps(new_data)
except UnicodeDecodeError:
continue
self._error(msg='Invalid unicode encoding encountered')
Rest = register_transport('rest')(Rest)
class Cli(CliBase):
CLI_PROMPTS_RE = [
re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$")
]
CLI_ERRORS_RE = [
re.compile(r"(?:unknown|incomplete|ambiguous) command", re.I),
]
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
# Config methods
def configure(self, commands, **kwargs):
cmds = ['configure terminal']
cmds.extend(to_list(commands))
if cmds[-1] != 'end':
cmds.append('end')
responses = self.execute(cmds)
return responses[1:]
def get_config(self, **kwargs):
return self.execute('show running-config')[0]
def load_config(self, commands, **kwargs):
return self.configure(commands)
def save_config(self):
self.execute(['copy running-config startup-config'])
Cli = register_transport('cli')(Cli)
class Ssh(object):
def __init__(self):
if not HAS_OPS:
msg = 'ops.dc lib is required but does not appear to be available'
raise NetworkError(msg)
self._opsidl = None
self._extschema = None
def configure(self, config):
if not self._opsidl:
(self._extschema, self._opsidl) = get_opsidl()
return ops.dc.write(self._extschema, self._opsidl)
def get_config(self):
if not self._opsidl:
(self._extschema, self._opsidl) = get_opsidl()
return ops.dc.read(self._extschema, self._opsidl)
def load_config(self, config):
return self.configure(config)
Ssh = register_transport('ssh', default=True)(Ssh)

@ -1,223 +0,0 @@
#!/usr/bin/python
#
# Copyright: 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: ops_command
version_added: "2.1"
author: "Peter Sprygada (@privateip)"
short_description: Run arbitrary commands on OpenSwitch devices.
description:
- Sends arbitrary commands to an OpenSwitch node and returns the results
read from the device. This module includes an
argument that will cause the module to wait for a specific condition
before returning or timing out if the condition is not met.
extends_documentation_fragment: openswitch
options:
commands:
description:
- List of commands to send to the remote ops device over the
configured provider. The resulting output from the command
is returned. If the I(wait_for) argument is provided, the
module is not returned until the condition is satisfied or
the number of retires as expired.
required: true
wait_for:
description:
- List of conditions to evaluate against the output of the
command. The task will wait for each condition to be true
before moving forward. If the conditional is not true
within the configured number of retries, the task fails.
See examples.
required: false
default: null
aliases: ['waitfor']
version_added: "2.2"
match:
description:
- The I(match) argument is used in conjunction with the
I(wait_for) argument to specify the match policy. Valid
values are C(all) or C(any). If the value is set to C(all)
then all conditionals in the I(wait_for) must be satisfied. If
the value is set to C(any) then only one of the values must be
satisfied.
required: false
default: all
choices: ['any', 'all']
version_added: "2.2"
retries:
description:
- Specifies the number of retries a command should by tried
before it is considered failed. The command is run on the
target device every retry and evaluated against the
I(wait_for) conditions.
required: false
default: 10
interval:
description:
- Configures the interval in seconds to wait between I(retries)
of the command. If the command does not pass the specified
conditions, the interval indicates how long to wait before
trying the command again.
required: false
default: 1
"""
EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
---
vars:
cli:
host: "{{ inventory_hostname }}"
username: netop
password: netop
transport: cli
---
- ops_command:
commands:
- show version
provider: "{{ cli }}"
- ops_command:
commands:
- show version
wait_for:
- "result[0] contains OpenSwitch"
provider: "{{ cli }}"
- ops_command:
commands:
- show version
- show interfaces
provider: "{{ cli }}"
"""
RETURN = """
stdout:
description: the set of responses from the commands
returned: always
type: list
sample: ['...', '...']
stdout_lines:
description: The value of stdout split into a list
returned: always
type: list
sample: [['...', '...'], ['...'], ['...']]
failed_conditions:
description: the conditionals that failed
returned: failed
type: list
sample: ['...', '...']
"""
import traceback
import ansible.module_utils.openswitch
from ansible.module_utils.netcli import CommandRunner
from ansible.module_utils.netcli import AddCommandError, FailedConditionsError
from ansible.module_utils.network import NetworkModule, NetworkError
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_native
VALID_KEYS = ['command', 'prompt', 'response']
def to_lines(stdout):
for item in stdout:
if isinstance(item, string_types):
item = str(item).split('\n')
yield item
def parse_commands(module):
for cmd in module.params['commands']:
if isinstance(cmd, string_types):
cmd = dict(command=cmd, output=None)
elif 'command' not in cmd:
module.fail_json(msg='command keyword argument is required')
elif not set(cmd.keys()).issubset(VALID_KEYS):
module.fail_json(msg='unknown keyword specified')
yield cmd
def main():
spec = dict(
# { command: <str>, prompt: <str>, response: <str> }
commands=dict(type='list', required=True),
wait_for=dict(type='list', aliases=['waitfor']),
match=dict(default='all', choices=['all', 'any']),
retries=dict(default=10, type='int'),
interval=dict(default=1, type='int')
)
module = NetworkModule(argument_spec=spec,
connect_on_load=False,
supports_check_mode=True)
commands = list(parse_commands(module))
conditionals = module.params['wait_for'] or list()
warnings = list()
runner = CommandRunner(module)
for cmd in commands:
if module.check_mode and not cmd['command'].startswith('show'):
warnings.append('only show commands are supported when using '
'check mode, not executing `%s`' % cmd['command'])
else:
if cmd['command'].startswith('conf'):
module.fail_json(msg='ops_command does not support running '
'config mode commands. Please use '
'ops_config instead')
try:
runner.add_command(**cmd)
except AddCommandError:
warnings.append('duplicate command detected: %s' % cmd)
for item in conditionals:
runner.add_conditional(item)
runner.retries = module.params['retries']
runner.interval = module.params['interval']
runner.match = module.params['match']
try:
runner.run()
except FailedConditionsError as e:
module.fail_json(msg=to_native(e), failed_conditions=e.failed_conditions,
exception=traceback.format_exc())
except NetworkError as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
result = dict(changed=False, stdout=list())
for cmd in commands:
try:
output = runner.get_command(cmd['command'])
except ValueError:
output = 'command not executed due to check_mode, see warnings'
result['stdout'].append(output)
result['warnings'] = warnings
result['stdout_lines'] = list(to_lines(result['stdout']))
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,306 +0,0 @@
#!/usr/bin/python
#
# Copyright: 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: ops_config
version_added: "2.1"
author: "Peter Sprygada (@privateip)"
short_description: Manage OpenSwitch configuration using CLI
description:
- OpenSwitch configurations use a simple block indent file syntax
for segmenting configuration into sections. This module provides
an implementation for working with ops configuration sections in
a deterministic way.
extends_documentation_fragment: openswitch
options:
lines:
description:
- The ordered set of commands that should be configured in the
section. The commands must be the exact same commands as found
in the device running-config. Be sure to note the configuration
command syntax as some commands are automatically modified by the
device config parser.
required: false
default: null
parents:
description:
- The ordered set of parents that uniquely identify the section
the commands should be checked against. If the parents argument
is omitted, the commands are checked against the set of top
level or global commands.
required: false
default: null
src:
description:
- The I(src) argument provides a path to the configuration file
to load into the remote system. The path can either be a full
system path to the configuration file if the value starts with /
or relative to the root of the implemented role or playbook.
This argument is mutually exclusive with the I(lines) and
I(parents) arguments.
required: false
default: null
version_added: "2.2"
before:
description:
- The ordered set of commands to push on to the command stack if
a change needs to be made. This allows the playbook designer
the opportunity to perform configuration commands prior to pushing
any changes without affecting how the set of commands are matched
against the system.
required: false
default: null
after:
description:
- The ordered set of commands to append to the end of the command
stack if a change needs to be made. Just like with I(before) this
allows the playbook designer to append a set of commands to be
executed after the command set.
required: false
default: null
match:
description:
- Instructs the module on the way to perform the matching of
the set of commands against the current device config. If
match is set to I(line), commands are matched line by line. If
match is set to I(strict), command lines are matched with respect
to position. If match is set to I(exact), command lines
must be an equal match. Finally, if match is set to I(none), the
module will not attempt to compare the source configuration with
the running configuration on the remote device.
required: false
default: line
choices: ['line', 'strict', 'exact', 'none']
replace:
description:
- Instructs the module on the way to perform the configuration
on the device. If the replace argument is set to I(line) then
the modified lines are pushed to the device in configuration
mode. If the replace argument is set to I(block) then the entire
command block is pushed to the device in configuration mode if any
line is not correct.
required: false
default: line
choices: ['line', 'block']
force:
description:
- The force argument instructs the module to not consider the
current devices running-config. When set to true, this will
cause the module to push the contents of I(src) into the device
without first checking if already configured.
- Note this argument should be considered deprecated. To achieve
the equivalent, set the C(match=none) which is idempotent. This argument
will be removed in a future release.
required: false
default: false
choices: ['yes', 'no']
config:
description:
- The module, by default, will connect to the remote device and
retrieve the current running-config to use as a base for comparing
against the contents of source. There are times when it is not
desirable to have the task get the current running-config for
every task in a playbook. The I(config) argument allows the
implementer to pass in the configuration to use as the base
config for comparison.
required: false
default: null
save:
description:
- The C(save) argument instructs the module to save the running-
config to the startup-config at the conclusion of the module
running. If check mode is specified, this argument is ignored.
required: false
default: no
choices: ['yes', 'no']
version_added: "2.2"
"""
EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
---
vars:
cli:
host: "{{ inventory_hostname }}"
username: netop
password: netop
---
- name: configure hostname over cli
ops_config:
lines:
- "hostname {{ inventory_hostname }}"
provider: "{{ cli }}"
- name: configure vlan 10 over cli
ops_config:
lines:
- no shutdown
parents:
- vlan 10
provider: "{{ cli }}"
- name: load config from file
ops_config:
src: ops01.cfg
backup: yes
provider: "{{ cli }}"
"""
RETURN = """
updates:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
sample: ['...', '...']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: string
sample: /playbooks/ansible/backup/ops_config.2016-07-16@22:28:34
"""
import traceback
from ansible.module_utils.openswitch import NetworkModule, NetworkError
from ansible.module_utils.netcfg import NetworkConfig, dumps
from ansible.module_utils._text import to_native
def check_args(module, warnings):
if module.params['force']:
warnings.append('The force argument is deprecated, please use '
'match=none instead. This argument will be '
'removed in the future')
def get_config(module, result):
contents = module.params['config']
if not contents:
contents = module.config.get_config()
return NetworkConfig(indent=4, contents=contents)
def get_candidate(module):
candidate = NetworkConfig(indent=4)
if module.params['src']:
candidate.load(module.params['src'])
elif module.params['lines']:
parents = module.params['parents'] or list()
candidate.add(module.params['lines'], parents=parents)
return candidate
def load_config(module, commands, result):
if not module.check_mode:
module.config(commands)
result['changed'] = True
def run(module, result):
match = module.params['match']
replace = module.params['replace']
path = module.params['parents']
candidate = get_candidate(module)
if match != 'none':
config = get_config(module, result)
configobjs = candidate.difference(config, path=path, match=match,
replace=replace)
else:
configobjs = candidate.items
if configobjs:
commands = dumps(configobjs, 'commands').split('\n')
if module.params['lines']:
if module.params['before']:
commands[:0] = module.params['before']
if module.params['after']:
commands.extend(module.params['after'])
result['updates'] = commands
# send the configuration commands to the device and merge
# them with the current running config
if not module.check_mode:
module.config.load_config(commands)
result['changed'] = True
if module.params['save']:
if not module.check_mode:
module.config.save_config()
result['changed'] = True
def main():
argument_spec = dict(
src=dict(type='path'),
lines=dict(aliases=['commands'], type='list'),
parents=dict(type='list'),
before=dict(type='list'),
after=dict(type='list'),
match=dict(default='line', choices=['line', 'strict', 'exact', 'none']),
replace=dict(default='line', choices=['line', 'block']),
# this argument is deprecated in favor of setting match: none
# it will be removed in a future version
force=dict(default=False, type='bool'),
config=dict(),
save=dict(type='bool', default=False),
backup=dict(type='bool', default=False),
# ops_config is only supported over Cli transport so force
# the value of transport to be cli
transport=dict(default='cli', choices=['cli'])
)
mutually_exclusive = [('lines', 'src')]
required_if = [('match', 'strict', ['lines']),
('match', 'exact', ['lines']),
('replace', 'block', ['lines'])]
module = NetworkModule(argument_spec=argument_spec,
connect_on_load=False,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
supports_check_mode=True)
if module.params['force'] is True:
module.params['match'] = 'none'
warnings = list()
check_args(module, warnings)
result = dict(changed=False, warnings=warnings)
if module.params['backup']:
result['__backup__'] = module.config.get_config()
try:
run(module, result)
except NetworkError as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -1,416 +0,0 @@
#!/usr/bin/python
#
# Copyright: 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: ops_facts
version_added: "2.1"
author: "Peter Sprygada (@privateip)"
short_description: Collect device specific facts from OpenSwitch
description:
- Collects facts from devices running the OpenSwitch operating
system. Fact collection is supported over both Cli and Rest
transports. This module prepends all of the base network fact keys
with C(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.
- The facts collected from pre Ansible 2.2 are still available and
are collected for backwards compatibility; however, these facts
should be considered deprecated and will be removed in a future
release.
extends_documentation_fragment: openswitch
options:
config:
description:
- When enabled, this argument will collect the current
running configuration from the remote device. If the
C(transport=rest) then the collected configuration will
be the full system configuration.
required: false
choices:
- true
- false
default: false
endpoints:
description:
- Accepts a list of endpoints to retrieve from the remote
device using the REST API. The endpoints should be valid
endpoints available on the device. This argument is only
valid when the C(transport=rest).
required: false
default: null
gather_subset:
description:
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
all, hardware, config, legacy, and interfaces. 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'
version_added: "2.2"
"""
EXAMPLES = """
# Note: examples below use the following provider dict to handle
# transport and authentication to the node.
---
vars:
cli:
host: "{{ inventory_hostname }}"
username: netop
password: netop
transport: cli
rest:
host: "{{ inventory_hostname }}"
username: netop
password: netop
transport: rest
---
- ops_facts:
gather_subset: all
provider: "{{ rest }}"
# Collect only the config and default facts
- ops_facts:
gather_subset: config
provider: "{{ cli }}"
# Do not collect config facts
- ops_facts:
gather_subset:
- "!config"
provider: "{{ cli }}"
- name: collect device facts
ops_facts:
provider: "{{ cli }}"
- name: include the config
ops_facts:
config: yes
provider: "{{ rest }}"
- name: include a set of rest endpoints
ops_facts:
endpoints:
- /system/interfaces/1
- /system/interfaces/2
provider: "{{ rest }}"
"""
RETURN = """
ansible_net_gather_subset:
description: The list of fact subsets collected from the device
returned: always
type: list
# default
ansible_net_model:
description: The model name returned from the device
returned: when transport is cli
type: str
ansible_net_serialnum:
description: The serial number of the remote device
returned: when transport is cli
type: str
ansible_net_version:
description: The operating system version running on the remote device
returned: always
type: str
ansible_net_hostname:
description: The configured hostname of the device
returned: always
type: string
ansible_net_image:
description: The image file the device is running
returned: when transport is cli
type: string
# config
ansible_net_config:
description: The current active config from the device
returned: when config is enabled
type: str
# legacy (pre Ansible 2.2)
config:
description: The current system configuration
returned: when enabled
type: string
sample: '....'
hostname:
description: returns the configured hostname
returned: always
type: string
sample: ops01
version:
description: The current version of OpenSwitch
returned: always
type: string
sample: '0.3.0'
endpoints:
description: The JSON response from the URL endpoint
returned: when endpoints argument is defined and transport is rest
type: list
sample: [{....}, {....}]
"""
import re
import ansible.module_utils.openswitch
from ansible.module_utils.netcli import CommandRunner, AddCommandError
from ansible.module_utils.network import NetworkModule
from ansible.module_utils.six import iteritems
def add_command(runner, command):
try:
runner.add_command(command)
except AddCommandError:
# AddCommandError is raised for any issue adding a command to
# the runner. Silently ignore the exception in this case
pass
class FactsBase(object):
def __init__(self, module, runner):
self.module = module
self.transport = module.params['transport']
self.runner = runner
self.facts = dict()
if self.transport == 'cli':
self.commands()
def commands(self):
raise NotImplementedError
def populate(self):
getattr(self, self.transport)()
def cli(self):
pass
def rest(self):
pass
class Default(FactsBase):
def commands(self):
add_command(self.runner, 'show system')
add_command(self.runner, 'show hostname')
def rest(self):
self.facts.update(self.get_system())
def cli(self):
data = self.runner.get_command('show system')
self.facts['version'] = self.parse_version(data)
self.facts['serialnum'] = self.parse_serialnum(data)
self.facts['model'] = self.parse_model(data)
self.facts['image'] = self.parse_image(data)
self.facts['hostname'] = self.runner.get_command('show hostname')
def parse_version(self, data):
match = re.search(r'OpenSwitch Version\s+: (\S+)', data)
if match:
return match.group(1)
def parse_model(self, data):
match = re.search(r'Platform\s+:\s(\S+)', data, re.M)
if match:
return match.group(1)
def parse_image(self, data):
match = re.search(r'\(Build: (\S+)\)', data, re.M)
if match:
return match.group(1)
def parse_serialnum(self, data):
match = re.search(r'Serial Number\s+: (\S+)', data)
if match:
return match.group(1)
def get_system(self):
response = self.module.connection.get('/system')
return dict(
hostname=response.json['configuration']['hostname'],
version=response.json['status']['switch_version']
)
class Config(FactsBase):
def commands(self):
add_command(self.runner, 'show running-config')
def cli(self):
self.facts['config'] = self.runner.get_command('show running-config')
class Legacy(FactsBase):
# facts from ops_facts 2.1
def commands(self):
add_command(self.runner, 'show system')
add_command(self.runner, 'show hostname')
if self.module.params['config']:
add_command(self.runner, 'show running-config')
def rest(self):
self.facts['_endpoints'] = self.get_endpoints()
self.facts.update(self.get_system())
if self.module.params['config']:
self.facts['_config'] = self.get_config()
def cli(self):
self.facts['_hostname'] = self.runner.get_command('show hostname')
data = self.runner.get_command('show system')
self.facts['_version'] = self.parse_version(data)
if self.module.params['config']:
self.facts['_config'] = self.runner.get_command('show running-config')
def parse_version(self, data):
match = re.search(r'OpenSwitch Version\s+: (\S+)', data)
if match:
return match.group(1)
def get_endpoints(self):
responses = list()
urls = self.module.params['endpoints'] or list()
for ep in urls:
response = self.module.connection.get(ep)
if response.headers['status'] != 200:
self.module.fail_json(msg=response.headers['msg'])
responses.append(response.json)
return responses
def get_system(self):
response = self.module.connection.get('/system')
return dict(
_hostname=response.json['configuration']['hostname'],
_version=response.json['status']['switch_version']
)
def get_config(self):
response = self.module.connection.get('/system/full-configuration')
return response.json
def check_args(module, warnings):
if module.params['transport'] != 'rest' and module.params['endpoints']:
warnings.append('Endpoints can only be collected when transport is '
'set to "rest". Endpoints will not be collected')
FACT_SUBSETS = dict(
default=Default,
config=Config,
legacy=Legacy
)
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
def main():
spec = dict(
gather_subset=dict(default=['!config'], type='list'),
# the next two arguments are legacy from pre 2.2 ops_facts
# these will be deprecated and ultimately removed
config=dict(default=False, type='bool'),
endpoints=dict(type='list'),
transport=dict(default='cli', choices=['cli', 'rest'])
)
module = NetworkModule(argument_spec=spec, supports_check_mode=True)
gather_subset = module.params['gather_subset']
warnings = list()
check_args(module, warnings)
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='Bad 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')
runable_subsets.add('legacy')
facts = dict()
facts['gather_subset'] = list(runable_subsets)
runner = CommandRunner(module)
instances = list()
for key in runable_subsets:
instances.append(FACT_SUBSETS[key](module, runner))
if module.params['transport'] == 'cli':
runner.run()
try:
for inst in instances:
inst.populate()
facts.update(inst.facts)
except Exception:
module.exit_json(out=module.from_json(runner.items))
ansible_facts = dict()
for key, value in iteritems(facts):
# this is to maintain capability with ops_facts 2.1
if key.startswith('_'):
ansible_facts[key[1:]] = value
else:
key = 'ansible_net_%s' % key
ansible_facts[key] = value
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
if __name__ == '__main__':
main()
Loading…
Cancel
Save