Add cli_config module (#42413)
* cli_config module Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * rename diff and replace Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * add nxos changes Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * nxos tests Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * remove severity * address review comment Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * add module diff Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * add iosxr test Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * address diff review Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * Add junos tests Signed-off-by: Trishna Guha <trishnaguha17@gmail.com> * vyos cliconf diff fix Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>
This commit is contained in:
parent
227bf61daa
commit
a8c24a5d5e
42 changed files with 960 additions and 4 deletions
326
lib/ansible/modules/network/cli/cli_config.py
Normal file
326
lib/ansible/modules/network/cli/cli_config.py
Normal file
|
@ -0,0 +1,326 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2018, Ansible by Red Hat, inc
|
||||
# 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': 'network'}
|
||||
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: cli_config
|
||||
version_added: "2.7"
|
||||
author: "Trishna Guha (@trishnaguha)"
|
||||
short_description: Push text based configuration to network devices over network_cli
|
||||
description:
|
||||
- This module provides platform agnostic way of pushing text based
|
||||
configuration to network devices over network_cli connection plugin.
|
||||
options:
|
||||
config:
|
||||
description:
|
||||
- The config to be pushed to the network device. This is a
|
||||
required argument.
|
||||
required: true
|
||||
type: 'str'
|
||||
commit:
|
||||
description:
|
||||
- The C(commit) argument instructs the module to push the
|
||||
configuration to the device. This is mapped to module check mode.
|
||||
type: 'bool'
|
||||
replace:
|
||||
description:
|
||||
- If the C(replace) argument is set to C(yes), it will replace
|
||||
the entire running-config of the device with the C(config)
|
||||
argument value. For NXOS devices, C(replace) argument takes
|
||||
path to the file on the device that will be used for replacing
|
||||
the entire running-config. Nexus 9K devices only support replace.
|
||||
Use I(net_put) or I(nxos_file_copy) module to copy the flat file
|
||||
to remote device and then use set the fullpath to this argument.
|
||||
type: 'str'
|
||||
rollback:
|
||||
description:
|
||||
- The C(rollback) argument instructs the module to rollback the
|
||||
current configuration to the identifier specified in the
|
||||
argument. If the specified rollback identifier does not
|
||||
exist on the remote device, the module will fail. To rollback
|
||||
to the most recent commit, set the C(rollback) argument to 0.
|
||||
commit_comment:
|
||||
description:
|
||||
- The C(commit_comment) argument specifies a text string to be used
|
||||
when committing the configuration. If the C(commit) argument
|
||||
is set to False, this argument is silently ignored. This argument
|
||||
is only valid for the platforms that support commit operation
|
||||
with comment.
|
||||
type: 'str'
|
||||
defaults:
|
||||
description:
|
||||
- The I(defaults) argument will influence how the running-config
|
||||
is collected from the device. When the value is set to true,
|
||||
the command used to collect the running-config is append with
|
||||
the all keyword. When the value is set to false, the command
|
||||
is issued without the all keyword.
|
||||
default: 'no'
|
||||
type: 'bool'
|
||||
multiline_delimiter:
|
||||
description:
|
||||
- This argument is used when pushing a multiline configuration
|
||||
element to the device. It specifies the character to use as
|
||||
the delimiting character. This only applies to the configuration
|
||||
action.
|
||||
type: 'str'
|
||||
diff_replace:
|
||||
description:
|
||||
- Instructs the module on the way to perform the configuration
|
||||
on the device. If the C(diff_replace) argument is set to I(line)
|
||||
then the modified lines are pushed to the device in configuration
|
||||
mode. If the 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. Note that this parameter will be ignored if
|
||||
the platform has onbox diff support.
|
||||
choices: ['line', 'block', 'config']
|
||||
diff_match:
|
||||
description:
|
||||
- Instructs the module on the way to perform the matching of
|
||||
the set of commands against the current device config. If C(diff_match)
|
||||
is set to I(line), commands are matched line by line. If C(diff_match)
|
||||
is set to I(strict), command lines are matched with respect to position.
|
||||
If C(diff_match) is set to I(exact), command lines must be an equal match.
|
||||
Finally, if C(diff_match) is set to I(none), the module will not attempt
|
||||
to compare the source configuration with the running configuration on the
|
||||
remote device. Note that this parameter will be ignored if the platform
|
||||
has onbox diff support.
|
||||
choices: ['line', 'strict', 'exact', 'none']
|
||||
diff_ignore_lines:
|
||||
description:
|
||||
- Use this argument to specify one or more lines that should be
|
||||
ignored during the diff. This is used for lines in the configuration
|
||||
that are automatically updated by the system. This argument takes
|
||||
a list of regular expressions or exact line matches.
|
||||
Note that this parameter will be ignored if the platform has onbox
|
||||
diff support.
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: configure device with config
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
|
||||
- name: configure device with config with defaults enabled
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
defaults: yes
|
||||
|
||||
- name: Use diff_match
|
||||
cli_config:
|
||||
config: |
|
||||
interface loopback999
|
||||
no description
|
||||
shutdown
|
||||
diff_match: none
|
||||
|
||||
- name: nxos replace config
|
||||
cli_config:
|
||||
replace: 'bootflash:nxoscfg'
|
||||
|
||||
- name: commit with comment
|
||||
cli_config:
|
||||
config: set system host-name foo
|
||||
commit_comment: this is a test
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
commands:
|
||||
description: The set of commands that will be pushed to the remote device
|
||||
returned: always
|
||||
type: list
|
||||
sample: ['interface Loopback999', 'no shutdown']
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.connection import Connection
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
def validate_args(module, capabilities):
|
||||
"""validate param if it is supported on the platform
|
||||
"""
|
||||
if (module.params['replace'] and
|
||||
not capabilities['device_operations']['supports_replace']):
|
||||
module.fail_json(msg='replace is not supported on this platform')
|
||||
|
||||
if (module.params['rollback'] and
|
||||
not capabilities['device_operations']['supports_rollback']):
|
||||
module.fail_json(msg='rollback is not supported on this platform')
|
||||
|
||||
if (module.params['commit_comment'] and
|
||||
not capabilities['device_operations']['supports_commit_comment']):
|
||||
module.fail_json(msg='commit_comment is not supported on this platform')
|
||||
|
||||
if (module.params['defaults'] and
|
||||
not capabilities['device_operations']['supports_defaults']):
|
||||
module.fail_json(msg='defaults is not supported on this platform')
|
||||
|
||||
if (module.params['multiline_delimiter'] and
|
||||
not capabilities['device_operations']['supports_multiline_delimiter']):
|
||||
module.fail_json(msg='multiline_delimiter is not supported on this platform')
|
||||
|
||||
if (module.params['diff_replace'] and
|
||||
not capabilities['device_operations']['supports_diff_replace']):
|
||||
module.fail_json(msg='diff_replace is not supported on this platform')
|
||||
|
||||
if (module.params['diff_match'] and
|
||||
not capabilities['device_operations']['supports_diff_match']):
|
||||
module.fail_json(msg='diff_match is not supported on this platform')
|
||||
|
||||
if (module.params['diff_ignore_lines'] and
|
||||
not capabilities['device_operations']['supports_diff_ignore_lines']):
|
||||
module.fail_json(msg='diff_ignore_lines is not supported on this platform')
|
||||
|
||||
|
||||
def run(module, capabilities, connection, candidate, running):
|
||||
result = {}
|
||||
resp = {}
|
||||
config_diff = []
|
||||
banner_diff = {}
|
||||
|
||||
replace = module.params['replace']
|
||||
rollback = module.params['rollback']
|
||||
commit_comment = module.params['commit_comment']
|
||||
multiline_delimiter = module.params['multiline_delimiter']
|
||||
diff_replace = module.params['diff_replace']
|
||||
diff_match = module.params['diff_match']
|
||||
diff_ignore_lines = module.params['diff_ignore_lines']
|
||||
|
||||
commit = not module.check_mode
|
||||
|
||||
if replace in ('yes', 'true', 'True'):
|
||||
replace = True
|
||||
elif replace in ('no', 'false', 'False'):
|
||||
replace = False
|
||||
|
||||
if capabilities['device_operations']['supports_generate_diff']:
|
||||
kwargs = {'candidate': candidate, 'running': running}
|
||||
if diff_match:
|
||||
kwargs.update({'diff_match': diff_match})
|
||||
if diff_replace:
|
||||
kwargs.update({'diff_replace': diff_replace})
|
||||
if diff_ignore_lines:
|
||||
kwargs.update({'diff_ignore_lines': diff_ignore_lines})
|
||||
|
||||
diff_response = connection.get_diff(**kwargs)
|
||||
|
||||
config_diff = diff_response.get('config_diff')
|
||||
banner_diff = diff_response.get('banner_diff')
|
||||
|
||||
if config_diff:
|
||||
if isinstance(config_diff, list):
|
||||
candidate = config_diff
|
||||
else:
|
||||
candidate = config_diff.splitlines()
|
||||
|
||||
kwargs = {'candidate': candidate, 'commit': commit, 'replace': replace,
|
||||
'comment': commit_comment}
|
||||
connection.edit_config(**kwargs)
|
||||
result['changed'] = True
|
||||
|
||||
if banner_diff:
|
||||
candidate = json.dumps(banner_diff)
|
||||
|
||||
kwargs = {'candidate': candidate, 'commit': commit}
|
||||
if multiline_delimiter:
|
||||
kwargs.update({'multiline_delimiter': multiline_delimiter})
|
||||
connection.edit_banner(**kwargs)
|
||||
result['changed'] = True
|
||||
|
||||
elif capabilities['device_operations']['supports_onbox_diff']:
|
||||
if diff_replace:
|
||||
module.warn('diff_replace is ignored as the device supports onbox diff')
|
||||
if diff_match:
|
||||
module.warn('diff_mattch is ignored as the device supports onbox diff')
|
||||
if diff_ignore_lines:
|
||||
module.warn('diff_ignore_lines is ignored as the device supports onbox diff')
|
||||
|
||||
if not isinstance(candidate, list):
|
||||
candidate = candidate.strip('\n').splitlines()
|
||||
|
||||
kwargs = {'candidate': candidate, 'commit': commit, 'replace': replace,
|
||||
'comment': commit_comment}
|
||||
resp = connection.edit_config(**kwargs)
|
||||
|
||||
if 'diff' in resp:
|
||||
result['changed'] = True
|
||||
|
||||
if module._diff:
|
||||
if 'diff' in resp:
|
||||
result['diff'] = {'prepared': resp['diff']}
|
||||
else:
|
||||
diff = ''
|
||||
if config_diff:
|
||||
if isinstance(config_diff, list):
|
||||
diff += '\n'.join(config_diff)
|
||||
else:
|
||||
diff += config_diff
|
||||
if banner_diff:
|
||||
diff += json.dumps(banner_diff)
|
||||
result['diff'] = {'prepared': diff}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
"""main entry point for execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
config=dict(required=True, type='str'),
|
||||
commit=dict(type='bool'),
|
||||
replace=dict(type='str'),
|
||||
rollback=dict(type='int'),
|
||||
commit_comment=dict(type='str'),
|
||||
defaults=dict(default=False, type='bool'),
|
||||
multiline_delimiter=dict(type='str'),
|
||||
diff_replace=dict(choices=['line', 'block', 'config']),
|
||||
diff_match=dict(choices=['line', 'strict', 'exact', 'none']),
|
||||
diff_ignore_lines=dict(type='list')
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
connection = Connection(module._socket_path)
|
||||
capabilities = module.from_json(connection.get_capabilities())
|
||||
|
||||
if capabilities:
|
||||
validate_args(module, capabilities)
|
||||
|
||||
if module.params['defaults']:
|
||||
if 'get_default_flag' in capabilities.get('rpc'):
|
||||
flags = connection.get_default_flag()
|
||||
else:
|
||||
flags = 'all'
|
||||
else:
|
||||
flags = []
|
||||
|
||||
candidate = to_text(module.params['config'])
|
||||
running = connection.get_config(flags=flags)
|
||||
|
||||
try:
|
||||
result.update(run(module, capabilities, connection, candidate, running))
|
||||
except Exception as exc:
|
||||
module.fail_json(msg=to_text(exc))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
31
lib/ansible/plugins/action/cli_config.py
Normal file
31
lib/ansible/plugins/action/cli_config.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
#
|
||||
# Copyright 2018 Red Hat Inc.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.plugins.action.normal import ActionModule as _ActionModule
|
||||
|
||||
|
||||
class ActionModule(_ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
if self._play_context.connection != 'network_cli':
|
||||
return {'failed': True, 'msg': 'Connection type %s is not valid for cli_config module' % self._play_context.connection}
|
||||
|
||||
return super(ActionModule, self).run(task_vars=task_vars)
|
|
@ -99,6 +99,7 @@ class Cliconf(CliconfBase):
|
|||
else:
|
||||
self.send_command('exit')
|
||||
|
||||
if diff_config:
|
||||
resp['diff'] = diff_config
|
||||
resp['response'] = results
|
||||
resp['request'] = requests
|
||||
|
|
16
test/integration/targets/eos_config/tasks/cli_config.yaml
Normal file
16
test/integration/targets/eos_config/tasks/cli_config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
- name: collect all cli_config test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli_config"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
register: test_cases
|
||||
delegate_to: localhost
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
|
@ -1,3 +1,4 @@
|
|||
---
|
||||
- { include: cli.yaml, tags: ['cli'] }
|
||||
- { include: cli_config.yaml, tags: ['cli_config'] }
|
||||
- { include: eapi.yaml, tags: ['eapi'] }
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup
|
||||
cli_config: &rm
|
||||
config: |
|
||||
interface Ethernet2
|
||||
no description
|
||||
no shutdown
|
||||
become: yes
|
||||
|
||||
- name: configure device with config
|
||||
cli_config: &conf
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
register: result
|
||||
become: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Idempotence
|
||||
cli_config: *conf
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
16
test/integration/targets/ios_config/tasks/cli_config.yaml
Normal file
16
test/integration/targets/ios_config/tasks/cli_config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
- name: collect all cli_config test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli_config"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
register: test_cases
|
||||
delegate_to: localhost
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
|
@ -1,2 +1,3 @@
|
|||
---
|
||||
- { include: cli.yaml, tags: ['cli'] }
|
||||
- { include: cli_config.yaml, tags: ['cli_config'] }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
ip access-list extended test
|
||||
permit ip host 192.0.2.1 any log
|
||||
permit ip host 192.0.2.2 any log
|
||||
permit ip host 192.0.2.3 any log
|
||||
permit ip host 192.0.2.4 any log
|
|
@ -0,0 +1,6 @@
|
|||
no ip access-list extended test
|
||||
ip access-list extended test
|
||||
permit ip host 192.0.2.1 any log
|
||||
permit ip host 192.0.2.2 any log
|
||||
permit ip host 192.0.2.3 any log
|
||||
permit ip host 192.0.2.4 any log
|
|
@ -0,0 +1,5 @@
|
|||
ip access-list extended test
|
||||
permit ip host 192.0.2.1 any log
|
||||
permit ip host 192.0.2.2 any log
|
||||
permit ip host 192.0.2.3 any log
|
||||
permit ip host 192.0.2.4 any log
|
|
@ -0,0 +1,5 @@
|
|||
ip access-list extended test
|
||||
permit ip host 192.0.2.1 any log
|
||||
permit ip host 192.0.2.2 any log
|
||||
permit ip host 192.0.2.3 any log
|
||||
permit ip host 192.0.2.4 any log
|
|
@ -0,0 +1,5 @@
|
|||
no ip access-list extended test
|
||||
ip access-list extended test
|
||||
permit ip host 192.0.2.1 any log
|
||||
permit ip host 192.0.2.2 any log
|
||||
permit ip host 192.0.2.3 any log
|
|
@ -0,0 +1,7 @@
|
|||
no ip access-list extended test
|
||||
ip access-list extended test
|
||||
permit ip host 192.0.2.1 any log
|
||||
permit ip host 192.0.2.2 any log
|
||||
permit ip host 192.0.2.3 any log
|
||||
permit ip host 192.0.2.4 any log
|
||||
permit ip host 192.0.2.5 any log
|
|
@ -0,0 +1,7 @@
|
|||
no ip access-list extended test
|
||||
ip access-list extended test
|
||||
permit ip host 192.0.2.1 any log
|
||||
permit ip host 192.0.2.2 any log
|
||||
permit ip host 192.0.2.3 any log
|
||||
permit ip host 192.0.2.4 any log
|
||||
permit ip host 192.0.2.5 any log
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup
|
||||
cli_config: &rm
|
||||
config: |
|
||||
interface loopback999
|
||||
no description
|
||||
shutdown
|
||||
diff_match: none
|
||||
|
||||
- name: configure device with config
|
||||
cli_config: &conf
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Idempotence
|
||||
cli_config: *conf
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: remove config
|
||||
cli_config: *rm
|
||||
|
||||
- name: configure device with config
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
defaults: yes
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_block_replace.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup - remove configuration
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/setupblock.j2') }}"
|
||||
diff_match: none
|
||||
|
||||
- name: block replace
|
||||
cli_config: &block
|
||||
config: "{{ lookup('template', 'basic/configblock.j2') }}"
|
||||
diff_replace: block
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: block replace (Idempotence)
|
||||
cli_config: *block
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config:
|
||||
config: no ip access-list extended test
|
||||
diff_match: none
|
||||
|
||||
- debug: msg="END cli_config/cli_block_replace.yaml on connection={{ ansible_connection }}"
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_exact_match.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup - remove configuration
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/setupexact.j2') }}"
|
||||
diff_match: none
|
||||
|
||||
- name: configure using exact match
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/configexact1.j2') }}"
|
||||
diff_match: exact
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: check using exact match
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/configexact2.j2') }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config:
|
||||
config: no ip access-list extended test
|
||||
diff_match: none
|
||||
|
||||
- debug: msg="END cli_config/cli_exact_match.yaml on connection={{ ansible_connection }}"
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_strict_match.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup - remove configuration
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/setupstrict.j2') }}"
|
||||
diff_match: none
|
||||
|
||||
- name: configure using strict match
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/configstrict1.j2') }}"
|
||||
diff_match: strict
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: teardown
|
||||
cli_config:
|
||||
config: no ip access-list extended test
|
||||
diff_match: none
|
||||
|
||||
- debug: msg="END cli_config/cli_strict_match.yaml on connection={{ ansible_connection }}"
|
16
test/integration/targets/iosxr_config/tasks/cli_config.yaml
Normal file
16
test/integration/targets/iosxr_config/tasks/cli_config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
- name: collect all cli test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli_config"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
register: test_cases
|
||||
delegate_to: localhost
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
|
@ -1,2 +1,3 @@
|
|||
---
|
||||
- { include: cli.yaml, tags: ['cli'] }
|
||||
- { include: cli_config.yaml, tags: ['cli_config'] }
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup
|
||||
cli_config: &rm
|
||||
config: |
|
||||
interface Loopback999
|
||||
no description
|
||||
no shutdown
|
||||
become: yes
|
||||
|
||||
- name: configure device with config
|
||||
cli_config: &conf
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
register: result
|
||||
become: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Idempotence
|
||||
cli_config: *conf
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
16
test/integration/targets/junos_config/tasks/cli_config.yaml
Normal file
16
test/integration/targets/junos_config/tasks/cli_config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
- name: collect all cli test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli_config"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
register: test_cases
|
||||
delegate_to: localhost
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
|
@ -1,2 +1,3 @@
|
|||
---
|
||||
- { include: netconf.yaml, tags: ['netconf'] }
|
||||
- { include: cli_config.yaml, tags: ['cli_config'] }
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup
|
||||
cli_config: &rm
|
||||
config: delete interfaces ge-0/0/1
|
||||
become: yes
|
||||
|
||||
- name: configure device with config
|
||||
cli_config: &conf
|
||||
config: set interfaces ge-0/0/1 description 'test-interface'
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Idempotence
|
||||
cli_config: *conf
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
16
test/integration/targets/nxos_config/tasks/cli_config.yaml
Normal file
16
test/integration/targets/nxos_config/tasks/cli_config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
- name: collect all cli_config test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli_config"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
register: test_cases
|
||||
delegate_to: localhost
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
|
@ -1,3 +1,4 @@
|
|||
---
|
||||
- { include: cli.yaml, tags: ['cli'] }
|
||||
- { include: nxapi.yaml, tags: ['nxapi'] }
|
||||
- { include: cli_config.yaml, tags: ['cli_config'] }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
interface Ethernet2/5
|
||||
interface loopback1
|
||||
description this is a test
|
||||
shutdown
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
ip access-list test
|
||||
10 permit ip 192.0.2.1/32 any log
|
||||
20 permit ip 192.0.2.2/32 any log
|
||||
30 permit ip 192.0.2.3/32 any log
|
||||
40 permit ip 192.0.2.4/32 any log
|
|
@ -0,0 +1,5 @@
|
|||
ip access-list test
|
||||
10 permit ip 192.0.2.1/32 any log
|
||||
20 permit ip 192.0.2.2/32 any log
|
||||
30 permit ip 192.0.2.3/32 any log
|
||||
40 permit ip 192.0.2.4/32 any log
|
|
@ -0,0 +1,6 @@
|
|||
ip access-list test
|
||||
10 permit ip 192.0.2.1/32 any log
|
||||
20 permit ip 192.0.2.2/32 any log
|
||||
30 permit ip 192.0.2.3/32 any log
|
||||
40 permit ip 192.0.2.4/32 any log
|
||||
50 permit ip 192.0.2.5/32 any log
|
|
@ -0,0 +1,6 @@
|
|||
no ip access-list test
|
||||
ip access-list test
|
||||
10 permit ip 192.0.2.1/32 any log
|
||||
20 permit ip 192.0.2.2/32 any log
|
||||
30 permit ip 192.0.2.3/32 any log
|
||||
40 permit ip 192.0.2.4/32 any log
|
|
@ -0,0 +1,7 @@
|
|||
no ip access-list test
|
||||
ip access-list test
|
||||
10 permit ip 192.0.2.1/32 any log
|
||||
20 permit ip 192.0.2.2/32 any log
|
||||
30 permit ip 192.0.2.3/32 any log
|
||||
40 permit ip 192.0.2.4/32 any log
|
||||
50 permit ip 192.0.2.5/32 any log
|
|
@ -0,0 +1,7 @@
|
|||
no ip access-list test
|
||||
ip access-list test
|
||||
10 permit ip 192.0.2.1/32 any log
|
||||
20 permit ip 192.0.2.2/32 any log
|
||||
30 permit ip 192.0.2.3/32 any log
|
||||
40 permit ip 192.0.2.4/32 any log
|
||||
50 permit ip 192.0.2.5/32 any log
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup
|
||||
cli_config: &rm
|
||||
config: |
|
||||
interface loopback1
|
||||
no description
|
||||
no shutdown
|
||||
diff_match: none
|
||||
|
||||
- name: configure device with config
|
||||
cli_config: &conf
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Idempotence
|
||||
cli_config: *conf
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: remove config
|
||||
cli_config: *rm
|
||||
|
||||
- name: configure device with config
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/config.j2') }}"
|
||||
defaults: yes
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_block_replace.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup - remove configuration
|
||||
cli_config: &rm
|
||||
config: "no ip access-list test"
|
||||
diff_match: none
|
||||
|
||||
- name: block replace
|
||||
cli_config: &block
|
||||
config: "{{ lookup('template', 'basic/configblock.j2') }}"
|
||||
diff_replace: block
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: block replace (Idempotence)
|
||||
cli_config: *block
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_block_replace.yaml on connection={{ ansible_connection }}"
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_exact_match.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup - remove configuration
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/setupexact.j2') }}"
|
||||
diff_match: none
|
||||
|
||||
- name: configure using exact match
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/configexact1.j2') }}"
|
||||
diff_match: exact
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: check using exact match
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/configexact2.j2') }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config:
|
||||
config: no ip access-list test
|
||||
diff_match: none
|
||||
|
||||
- debug: msg="END cli_config/cli_exact_match.yaml on connection={{ ansible_connection }}"
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_strict_match.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup - remove configuration
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/setupstrict.j2') }}"
|
||||
diff_match: none
|
||||
|
||||
- name: configure using strict match
|
||||
cli_config:
|
||||
config: "{{ lookup('template', 'basic/configstrict1.j2') }}"
|
||||
diff_match: strict
|
||||
diff_replace: block
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: teardown
|
||||
cli_config:
|
||||
config: no ip access-list test
|
||||
diff_match: none
|
||||
|
||||
- debug: msg="END cli_config/cli_strict_match.yaml on connection={{ ansible_connection }}"
|
16
test/integration/targets/vyos_config/tasks/cli_config.yaml
Normal file
16
test/integration/targets/vyos_config/tasks/cli_config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
- name: collect all cli_config test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli_config"
|
||||
patterns: "{{ testcase }}.yaml"
|
||||
register: test_cases
|
||||
delegate_to: localhost
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test case (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
|
@ -1,2 +1,3 @@
|
|||
---
|
||||
- { include: cli.yaml, tags: ['cli'] }
|
||||
- { include: cli_config.yaml, tags: ['cli_config'] }
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup - remove interface description
|
||||
cli_config: &rm
|
||||
config: delete interfaces loopback lo description
|
||||
|
||||
- name: configure device with config
|
||||
cli_config: &conf
|
||||
config: set interfaces loopback lo description 'this is a test'
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: Idempotence
|
||||
cli_config: *conf
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_basic.yaml on connection={{ ansible_connection }}"
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
- debug: msg="START cli_config/cli_comment.yaml on connection={{ ansible_connection }}"
|
||||
|
||||
- name: setup
|
||||
cli_config: &rm
|
||||
config: set system host-name {{ inventory_hostname_short }}
|
||||
|
||||
- name: configure using comment
|
||||
cli_config:
|
||||
config: set system host-name foo
|
||||
commit_comment: this is a test
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == true"
|
||||
|
||||
- name: collect system commits
|
||||
vyos_command:
|
||||
commands: show system commit
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "'this is a test' in result.stdout_lines[0][1]"
|
||||
|
||||
- name: teardown
|
||||
cli_config: *rm
|
||||
|
||||
- debug: msg="END cli_config/cli_comment.yaml on connection={{ ansible_connection }}"
|
Loading…
Reference in a new issue