2016-01-10 16:33:00 +01:00
|
|
|
#
|
2017-02-16 16:53:03 +01:00
|
|
|
# (c) 2017 Red Hat, Inc.
|
2016-01-10 16:33:00 +01:00
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
#
|
2017-02-16 16:53:03 +01:00
|
|
|
from contextlib import contextmanager
|
|
|
|
|
2017-04-25 19:10:16 +02:00
|
|
|
from xml.etree.ElementTree import Element, SubElement, fromstring
|
2017-02-16 16:53:03 +01:00
|
|
|
|
2017-04-08 01:22:17 +02:00
|
|
|
from ansible.module_utils.basic import env_fallback, return_values
|
2017-03-12 17:45:00 +01:00
|
|
|
from ansible.module_utils.netconf import send_request, children
|
2017-02-16 16:53:03 +01:00
|
|
|
from ansible.module_utils.netconf import discard_changes, validate
|
2017-03-13 12:27:45 +01:00
|
|
|
from ansible.module_utils.six import string_types
|
2017-04-27 17:44:26 +02:00
|
|
|
from ansible.module_utils._text import to_text
|
2017-02-16 16:53:03 +01:00
|
|
|
|
|
|
|
ACTIONS = frozenset(['merge', 'override', 'replace', 'update', 'set'])
|
|
|
|
JSON_ACTIONS = frozenset(['merge', 'override', 'update'])
|
|
|
|
FORMATS = frozenset(['xml', 'text', 'json'])
|
|
|
|
CONFIG_FORMATS = frozenset(['xml', 'text', 'json', 'set'])
|
|
|
|
|
|
|
|
junos_argument_spec = {
|
|
|
|
'host': dict(),
|
|
|
|
'port': dict(type='int'),
|
|
|
|
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
|
|
|
|
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
|
|
|
|
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
|
2017-04-18 18:29:38 +02:00
|
|
|
'timeout': dict(type='int'),
|
2017-04-07 22:34:47 +02:00
|
|
|
'provider': dict(type='dict'),
|
2017-03-12 14:37:45 +01:00
|
|
|
'transport': dict()
|
2017-02-16 16:53:03 +01:00
|
|
|
}
|
|
|
|
|
2017-04-18 18:29:38 +02:00
|
|
|
# Add argument's default value here
|
|
|
|
ARGS_DEFAULT_VALUE = {
|
|
|
|
'timeout': 10
|
|
|
|
}
|
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-06-02 14:06:38 +02:00
|
|
|
def get_argspec():
|
|
|
|
return junos_argument_spec
|
|
|
|
|
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
def check_args(module, warnings):
|
|
|
|
provider = module.params['provider'] or {}
|
|
|
|
for key in junos_argument_spec:
|
2017-04-17 21:06:24 +02:00
|
|
|
if key not in ('provider',) and module.params[key]:
|
2017-02-16 16:53:03 +01:00
|
|
|
warnings.append('argument %s has been deprecated and will be '
|
2017-06-02 13:14:11 +02:00
|
|
|
'removed in a future version' % key)
|
2017-02-16 16:53:03 +01:00
|
|
|
|
2017-04-18 18:29:38 +02:00
|
|
|
# set argument's default value if not provided in input
|
|
|
|
# This is done to avoid unwanted argument deprecation warning
|
|
|
|
# in case argument is not given as input (outside provider).
|
|
|
|
for key in ARGS_DEFAULT_VALUE:
|
|
|
|
if not module.params.get(key, None):
|
|
|
|
module.params[key] = ARGS_DEFAULT_VALUE[key]
|
|
|
|
|
2017-04-08 01:22:17 +02:00
|
|
|
if provider:
|
|
|
|
for param in ('password',):
|
|
|
|
if provider.get(param):
|
|
|
|
module.no_log_values.update(return_values(provider[param]))
|
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-04-05 13:11:11 +02:00
|
|
|
def _validate_rollback_id(module, value):
|
2017-02-16 16:53:03 +01:00
|
|
|
try:
|
|
|
|
if not 0 <= int(value) <= 49:
|
|
|
|
raise ValueError
|
|
|
|
except ValueError:
|
|
|
|
module.fail_json(msg='rollback must be between 0 and 49')
|
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
def load_configuration(module, candidate=None, action='merge', rollback=None, format='xml'):
|
|
|
|
|
|
|
|
if all((candidate is None, rollback is None)):
|
|
|
|
module.fail_json(msg='one of candidate or rollback must be specified')
|
|
|
|
|
|
|
|
elif all((candidate is not None, rollback is not None)):
|
|
|
|
module.fail_json(msg='candidate and rollback are mutually exclusive')
|
|
|
|
|
|
|
|
if format not in FORMATS:
|
|
|
|
module.fail_json(msg='invalid format specified')
|
|
|
|
|
|
|
|
if format == 'json' and action not in JSON_ACTIONS:
|
|
|
|
module.fail_json(msg='invalid action for format json')
|
|
|
|
elif format in ('text', 'xml') and action not in ACTIONS:
|
|
|
|
module.fail_json(msg='invalid action format %s' % format)
|
|
|
|
if action == 'set' and not format == 'text':
|
|
|
|
module.fail_json(msg='format must be text when action is set')
|
|
|
|
|
|
|
|
if rollback is not None:
|
2017-04-05 13:11:11 +02:00
|
|
|
_validate_rollback_id(module, rollback)
|
2017-02-16 16:53:03 +01:00
|
|
|
xattrs = {'rollback': str(rollback)}
|
2016-04-06 04:44:07 +02:00
|
|
|
else:
|
2017-02-16 16:53:03 +01:00
|
|
|
xattrs = {'action': action, 'format': format}
|
2016-04-06 04:44:07 +02:00
|
|
|
|
2017-03-11 17:26:42 +01:00
|
|
|
obj = Element('load-configuration', xattrs)
|
2016-04-06 04:44:07 +02:00
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
if candidate is not None:
|
|
|
|
lookup = {'xml': 'configuration', 'text': 'configuration-text',
|
|
|
|
'set': 'configuration-set', 'json': 'configuration-json'}
|
2016-04-06 04:44:07 +02:00
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
if action == 'set':
|
2017-03-11 17:26:42 +01:00
|
|
|
cfg = SubElement(obj, 'configuration-set')
|
2016-09-04 14:37:33 +02:00
|
|
|
else:
|
2017-03-11 17:26:42 +01:00
|
|
|
cfg = SubElement(obj, lookup[format])
|
2017-03-13 12:27:45 +01:00
|
|
|
|
|
|
|
if isinstance(candidate, string_types):
|
2017-04-25 19:10:16 +02:00
|
|
|
if format == 'xml':
|
|
|
|
cfg.append(fromstring(candidate))
|
|
|
|
else:
|
2017-04-27 17:44:26 +02:00
|
|
|
cfg.text = to_text(candidate, encoding='latin1')
|
2017-03-13 12:27:45 +01:00
|
|
|
else:
|
|
|
|
cfg.append(candidate)
|
2017-02-16 16:53:03 +01:00
|
|
|
return send_request(module, obj)
|
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
def get_configuration(module, compare=False, format='xml', rollback='0'):
|
|
|
|
if format not in CONFIG_FORMATS:
|
|
|
|
module.fail_json(msg='invalid config format specified')
|
|
|
|
xattrs = {'format': format}
|
|
|
|
if compare:
|
2017-04-05 13:11:11 +02:00
|
|
|
_validate_rollback_id(module, rollback)
|
2017-02-16 16:53:03 +01:00
|
|
|
xattrs['compare'] = 'rollback'
|
|
|
|
xattrs['rollback'] = str(rollback)
|
2017-03-11 17:26:42 +01:00
|
|
|
return send_request(module, Element('get-configuration', xattrs))
|
2017-02-16 16:53:03 +01:00
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
def commit_configuration(module, confirm=False, check=False, comment=None, confirm_timeout=None):
|
2017-03-11 17:26:42 +01:00
|
|
|
obj = Element('commit-configuration')
|
2017-02-16 16:53:03 +01:00
|
|
|
if confirm:
|
2017-03-11 17:26:42 +01:00
|
|
|
SubElement(obj, 'confirmed')
|
2017-02-16 16:53:03 +01:00
|
|
|
if check:
|
2017-03-11 17:26:42 +01:00
|
|
|
SubElement(obj, 'check')
|
2017-02-16 16:53:03 +01:00
|
|
|
if comment:
|
2017-03-12 17:45:00 +01:00
|
|
|
subele = SubElement(obj, 'log')
|
|
|
|
subele.text = str(comment)
|
2017-02-16 16:53:03 +01:00
|
|
|
if confirm_timeout:
|
2017-03-12 17:45:00 +01:00
|
|
|
subele = SubElement(obj, 'confirm-timeout')
|
2017-05-18 09:08:26 +02:00
|
|
|
subele.text = str(confirm_timeout)
|
2017-02-16 16:53:03 +01:00
|
|
|
return send_request(module, obj)
|
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-03-12 14:37:45 +01:00
|
|
|
def command(module, command, format='text', rpc_only=False):
|
|
|
|
xattrs = {'format': format}
|
|
|
|
if rpc_only:
|
|
|
|
command += ' | display xml rpc'
|
|
|
|
xattrs['format'] = 'text'
|
|
|
|
return send_request(module, Element('command', xattrs, text=command))
|
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
|
|
|
def lock_configuration(x):
|
|
|
|
return send_request(x, Element('lock-configuration'))
|
|
|
|
|
|
|
|
|
|
|
|
def unlock_configuration(x):
|
|
|
|
return send_request(x, Element('unlock-configuration'))
|
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def locked_config(module):
|
|
|
|
try:
|
|
|
|
lock_configuration(module)
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
unlock_configuration(module)
|
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
def get_diff(module):
|
2017-03-13 12:27:45 +01:00
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
reply = get_configuration(module, compare=True, format='text')
|
2017-03-11 17:26:42 +01:00
|
|
|
output = reply.find('.//configuration-output')
|
2017-03-12 17:45:00 +01:00
|
|
|
if output is not None:
|
2017-04-27 17:44:26 +02:00
|
|
|
return to_text(output.text, encoding='latin1').strip()
|
2017-02-16 16:53:03 +01:00
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-04-04 21:00:00 +02:00
|
|
|
def load_config(module, candidate, warnings, action='merge', commit=False, format='xml',
|
2017-03-12 14:37:45 +01:00
|
|
|
comment=None, confirm=False, confirm_timeout=None):
|
|
|
|
|
2017-05-30 08:25:25 +02:00
|
|
|
if not candidate:
|
|
|
|
return
|
|
|
|
|
2017-02-16 16:53:03 +01:00
|
|
|
with locked_config(module):
|
2017-03-13 12:27:45 +01:00
|
|
|
if isinstance(candidate, list):
|
|
|
|
candidate = '\n'.join(candidate)
|
|
|
|
|
2017-03-12 17:45:00 +01:00
|
|
|
reply = load_configuration(module, candidate, action=action, format=format)
|
2017-04-04 21:00:00 +02:00
|
|
|
if isinstance(reply, list):
|
|
|
|
warnings.extend(reply)
|
2017-02-16 16:53:03 +01:00
|
|
|
|
|
|
|
validate(module)
|
|
|
|
diff = get_diff(module)
|
|
|
|
|
|
|
|
if diff:
|
|
|
|
if commit:
|
2017-03-12 14:37:45 +01:00
|
|
|
commit_configuration(module, confirm=confirm, comment=comment,
|
|
|
|
confirm_timeout=confirm_timeout)
|
2017-02-16 16:53:03 +01:00
|
|
|
else:
|
|
|
|
discard_changes(module)
|
2016-09-04 14:37:33 +02:00
|
|
|
|
|
|
|
return diff
|
2017-06-02 05:34:57 +02:00
|
|
|
|
2017-06-02 13:14:11 +02:00
|
|
|
|
2017-06-02 05:34:57 +02:00
|
|
|
def get_param(module, key):
|
|
|
|
return module.params[key] or module.params['provider'].get(key)
|