* adds commit replace with config file for iosxr (#35564)

* * adds commit replace with config file for iosxr
* fixes dci failure in iosxr_logging

* * review comment changes
pull/22851/merge
Kedar Kekan 7 years ago committed by John R Barker
parent 2479b6d635
commit 684e953b50

@ -29,6 +29,7 @@
import json
from difflib import Differ
from copy import deepcopy
from time import sleep
from ansible.module_utils._text import to_text, to_bytes
from ansible.module_utils.basic import env_fallback
@ -415,7 +416,14 @@ def load_config(module, command_filter, commit=False, replace=False,
if module._diff:
diff = get_config_diff(module)
if commit:
if replace:
cmd = list()
cmd.append({'command': 'commit replace',
'prompt': 'This commit will replace or remove the entire running configuration',
'answer': 'yes'})
cmd.append('end')
conn.edit_config(cmd)
elif commit:
commit_config(module, comment=comment)
conn.edit_config('end')
else:
@ -428,20 +436,36 @@ def run_command(module, commands):
conn = get_connection(module)
responses = list()
for cmd in to_list(commands):
try:
cmd = json.loads(cmd)
command = cmd['command']
prompt = cmd['prompt']
answer = cmd['answer']
if isinstance(cmd, str):
cmd = json.loads(cmd)
command = cmd.get('command', None)
prompt = cmd.get('prompt', None)
answer = cmd.get('answer', None)
sendonly = cmd.get('sendonly', False)
newline = cmd.get('newline', True)
except:
command = cmd
prompt = None
answer = None
sendonly = False
newline = True
out = conn.get(command, prompt, answer)
out = conn.get(command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
try:
responses.append(to_text(out, errors='surrogate_or_strict'))
except UnicodeError:
module.fail_json(msg=u'failed to decode output from {0}:{1}'.format(cmd, to_text(out)))
return responses
def copy_file(module, src, dst, proto='scp'):
conn = get_connection(module)
conn.copy_file(source=src, destination=dst, proto=proto)
def get_file(module, src, dst, proto='scp'):
conn = get_connection(module)
conn.get_file(source=src, destination=dst, proto=proto)

@ -94,7 +94,7 @@ tasks:
commands:
- show version
- show interfaces
- [{ command: example command that prompts, prompt: expected prompt, answer: yes}]
- { command: example command that prompts, prompt: expected prompt, answer: yes}
- name: run multiple commands and evaluate the output
iosxr_command:

@ -25,7 +25,9 @@ description:
a deterministic way.
extends_documentation_fragment: iosxr
notes:
- Tested against IOS XR 6.1.2
- Tested against IOS XRv 6.1.2
- Avoid service disrupting changes (viz. Management IP) from config replace.
- Do not use C(end) in the replace config file.
options:
lines:
description:
@ -164,6 +166,7 @@ EXAMPLES = """
- name: load a config from disk and replace the current config
iosxr_config:
src: config.cfg
replace: config
backup: yes
"""
@ -181,13 +184,26 @@ backup_path:
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.iosxr import load_config, get_config
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, copy_file
from ansible.module_utils.network.common.config import NetworkConfig, dumps
DEFAULT_COMMIT_COMMENT = 'configured by iosxr_config'
def copy_file_to_node(module):
""" Copy config file to IOS-XR node. We use SFTP because older IOS-XR versions don't handle SCP very well.
"""
src = '/tmp/ansible_config.txt'
file = open(src, 'wb')
file.write(module.params['src'])
file.close()
dst = '/harddisk:/ansible_config.txt'
copy_file(module, src, dst, 'sftp')
return True
def check_args(module, warnings):
if module.params['comment']:
if len(module.params['comment']) > 60:
@ -224,18 +240,30 @@ def run(module, result):
admin = module.params['admin']
check_mode = module.check_mode
candidate = get_candidate(module)
candidate_config = get_candidate(module)
running_config = get_running_config(module)
commands = None
if match != 'none' and replace != 'config':
contents = get_running_config(module)
configobj = NetworkConfig(contents=contents, indent=1)
commands = candidate.difference(configobj, path=path, match=match,
replace=replace)
commands = candidate_config.difference(running_config, path=path, match=match, replace=replace)
elif replace_config:
can_config = candidate_config.difference(running_config, path=path, match=match, replace=replace)
candidate = dumps(can_config, 'commands').split('\n')
run_config = running_config.difference(candidate_config, path=path, match=match, replace=replace)
running = dumps(run_config, 'commands').split('\n')
if len(candidate) > 1 or len(running) > 1:
ret = copy_file_to_node(module)
if not ret:
module.fail_json(msg='Copy of config file to the node failed')
commands = ['load harddisk:/ansible_config.txt']
else:
commands = candidate.items
commands = candidate_config.items
if commands:
commands = dumps(commands, 'commands').split('\n')
if not replace_config:
commands = dumps(commands, 'commands').split('\n')
if any((module.params['lines'], module.params['src'])):
if module.params['before']:
@ -247,10 +275,10 @@ def run(module, result):
result['commands'] = commands
commit = not check_mode
diff = load_config(module, commands, commit=commit, replace=replace_config,
comment=comment, admin=admin)
diff = load_config(module, commands, commit=commit, replace=replace_config, comment=comment, admin=admin)
if diff:
result['diff'] = dict(prepared=diff)
result['changed'] = True

@ -586,7 +586,7 @@ class NCConfiguration(ConfigBase):
elif item['dest'] == 'host' and item['name'] in host_list:
item['level'] = severity_level[item['level']]
host_params.append(item)
elif item['dest'] == 'console' and have_console and have_console_enable:
elif item['dest'] == 'console' and have_console:
console_params.update({'console-level': item['level']})
elif item['dest'] == 'monitor' and have_monitor:
monitor_params.update({'monitor-level': item['level']})

@ -34,7 +34,6 @@ try:
except ImportError:
HAS_SCP = False
try:
from __main__ import display
except ImportError:
@ -135,7 +134,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
pass
@abstractmethod
def edit_config(self, commands):
def edit_config(self, commands=None):
"""Loads the specified commands into the remote device
This method will load the commands into the remote device. This
method will make sure the device is in the proper context before
@ -150,7 +149,7 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
pass
@abstractmethod
def get(self, command, prompt=None, answer=None, sendonly=False):
def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True):
"""Execute specified command on remote device
This method will retrieve the specified data and
return it to the caller as a string.
@ -181,18 +180,26 @@ class CliconfBase(with_metaclass(ABCMeta, object)):
"Discard changes in candidate datastore"
return self._connection.method_not_found("discard_changes is not supported by network_os %s" % self._play_context.network_os)
def put_file(self, source, destination):
"""Copies file over scp to remote device"""
if not HAS_SCP:
self._connection.internal_error("Required library scp is not installed. Please install it using `pip install scp`")
ssh = self._connection._connect_uncached()
with SCPClient(ssh.get_transport()) as scp:
scp.put(source, destination)
def fetch_file(self, source, destination):
"""Fetch file over scp from remote device"""
if not HAS_SCP:
self._connection.internal_error("Required library scp is not installed. Please install it using `pip install scp`")
ssh = self._connection._connect_uncached()
with SCPClient(ssh.get_transport()) as scp:
scp.get(source, destination)
def copy_file(self, source=None, destination=None, proto='scp'):
"""Copies file over scp/sftp to remote device"""
ssh = self._connection.paramiko_conn._connect_uncached()
if proto == 'scp':
if not HAS_SCP:
self._connection.internal_error("Required library scp is not installed. Please install it using `pip install scp`")
with SCPClient(ssh.get_transport()) as scp:
scp.put(source, destination)
elif proto == 'sftp':
with ssh.open_sftp() as sftp:
sftp.put(source, destination)
def get_file(self, source=None, destination=None, proto='scp'):
"""Fetch file over scp/sftp from remote device"""
ssh = self._connection.paramiko_conn._connect_uncached()
if proto == 'scp':
if not HAS_SCP:
self._connection.internal_error("Required library scp is not installed. Please install it using `pip install scp`")
with SCPClient(ssh.get_transport()) as scp:
scp.get(source, destination)
elif proto == 'sftp':
with ssh.open_sftp() as sftp:
sftp.get(source, destination)

@ -67,12 +67,27 @@ class Cliconf(CliconfBase):
return self.send_command(cmd)
def edit_config(self, command):
for cmd in chain(to_list(command)):
self.send_command(cmd)
def get(self, command, prompt=None, answer=None, sendonly=False):
return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly)
def edit_config(self, commands=None):
for cmd in chain(to_list(commands)):
try:
if isinstance(cmd, str):
cmd = json.loads(cmd)
command = cmd.get('command', None)
prompt = cmd.get('prompt', None)
answer = cmd.get('answer', None)
sendonly = cmd.get('sendonly', False)
newline = cmd.get('newline', True)
except:
command = cmd
prompt = None
answer = None
sendonly = None
newline = None
self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
def get(self, command=None, prompt=None, answer=None, sendonly=False, newline=True):
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline)
def commit(self, comment=None):
if comment:

@ -283,10 +283,10 @@ class Connection(ConnectionBase):
if self.connected:
return
p = connection_loader.get('paramiko', self._play_context, '/dev/null')
p.set_options(direct={'look_for_keys': not bool(self._play_context.password and not self._play_context.private_key_file)})
p.force_persistence = self.force_persistence
ssh = p._connect()
self.paramiko_conn = connection_loader.get('paramiko', self._play_context, '/dev/null')
self.paramiko_conn.set_options(direct={'look_for_keys': not bool(self._play_context.password and not self._play_context.private_key_file)})
self.paramiko_conn.force_persistence = self.force_persistence
ssh = self.paramiko_conn._connect()
display.vvvv('ssh connection done, setting terminal', host=self._play_context.remote_addr)

@ -3,7 +3,7 @@
- name: run invalid command
iosxr_command:
commands: [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
commands: {command: 'show foo', prompt: 'fooprompt', answer: 'yes'}
register: result
ignore_errors: yes
@ -15,7 +15,7 @@
iosxr_command:
commands:
- show version
- [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
- {command: 'show foo', prompt: 'fooprompt', answer: 'yes'}
register: result
ignore_errors: yes

@ -0,0 +1,33 @@
hostname iosxr01
line default
transport input ssh
!
interface Loopback888
description test for ansible
shutdown
!
interface MgmtEth0/0/CPU0/0
ipv4 address dhcp
!
interface preconfigure GigabitEthernet0/0/0/3
description test-interface-3
mtu 256
speed 100
duplex full
!
interface GigabitEthernet0/0/0/0
shutdown
!
interface GigabitEthernet0/0/0/1
shutdown
!
router static
address-family ipv4 unicast
0.0.0.0/0 10.0.2.2
!
!
netconf-yang agent
ssh
!
ssh server v2
ssh server netconf vrf default

@ -0,0 +1,27 @@
hostname iosxr01
line default
transport input ssh
!
interface Loopback888
description test for ansible
shutdown
!
interface MgmtEth0/0/CPU0/0
ipv4 address dhcp
!
interface GigabitEthernet0/0/0/0
shutdown
!
interface GigabitEthernet0/0/0/1
shutdown
!
router static
address-family ipv4 unicast
0.0.0.0/0 10.0.2.2
!
!
netconf-yang agent
ssh
!
ssh server v2
ssh server netconf vrf default

@ -0,0 +1,45 @@
---
- debug: msg="START cli/replace_config.yaml on connection={{ ansible_connection }}"
- name: setup
iosxr_config:
commands:
- no interface GigabitEthernet0/0/0/3
- name: replace config (add preconfigured interface)
iosxr_config: &addreplace
src: "{{ role_path }}/fixtures/config_add_interface.txt"
replace: config
backup: yes
register: result
- assert:
that:
- '"load harddisk:/ansible_config.txt" in result.commands'
- name: replace config (add preconfigured interface)(idempotence)
iosxr_config: *addreplace
register: result
- assert: &false
that:
- 'result.changed == false'
- name: replace config (del preconfigured interface)
iosxr_config: &delreplace
src: "{{ role_path }}/fixtures/config_del_interface.txt"
replace: config
backup: yes
register: result
- assert:
that:
- '"load harddisk:/ansible_config.txt" in result.commands'
- name: replace config (del preconfigured interface)(idempotence)
iosxr_config: *delreplace
register: result
- assert: *false
- debug: msg="END cli/replace_config.yaml on connection={{ ansible_connection }}"
Loading…
Cancel
Save