diff --git a/CHANGELOG.md b/CHANGELOG.md index 11e178d0b5..189b0d1520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Ansible Changes By Release (https://github.com/ansible/ansible/pull/38646) * Fix eos_logging idempotency issue when trying to set both logging destination & facility (https://github.com/ansible/ansible/pull/40604) +* Fix ios_logging idempotency issue when trying to set both logging destination & facility + (https://github.com/ansible/ansible/pull/41076) diff --git a/lib/ansible/modules/network/ios/ios_logging.py b/lib/ansible/modules/network/ios/ios_logging.py index f162df3331..24ba5e08ad 100644 --- a/lib/ansible/modules/network/ios/ios_logging.py +++ b/lib/ansible/modules/network/ios/ios_logging.py @@ -136,6 +136,7 @@ def validate_size(value, module): def map_obj_to_commands(updates, module): + dest_group = ('console', 'monitor', 'buffered', 'on') commands = list() want, have = updates for w in want: @@ -147,20 +148,33 @@ def map_obj_to_commands(updates, module): state = w['state'] del w['state'] + if facility: + w['dest'] = 'facility' + if state == 'absent' and w in have: - if dest == 'host': - commands.append('no logging host {0}'.format(name)) - elif dest: - commands.append('no logging {0}'.format(dest)) - else: - module.fail_json(msg='dest must be among console, monitor, buffered, host, on') + if dest: + if dest == 'host': + commands.append('no logging host {0}'.format(name)) + + elif dest in dest_group: + commands.append('no logging {0}'.format(dest)) + + else: + module.fail_json(msg='dest must be among console, monitor, buffered, host, on') if facility: commands.append('no logging facility {0}'.format(facility)) if state == 'present' and w not in have: if facility: - commands.append('logging facility {0}'.format(facility)) + present = False + + for entry in have: + if entry['dest'] == 'facility' and entry['facility'] == facility: + present = True + + if not present: + commands.append('logging facility {0}'.format(facility)) if dest == 'host': commands.append('logging host {0}'.format(name)) @@ -169,7 +183,17 @@ def map_obj_to_commands(updates, module): commands.append('logging on') elif dest == 'buffered' and size: - commands.append('logging buffered {0}'.format(size)) + present = False + + for entry in have: + if entry['dest'] == 'buffered' and entry['size'] == size and entry['level'] == level: + present = True + + if not present: + if level and level != 'debugging': + commands.append('logging buffered {0} {1}'.format(size, level)) + else: + commands.append('logging buffered {0}'.format(size)) else: dest_cmd = 'logging {0}'.format(dest) @@ -194,18 +218,12 @@ def parse_size(line, dest): size = None if dest == 'buffered': - match = re.search(r'logging buffered (\S+)', line, re.M) + match = re.search(r'logging buffered(?: (\d+))?(?: [a-z]+)?', line, re.M) if match: - try: - int_size = int(match.group(1)) - except ValueError: - int_size = None - - if int_size: - if isinstance(int_size, int): - size = str(match.group(1)) - else: - size = str(4096) + if match.group(1) is not None: + size = match.group(1) + else: + size = "4096" return size @@ -229,12 +247,13 @@ def parse_level(line, dest): level = 'debugging' else: - match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M) - if match: - if match.group(1) in level_group: - level = match.group(1) - else: - level = 'debugging' + if dest == 'buffered': + match = re.search(r'logging buffered(?: \d+)?(?: ([a-z]+))?', line, re.M) + else: + match = re.search(r'logging {0} (\S+)'.format(dest), line, re.M) + + if match and match.group(1) in level_group: + level = match.group(1) else: level = 'debugging' @@ -321,7 +340,6 @@ def map_params_to_obj(module, required_if=None): 'level': module.params['level'], 'state': module.params['state'] }) - return obj @@ -375,5 +393,6 @@ def main(): module.exit_json(**result) + if __name__ == '__main__': main() diff --git a/test/integration/targets/ios_logging/tests/cli/basic.yaml b/test/integration/targets/ios_logging/tests/cli/basic.yaml index 4061b279ea..20c9c2a7fc 100644 --- a/test/integration/targets/ios_logging/tests/cli/basic.yaml +++ b/test/integration/targets/ios_logging/tests/cli/basic.yaml @@ -1,7 +1,7 @@ --- # ensure logging configs are empty - name: Remove host logging - ios_logging: + ios_logging: &remove_host dest: host name: 172.16.0.1 state: absent @@ -45,16 +45,12 @@ authorize: yes register: result -- assert: +- assert: &unchanged that: - 'result.changed == false' - name: Delete/disable host logging - ios_logging: - dest: host - name: 172.16.0.1 - state: absent - authorize: yes + ios_logging: *remove_host register: result - assert: @@ -63,16 +59,10 @@ - '"no logging host 172.16.0.1" in result.commands' - name: Delete/disable host logging (idempotent) - ios_logging: - dest: host - name: 172.16.0.1 - state: absent - authorize: yes + ios_logging: *remove_host register: result -- assert: - that: - - 'result.changed == false' +- assert: *unchanged - name: Console logging with level warnings ios_logging: @@ -113,11 +103,34 @@ - '"logging buffered 9000" in result.commands' - '"logging console notifications" in result.commands' +- name: Set both logging destination and facility + ios_logging: &set_both + dest: buffered + facility: uucp + level: alerts + size: 4096 + state: present + authorize: yes + register: result + +- assert: + that: + - 'result.changed == true' + - '"logging buffered 4096 alerts" in result.commands' + - '"logging facility uucp" in result.commands' + +- name: Set both logging destination and facility (idempotent) + ios_logging: *set_both + register: result + +- assert: *unchanged + - name: remove logging as collection tearDown ios_logging: aggregate: - { dest: console, level: notifications } - - { dest: buffered, size: 9000 } + - { dest: buffered, size: 4096, level: alerts } + - { facility: uucp } state: absent authorize: yes register: result @@ -127,3 +140,4 @@ - 'result.changed == true' - '"no logging console" in result.commands' - '"no logging buffered" in result.commands' + - '"no logging facility uucp" in result.commands'