diff --git a/lib/ansible/module_utils/network/iosxr/iosxr.py b/lib/ansible/module_utils/network/iosxr/iosxr.py index 6889e86eea6..c8068fb36be 100644 --- a/lib/ansible/module_utils/network/iosxr/iosxr.py +++ b/lib/ansible/module_utils/network/iosxr/iosxr.py @@ -27,6 +27,7 @@ # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # import json +import re from difflib import Differ from copy import deepcopy from time import sleep @@ -334,7 +335,8 @@ def discard_config(module): conn.discard_changes() -def commit_config(module, comment=None, confirmed=False, confirm_timeout=None, persist=False, check=False): +def commit_config(module, comment=None, confirmed=False, confirm_timeout=None, + persist=False, check=False, label=None): conn = get_connection(module) reply = None @@ -344,7 +346,7 @@ def commit_config(module, comment=None, confirmed=False, confirm_timeout=None, p if is_netconf(module): reply = conn.commit(confirmed=confirmed, timeout=confirm_timeout, persist=persist) elif is_cliconf(module): - reply = conn.commit(comment=comment) + reply = conn.commit(comment=comment, label=label) return reply @@ -373,8 +375,18 @@ def get_config(module, config_filter=None, source='running'): return cfg +def check_existing_commit_labels(conn, label): + out = conn.get(command='show configuration history detail | include %s' % label) + label_exist = re.search(label, out, re.M) + if label_exist: + return True + else: + return False + + def load_config(module, command_filter, commit=False, replace=False, - comment=None, admin=False, running=None, nc_get_filter=None): + comment=None, admin=False, running=None, nc_get_filter=None, + label=None): conn = get_connection(module) @@ -404,6 +416,16 @@ def load_config(module, command_filter, commit=False, replace=False, elif is_cliconf(module): # to keep the pre-cliconf behaviour, make a copy, avoid adding commands to input list cmd_filter = deepcopy(command_filter) + # If label is present check if label already exist before entering + # config mode + if label: + old_label = check_existing_commit_labels(conn, label) + if old_label: + module.fail_json( + msg='commit label {%s} is already used for' + ' an earlier commit, please choose a different label' + ' and rerun task' % label + ) cmd_filter.insert(0, 'configure terminal') if admin: cmd_filter.insert(0, 'admin') @@ -424,7 +446,7 @@ def load_config(module, command_filter, commit=False, replace=False, cmd.append('end') conn.edit_config(cmd) elif commit: - commit_config(module, comment=comment) + commit_config(module, comment=comment, label=label) conn.edit_config('end') if admin: conn.edit_config('exit') diff --git a/lib/ansible/modules/network/iosxr/iosxr_config.py b/lib/ansible/modules/network/iosxr/iosxr_config.py index 50d9b31b139..a18da28cc91 100644 --- a/lib/ansible/modules/network/iosxr/iosxr_config.py +++ b/lib/ansible/modules/network/iosxr/iosxr_config.py @@ -135,6 +135,14 @@ options: type: bool default: 'no' version_added: "2.4" + label: + description: + - Allows a commit label to be specified to be included when the + configuration is committed. A valid label must begin with an alphabet + and not exceed 30 characters, only alphabets, digits, hyphens and + underscores are allowed. If the configuration is not changed or + committed, this argument is ignored. + version_added: "2.7" """ EXAMPLES = """ @@ -239,6 +247,18 @@ def check_args(module, warnings): if module.params['comment']: if len(module.params['comment']) > 60: module.fail_json(msg='comment argument cannot be more than 60 characters') + if module.params['label']: + label = module.params['label'] + if len(label) > 30: + module.fail_json(msg='label argument cannot be more than 30 characters') + if not label[0].isalpha(): + module.fail_json(msg='label argument must begin with an alphabet') + valid_chars = re.match(r'[\w-]*$', label) + if not valid_chars: + module.fail_json( + msg='label argument must only contain alphabets,' + + 'digits, underscores or hyphens' + ) if module.params['force']: warnings.append('The force argument is deprecated, please use ' 'match=none instead. This argument will be ' @@ -340,6 +360,7 @@ def run(module, result): comment = module.params['comment'] admin = module.params['admin'] check_mode = module.check_mode + label = module.params['label'] candidate_config = get_candidate(module) running_config = get_running_config(module) @@ -376,7 +397,11 @@ 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, + label=label + ) if diff: result['diff'] = dict(prepared=diff) @@ -405,7 +430,8 @@ def main(): config=dict(), backup=dict(type='bool', default=False), comment=dict(default=DEFAULT_COMMIT_COMMENT), - admin=dict(type='bool', default=False) + admin=dict(type='bool', default=False), + label=dict() ) argument_spec.update(iosxr_argument_spec) diff --git a/lib/ansible/plugins/cliconf/iosxr.py b/lib/ansible/plugins/cliconf/iosxr.py index 201f6bb024a..61d1f899e44 100644 --- a/lib/ansible/plugins/cliconf/iosxr.py +++ b/lib/ansible/plugins/cliconf/iosxr.py @@ -89,9 +89,13 @@ class Cliconf(CliconfBase): 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: + def commit(self, comment=None, label=None): + if comment and label: + command = 'commit label {0} comment {1}'.format(label, comment) + elif comment: command = 'commit comment {0}'.format(comment) + elif label: + command = 'commit label {0}'.format(label) else: command = 'commit' self.send_command(command) diff --git a/test/integration/targets/iosxr_config/tests/cli/commit_label.yaml b/test/integration/targets/iosxr_config/tests/cli/commit_label.yaml new file mode 100644 index 00000000000..f62ad35bee9 --- /dev/null +++ b/test/integration/targets/iosxr_config/tests/cli/commit_label.yaml @@ -0,0 +1,70 @@ +--- +- debug: msg="START cli/commit_label.yaml on connection={{ ansible_connection }}" + +- name: setup + iosxr_config: + commands: + - no description + - no shutdown + parents: + - interface Loopback999 + match: none + +- name: get a unique and valid label + set_fact: + label: "ansible_{{ 1001 | random | to_uuid | truncate(20, true, '_') }}" + +- name: configure device with a label and a comment + iosxr_config: + src: basic/config.j2 + comment: "this is sensible commit message" + label: "{{ label }}" + register: result + +- assert: + that: + - "result.changed == true" + +- name: setup + iosxr_config: + commands: + - no description + - no shutdown + parents: + - interface Loopback999 + match: none + +- name: Try to commit with old label, fail with a msg that label is alreay used + iosxr_config: + src: basic/config.j2 + label: "{{ label }}" + register: result + ignore_errors: true + +- assert: + that: + - "result.changed == false" + - "'already used' in result.msg" + +- name: setup + iosxr_config: + commands: + - no description + - no shutdown + parents: + - interface Loopback999 + match: none + +- name: Try to commit with invalid chars($) in label + iosxr_config: + src: basic/config.j2 + label: 'ansible_iosxr_config_$' + register: result + ignore_errors: true + +- assert: + that: + - "result.changed == false" + - "'only contain alphabets' in result.msg" + +- debug: msg="END cli/commit_label.yaml on connection={{ ansible_connection }}"